ViennaLS
Loading...
Searching...
No Matches
lsToSurfaceMesh.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <map>
6#include <unordered_map>
7#include <unordered_set>
8
9#include <hrleSparseCellIterator.hpp>
10#include <hrleSparseStarIterator.hpp>
12#include <lsDomain.hpp>
13#include <lsExpand.hpp>
15#include <lsMarchingCubes.hpp>
16#include <lsMesh.hpp>
17
18namespace viennals {
19
20using namespace viennacore;
21
25template <class T, int D> class ToSurfaceMesh {
26protected:
29 using hrleIndex = viennahrle::Index<D>;
30 using ConstSparseIterator = viennahrle::ConstSparseIterator<hrleDomainType>;
32 viennahrle::ConstSparseCellIterator<hrleDomainType>;
33
34 std::vector<SmartPointer<lsDomainType>> levelSets;
35 SmartPointer<Mesh<T>> mesh = nullptr;
36
37 const T epsilon;
39 bool updatePointData = true;
41
42 struct I3 {
43 int x, y, z;
44 bool operator==(const I3 &o) const {
45 return x == o.x && y == o.y && z == o.z;
46 }
47 };
48
49 struct I3Hash {
50 size_t operator()(const I3 &k) const {
51 // 64-bit mix
52 uint64_t a = (uint64_t)(uint32_t)k.x;
53 uint64_t b = (uint64_t)(uint32_t)k.y;
54 uint64_t c = (uint64_t)(uint32_t)k.z;
55 uint64_t h = a * 0x9E3779B185EBCA87ULL;
56 h ^= b + 0xC2B2AE3D27D4EB4FULL + (h << 6) + (h >> 2);
57 h ^= c + 0x165667B19E3779F9ULL + (h << 6) + (h >> 2);
58 return (size_t)h;
59 }
60 };
61
62 // Context for mesh generation to share between helpers
63 std::unordered_map<I3, unsigned, I3Hash> nodeIdByBin;
64 std::unordered_set<I3, I3Hash> uniqueElements;
65 std::vector<Vec3D<T>> currentNormals;
66 std::vector<T> currentMaterials;
68 SmartPointer<lsDomainType> currentLevelSet = nullptr;
69 typename PointData<T>::VectorDataType *normalVectorData = nullptr;
70
71 // Store sharp corner nodes created during this cell iteration
72 std::vector<std::pair<unsigned, Vec3D<T>>> matSharpCornerNodes;
73
74 static constexpr unsigned int corner0[12] = {0, 1, 2, 0, 4, 5,
75 6, 4, 0, 1, 3, 2};
76 static constexpr unsigned int corner1[12] = {1, 3, 3, 2, 5, 7,
77 7, 6, 4, 5, 7, 6};
78 static constexpr unsigned int direction[12] = {0, 1, 0, 1, 0, 1,
79 0, 1, 2, 2, 2, 2};
80
81public:
82 explicit ToSurfaceMesh(double mnd = 0.05, double eps = 1e-12)
83 : minNodeDistanceFactor(mnd), epsilon(eps) {}
84
85 ToSurfaceMesh(const SmartPointer<lsDomainType> passedLevelSet,
86 SmartPointer<Mesh<T>> passedMesh, double mnd = 0.05,
87 double eps = 1e-12)
88 : levelSets({passedLevelSet}), mesh(passedMesh),
89 minNodeDistanceFactor(mnd), epsilon(eps) {}
90
91 void setLevelSet(SmartPointer<lsDomainType> passedlsDomain) {
92 levelSets = {passedlsDomain};
93 normalVectorData = nullptr;
94 }
95
96 void setMesh(SmartPointer<Mesh<T>> passedMesh) { mesh = passedMesh; }
97
98 void setUpdatePointData(bool update) { updatePointData = update; }
99
100 void setSharpCorners(bool check) { generateSharpCorners = check; }
101
102 virtual void apply() {
104 if (currentLevelSet == nullptr) {
105 Logger::getInstance()
106 .addError("No level set was passed to ToSurfaceMesh.")
107 .print();
108 return;
109 }
110 if (mesh == nullptr) {
111 Logger::getInstance()
112 .addError("No mesh was passed to ToSurfaceMesh.")
113 .print();
114 return;
115 }
116
117 if (currentLevelSet->getNumberOfPoints() == 0) {
118 VIENNACORE_LOG_WARNING(
119 "ToSurfaceMesh: Level set is empty. No mesh will be created.");
120 return;
121 }
122
123 mesh->clear();
124 if (currentLevelSet != nullptr) {
125 if (currentLevelSet->getLevelSetWidth() < 2) {
126 VIENNACORE_LOG_WARNING("Levelset is less than 2 layers wide. Expanding "
127 "levelset to 2 layers.");
129 }
130 }
131
132 ConstSparseIterator valueIt(currentLevelSet->getDomain());
133
134 typedef std::map<hrleIndex, unsigned> nodeContainerType;
135
136 nodeContainerType nodes[D];
137 nodeContainerType faceNodes[D];
138 nodeContainerType cornerNodes;
139 const bool sharpCorners = generateSharpCorners;
140 typename nodeContainerType::iterator nodeIt;
141
142 // save how data should be transferred to new level set
143 // list of indices into the old pointData vector
144 std::vector<std::vector<unsigned>> newDataSourceIds;
145 // there is no multithreading here, so just use 1
146 if (updatePointData)
147 newDataSourceIds.resize(1);
148
149 // If sharp corners are enabled, calculate normals first as they are needed
150 // for feature reconstruction
151 if (sharpCorners) {
153 normalCalculator.setMethod(
155 normalCalculator.setMaxValue(std::numeric_limits<T>::max());
156 normalCalculator.apply();
157 normalVectorData = currentLevelSet->getPointData().getVectorData(
159 }
160
161 // iterate over all cells with active points
162 for (ConstSparseCellIterator cellIt(currentLevelSet->getDomain());
163 !cellIt.isFinished(); cellIt.next()) {
164
165 // Clear node caches for dimensions that have moved out of scope
166 if (!sharpCorners) {
167 for (int u = 0; u < D; u++) {
168 while (!nodes[u].empty() &&
169 nodes[u].begin()->first < hrleIndex(cellIt.getIndices()))
170 nodes[u].erase(nodes[u].begin());
171
172 while (!faceNodes[u].empty() &&
173 faceNodes[u].begin()->first < hrleIndex(cellIt.getIndices()))
174 faceNodes[u].erase(faceNodes[u].begin());
175
176 if (u == 0) {
177 while (!cornerNodes.empty() &&
178 cornerNodes.begin()->first < hrleIndex(cellIt.getIndices()))
179 cornerNodes.erase(cornerNodes.begin());
180 }
181 }
182 }
183
184 // Calculate signs of all corners to determine the marching cubes case
185 unsigned signs = 0;
186 bool hasZero = false;
187 for (int i = 0; i < (1 << D); i++) {
188 T val = cellIt.getCorner(i).getValue();
189 if (val >= T(0))
190 signs |= (1 << i);
191 if (std::abs(val) <= epsilon)
192 hasZero = true;
193 }
194
195 // all corners have the same sign, so no surface here
196 if (signs == 0)
197 continue;
198 if (signs == (1 << (1 << D)) - 1 && !hasZero)
199 continue;
200
201 // Attempt to generate sharp features if enabled
202 bool perfectCornerFound = false;
203 if (sharpCorners) {
204 int countNeg = 0;
205 int countPos = 0;
206 int negMask = 0;
207 int posMask = 0;
208 for (int i = 0; i < (1 << D); ++i) {
209 T val = cellIt.getCorner(i).getValue();
210 if (val < -epsilon) {
211 countNeg++;
212 negMask |= (1 << i);
213 } else if (val > epsilon) {
214 countPos++;
215 posMask |= (1 << i);
216 } else {
217 if (val >= 0) {
218 countPos++;
219 posMask |= (1 << i);
220 } else {
221 countNeg++;
222 negMask |= (1 << i);
223 }
224 }
225 }
226
227 // Check for perfect corner (2D only)
228 if constexpr (D == 2) {
229 perfectCornerFound = generateSharpCorner2D(
230 cellIt, countNeg, countPos, negMask, posMask, nodes,
231 &newDataSourceIds[0], valueIt);
232 } else if constexpr (D == 3) {
233 if (countNeg == 2 || countPos == 2) {
234 // Try to generate a sharp edge (2 corners active)
235 perfectCornerFound = generateSharpEdge3D(
236 cellIt, countNeg, countPos, negMask, posMask, nodes, faceNodes,
237 &newDataSourceIds[0], valueIt);
238 } else if (countNeg == 1 || countPos == 1) {
239 // Try to generate a sharp corner (1 corner active)
240 perfectCornerFound = generateSharpCorner3D(
241 cellIt, countNeg, countPos, negMask, posMask, nodes,
242 cornerNodes, faceNodes, &newDataSourceIds[0], valueIt);
243 } else if (countNeg == 3 || countPos == 3) {
244 // Try to generate an L-shape corner (3 corners active)
245 perfectCornerFound = generateSharpL3D(
246 cellIt, countNeg, countPos, negMask, posMask, nodes, faceNodes,
247 &newDataSourceIds[0], valueIt);
248 }
249 }
250 }
251
252 // If a sharp feature was successfully generated, skip standard Marching
253 // Cubes for this cell
254 if (perfectCornerFound)
255 continue;
256
257 if constexpr (D == 3) {
258 if (sharpCorners) {
259 // Stitch to perfect corners/edges
260 // Check if neighbors have generated nodes on shared faces that we
261 // need to connect to
262 for (int axis = 0; axis < 3; ++axis) {
263 for (int d = 0; d < 2; ++d) {
264 hrleIndex faceKey(cellIt.getIndices());
265 if (d == 1)
266 faceKey[axis]++;
267
268 auto it = faceNodes[axis].find(faceKey);
269 if (it != faceNodes[axis].end()) {
270 const unsigned faceNodeId = it->second;
271 const int *Triangles =
273
274 for (; Triangles[0] != -1; Triangles += 3) {
275 std::vector<unsigned> face_edge_nodes;
276 for (int i = 0; i < 3; ++i) {
277 int edge = Triangles[i];
278 int c0 = corner0[edge];
279 int c1 = corner1[edge];
280 bool onFace =
281 (((c0 >> axis) & 1) == d) && (((c1 >> axis) & 1) == d);
282 if (onFace) {
283 face_edge_nodes.push_back(
284 getNode(cellIt, edge, nodes, &newDataSourceIds[0]));
285 }
286 }
287 if (face_edge_nodes.size() == 2) {
289 {face_edge_nodes[0], face_edge_nodes[1], faceNodeId});
290 }
291 }
292 }
293 }
294 }
295 }
296 }
297
298 // Standard Marching Cubes / Squares algorithm
299 // for each element
300 const int *Triangles =
303
304 for (; Triangles[0] != -1; Triangles += D) {
305 std::array<unsigned, D> nodeNumbers;
306
307 // for each node
308 for (int n = 0; n < D; n++) {
309 // get existing node or create new one if it does not exist yet
310 nodeNumbers[n] =
311 getNode(cellIt, Triangles[n], nodes, &newDataSourceIds[0]);
312 }
313
314 insertElement(nodeNumbers); // insert new surface element
315 }
316 }
317
318 scaleMesh(currentLevelSet->getGrid().getGridDelta());
319
320 // now copy old data into new level set
321 if (updatePointData && !newDataSourceIds[0].empty()) {
322 mesh->getPointData().translateFromMultiData(
323 currentLevelSet->getPointData(), newDataSourceIds);
324 }
325 }
326
327protected:
328 void scaleMesh(const T gridDelta) {
329 // Scale the mesh to global coordinates
330 for (auto &node : mesh->nodes) {
331 node = node * gridDelta;
332 }
333 mesh->minimumExtent = mesh->minimumExtent * gridDelta;
334 mesh->maximumExtent = mesh->maximumExtent * gridDelta;
335 }
336
337 template <class OffsetType>
338 Vec3D<T> interpWithOffset(const unsigned p0, const T d0, const T d1,
339 const unsigned dir,
340 const OffsetType &offset) const {
341 Vec3D<T> cc{};
342 for (int z = 0; z < D; z++) {
343 if (z != dir) {
344 cc[z] =
345 static_cast<T>(offset[z] + viennahrle::BitMaskToIndex<D>(p0)[z]);
346 } else {
347 if (d0 == -d1) {
348 cc[z] = static_cast<T>(offset[z]) + T(0.5);
349 } else {
350 if (std::abs(d0) <= std::abs(d1)) {
351 cc[z] = static_cast<T>(offset[z]) + (d0 / (d0 - d1));
352 } else {
353 cc[z] = static_cast<T>(offset[z] + 1) - (d1 / (d1 - d0));
354 }
355 }
356 cc[z] = std::max(cc[z], offset[z] + epsilon);
357 cc[z] = std::min(cc[z], (offset[z] + 1) - epsilon);
358 }
359 }
360 return cc;
361 }
362
363 // Helper to get or create a node on an edge using linear interpolation
364 // Compute the interpolated position of a node on an edge without
365 // inserting it
366 std::tuple<Vec3D<T>, std::size_t>
367 computeNodePosition(const ConstSparseCellIterator &cellIt, int edge) const {
368 const unsigned p0 = corner0[edge];
369 const unsigned p1 = corner1[edge];
370 const auto dir = direction[edge];
371 const T d0 = cellIt.getCorner(p0).getValue();
372 const T d1 = cellIt.getCorner(p1).getValue();
373
374 std::size_t pointId = 0;
375 if (std::abs(d0) <= std::abs(d1)) {
376 pointId = cellIt.getCorner(p0).getPointId();
377 } else {
378 pointId = cellIt.getCorner(p1).getPointId();
379 }
380
381 Vec3D<T> cc = interpWithOffset(p0, d0, d1, dir, cellIt.getIndices());
382
383 return {cc, pointId};
384 }
385
386 unsigned getNode(const ConstSparseCellIterator &cellIt, int edge,
387 std::map<hrleIndex, unsigned> *nodes,
388 std::vector<unsigned> *newDataSourceIds) {
389
390 const unsigned p0 = corner0[edge];
391 // determine direction of edge
392 auto dir = direction[edge];
393
394 // look for existing surface node
395 hrleIndex d(cellIt.getIndices());
396 d += viennahrle::BitMaskToIndex<D>(p0);
397
398 if (nodes[dir].find(d) != nodes[dir].end()) {
399 // Node already exists
400 return nodes[dir][d];
401 }
402
403 // Create new node
404 auto [nodePos, lsPointId] = computeNodePosition(cellIt, edge);
405 if (updatePointData && newDataSourceIds)
406 newDataSourceIds->push_back(lsPointId);
407
408 unsigned nodeId = insertNode(nodePos);
409 nodes[dir][d] = nodeId;
410 return nodeId;
411 }
412
413 // Helper to stitch a sharp feature node on a face to the standard mesh in a
414 // neighboring cell (3D only)
415 template <int Dim = D>
416 std::enable_if_t<Dim == 3, void>
418 bool isHighFace, unsigned faceNodeId,
419 std::map<hrleIndex, unsigned> *nodes,
420 ConstSparseIterator &valueIt) {
421 // Backward stitching: Check if neighbor on this face is "past" and needs
422 // stitching
423 hrleIndex neighborIdx = cellIt.getIndices();
424 if (isHighFace)
425 neighborIdx[axis]++;
426 else
427 neighborIdx[axis]--;
428
429 if (neighborIdx < cellIt.getIndices()) {
430 unsigned nSigns = 0;
431 auto &grid = currentLevelSet->getGrid();
432 for (int i = 0; i < 8; ++i) {
433 hrleIndex cIdx = neighborIdx + viennahrle::BitMaskToIndex<D>(i);
434 if (!grid.isOutsideOfDomain(cIdx)) {
435 valueIt.goToIndices(cIdx);
436 if (valueIt.getValue() >= 0)
437 nSigns |= (1 << i);
438 }
439 }
440
441 if (nSigns != 0 && nSigns != 255) {
442 const int *nTriangles = lsInternal::MarchingCubes::polygonize3d(nSigns);
443 int nFaceD = isHighFace ? 0 : 1;
444
445 for (; nTriangles[0] != -1; nTriangles += 3) {
446 std::vector<unsigned> face_edge_nodes;
447 for (int i = 0; i < 3; ++i) {
448 int edge = nTriangles[i];
449 int c0 = corner0[edge];
450 int c1 = corner1[edge];
451 bool onFace = (((c0 >> axis) & 1) == nFaceD) &&
452 (((c1 >> axis) & 1) == nFaceD);
453 if (onFace) {
454 unsigned p0 = corner0[edge];
455 auto dir = direction[edge];
456 hrleIndex d = neighborIdx + viennahrle::BitMaskToIndex<D>(p0);
457 auto itN = nodes[dir].find(d);
458 if (itN != nodes[dir].end()) {
459 face_edge_nodes.push_back(itN->second);
460 }
461 }
462 }
463 if (face_edge_nodes.size() == 2) {
464 insertElement(std::array<unsigned, 3>{
465 face_edge_nodes[0], face_edge_nodes[1], faceNodeId});
466 }
467 }
468 }
469 }
470 }
471
472 // Solves for a sharp corner position in 2D by intersecting normals
473 template <int Dim = D>
474 std::enable_if_t<Dim == 2, bool>
476 Vec3D<T> &cornerPos) const {
477 assert(normalVectorData &&
478 "Normal vector data must be computed for sharp corner generation.");
479
480 auto getTransformedGradient = [&](int cornerID) {
481 auto corner = cellIt.getCorner(cornerID ^ transform);
482 if (corner.isDefined()) {
483 auto normal = (*normalVectorData)[corner.getPointId()];
484 normal[2] = 0;
485 if ((transform & 1) != 0)
486 normal[0] = -normal[0];
487 if ((transform & 2) != 0)
488 normal[1] = -normal[1];
489 return normal;
490 }
491 return Vec3D<T>{};
492 };
493
494 Vec3D<T> norm1 = getTransformedGradient(1); // neighbor in x
495 Vec3D<T> norm2 = getTransformedGradient(2); // neighbor in y
496
497 if (std::abs(DotProduct(norm1, norm2)) >= 0.8) {
498 return false;
499 }
500
501 auto calculateNodePos = [&](int edge) {
502 const unsigned p0 = corner0[edge];
503 const unsigned p1 = corner1[edge];
504 const auto dir = direction[edge];
505 const T d0 = cellIt.getCorner(p0 ^ transform).getValue();
506 const T d1 = cellIt.getCorner(p1 ^ transform).getValue();
507 Vec3D<T> pos = interpWithOffset(p0, d0, d1, dir, Vec3D<T>{0, 0, 0});
508 return pos;
509 };
510
511 auto pX = calculateNodePos(0); // Edge along x-axis from corner 0
512 auto pY = calculateNodePos(3); // Edge along y-axis from corner 0
513
514 double d1 = DotProduct(norm1, pX);
515 double d2 = DotProduct(norm2, pY);
516 double det = norm1[0] * norm2[1] - norm1[1] * norm2[0];
517
518 if (std::abs(det) > 1e-6 && std::isfinite(det)) {
519 cornerPos[0] = (d1 * norm2[1] - d2 * norm1[1]) / det;
520 cornerPos[1] = (d2 * norm1[0] - d1 * norm2[0]) / det;
521 } else {
522 for (int i = 0; i < D; ++i)
523 cornerPos[i] = (pX[i] + pY[i]) * T(0.5);
524 }
525
526 return true;
527 }
528
529 // Wrapper for 2D sharp corner generation handling different
530 // rotations/reflections
531 template <int Dim = D>
532 std::enable_if_t<Dim == 2, bool> generateSharpCorner2D(
533 ConstSparseCellIterator &cellIt, int countNeg, int countPos, int negMask,
534 int posMask, std::map<hrleIndex, unsigned> *nodes,
535 std::vector<unsigned> *newDataSourceIds, ConstSparseIterator &valueIt) {
536
537 int cornerIdx = -1;
538 if (countNeg == 1) {
539 for (int i = 0; i < 4; ++i)
540 if ((negMask >> i) & 1) {
541 cornerIdx = i;
542 break;
543 }
544 } else if (countPos == 1) {
545 for (int i = 0; i < 4; ++i)
546 if ((posMask >> i) & 1) {
547 cornerIdx = i;
548 break;
549 }
550 }
551
552 if (cornerIdx != -1) {
553 // Check if this corner is also a corner in any neighboring cell
554 bool isSharedCorner = false;
555 auto pIdx =
556 cellIt.getIndices() + viennahrle::BitMaskToIndex<D>(cornerIdx);
557 T pVal = cellIt.getCorner(cornerIdx).getValue();
558 auto &grid = currentLevelSet->getGrid();
559
560 for (int i = 0; i < (1 << D); ++i) {
561 if (i == cornerIdx)
562 continue;
563
564 hrleIndex neighborIndices;
565 for (int k = 0; k < D; ++k)
566 neighborIndices[k] = pIdx[k] - ((i >> k) & 1);
567
568 bool neighborIsCorner = true;
569
570 // Check edge-connected neighbors in the neighbor cell
571 for (int k = 0; k < D; ++k) {
572 int neighborLocal = i ^ (1 << k);
573 auto checkIdx =
574 neighborIndices + viennahrle::BitMaskToIndex<D>(neighborLocal);
575 if (grid.isOutsideOfDomain(checkIdx)) {
576 checkIdx = grid.globalIndices2LocalIndices(checkIdx);
577 }
578 valueIt.goToIndices(checkIdx);
579 T nVal = valueIt.getValue();
580 bool pSign = (pVal >= 0);
581 bool nSign = (nVal >= 0);
582 if (pSign == nSign) {
583 neighborIsCorner = false;
584 break;
585 }
586 }
587
588 if (neighborIsCorner) {
589 isSharedCorner = true;
590 break;
591 }
592 }
593
594 if (!isSharedCorner) {
595 Vec3D<T> cornerPos{};
596 if (generateCanonicalSharpCorner2D(cellIt, cornerIdx, cornerPos)) {
597
598 // inverse transform cornerPos from canonical local to this cell's
599 // local
600 if ((cornerIdx & 1) != 0)
601 cornerPos[0] = T(1.0) - cornerPos[0];
602 if ((cornerIdx & 2) != 0)
603 cornerPos[1] = T(1.0) - cornerPos[1];
604
605 // convert to global coordinates
606 for (int i = 0; i < D; ++i) {
607 cornerPos[i] = (cornerPos[i] + cellIt.getIndices(i));
608 }
609
610 // Determine edges for this corner
611 int edgeX = -1, edgeY = -1;
612 if (cornerIdx == 0) {
613 edgeX = 0;
614 edgeY = 3;
615 } else if (cornerIdx == 1) {
616 edgeX = 0;
617 edgeY = 1;
618 } else if (cornerIdx == 2) {
619 edgeX = 2;
620 edgeY = 3;
621 } else if (cornerIdx == 3) {
622 edgeX = 2;
623 edgeY = 1;
624 }
625
626 unsigned nX = getNode(cellIt, edgeX, nodes, newDataSourceIds);
627 unsigned nY = getNode(cellIt, edgeY, nodes, newDataSourceIds);
628 unsigned nCorner = insertNode(cornerPos);
629
630 // Store this sharp corner node for potential use by derived classes
631 matSharpCornerNodes.push_back({nCorner, cornerPos});
632
633 if (updatePointData && newDataSourceIds) {
634 assert(!newDataSourceIds->empty());
635 newDataSourceIds->push_back(
636 newDataSourceIds->back()); // TODO: improve point data source
637 }
638
639 insertElement({nX, nCorner});
640 insertElement({nCorner, nY});
641 return true;
642 }
643 }
644 }
645 return false;
646 }
647
648 Vec3D<T> getNormal(int idx, const ConstSparseCellIterator &cellIt,
649 bool inverted) const {
650 assert(normalVectorData);
651 auto corner = cellIt.getCorner(idx);
652 if (corner.isDefined()) {
653 Vec3D<T> n = (*normalVectorData)[corner.getPointId()];
654 if (inverted)
655 for (int i = 0; i < 3; ++i)
656 n[i] = -n[i];
657 return n;
658 }
659 return Vec3D<T>{};
660 }
661
662 // Generates geometry for an "L-shaped" configuration (3 active corners) in 3D
663 template <int Dim = D>
664 std::enable_if_t<Dim == 3, bool> generateSharpL3D(
665 ConstSparseCellIterator &cellIt, int countNeg, int countPos, int negMask,
666 int posMask, std::map<hrleIndex, unsigned> *nodes,
667 std::map<hrleIndex, unsigned> *faceNodes,
668 std::vector<unsigned> *newDataSourceIds, ConstSparseIterator &valueIt) {
669
670 bool inverted = false;
671 int mask = 0;
672 if (countNeg == 3) {
673 mask = negMask;
674 inverted = false;
675 } else if (countPos == 3) {
676 mask = posMask;
677 inverted = true;
678 } else {
679 return false;
680 }
681
682 // Check for L-shape: 3 points on a face
683 int C = -1, A = -1, B = -1;
684 for (int i = 0; i < 8; ++i) {
685 if ((mask >> i) & 1) {
686 int neighbors = 0;
687 for (int k = 0; k < 3; ++k) {
688 if ((mask >> (i ^ (1 << k))) & 1)
689 neighbors++;
690 }
691 if (neighbors == 2) {
692 C = i;
693 break;
694 }
695 }
696 }
697
698 if (C == -1)
699 return false;
700
701 // Identify A and B
702 for (int k = 0; k < 3; ++k) {
703 int n = C ^ (1 << k);
704 if ((mask >> n) & 1) {
705 if (A == -1)
706 A = n;
707 else
708 B = n;
709 }
710 }
711
712 assert(normalVectorData &&
713 "Normal vector data must be computed for sharp feature generation.");
714
715 // Determine axes
716 int axisA = 0;
717 while ((C ^ A) != (1 << axisA))
718 axisA++;
719 int axisB = 0;
720 while ((C ^ B) != (1 << axisB))
721 axisB++;
722 int axisZ = 3 - axisA - axisB;
723
724 // Solve 2D face problem to find sharp point on a face
725 auto calculateFace = [&](int axis, int corner, int n1,
726 int n2) -> std::optional<Vec3D<T>> {
727 hrleIndex faceIdx = cellIt.getIndices();
728 if ((corner >> axis) & 1)
729 faceIdx[axis]++;
730
731 if (faceNodes[axis].find(faceIdx) != faceNodes[axis].end()) {
732 unsigned nodeId = faceNodes[axis][faceIdx];
733 Vec3D<T> pos = mesh->nodes[nodeId];
734 for (int d = 0; d < 3; ++d)
735 pos[d] = pos[d] - cellIt.getIndices(d);
736 return pos;
737 }
738
739 Vec3D<T> N1 = getNormal(n1, cellIt, inverted);
740 Vec3D<T> N2 = getNormal(n2, cellIt, inverted);
741
742 if (std::abs(DotProduct(N1, N2)) >= 0.8)
743 return std::nullopt;
744
745 int u = (axis + 1) % 3;
746 int v = (axis + 2) % 3;
747
748 int axis1 = 0;
749 while ((corner ^ n1) != (1 << axis1))
750 axis1++;
751 T t1 = getInterp(corner, n1, cellIt, inverted);
752
753 int axis2 = 0;
754 while ((corner ^ n2) != (1 << axis2))
755 axis2++;
756 T t2 = getInterp(corner, n2, cellIt, inverted);
757
758 Vec3D<T> P1;
759 P1[0] = ((corner >> 0) & 1);
760 P1[1] = ((corner >> 1) & 1);
761 P1[2] = ((corner >> 2) & 1);
762 P1[axis1] = ((corner >> axis1) & 1) ? (1.0 - t1) : t1;
763
764 Vec3D<T> P2;
765 P2[0] = ((corner >> 0) & 1);
766 P2[1] = ((corner >> 1) & 1);
767 P2[2] = ((corner >> 2) & 1);
768 P2[axis2] = ((corner >> axis2) & 1) ? (1.0 - t2) : t2;
769
770 double det = N1[u] * N2[v] - N1[v] * N2[u];
771 if (std::abs(det) < 1e-6)
772 return std::nullopt;
773
774 double c1 = N1[u] * P1[u] + N1[v] * P1[v];
775 double c2 = N2[u] * P2[u] + N2[v] * P2[v];
776
777 Vec3D<T> P;
778 P[axis] = P1[axis];
779 P[u] = (c1 * N2[v] - c2 * N1[v]) / det;
780 P[v] = (c2 * N1[u] - c1 * N2[u]) / det;
781
782 if (Norm2(P - P1) > 9.0 || Norm2(P - P2) > 9.0)
783 return std::nullopt;
784
785 return P;
786 };
787
788 auto commitFace = [&](int axis, int corner, Vec3D<T> P) -> unsigned {
789 hrleIndex faceIdx = cellIt.getIndices();
790 if ((corner >> axis) & 1)
791 faceIdx[axis]++;
792
793 if (faceNodes[axis].find(faceIdx) != faceNodes[axis].end()) {
794 return faceNodes[axis][faceIdx];
795 }
796
797 Vec3D<T> globalP = P;
798 for (int d = 0; d < 3; ++d)
799 globalP[d] = (globalP[d] + cellIt.getIndices(d));
800 unsigned nodeId = insertNode(globalP);
801 faceNodes[axis][faceIdx] = nodeId;
802 if (updatePointData && newDataSourceIds)
803 newDataSourceIds->push_back(cellIt.getCorner(corner).getPointId());
804
805 stitchToNeighbor(cellIt, axis, (corner >> axis) & 1, nodeId, nodes,
806 valueIt);
807
808 return nodeId;
809 };
810
811 int D_corner = A ^ (1 << axisB);
812 int A_z = A ^ (1 << axisZ);
813 int B_z = B ^ (1 << axisZ);
814 int C_z = C ^ (1 << axisZ);
815
816 auto P_A_opt = calculateFace(axisA, A, D_corner, A_z);
817 auto P_B_opt = calculateFace(axisB, B, D_corner, B_z);
818
819 if (!P_A_opt.has_value() || !P_B_opt.has_value())
820 return false;
821
822 auto const &P_A = P_A_opt.value();
823 auto const &P_B = P_B_opt.value();
824
825 // Construct S
826 Vec3D<T> S;
827 S[axisA] = P_B[axisA];
828 S[axisB] = P_A[axisB];
829 S[axisZ] = (P_A[axisZ] + P_B[axisZ]) * 0.5;
830
831 unsigned nS = insertNode([&]() {
832 Vec3D<T> gS = S;
833 for (int i = 0; i < 3; ++i)
834 gS[i] = (gS[i] + cellIt.getIndices(i));
835 return gS;
836 }());
837 if (updatePointData && newDataSourceIds)
838 newDataSourceIds->push_back(cellIt.getCorner(C).getPointId());
839
840 // Calculate S_Face
841 Vec3D<T> S_Face = S;
842 S_Face[axisZ] = static_cast<T>((C >> axisZ) & 1);
843
844 unsigned nS_Face;
845 auto faceIdxZ = cellIt.getIndices();
846 if ((C >> axisZ) & 1)
847 faceIdxZ[axisZ]++;
848
849 if (faceNodes[axisZ].find(faceIdxZ) != faceNodes[axisZ].end()) {
850 nS_Face = faceNodes[axisZ][faceIdxZ];
851 } else {
852 nS_Face = insertNode([&]() {
853 Vec3D<T> gS = S_Face;
854 for (int i = 0; i < 3; ++i)
855 gS[i] = (gS[i] + cellIt.getIndices(i));
856 return gS;
857 }());
858 faceNodes[axisZ][faceIdxZ] = nS_Face;
859 if (updatePointData && newDataSourceIds)
860 newDataSourceIds->push_back(cellIt.getCorner(C).getPointId());
861
862 stitchToNeighbor(cellIt, axisZ, (C >> axisZ) & 1, nS_Face, nodes,
863 valueIt);
864 }
865
866 // Get face nodes
867 unsigned nNA = commitFace(axisA, A, P_A);
868 unsigned nNB = commitFace(axisB, B, P_B);
869
870 // Get boundary intersection nodes
871 auto getEdgeNode = [&](int v1, int v2) {
872 int edgeIdx = -1;
873 for (int e = 0; e < 12; ++e) {
874 if ((corner0[e] == static_cast<unsigned>(v1) &&
875 corner1[e] == static_cast<unsigned>(v2)) ||
876 (corner0[e] == static_cast<unsigned>(v2) &&
877 corner1[e] == static_cast<unsigned>(v1))) {
878 edgeIdx = e;
879 break;
880 }
881 }
882 return this->getNode(cellIt, edgeIdx, nodes, newDataSourceIds);
883 };
884
885 unsigned nI_AD = getEdgeNode(A, D_corner);
886 unsigned nI_AZ = getEdgeNode(A, A_z);
887 unsigned nI_BD = getEdgeNode(B, D_corner);
888 unsigned nI_BZ = getEdgeNode(B, B_z);
889 unsigned nI_CZ = getEdgeNode(C, C_z);
890
891 // Winding order
892 int parity = (C & 1) + ((C >> 1) & 1) + ((C >> 2) & 1);
893 bool flip = (parity % 2 != 0) ^ inverted;
894
895 int vA = 0;
896 while ((A ^ C) != (1 << vA))
897 vA++;
898 int vB = 0;
899 while ((B ^ C) != (1 << vB))
900 vB++;
901 bool is_cyclic = ((vA + 1) % 3 == vB);
902 if (!is_cyclic) {
903 std::swap(nNA, nNB);
904 std::swap(nI_AD, nI_BD);
905 std::swap(nI_AZ, nI_BZ);
906 }
907
908 if (!flip) {
909 insertElement({nS, nNA, nI_AD});
910 insertElement({nS, nI_AD, nS_Face});
911 insertElement({nS, nS_Face, nI_BD});
912 insertElement({nS, nI_BD, nNB});
913 insertElement({nS, nNB, nI_BZ});
914 insertElement({nS, nI_BZ, nI_CZ});
915 insertElement({nS, nI_CZ, nI_AZ});
916 insertElement({nS, nI_AZ, nNA});
917 } else {
918 insertElement({nS, nI_AD, nNA});
919 insertElement({nS, nS_Face, nI_AD});
920 insertElement({nS, nI_BD, nS_Face});
921 insertElement({nS, nNB, nI_BD});
922 insertElement({nS, nI_BZ, nNB});
923 insertElement({nS, nI_CZ, nI_BZ});
924 insertElement({nS, nI_AZ, nI_CZ});
925 insertElement({nS, nNA, nI_AZ});
926 }
927
928 return true;
929 }
930
931 // Generates geometry for a sharp edge (2 active corners) in 3D (canonical
932 // orientation)
933 template <int Dim = D>
934 std::enable_if_t<Dim == 3, bool> generateCanonicalSharpEdge3D(
935 ConstSparseCellIterator &cellIt, int transform, int axis, bool inverted,
936 std::map<hrleIndex, unsigned> *nodes,
937 std::map<hrleIndex, unsigned> *faceNodes,
938 std::vector<unsigned> *newDataSourceIds, ConstSparseIterator &valueIt) {
939 assert(normalVectorData &&
940 "Normal vector data must be computed for sharp edge generation.");
941
942 // Helper to map global vector to canonical frame
943 auto toCanonical = [&](Vec3D<T> v) {
944 Vec3D<T> res;
945 // 1. Apply reflection (transform)
946 if (transform & 1)
947 v[0] = -v[0];
948 if (transform & 2)
949 v[1] = -v[1];
950 if (transform & 4)
951 v[2] = -v[2];
952 // 2. Apply rotation (axis permutation)
953 // axis=0: XYZ -> XYZ (0,1,2)
954 // axis=1: XYZ -> YZX (1,2,0) - puts Y in X place
955 // axis=2: XYZ -> ZXY (2,0,1) - puts Z in X place
956 if (axis == 0)
957 res = v;
958 else if (axis == 1)
959 res = Vec3D<T>{v[1], v[2], v[0]};
960 else
961 res = Vec3D<T>{v[2], v[0], v[1]};
962 return res;
963 };
964
965 // Helper to map canonical point back to global frame
966 auto fromCanonical = [&](Vec3D<T> v) {
967 Vec3D<T> res;
968 // 1. Inverse rotation
969 if (axis == 0)
970 res = v;
971 else if (axis == 1)
972 res = Vec3D<T>{v[2], v[0], v[1]};
973 else
974 res = Vec3D<T>{v[1], v[2], v[0]};
975 // 2. Inverse reflection (same as forward for 0/1 swap)
976 if (transform & 1)
977 res[0] = T(1.0) - res[0];
978 if (transform & 2)
979 res[1] = T(1.0) - res[1];
980 if (transform & 4)
981 res[2] = T(1.0) - res[2];
982 return res;
983 };
984
985 // Calculate face nodes P0 (at x=0) and P1 (at x=1) in canonical frame
986 Vec3D<T> P[2];
987 unsigned faceNodeIds[2];
988 bool nodeExists[2] = {false, false};
989
990 // Inverse map indices helper
991 auto mapIdx = [&](int c) {
992 // c is canonical index.
993 // 1. Inverse rotation:
994 int x = (c & 1), y = (c >> 1) & 1, z = (c >> 2) & 1;
995 int gx, gy, gz;
996 if (axis == 0) {
997 gx = x;
998 gy = y;
999 gz = z;
1000 } else if (axis == 1) {
1001 gx = z;
1002 gy = x;
1003 gz = y;
1004 } else {
1005 gx = y;
1006 gy = z;
1007 gz = x;
1008 }
1009
1010 // 2. Inverse reflection
1011 if (transform & 1)
1012 gx = 1 - gx;
1013 if (transform & 2)
1014 gy = 1 - gy;
1015 if (transform & 4)
1016 gz = 1 - gz;
1017
1018 return gx | (gy << 1) | (gz << 2);
1019 };
1020
1021 // First pass: Calculate and check validity without modifying mesh
1022 for (int k = 0; k < 2; ++k) {
1023 // Check if face node already exists
1024 // k=0 -> x=0 face (left). In global, this corresponds to the face
1025 // perpendicular to 'axis' at the 'transform' side. If transform has bit
1026 // 'axis' set, then x=0 in canonical is x=1 in global (relative to cell
1027 // origin). We need to be careful with the face index key. Let's compute
1028 // the global index of the face.
1029 hrleIndex faceIdx = cellIt.getIndices();
1030 // If k=0 (canonical x=0), and transform has bit 'axis' set (flipped),
1031 // this is global x=1 face. If k=1 (canonical x=1), and transform has bit
1032 // 'axis' set, this is global x=0 face.
1033 bool isHighFace = (k == 1) ^ ((transform >> axis) & 1);
1034 if (isHighFace)
1035 faceIdx[axis]++;
1036
1037 auto it = faceNodes[axis].find(faceIdx);
1038 if (it != faceNodes[axis].end()) {
1039 nodeExists[k] = true;
1040 faceNodeIds[k] = it->second;
1041 Vec3D<T> relPos = mesh->nodes[faceNodeIds[k]];
1042 for (int d = 0; d < 3; ++d) {
1043 relPos[d] -= static_cast<T>(cellIt.getIndices(d));
1044 }
1045 P[k] = toCanonical(relPos);
1046 } else {
1047 // Calculate P[k]
1048 // For k=0 (x=0): Active corner is 0 (000). Neighbors 2 (010) and 4
1049 // (001). For k=1 (x=1): Active corner is 1 (100). Neighbors 3 (110) and
1050 // 5 (101). We need to map these canonical indices back to global to get
1051 // normals/values.
1052
1053 int c0 = k; // 0 or 1
1054 int c_y = c0 | 2; // Neighbor in Y (canonical)
1055 int c_z = c0 | 4; // Neighbor in Z (canonical)
1056
1057 c0 = mapIdx(c0);
1058 c_y = mapIdx(c_y);
1059 c_z = mapIdx(c_z);
1060
1061 Vec3D<T> n_y = toCanonical(getNormal(c_y, cellIt, inverted));
1062 Vec3D<T> n_z = toCanonical(getNormal(c_z, cellIt, inverted));
1063
1064 if (std::abs(DotProduct(n_y, n_z)) >= 0.8) {
1065 return false;
1066 }
1067
1068 // Solve 2D problem in YZ plane
1069 // Line 1: passes through intersection on edge c0-c_y. Normal n_y
1070 // (projected). Line 2: passes through intersection on edge c0-c_z.
1071 // Normal n_z (projected).
1072 T t_y = getInterp(c0, c_y, cellIt, inverted); // Fraction along Y axis
1073 T t_z = getInterp(c0, c_z, cellIt, inverted); // Fraction along Z axis
1074
1075 // In canonical local frame relative to c0:
1076 // Point on Y-edge: (0, t_y, 0)
1077 // Point on Z-edge: (0, 0, t_z)
1078 // Normals n_y and n_z.
1079 // Solve for (y, z):
1080 // n_y.y * (y - t_y) + n_y.z * (z - 0) = 0
1081 // n_z.y * (y - 0) + n_z.z * (z - t_z) = 0
1082
1083 double det = n_y[1] * n_z[2] - n_y[2] * n_z[1];
1084 if (std::abs(det) < 1e-6)
1085 return false;
1086
1087 double d1 = n_y[1] * t_y;
1088 double d2 = n_z[2] * t_z;
1089
1090 double y = (d1 * n_z[2] - d2 * n_y[2]) / det;
1091 double z = (d2 * n_y[1] - d1 * n_z[1]) / det;
1092
1093 P[k] = Vec3D<T>{(T)k, (T)y, (T)z};
1094
1095 // Check if the point is too far away from the intersection points
1096 // 2 grid deltas = 2.0 in canonical coordinates
1097 if (Norm2(P[k] - Vec3D<T>{(T)k, t_y, 0}) > 9.0 ||
1098 Norm2(P[k] - Vec3D<T>{(T)k, 0, t_z}) > 9.0) {
1099 return false;
1100 }
1101 }
1102 }
1103
1104 // Second pass: Commit nodes and stitch
1105 for (int k = 0; k < 2; ++k) {
1106 if (!nodeExists[k]) {
1107 int c0 = k;
1108 hrleIndex faceIdx = cellIt.getIndices();
1109 bool isHighFace = (k == 1) ^ ((transform >> axis) & 1);
1110 if (isHighFace)
1111 faceIdx[axis]++;
1112
1113 Vec3D<T> globalP = fromCanonical(P[k]);
1114 for (int d = 0; d < 3; ++d)
1115 globalP[d] = (globalP[d] + cellIt.getIndices(d));
1116
1117 faceNodeIds[k] = insertNode(globalP);
1118 faceNodes[axis][faceIdx] = faceNodeIds[k];
1119 if (updatePointData && newDataSourceIds)
1120 newDataSourceIds->push_back(
1121 cellIt.getCorner(mapIdx(c0)).getPointId());
1122
1123 stitchToNeighbor(cellIt, axis, isHighFace, faceNodeIds[k], nodes,
1124 valueIt);
1125 }
1126 }
1127
1128 // Calculate edge intersection points in canonical frame
1129 // E02: on edge 0-2 (Y axis). (0, t_02, 0)
1130 // E04: on edge 0-4 (Z axis). (0, 0, t_04)
1131 // E13: on edge 1-3 (Y axis). (1, t_13, 0)
1132 // E15: on edge 1-5 (Z axis). (1, 0, t_15)
1133
1134 // We need to get the node IDs for these. We can use the existing getNode
1135 // helper, but we need to map canonical edges to global edges.
1136 auto getEdgeNode = [&](int c1, int c2) {
1137 // Map canonical corners to global
1138 int g1 = mapIdx(c1);
1139 int g2 = mapIdx(c2);
1140
1141 // Find edge index
1142 int edgeIdx = -1;
1143 for (int e = 0; e < 12; ++e) {
1144 if ((corner0[e] == static_cast<unsigned>(g1) &&
1145 corner1[e] == static_cast<unsigned>(g2)) ||
1146 (corner0[e] == static_cast<unsigned>(g2) &&
1147 corner1[e] == static_cast<unsigned>(g1))) {
1148 edgeIdx = e;
1149 break;
1150 }
1151 }
1152 return this->getNode(cellIt, edgeIdx, nodes, newDataSourceIds);
1153 };
1154
1155 unsigned n02 = getEdgeNode(0, 2);
1156 unsigned n04 = getEdgeNode(0, 4);
1157 unsigned n13 = getEdgeNode(1, 3);
1158 unsigned n15 = getEdgeNode(1, 5);
1159
1160 // Generate Quads (split into triangles)
1161 // Quad 1 (Y-interface): E02, P0, P1, E13. Normal +Y.
1162 // Winding for +Y normal: E02 -> P0 -> P1 -> E13.
1163 insertElement({n02, faceNodeIds[0], faceNodeIds[1]});
1164 insertElement({n02, faceNodeIds[1], n13});
1165
1166 // Quad 2 (Z-interface): E04, E15, P1, P0. Normal +Z.
1167 // Winding for +Z normal: E04 -> E15 -> P1 -> P0.
1168 insertElement({n04, n15, faceNodeIds[1]});
1169 insertElement({n04, faceNodeIds[1], faceNodeIds[0]});
1170
1171 return true;
1172 }
1173
1174 // Wrapper for 3D sharp edge generation handling different
1175 // rotations/reflections
1176 template <int Dim = D>
1177 std::enable_if_t<Dim == 3, bool> generateSharpEdge3D(
1178 ConstSparseCellIterator &cellIt, int countNeg, int countPos, int negMask,
1179 int posMask, std::map<hrleIndex, unsigned> *nodes,
1180 std::map<hrleIndex, unsigned> *faceNodes,
1181 std::vector<unsigned> *newDataSourceIds, ConstSparseIterator &valueIt) {
1182
1183 bool inverted = false;
1184 int mask = 0;
1185 if (countNeg == 2) {
1186 mask = negMask;
1187 inverted = false;
1188 } else if (countPos == 2) {
1189 mask = posMask;
1190 inverted = true;
1191 } else {
1192 return false;
1193 }
1194
1195 // Check if the two vertices share an edge
1196 int v1 = -1, v2 = -1;
1197 for (int i = 0; i < 8; ++i) {
1198 if ((mask >> i) & 1) {
1199 if (v1 == -1)
1200 v1 = i;
1201 else
1202 v2 = i;
1203 }
1204 }
1205
1206 int diff = v1 ^ v2;
1207 if ((diff & (diff - 1)) != 0)
1208 return false; // Not connected by a single edge
1209
1210 int axis = 0;
1211 while ((diff >> (axis + 1)) > 0)
1212 axis++;
1213
1214 // Transform maps v1 to 0.
1215 int transform = v1;
1216
1217 return generateCanonicalSharpEdge3D(cellIt, transform, axis, inverted,
1218 nodes, faceNodes, newDataSourceIds,
1219 valueIt);
1220 }
1221
1222 // Generates geometry for a sharp corner (1 active corner) in 3D (canonical
1223 // orientation)
1225 int transform, bool inverted,
1226 std::map<hrleIndex, unsigned> *nodes,
1227 std::map<hrleIndex, unsigned> *faceNodes,
1228 std::vector<unsigned> *newDataSourceIds,
1229 ConstSparseIterator &valueIt,
1230 Vec3D<T> &cornerPos) {
1231 assert(normalVectorData &&
1232 "Normal vector data must be available for sharp corner generation");
1233
1234 // Helper to map global vector to canonical frame (just reflection)
1235 auto toCanonical = [&](Vec3D<T> v) {
1236 if (transform & 1)
1237 v[0] = -v[0];
1238 if (transform & 2)
1239 v[1] = -v[1];
1240 if (transform & 4)
1241 v[2] = -v[2];
1242 return v;
1243 };
1244
1245 // Helper to map canonical point back to global frame
1246 auto fromCanonical = [&](Vec3D<T> v) {
1247 if (transform & 1)
1248 v[0] = T(1.0) - v[0];
1249 if (transform & 2)
1250 v[1] = T(1.0) - v[1];
1251 if (transform & 4)
1252 v[2] = T(1.0) - v[2];
1253 return v;
1254 };
1255
1256 Vec3D<T> norm1, norm2, norm3;
1257 double d1, d2, d3;
1258 Vec3D<T> P1, P2, P4;
1259
1260 const int g1 = transform ^ 1;
1261 const int g2 = transform ^ 2;
1262 const int g4 = transform ^ 4;
1263
1264 norm1 = toCanonical(getNormal(g1, cellIt, inverted));
1265 norm2 = toCanonical(getNormal(g2, cellIt, inverted));
1266 norm3 = toCanonical(getNormal(g4, cellIt, inverted));
1267
1268 const T t1 = getInterp(transform, g1, cellIt, inverted);
1269 const T t2 = getInterp(transform, g2, cellIt, inverted);
1270 const T t4 = getInterp(transform, g4, cellIt, inverted);
1271
1272 P1 = {t1, 0, 0};
1273 P2 = {0, t2, 0};
1274 P4 = {0, 0, t4};
1275
1276 d1 = DotProduct(norm1, P1);
1277 d2 = DotProduct(norm2, P2);
1278 d3 = DotProduct(norm3, P4);
1279
1280 if (std::abs(DotProduct(norm1, norm2)) >= 0.8 ||
1281 std::abs(DotProduct(norm1, norm3)) >= 0.8 ||
1282 std::abs(DotProduct(norm2, norm3)) >= 0.8) {
1283 return false;
1284 }
1285
1286 // We solve the system of 3 plane equations:
1287 // norm1 . S = d1
1288 // norm2 . S = d2
1289 // norm3 . S = d3
1290 // This is a linear system Ax = b, where A is the matrix of normals, x is
1291 // the corner position S, and b is the vector of dot products.
1292
1293 // Using Cramer's rule to solve the system
1294
1295 const Vec3D<T> c23 = CrossProduct(norm2, norm3);
1296 const Vec3D<T> c31 = CrossProduct(norm3, norm1);
1297 const Vec3D<T> c12 = CrossProduct(norm1, norm2);
1298
1299 const double det = DotProduct(norm1, c23);
1300
1301 if (std::abs(det) < epsilon)
1302 return false; // Planes are parallel or linearly dependent
1303
1304 const T invDet = 1.0 / det;
1305 Vec3D<T> S;
1306 for (int i = 0; i < 3; ++i) {
1307 S[i] = (d1 * c23[i] + d2 * c31[i] + d3 * c12[i]) * invDet;
1308 }
1309
1310 if (Norm2(S - P1) > 9.0 || Norm2(S - P2) > 9.0 || Norm2(S - P4) > 9.0) {
1311 return false;
1312 }
1313
1314 // Transform corner position back from canonical to global coordinates
1315 Vec3D<T> globalS = fromCanonical(S);
1316 for (int d = 0; d < 3; ++d) {
1317 cornerPos[d] = (globalS[d] + cellIt.getIndices(d));
1318 }
1319
1320 return true;
1321 }
1322
1323 // Wrapper for 3D sharp corner generation handling different
1324 // rotations/reflections
1325 template <int Dim = D>
1326 std::enable_if_t<Dim == 3, bool> generateSharpCorner3D(
1327 ConstSparseCellIterator &cellIt, int countNeg, int countPos, int negMask,
1328 int posMask, std::map<hrleIndex, unsigned> *nodes,
1329 std::map<hrleIndex, unsigned> &cornerNodes,
1330 std::map<hrleIndex, unsigned> *faceNodes,
1331 std::vector<unsigned> *newDataSourceIds, ConstSparseIterator &valueIt) {
1332 assert(normalVectorData &&
1333 "Normal vector data must be available for sharp corner generation");
1334
1335 bool inverted = false;
1336 int transform = -1;
1337 // bool is3vs5 = false;
1338
1339 if (countNeg == 1) {
1340 transform = 0;
1341 while (!((negMask >> transform) & 1))
1342 transform++;
1343 inverted = false;
1344 } else if (countPos == 1) {
1345 transform = 0;
1346 while (!((posMask >> transform) & 1))
1347 transform++;
1348 inverted = true;
1349 }
1350
1351 if (transform == -1)
1352 return false;
1353
1354 Vec3D<T> cornerPos;
1355 if (generateCanonicalSharpCorner3D(cellIt, transform, inverted, nodes,
1356 faceNodes, newDataSourceIds, valueIt,
1357 cornerPos)) {
1358 unsigned nCorner;
1359 hrleIndex cornerIdx = cellIt.getIndices();
1360 cornerIdx += viennahrle::BitMaskToIndex<D>(transform);
1361
1362 auto it = cornerNodes.find(cornerIdx);
1363 if (it != cornerNodes.end()) {
1364 nCorner = it->second;
1365 } else {
1366 nCorner = insertNode(cornerPos);
1367 cornerNodes[cornerIdx] = nCorner;
1368
1369 // Store this sharp corner node for potential use by derived classes
1370 matSharpCornerNodes.push_back({nCorner, cornerPos});
1371
1372 if (updatePointData && newDataSourceIds)
1373 newDataSourceIds->push_back(cellIt.getCorner(transform).getPointId());
1374 }
1375
1376 // Get edge nodes
1377 auto getEdgeNode = [&](int neighbor) {
1378 int v1 = transform;
1379 int v2 = neighbor;
1380 int edgeIdx = -1;
1381 for (int e = 0; e < 12; ++e) {
1382 if ((corner0[e] == static_cast<unsigned>(v1) &&
1383 corner1[e] == static_cast<unsigned>(v2)) ||
1384 (corner0[e] == static_cast<unsigned>(v2) &&
1385 corner1[e] == static_cast<unsigned>(v1))) {
1386 edgeIdx = e;
1387 break;
1388 }
1389 }
1390 return this->getNode(cellIt, edgeIdx, nodes, newDataSourceIds);
1391 };
1392
1393 unsigned n1 = getEdgeNode(transform ^ 1);
1394 unsigned n2 = getEdgeNode(transform ^ 2);
1395 unsigned n4 = getEdgeNode(transform ^ 4);
1396
1397 // Try to generate cube corner topology
1398 {
1399 // Re-calculate canonical normals and plane constants
1400 auto toCanonical = [&](Vec3D<T> v) {
1401 if (transform & 1)
1402 v[0] = -v[0];
1403 if (transform & 2)
1404 v[1] = -v[1];
1405 if (transform & 4)
1406 v[2] = -v[2];
1407 return v;
1408 };
1409
1410 auto fromCanonical = [&](Vec3D<T> v) {
1411 if (transform & 1)
1412 v[0] = T(1.0) - v[0];
1413 if (transform & 2)
1414 v[1] = T(1.0) - v[1];
1415 if (transform & 4)
1416 v[2] = T(1.0) - v[2];
1417 return v;
1418 };
1419
1420 Vec3D<T> norm1, norm2, norm3;
1421 double d1, d2, d3;
1422
1423 // Identify neighbors
1424 int n1_idx = -1, n2_idx = -1, n3_idx = -1;
1425 int neighbors[] = {transform ^ 1, transform ^ 2, transform ^ 4};
1426
1427 for (int n_idx : neighbors) {
1428 T val = cellIt.getCorner(n_idx).getValue();
1429 // If signs are opposite (product < 0), this is a face neighbor
1430 if (val * cellIt.getCorner(transform).getValue() < 0) {
1431 if (n1_idx == -1)
1432 n1_idx = n_idx;
1433 else
1434 n2_idx = n_idx;
1435 } else {
1436 n3_idx = n_idx;
1437 }
1438 }
1439
1440 // Standard 1-vs-7 case
1441 norm1 = toCanonical(getNormal(transform ^ 1, cellIt, inverted));
1442 norm2 = toCanonical(getNormal(transform ^ 2, cellIt, inverted));
1443 norm3 = toCanonical(getNormal(transform ^ 4, cellIt, inverted));
1444
1445 d1 = norm1[0] * getInterp(transform, transform ^ 1, cellIt, inverted);
1446 d2 = norm2[1] * getInterp(transform, transform ^ 2, cellIt, inverted);
1447 d3 = norm3[2] * getInterp(transform, transform ^ 4, cellIt, inverted);
1448
1449 auto solve2x2 = [](double a1, double b1, double c1, double a2,
1450 double b2, double c2, T &res1, T &res2) {
1451 double det = a1 * b2 - a2 * b1;
1452 if (std::abs(det) < 1e-6)
1453 return false;
1454 res1 = (c1 * b2 - c2 * b1) / det;
1455 res2 = (a1 * c2 - a2 * c1) / det;
1456 return true;
1457 };
1458
1459 Vec3D<T> Fx, Fy, Fz;
1460 Fx[0] = 0.0;
1461 Fy[1] = 0.0;
1462 Fz[2] = 0.0;
1463
1464 bool valid = true;
1465 valid &= solve2x2(norm2[1], norm2[2], d2 - norm2[0] * Fx[0], norm3[1],
1466 norm3[2], d3 - norm3[0] * Fx[0], Fx[1], Fx[2]);
1467 valid &= solve2x2(norm1[0], norm1[2], d1 - norm1[1] * Fy[1], norm3[0],
1468 norm3[2], d3 - norm3[1] * Fy[1], Fy[0], Fy[2]);
1469 valid &= solve2x2(norm1[0], norm1[1], d1 - norm1[2] * Fz[2], norm2[0],
1470 norm2[1], d2 - norm2[2] * Fz[2], Fz[0], Fz[1]);
1471
1472 auto checkBounds = [](Vec3D<T> p) {
1473 return p[0] >= -1e-4 && p[0] <= 1.0 + 1e-4 && p[1] >= -1e-4 &&
1474 p[1] <= 1.0 + 1e-4 && p[2] >= -1e-4 && p[2] <= 1.0 + 1e-4;
1475 };
1476
1477 if (valid && checkBounds(Fx) && checkBounds(Fy) && checkBounds(Fz)) {
1478 auto getFaceNode = [&](int axis, Vec3D<T> pos) {
1479 hrleIndex faceIdx = cellIt.getIndices();
1480 if ((transform >> axis) & 1)
1481 faceIdx[axis]++;
1482
1483 if (faceNodes[axis].find(faceIdx) != faceNodes[axis].end()) {
1484 return faceNodes[axis][faceIdx];
1485 }
1486 Vec3D<T> globalPos = fromCanonical(pos);
1487 for (int d = 0; d < 3; ++d)
1488 globalPos[d] = (globalPos[d] + cellIt.getIndices(d));
1489 unsigned nodeId = insertNode(globalPos);
1490 faceNodes[axis][faceIdx] = nodeId;
1491 if (updatePointData && newDataSourceIds)
1492 newDataSourceIds->push_back(
1493 cellIt.getCorner(transform).getPointId());
1494 stitchToNeighbor(cellIt, axis, (transform >> axis) & 1, nodeId,
1495 nodes, valueIt);
1496 return nodeId;
1497 };
1498
1499 unsigned nFx = getFaceNode(0, Fx);
1500 unsigned nFy = getFaceNode(1, Fy);
1501 unsigned nFz = getFaceNode(2, Fz);
1502
1503 // Calculate parity of the transform (number of reflections)
1504 int parity =
1505 (transform & 1) + ((transform >> 1) & 1) + ((transform >> 2) & 1);
1506
1507 // Flip winding if parity is odd XOR inverted is true.
1508 bool flip = (parity % 2 != 0) ^ inverted;
1509
1510 if (!flip) {
1511 insertElement({nCorner, nFy, n1});
1512 insertElement({nCorner, n1, nFz});
1513 insertElement({nCorner, nFz, n2});
1514 insertElement({nCorner, n2, nFx});
1515 insertElement({nCorner, nFx, n4});
1516 insertElement({nCorner, n4, nFy});
1517 } else {
1518 insertElement({nCorner, n1, nFy});
1519 insertElement({nCorner, nFz, n1});
1520 insertElement({nCorner, n2, nFz});
1521 insertElement({nCorner, nFx, n2});
1522 insertElement({nCorner, n4, nFx});
1523 insertElement({nCorner, nFy, n4});
1524 }
1525 return true;
1526 }
1527 }
1528
1529 // Triangles
1530 // Cycle around normal (1,1,1) in canonical is 1->2->4->1 (x->y->z->x)
1531 // Triangles: (n1, S, n4), (n4, S, n2), (n2, S, n1)
1532 // If inverted, flip winding.
1533
1534 // Calculate parity of the transform (number of reflections)
1535 int parity =
1536 (transform & 1) + ((transform >> 1) & 1) + ((transform >> 2) & 1);
1537
1538 // Flip winding if parity is odd XOR inverted is true.
1539 bool flip = (parity % 2 != 0) ^ inverted;
1540
1541 if (!flip) {
1542 insertElement({n1, nCorner, n4});
1543 insertElement({n4, nCorner, n2});
1544 insertElement({n2, nCorner, n1});
1545 } else {
1546 insertElement({n1, n4, nCorner});
1547 insertElement({n4, n2, nCorner});
1548 insertElement({n2, n1, nCorner});
1549 }
1550
1551 return true;
1552 }
1553
1554 return false;
1555 }
1556
1557 static inline bool
1558 triangleMisformed(const std::array<unsigned, D> &nodeNumbers) noexcept {
1559 if constexpr (D == 3) {
1560 return nodeNumbers[0] == nodeNumbers[1] ||
1561 nodeNumbers[0] == nodeNumbers[2] ||
1562 nodeNumbers[1] == nodeNumbers[2];
1563 } else {
1564 return nodeNumbers[0] == nodeNumbers[1];
1565 }
1566 }
1567
1568 Vec3D<T> calculateNormal(const std::array<unsigned, D> &nodeNumbers) const {
1569 if constexpr (D == 2) {
1570 return Vec3D<T>{
1571 -(mesh->nodes[nodeNumbers[1]][1] - mesh->nodes[nodeNumbers[0]][1]),
1572 mesh->nodes[nodeNumbers[1]][0] - mesh->nodes[nodeNumbers[0]][0],
1573 T(0)};
1574 } else {
1575 return calculateNormal(mesh->nodes[nodeNumbers[0]],
1576 mesh->nodes[nodeNumbers[1]],
1577 mesh->nodes[nodeNumbers[2]]);
1578 }
1579 }
1580
1581 static inline Vec3D<T> calculateNormal(const Vec3D<T> &nodeA,
1582 const Vec3D<T> &nodeB,
1583 const Vec3D<T> &nodeC) noexcept {
1584 return CrossProduct(nodeB - nodeA, nodeC - nodeA);
1585 }
1586
1587 T getInterp(int p_a, int p_b, const ConstSparseCellIterator &cellIt,
1588 bool inverted) const {
1589 auto getValue = [&](int idx) {
1590 T val = cellIt.getCorner(idx).getValue();
1591 return inverted ? -val : val;
1592 };
1593 const T v_a = getValue(p_a);
1594 const T v_b = getValue(p_b);
1595 if (std::abs(v_a - v_b) < epsilon)
1596 return T(0.5);
1597 return v_a / (v_a - v_b);
1598 }
1599
1600 bool insertElement(const std::array<unsigned, D> &nodeNumbers) {
1601 if (triangleMisformed(nodeNumbers))
1602 return false;
1603
1604 auto elementI3 = [](const std::array<unsigned, D> &element) -> I3 {
1605 if constexpr (D == 2)
1606 return {(int)element[0], (int)element[1], 0};
1607 else
1608 return {(int)element[0], (int)element[1], (int)element[2]};
1609 };
1610
1611 if (uniqueElements.insert(elementI3(nodeNumbers)).second) {
1612 auto normal = calculateNormal(nodeNumbers);
1613
1614 T n2 = DotProduct(normal, normal);
1615 if (n2 > epsilon) {
1616 mesh->insertNextElement(nodeNumbers);
1617 T invn = T(1) / std::sqrt(n2);
1618 for (int d = 0; d < D; d++) {
1619 normal[d] *= invn;
1620 }
1621 currentNormals.push_back(normal);
1623
1624 return true;
1625 }
1626 }
1627
1628 return false;
1629 }
1630
1631 unsigned insertNode(Vec3D<T> const &pos) {
1632 auto quantize = [this](const Vec3D<T> &p) -> I3 {
1633 const T inv = T(1) / minNodeDistanceFactor;
1634 return {(int)std::llround(p[0] * inv), (int)std::llround(p[1] * inv),
1635 (int)std::llround(p[2] * inv)};
1636 };
1637
1638 if (minNodeDistanceFactor > 0) {
1639 if (auto it = nodeIdByBin.find(quantize(pos)); it != nodeIdByBin.end()) {
1640 // Node already exists nearby. Average position to improve accuracy.
1641 auto nodeIdx = it->second;
1642 mesh->nodes[nodeIdx] = (mesh->nodes[nodeIdx] + pos) * T(0.5);
1643 return nodeIdx;
1644 }
1645 }
1646
1647 // insert new node
1648 unsigned newNodeId = mesh->insertNextNode(pos);
1649 if (minNodeDistanceFactor > 0) {
1650 nodeIdByBin.emplace(quantize(pos), newNodeId);
1651 }
1652 mesh->minimumExtent = Min(mesh->minimumExtent, pos);
1653 mesh->maximumExtent = Max(mesh->maximumExtent, pos);
1654 return newNodeId;
1655 }
1656};
1657
1658// add all template specialisations for this class
1659PRECOMPILE_PRECISION_DIMENSION(ToSurfaceMesh)
1660
1661} // namespace viennals
constexpr int D
Definition Epitaxy.cpp:12
double T
Definition Epitaxy.cpp:13
static const int * polygonize2d(unsigned int signs)
signs = signs of the corners in lexicographic order (1bit per corner)
Definition lsMarchingCubes.hpp:312
static const int * polygonize3d(unsigned int signs)
signs = signs of the corners in lexicographic order (1bit per corner)
Definition lsMarchingCubes.hpp:324
This algorithm is used to compute the normal vectors for all points with level set values <= maxValue...
Definition lsCalculateNormalVectors.hpp:40
static constexpr char normalVectorsLabel[]
Definition lsCalculateNormalVectors.hpp:52
void apply()
Definition lsCalculateNormalVectors.hpp:95
void setMethod(NormalCalculationMethodEnum passedMethod)
Definition lsCalculateNormalVectors.hpp:79
void setMaxValue(const T passedMaxValue)
Definition lsCalculateNormalVectors.hpp:67
Class containing all information about the level set, including the dimensions of the domain,...
Definition lsDomain.hpp:27
viennahrle::Domain< T, D > DomainType
Definition lsDomain.hpp:32
Expands the levelSet to the specified number of layers. The largest value in the levelset is thus wid...
Definition lsExpand.hpp:17
void apply()
Apply the expansion to the specified width.
Definition lsExpand.hpp:44
This class holds an explicit mesh, which is always given in 3 dimensions. If it describes a 2D mesh,...
Definition lsMesh.hpp:21
SmartPointer< Mesh< T > > mesh
Definition lsToSurfaceMesh.hpp:35
ToSurfaceMesh(const SmartPointer< lsDomainType > passedLevelSet, SmartPointer< Mesh< T > > passedMesh, double mnd=0.05, double eps=1e-12)
Definition lsToSurfaceMesh.hpp:85
static constexpr unsigned int direction[12]
Definition lsToSurfaceMesh.hpp:78
std::enable_if_t< Dim==3, bool > generateSharpEdge3D(ConstSparseCellIterator &cellIt, int countNeg, int countPos, int negMask, int posMask, std::map< hrleIndex, unsigned > *nodes, std::map< hrleIndex, unsigned > *faceNodes, std::vector< unsigned > *newDataSourceIds, ConstSparseIterator &valueIt)
Definition lsToSurfaceMesh.hpp:1177
unsigned getNode(const ConstSparseCellIterator &cellIt, int edge, std::map< hrleIndex, unsigned > *nodes, std::vector< unsigned > *newDataSourceIds)
Definition lsToSurfaceMesh.hpp:386
void setMesh(SmartPointer< Mesh< T > > passedMesh)
Definition lsToSurfaceMesh.hpp:96
Vec3D< T > interpWithOffset(const unsigned p0, const T d0, const T d1, const unsigned dir, const OffsetType &offset) const
Definition lsToSurfaceMesh.hpp:338
SmartPointer< lsDomainType > currentLevelSet
Definition lsToSurfaceMesh.hpp:68
std::enable_if_t< Dim==3, bool > generateCanonicalSharpEdge3D(ConstSparseCellIterator &cellIt, int transform, int axis, bool inverted, std::map< hrleIndex, unsigned > *nodes, std::map< hrleIndex, unsigned > *faceNodes, std::vector< unsigned > *newDataSourceIds, ConstSparseIterator &valueIt)
Definition lsToSurfaceMesh.hpp:934
bool insertElement(const std::array< unsigned, D > &nodeNumbers)
Definition lsToSurfaceMesh.hpp:1600
std::enable_if_t< Dim==3, void > stitchToNeighbor(const ConstSparseCellIterator &cellIt, int axis, bool isHighFace, unsigned faceNodeId, std::map< hrleIndex, unsigned > *nodes, ConstSparseIterator &valueIt)
Definition lsToSurfaceMesh.hpp:417
bool generateSharpCorners
Definition lsToSurfaceMesh.hpp:40
T getInterp(int p_a, int p_b, const ConstSparseCellIterator &cellIt, bool inverted) const
Definition lsToSurfaceMesh.hpp:1587
std::vector< SmartPointer< lsDomainType > > levelSets
Definition lsToSurfaceMesh.hpp:34
typename lsDomainType::DomainType hrleDomainType
Definition lsToSurfaceMesh.hpp:28
static bool triangleMisformed(const std::array< unsigned, D > &nodeNumbers) noexcept
Definition lsToSurfaceMesh.hpp:1558
Vec3D< T > getNormal(int idx, const ConstSparseCellIterator &cellIt, bool inverted) const
Definition lsToSurfaceMesh.hpp:648
std::enable_if_t< Dim==2, bool > generateSharpCorner2D(ConstSparseCellIterator &cellIt, int countNeg, int countPos, int negMask, int posMask, std::map< hrleIndex, unsigned > *nodes, std::vector< unsigned > *newDataSourceIds, ConstSparseIterator &valueIt)
Definition lsToSurfaceMesh.hpp:532
std::enable_if_t< Dim==2, bool > generateCanonicalSharpCorner2D(ConstSparseCellIterator &cellIt, int transform, Vec3D< T > &cornerPos) const
Definition lsToSurfaceMesh.hpp:475
std::unordered_map< I3, unsigned, I3Hash > nodeIdByBin
Definition lsToSurfaceMesh.hpp:63
const T epsilon
Definition lsToSurfaceMesh.hpp:37
viennals::Domain< T, D > lsDomainType
Definition lsToSurfaceMesh.hpp:27
viennahrle::ConstSparseIterator< hrleDomainType > ConstSparseIterator
Definition lsToSurfaceMesh.hpp:30
bool generateCanonicalSharpCorner3D(ConstSparseCellIterator &cellIt, int transform, bool inverted, std::map< hrleIndex, unsigned > *nodes, std::map< hrleIndex, unsigned > *faceNodes, std::vector< unsigned > *newDataSourceIds, ConstSparseIterator &valueIt, Vec3D< T > &cornerPos)
Definition lsToSurfaceMesh.hpp:1224
std::enable_if_t< Dim==3, bool > generateSharpL3D(ConstSparseCellIterator &cellIt, int countNeg, int countPos, int negMask, int posMask, std::map< hrleIndex, unsigned > *nodes, std::map< hrleIndex, unsigned > *faceNodes, std::vector< unsigned > *newDataSourceIds, ConstSparseIterator &valueIt)
Definition lsToSurfaceMesh.hpp:664
viennahrle::ConstSparseCellIterator< hrleDomainType > ConstSparseCellIterator
Definition lsToSurfaceMesh.hpp:31
std::unordered_set< I3, I3Hash > uniqueElements
Definition lsToSurfaceMesh.hpp:64
void scaleMesh(const T gridDelta)
Definition lsToSurfaceMesh.hpp:328
std::vector< std::pair< unsigned, Vec3D< T > > > matSharpCornerNodes
Definition lsToSurfaceMesh.hpp:72
static constexpr unsigned int corner1[12]
Definition lsToSurfaceMesh.hpp:76
std::enable_if_t< Dim==3, bool > generateSharpCorner3D(ConstSparseCellIterator &cellIt, int countNeg, int countPos, int negMask, int posMask, std::map< hrleIndex, unsigned > *nodes, std::map< hrleIndex, unsigned > &cornerNodes, std::map< hrleIndex, unsigned > *faceNodes, std::vector< unsigned > *newDataSourceIds, ConstSparseIterator &valueIt)
Definition lsToSurfaceMesh.hpp:1326
static Vec3D< T > calculateNormal(const Vec3D< T > &nodeA, const Vec3D< T > &nodeB, const Vec3D< T > &nodeC) noexcept
Definition lsToSurfaceMesh.hpp:1581
const double minNodeDistanceFactor
Definition lsToSurfaceMesh.hpp:38
void setUpdatePointData(bool update)
Definition lsToSurfaceMesh.hpp:98
ToSurfaceMesh(double mnd=0.05, double eps=1e-12)
Definition lsToSurfaceMesh.hpp:82
unsigned insertNode(Vec3D< T > const &pos)
Definition lsToSurfaceMesh.hpp:1631
viennahrle::Index< D > hrleIndex
Definition lsToSurfaceMesh.hpp:29
bool updatePointData
Definition lsToSurfaceMesh.hpp:39
std::tuple< Vec3D< T >, std::size_t > computeNodePosition(const ConstSparseCellIterator &cellIt, int edge) const
Definition lsToSurfaceMesh.hpp:367
PointData< T >::VectorDataType * normalVectorData
Definition lsToSurfaceMesh.hpp:69
std::vector< Vec3D< T > > currentNormals
Definition lsToSurfaceMesh.hpp:65
std::vector< T > currentMaterials
Definition lsToSurfaceMesh.hpp:66
void setSharpCorners(bool check)
Definition lsToSurfaceMesh.hpp:100
virtual void apply()
Definition lsToSurfaceMesh.hpp:102
T currentMaterialId
Definition lsToSurfaceMesh.hpp:67
void setLevelSet(SmartPointer< lsDomainType > passedlsDomain)
Definition lsToSurfaceMesh.hpp:91
Vec3D< T > calculateNormal(const std::array< unsigned, D > &nodeNumbers) const
Definition lsToSurfaceMesh.hpp:1568
static constexpr unsigned int corner0[12]
Definition lsToSurfaceMesh.hpp:74
#define PRECOMPILE_PRECISION_DIMENSION(className)
Definition lsPreCompileMacros.hpp:24
Definition lsAdvect.hpp:41
@ ONE_SIDED_MIN_MOD
Definition lsCalculateNormalVectors.hpp:22
Definition lsToSurfaceMesh.hpp:49
size_t operator()(const I3 &k) const
Definition lsToSurfaceMesh.hpp:50
Definition lsToSurfaceMesh.hpp:42
bool operator==(const I3 &o) const
Definition lsToSurfaceMesh.hpp:44
int x
Definition lsToSurfaceMesh.hpp:43
int y
Definition lsToSurfaceMesh.hpp:43
int z
Definition lsToSurfaceMesh.hpp:43