ViennaLS
Loading...
Searching...
No Matches
lsToMultiSurfaceMesh.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <lsMaterialMap.hpp>
4#include <lsToSurfaceMesh.hpp>
5
6namespace viennals {
7
8using namespace viennacore;
9
10template <class NumericType, int D>
11class ToMultiSurfaceMesh : public ToSurfaceMesh<NumericType, D> {
12 using lsDomainType = viennals::Domain<NumericType, D>;
13 using hrleDomainType = typename lsDomainType::DomainType;
14 using hrleIndex = viennahrle::Index<D>;
15
28
29 SmartPointer<MaterialMap> materialMap = nullptr;
30
31 // Structure to track sharp corner nodes from each material
32 struct SharpCornerNode {
33 unsigned nodeId;
34 Vec3D<NumericType> position;
35 };
36
37 // Find the closest sharp corner node from materials below l, subject to a
38 // grid-level layer thickness check. Returns the node ID or -1 if none found.
39 //
40 // activeGridIdx : global grid index of the "active" (single-minority sign)
41 // corner of the cell in which material l's sharp corner was generated.
42 // lsLAtActive : LS value of material l at that grid corner.
43 //
44 // For conformal deposition of material l (thickness t) on material m:
45 // LS_l = LS_m - t everywhere, so
46 // delta = LS_m(activeGridIdx) - LS_l(activeGridIdx) ≈ t.
47 // Skipping snap when delta > layerThreshold prevents merging corners that
48 // belong to physically distinct surfaces separated by a real layer.
49 int findNearestLowerCorner(
50 unsigned l, const Vec3D<NumericType> &pos,
51 const std::unordered_map<unsigned, std::vector<SharpCornerNode>>
52 &sharpCornerNodes,
53 const hrleIndex &activeGridIdx, NumericType lsLAtActive) {
54 int snapId = -1;
56 // Minimum layer thickness (in grid units) that suppresses snapping.
57 const NumericType layerThreshold = NumericType(this->minNodeDistanceFactor);
58 for (unsigned m = 0; m < l; ++m) {
59 auto it = sharpCornerNodes.find(m);
60 if (it == sharpCornerNodes.end())
61 continue;
62 // Evaluate LS_m at the active grid corner of material l's corner cell.
63 // delta approximates the local layer thickness between material m and l.
64 viennahrle::ConstSparseIterator<hrleDomainType> mIt(
65 levelSets[m]->getDomain());
66 mIt.goToIndices(activeGridIdx);
67 const NumericType lsMAtActive = mIt.getValue();
68 // Only apply the layer-thickness check when the active corner is inside
69 // BOTH materials (LS_m < 0 and LS_l < 0). If LS_m > 0, the active corner
70 // lies outside material m (e.g. inside a trench cavity), meaning the two
71 // surfaces meet at a genuine junction — not a thin-layer overlap — and
72 // the snap should be allowed regardless of delta.
73 if (lsMAtActive < NumericType(0) &&
74 lsMAtActive - lsLAtActive > layerThreshold)
75 continue; // thin conformal layer present — do not snap
76 for (const auto &scn : it->second) {
77 NumericType dist2 = NumericType(0);
78 for (int i = 0; i < D; ++i) {
79 NumericType d = scn.position[i] - pos[i];
80 dist2 += d * d;
81 }
82 if (dist2 < minDist2) {
83 minDist2 = dist2;
84 snapId = scn.nodeId;
85 }
86 }
87 }
88 return snapId;
89 }
90
91 // In 2D, check if cornerPos lies on any edge of a previous material's mesh.
92 // If so, split that edge to include cornerNodeId.
93 void splitPreviousMaterialEdge(unsigned l, unsigned cornerNodeId,
94 const Vec3D<NumericType> &cornerPos,
95 size_t meshSizeBefore) {
96 if constexpr (D != 2)
97 return;
98 if (l == 0)
99 return;
100
101 for (size_t lineIdx = 0; lineIdx < mesh->lines.size(); ++lineIdx) {
102 unsigned mat = currentMaterials[lineIdx];
103 if (mat >= l)
104 continue;
105
106 auto &line = mesh->lines[lineIdx];
107 unsigned node0 = line[0];
108 unsigned node1 = line[1];
109
110 auto const &pos0 = mesh->nodes[node0];
111 auto const &pos1 = mesh->nodes[node1];
112 auto lineVec = pos1 - pos0;
113
114 NumericType lineLen2 = NumericType(0);
115 NumericType dot = NumericType(0);
116 for (int i = 0; i < D; ++i) {
117 lineLen2 += lineVec[i] * lineVec[i];
118 dot += (cornerPos[i] - pos0[i]) * lineVec[i];
119 }
120
121 if (lineLen2 < epsilon)
122 continue;
123
124 NumericType t = dot / lineLen2;
125 if (t <= NumericType(0) || t >= NumericType(1))
126 continue;
127
128 // Distance from corner to closest point on line
129 NumericType dist2 = NumericType(0);
130 for (int i = 0; i < D; ++i) {
131 NumericType d = cornerPos[i] - (pos0[i] + t * lineVec[i]);
132 dist2 += d * d;
133 }
134 if (dist2 >= NumericType(this->minNodeDistanceFactor))
135 continue;
136
137 // Split the edge unconditionally to ensure the bottom material remains
138 // closed
139 uniqueElements.erase({(int)node0, (int)node1, 0});
140 uniqueElements.insert({(int)node0, (int)cornerNodeId, 0});
141 uniqueElements.insert({(int)cornerNodeId, (int)node1, 0});
142
143 line[1] = cornerNodeId;
144 mesh->lines.push_back({cornerNodeId, node1});
145 currentMaterials.push_back(mat);
146 Vec3D<NumericType> normal = currentNormals[lineIdx];
147 currentNormals.push_back(normal);
148
149 // Remove any duplicate segments from the current material (l)
150 for (size_t k = meshSizeBefore; k < mesh->lines.size();) {
151 if (currentMaterials[k] != static_cast<NumericType>(l)) {
152 ++k;
153 continue;
154 }
155
156 const auto &rl = mesh->lines[k];
157 bool isSeg1 = (rl[0] == node0 && rl[1] == cornerNodeId) ||
158 (rl[0] == cornerNodeId && rl[1] == node0);
159 bool isSeg2 = (rl[0] == cornerNodeId && rl[1] == node1) ||
160 (rl[0] == node1 && rl[1] == cornerNodeId);
161
162 if (isSeg1 || isSeg2) {
163 mesh->lines.erase(mesh->lines.begin() + k);
164 currentMaterials.erase(currentMaterials.begin() + k);
165 currentNormals.erase(currentNormals.begin() + k);
166 } else {
167 ++k;
168 }
169 }
170
171 break; // Corner can only be on one edge
172 }
173 }
174
175 bool getSegmentIntersection(const Vec3D<NumericType> &p1,
176 const Vec3D<NumericType> &p2,
177 const Vec3D<NumericType> &p3,
178 const Vec3D<NumericType> &p4,
179 Vec3D<NumericType> &intersection,
180 NumericType &t) const {
181 NumericType x1 = p1[0], y1 = p1[1];
182 NumericType x2 = p2[0], y2 = p2[1];
183 NumericType x3 = p3[0], y3 = p3[1];
184 NumericType x4 = p4[0], y4 = p4[1];
185
186 NumericType denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
187 if (std::abs(denom) < epsilon)
188 return false;
189
190 NumericType ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;
191 NumericType ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom;
192
193 if (ua > epsilon && ua < 1.0 - epsilon && ub > epsilon &&
194 ub < 1.0 - epsilon) {
195 intersection[0] = x1 + ua * (x2 - x1);
196 intersection[1] = y1 + ua * (y2 - y1);
197 if constexpr (D == 3)
198 intersection[2] = 0;
199 t = ua;
200 return true;
201 }
202 return false;
203 }
204
205 // In 2D, check if the polymer edge at pIdx crosses BOTH corner edges of any
206 // lower material corner node that lies in the same cell (cellIndices).
207 // When both crossings are found simultaneously:
208 // - Split each mask corner edge at its intersection node.
209 // - Keep Part A (pNode0→intNode1) and Part C (intNode2→pNode1) of the
210 // polymer edge.
211 // - DISCARD Part B (intNode1→intNode2), which lies fictitiously inside the
212 // lower material.
213 void handleCellCornerCrossings(
214 size_t pIdx,
215 const std::unordered_map<unsigned, std::vector<SharpCornerNode>>
216 &sharpCornerNodes,
217 unsigned l, const hrleIndex &cellIndices) {
218 if constexpr (D != 2)
219 return;
220
221 unsigned pNode0 = mesh->lines[pIdx][0];
222 unsigned pNode1 = mesh->lines[pIdx][1];
223 Vec3D<NumericType> pp0 = mesh->nodes[pNode0];
224 Vec3D<NumericType> pp1 = mesh->nodes[pNode1];
225
226 for (unsigned m = 0; m < l; ++m) {
227 auto cit = sharpCornerNodes.find(m);
228 if (cit == sharpCornerNodes.end())
229 continue;
230
231 for (const auto &mscn : cit->second) {
232 // Cell-local check: the lower material's corner must be in this cell
233 bool inCell = true;
234 for (int i = 0; i < D; ++i) {
235 if (mscn.position[i] < NumericType(cellIndices[i]) - epsilon ||
236 mscn.position[i] > NumericType(cellIndices[i] + 1) + epsilon) {
237 inCell = false;
238 break;
239 }
240 }
241 if (!inCell)
242 continue;
243
244 // Find the two corner edges of material m incident to this corner node
245 size_t edge1Idx = SIZE_MAX, edge2Idx = SIZE_MAX;
246 for (size_t eIdx = 0; eIdx < pIdx; ++eIdx) {
247 if (static_cast<unsigned>(currentMaterials[eIdx]) != m)
248 continue;
249 if (mesh->lines[eIdx][0] != mscn.nodeId &&
250 mesh->lines[eIdx][1] != mscn.nodeId)
251 continue;
252 if (edge1Idx == SIZE_MAX)
253 edge1Idx = eIdx;
254 else if (edge2Idx == SIZE_MAX) {
255 edge2Idx = eIdx;
256 break;
257 }
258 }
259 if (edge1Idx == SIZE_MAX || edge2Idx == SIZE_MAX)
260 continue;
261
262 // Check if the polymer edge crosses BOTH corner edges
263 Vec3D<NumericType> int1{}, int2{};
264 NumericType t1 = 0, t2 = 0;
265 bool cross1 = getSegmentIntersection(
266 pp0, pp1, mesh->nodes[mesh->lines[edge1Idx][0]],
267 mesh->nodes[mesh->lines[edge1Idx][1]], int1, t1);
268 bool cross2 = getSegmentIntersection(
269 pp0, pp1, mesh->nodes[mesh->lines[edge2Idx][0]],
270 mesh->nodes[mesh->lines[edge2Idx][1]], int2, t2);
271
272 if (!cross1 || !cross2)
273 continue;
274
275 // Sort by parameter along polymer edge so t1 < t2
276 if (t1 > t2) {
277 std::swap(t1, t2);
278 std::swap(int1, int2);
279 std::swap(edge1Idx, edge2Idx);
280 }
281
282 // Insert intersection nodes
283 unsigned intNode1 = this->insertNode(int1);
284 unsigned intNode2 = this->insertNode(int2);
285
286 // Save metadata before any array modifications
287 NumericType matM1 = currentMaterials[edge1Idx];
288 Vec3D<NumericType> normalM1 = currentNormals[edge1Idx];
289 NumericType matM2 = currentMaterials[edge2Idx];
290 Vec3D<NumericType> normalM2 = currentNormals[edge2Idx];
291 NumericType matL = currentMaterials[pIdx];
292 Vec3D<NumericType> normalL = currentNormals[pIdx];
293 unsigned e1n0 = mesh->lines[edge1Idx][0];
294 unsigned e1n1 = mesh->lines[edge1Idx][1];
295 unsigned e2n0 = mesh->lines[edge2Idx][0];
296 unsigned e2n1 = mesh->lines[edge2Idx][1];
297
298 // Split mask edge1 at intNode1
299 uniqueElements.erase({(int)e1n0, (int)e1n1, 0});
300 mesh->lines[edge1Idx][1] = intNode1;
301 uniqueElements.insert({(int)e1n0, (int)intNode1, 0});
302 mesh->lines.push_back({intNode1, e1n1});
303 currentMaterials.push_back(matM1);
304 currentNormals.push_back(normalM1);
305 uniqueElements.insert({(int)intNode1, (int)e1n1, 0});
306
307 // Split mask edge2 at intNode2
308 uniqueElements.erase({(int)e2n0, (int)e2n1, 0});
309 mesh->lines[edge2Idx][1] = intNode2;
310 uniqueElements.insert({(int)e2n0, (int)intNode2, 0});
311 mesh->lines.push_back({intNode2, e2n1});
312 currentMaterials.push_back(matM2);
313 currentNormals.push_back(normalM2);
314 uniqueElements.insert({(int)intNode2, (int)e2n1, 0});
315
316 // Polymer edge:
317 // Part A (pNode0→intNode1) — kept
318 // Part B (intNode1→intNode2) — DISCARDED (fictitiously inside mask)
319 // Part C (intNode2→pNode1) — kept
320 uniqueElements.erase({(int)pNode0, (int)pNode1, 0});
321 mesh->lines[pIdx][1] = intNode1;
322 uniqueElements.insert({(int)pNode0, (int)intNode1, 0});
323 mesh->lines.push_back({intNode2, pNode1});
324 currentMaterials.push_back(matL);
325 currentNormals.push_back(normalL);
326 uniqueElements.insert({(int)intNode2, (int)pNode1, 0});
327
328 return; // Only one lower-material corner can be in this cell
329 }
330 }
331 }
332
333 // In 2D, for a just-inserted regular MC edge at lineIdx, iterates
334 // lower-material sharp corner nodes once and performs two checks per node:
335 // 1. Point-on-edge: if a corner lies on the edge, split it to close the
336 // shared boundary between adjacent materials.
337 // 2. Double-crossing (cell-local): if the edge crosses both arms of the
338 // corner's L-shape, discard the middle segment that lies fictitiously
339 // inside the lower material (see handleCellCornerCrossings for the same
340 // logic applied to polymer sharp-corner edges).
341 // Returns after the first hit of either kind.
342 void handleEdgeCornerInteractions(
343 size_t lineIdx,
344 const std::unordered_map<unsigned, std::vector<SharpCornerNode>>
345 &sharpCornerNodes,
346 unsigned l, const hrleIndex &cellIndices) {
347 if constexpr (D != 2)
348 return;
349 if (l == 0 || lineIdx >= mesh->lines.size())
350 return;
351
352 unsigned node0 = mesh->lines[lineIdx][0];
353 unsigned node1 = mesh->lines[lineIdx][1];
354 // Copy positions — insertNode may reallocate mesh->nodes
355 Vec3D<NumericType> pos0 = mesh->nodes[node0];
356 Vec3D<NumericType> pos1 = mesh->nodes[node1];
357 auto lineVec = pos1 - pos0;
358
359 NumericType lineLen2 = NumericType(0);
360 for (int i = 0; i < D; ++i)
361 lineLen2 += lineVec[i] * lineVec[i];
362 if (lineLen2 < epsilon)
363 return;
364
365 for (unsigned m = 0; m < l; ++m) {
366 auto it = sharpCornerNodes.find(m);
367 if (it == sharpCornerNodes.end())
368 continue;
369
370 for (const auto &scn : it->second) {
371
372 // --- Check 1: point-on-edge (shared boundary) ---
373 if (scn.nodeId != node0 && scn.nodeId != node1) {
374 NumericType dot = NumericType(0);
375 for (int i = 0; i < D; ++i)
376 dot += (scn.position[i] - pos0[i]) * lineVec[i];
377 NumericType t = dot / lineLen2;
378 if (t > NumericType(0) && t < NumericType(1)) {
379 NumericType dist2 = NumericType(0);
380 for (int i = 0; i < D; ++i) {
381 NumericType d = scn.position[i] - (pos0[i] + t * lineVec[i]);
382 dist2 += d * d;
383 }
384 if (dist2 < NumericType(this->minNodeDistanceFactor)) {
385 unsigned cId = scn.nodeId;
386 bool keep0 = !uniqueElements.count({(int)cId, (int)node0, 0}) &&
387 !uniqueElements.count({(int)node0, (int)cId, 0});
388 bool keep1 = !uniqueElements.count({(int)node1, (int)cId, 0}) &&
389 !uniqueElements.count({(int)cId, (int)node1, 0});
390 uniqueElements.erase({(int)node0, (int)node1, 0});
391 auto &line = mesh->lines[lineIdx];
392 if (keep0 && keep1) {
393 line[1] = cId;
394 mesh->lines.push_back({cId, node1});
395 currentMaterials.push_back(currentMaterials[lineIdx]);
396 currentNormals.push_back(currentNormals[lineIdx]);
397 uniqueElements.insert({(int)node0, (int)cId, 0});
398 uniqueElements.insert({(int)cId, (int)node1, 0});
399 } else if (keep0) {
400 line[1] = cId;
401 uniqueElements.insert({(int)node0, (int)cId, 0});
402 } else if (keep1) {
403 line[0] = cId;
404 uniqueElements.insert({(int)cId, (int)node1, 0});
405 } else {
406 mesh->lines.erase(mesh->lines.begin() + lineIdx);
407 currentMaterials.erase(currentMaterials.begin() + lineIdx);
408 currentNormals.erase(currentNormals.begin() + lineIdx);
409 }
410 return;
411 }
412 }
413 }
414
415 // --- Check 2: double-crossing (cell-local) ---
416 bool inCell = true;
417 for (int i = 0; i < D; ++i) {
418 if (scn.position[i] < NumericType(cellIndices[i]) - epsilon ||
419 scn.position[i] > NumericType(cellIndices[i] + 1) + epsilon) {
420 inCell = false;
421 break;
422 }
423 }
424 if (!inCell)
425 continue;
426
427 size_t edge1Idx = SIZE_MAX, edge2Idx = SIZE_MAX;
428 for (size_t eIdx = 0; eIdx < lineIdx; ++eIdx) {
429 if (static_cast<unsigned>(currentMaterials[eIdx]) != m)
430 continue;
431 if (mesh->lines[eIdx][0] != scn.nodeId &&
432 mesh->lines[eIdx][1] != scn.nodeId)
433 continue;
434 if (edge1Idx == SIZE_MAX)
435 edge1Idx = eIdx;
436 else if (edge2Idx == SIZE_MAX) {
437 edge2Idx = eIdx;
438 break;
439 }
440 }
441 if (edge1Idx == SIZE_MAX || edge2Idx == SIZE_MAX)
442 continue;
443
444 Vec3D<NumericType> int1{}, int2{};
445 NumericType t1 = 0, t2 = 0;
446 bool cross1 = getSegmentIntersection(
447 pos0, pos1, mesh->nodes[mesh->lines[edge1Idx][0]],
448 mesh->nodes[mesh->lines[edge1Idx][1]], int1, t1);
449 bool cross2 = getSegmentIntersection(
450 pos0, pos1, mesh->nodes[mesh->lines[edge2Idx][0]],
451 mesh->nodes[mesh->lines[edge2Idx][1]], int2, t2);
452 if (!cross1 || !cross2)
453 continue;
454
455 if (t1 > t2) {
456 std::swap(t1, t2);
457 std::swap(int1, int2);
458 std::swap(edge1Idx, edge2Idx);
459 }
460
461 unsigned intNode1 = this->insertNode(int1);
462 unsigned intNode2 = this->insertNode(int2);
463
464 NumericType matM1 = currentMaterials[edge1Idx];
465 Vec3D<NumericType> normalM1 = currentNormals[edge1Idx];
466 NumericType matM2 = currentMaterials[edge2Idx];
467 Vec3D<NumericType> normalM2 = currentNormals[edge2Idx];
468 NumericType matL = currentMaterials[lineIdx];
469 Vec3D<NumericType> normalL = currentNormals[lineIdx];
470 unsigned e1n0 = mesh->lines[edge1Idx][0],
471 e1n1 = mesh->lines[edge1Idx][1];
472 unsigned e2n0 = mesh->lines[edge2Idx][0],
473 e2n1 = mesh->lines[edge2Idx][1];
474
475 uniqueElements.erase({(int)e1n0, (int)e1n1, 0});
476 mesh->lines[edge1Idx][1] = intNode1;
477 uniqueElements.insert({(int)e1n0, (int)intNode1, 0});
478 mesh->lines.push_back({intNode1, e1n1});
479 currentMaterials.push_back(matM1);
480 currentNormals.push_back(normalM1);
481 uniqueElements.insert({(int)intNode1, (int)e1n1, 0});
482
483 uniqueElements.erase({(int)e2n0, (int)e2n1, 0});
484 mesh->lines[edge2Idx][1] = intNode2;
485 uniqueElements.insert({(int)e2n0, (int)intNode2, 0});
486 mesh->lines.push_back({intNode2, e2n1});
487 currentMaterials.push_back(matM2);
488 currentNormals.push_back(normalM2);
489 uniqueElements.insert({(int)intNode2, (int)e2n1, 0});
490
491 uniqueElements.erase({(int)node0, (int)node1, 0});
492 mesh->lines[lineIdx][1] = intNode1;
493 uniqueElements.insert({(int)node0, (int)intNode1, 0});
494 mesh->lines.push_back({intNode2, node1});
495 currentMaterials.push_back(matL);
496 currentNormals.push_back(normalL);
497 uniqueElements.insert({(int)intNode2, (int)node1, 0});
498
499 return;
500 }
501 }
502 }
503
504 // Handle snapping/splitting of sharp corners between materials.
505 // activeGridIdx and lsLAtActive identify the active grid corner of the
506 // current cell (see findNearestLowerCorner) for the layer thickness check.
507 // cellIndices is the integer grid index of the current cell, used to restrict
508 // the double-crossing check to lower material corners in the same cell.
509 void
510 snapSharpCorners(unsigned l, size_t meshSizeBefore,
511 std::unordered_map<unsigned, std::vector<SharpCornerNode>>
512 &sharpCornerNodes,
513 const hrleIndex &activeGridIdx, NumericType lsLAtActive,
514 const hrleIndex &cellIndices) {
515 for (const auto &cornerPair : matSharpCornerNodes) {
516 unsigned cornerNodeId = cornerPair.first;
517 Vec3D<NumericType> cornerPos = cornerPair.second;
518
519 int snapToNodeId =
520 (l > 0) ? findNearestLowerCorner(l, cornerPos, sharpCornerNodes,
521 activeGridIdx, lsLAtActive)
522 : -1;
523
524 if (snapToNodeId >= 0) {
525 // Snap: replace cornerNodeId with snapToNodeId in recent elements
526 if (snapToNodeId != (int)cornerNodeId) {
527 if constexpr (D == 2) {
528 using I3 = typename ToSurfaceMesh<NumericType, D>::I3;
529 for (size_t i = mesh->lines.size(); i-- > meshSizeBefore;) {
530 for (int j = 0; j < 2; ++j) {
531 if (mesh->lines[i][j] == cornerNodeId)
532 mesh->lines[i][j] = snapToNodeId;
533 }
534
535 unsigned n0 = mesh->lines[i][0];
536 unsigned n1 = mesh->lines[i][1];
537 bool isDuplicate = uniqueElements.count({(int)n0, (int)n1, 0}) ||
538 uniqueElements.count({(int)n1, (int)n0, 0});
539
540 if (n0 == n1 || isDuplicate) {
541 mesh->lines.erase(mesh->lines.begin() + i);
542 currentMaterials.erase(currentMaterials.begin() + i);
543 currentNormals.erase(currentNormals.begin() + i);
544 } else {
545 uniqueElements.insert({(int)n0, (int)n1, 0});
546 }
547 }
548 }
549 }
550 sharpCornerNodes[l].push_back(
551 {static_cast<unsigned>(snapToNodeId), cornerPos});
552 } else {
553 // No snap - keep new corner and split any overlapping edge
554 sharpCornerNodes[l].push_back({cornerNodeId, cornerPos});
555 splitPreviousMaterialEdge(l, cornerNodeId, cornerPos, meshSizeBefore);
556 // For thin-layer case: the endpoints (nX, nY) of the polymer's sharp
557 // corner edges may lie on previous material surface edges.
558 // Collect all endpoints first, then apply all splits before crossing
559 // checks. Interleaving the two would risk index-shifting: erases inside
560 // splitPreviousMaterialEdge's duplicate-removal loop can invalidate
561 // pIdx before handleCellCornerCrossings is called.
562 size_t sizeNow = mesh->lines.size();
563 std::vector<std::pair<unsigned, Vec3D<NumericType>>> endpointsToSplit;
564 for (size_t pIdx = meshSizeBefore; pIdx < sizeNow; ++pIdx) {
565 if (static_cast<unsigned>(currentMaterials[pIdx]) != l)
566 continue;
567 unsigned n0 = mesh->lines[pIdx][0];
568 unsigned n1 = mesh->lines[pIdx][1];
569 if (n0 != cornerNodeId)
570 endpointsToSplit.push_back({n0, mesh->nodes[n0]});
571 if (n1 != cornerNodeId)
572 endpointsToSplit.push_back({n1, mesh->nodes[n1]});
573 }
574 for (auto &[nodeId, pos] : endpointsToSplit)
575 splitPreviousMaterialEdge(l, nodeId, pos, meshSizeBefore);
576 // Run crossing checks after all lower-material splits are complete.
577 sizeNow = mesh->lines.size();
578 for (size_t pIdx = meshSizeBefore; pIdx < sizeNow; ++pIdx) {
579 if (static_cast<unsigned>(currentMaterials[pIdx]) != l)
580 continue;
581 handleCellCornerCrossings(pIdx, sharpCornerNodes, l, cellIndices);
582 }
583 }
584 }
585 }
586
587public:
588 ToMultiSurfaceMesh(double minNodeDistFactor = 0.01, double eps = 1e-12)
589 : ToSurfaceMesh<NumericType, D>(minNodeDistFactor, eps) {}
590
591 ToMultiSurfaceMesh(SmartPointer<lsDomainType> passedLevelSet,
592 SmartPointer<viennals::Mesh<NumericType>> passedMesh,
593 double minNodeDistFactor = 0.01, double eps = 1e-12)
594 : ToSurfaceMesh<NumericType, D>(passedLevelSet, passedMesh,
595 minNodeDistFactor, eps) {}
596
598 std::vector<SmartPointer<lsDomainType>> const &passedLevelSets,
599 SmartPointer<viennals::Mesh<NumericType>> passedMesh,
600 double minNodeDistFactor = 0.01, double eps = 1e-12)
601 : ToSurfaceMesh<NumericType, D>(minNodeDistFactor, eps) {
602 levelSets = passedLevelSets;
603 mesh = passedMesh;
604 }
605
607 double minNodeDistFactor = 0.01, double eps = 1e-12)
608 : ToSurfaceMesh<NumericType, D>(minNodeDistFactor, eps) {
609 mesh = passedMesh;
610 }
611
612 void insertNextLevelSet(SmartPointer<lsDomainType> passedLevelSet) {
613 levelSets.push_back(passedLevelSet);
614 }
615
616 void clearLevelSets() { levelSets.clear(); }
617
618 void setMaterialMap(SmartPointer<MaterialMap> passedMaterialMap) {
619 materialMap = passedMaterialMap;
620 }
621
622 void apply() override {
623 if (levelSets.empty()) {
624 Logger::getInstance()
625 .addError("No level set was passed to ToMultiSurfaceMesh.")
626 .print();
627 return;
628 }
629 if (mesh == nullptr) {
630 Logger::getInstance()
631 .addError("No mesh was passed to ToMultiSurfaceMesh.")
632 .print();
633 return;
634 }
635
636 mesh->clear();
637 for (unsigned i = 0; i < D; ++i) {
638 mesh->minimumExtent[i] = std::numeric_limits<NumericType>::max();
639 mesh->maximumExtent[i] = std::numeric_limits<NumericType>::lowest();
640 }
641
642 using nodeContainerType = std::map<viennahrle::Index<D>, unsigned>;
643
644 nodeContainerType nodes[D];
645 nodeContainerType faceNodes[D];
646 nodeContainerType cornerNodes;
647
648 nodeIdByBin.clear();
649 uniqueElements.clear();
650 currentNormals.clear();
651 currentMaterials.clear();
652
653 const bool useMaterialMap = materialMap != nullptr;
654 const bool sharpCorners = generateSharpCorners;
655
656 if (sharpCorners && D == 3) {
657 Logger::getInstance()
658 .addWarning("Sharp corner generation in 3D is experimental and may "
659 "produce suboptimal meshes. Use with caution.")
660 .print();
661 }
662
663 // an iterator for each level set
664 std::vector<viennahrle::ConstSparseCellIterator<hrleDomainType>> cellIts;
665 for (const auto &ls : levelSets)
666 cellIts.emplace_back(ls->getDomain());
667
668 // Explicit storage for sharp corner nodes, keyed by material index
669 std::unordered_map<unsigned, std::vector<SharpCornerNode>> sharpCornerNodes;
670
671 for (unsigned l = 0; l < levelSets.size(); l++) {
673 currentMaterialId = useMaterialMap ? materialMap->getMaterialId(l)
674 : static_cast<NumericType>(l);
675
676 normalVectorData = nullptr;
677 if (sharpCorners) {
680 normalCalculator.setMethod(
682 normalCalculator.setMaxValue(std::numeric_limits<NumericType>::max());
683 normalCalculator.apply();
684 normalVectorData = currentLevelSet->getPointData().getVectorData(
686 }
687
688 viennahrle::ConstSparseIterator<hrleDomainType> valueIt(
689 currentLevelSet->getDomain());
690
691 // iterate over all active surface points
692 for (auto cellIt = cellIts[l]; !cellIt.isFinished(); cellIt.next()) {
693 for (int u = 0; u < D; u++) {
694 while (!nodes[u].empty() &&
695 nodes[u].begin()->first <
696 viennahrle::Index<D>(cellIt.getIndices()))
697 nodes[u].erase(nodes[u].begin());
698
699 if constexpr (D == 3) {
700 while (!faceNodes[u].empty() &&
701 faceNodes[u].begin()->first <
702 viennahrle::Index<D>(cellIt.getIndices()))
703 faceNodes[u].erase(faceNodes[u].begin());
704
705 if (u == 0) {
706 while (!cornerNodes.empty() &&
707 cornerNodes.begin()->first <
708 viennahrle::Index<D>(cellIt.getIndices()))
709 cornerNodes.erase(cornerNodes.begin());
710 }
711 }
712 }
713
714 unsigned signs = 0;
715 for (int i = 0; i < (1 << D); i++) {
716 if (cellIt.getCorner(i).getValue() >= NumericType(0))
717 signs |= (1 << i);
718 }
719
720 // all corners have the same sign, so no surface here
721 if (signs == 0)
722 continue;
723 if (signs == (1 << (1 << D)) - 1)
724 continue;
725
726 // Check if this cell is at a material boundary with the immediate
727 // material below
728 bool atMaterialBoundary = false;
729 unsigned touchingMaterial = 0; // Which material we're touching
730 if (sharpCorners && l > 0) {
731 touchingMaterial = l - 1;
732 viennahrle::ConstSparseIterator<hrleDomainType> prevIt(
733 levelSets[touchingMaterial]->getDomain());
734 for (int i = 0; i < (1 << D); ++i) {
735 hrleIndex cornerIdx =
736 cellIt.getIndices() + viennahrle::BitMaskToIndex<D>(i);
737 prevIt.goToIndices(cornerIdx);
738 if (prevIt.getValue() <=
739 NumericType(0)) { // Inside previous material
740 atMaterialBoundary = true;
741 break;
742 }
743 }
744 }
745
746 // Attempt to generate sharp features if enabled
747 bool perfectCornerFound = false;
748 if (sharpCorners) {
749 int countNeg = 0;
750 int countPos = 0;
751 int negMask = 0;
752 int posMask = 0;
753 for (int i = 0; i < (1 << D); ++i) {
754 NumericType val = cellIt.getCorner(i).getValue();
755 if (val < -epsilon) {
756 countNeg++;
757 negMask |= (1 << i);
758 } else if (val > epsilon) {
759 countPos++;
760 posMask |= (1 << i);
761 } else {
762 if (val >= NumericType(0)) {
763 countPos++;
764 posMask |= (1 << i);
765 } else {
766 countNeg++;
767 negMask |= (1 << i);
768 }
769 }
770 }
771
772 // Clear the base class's recent corner nodes before generation
773 matSharpCornerNodes.clear();
774
775 // Track mesh size before generating sharp corners
776 size_t meshSizeBefore = 0;
777 if constexpr (D == 2) {
778 meshSizeBefore = mesh->lines.size();
779 perfectCornerFound =
780 this->generateSharpCorner2D(cellIt, countNeg, countPos, negMask,
781 posMask, nodes, nullptr, valueIt);
782 } else if constexpr (D == 3) {
783 if (countNeg == 2 || countPos == 2) {
784 perfectCornerFound = this->generateSharpEdge3D(
785 cellIt, countNeg, countPos, negMask, posMask, nodes,
786 faceNodes, nullptr, valueIt);
787 } else if (countNeg == 1 || countPos == 1) {
788 perfectCornerFound = this->generateSharpCorner3D(
789 cellIt, countNeg, countPos, negMask, posMask, nodes,
790 cornerNodes, faceNodes, nullptr, valueIt);
791 } else if (countNeg == 3 || countPos == 3) {
792 perfectCornerFound = this->generateSharpL3D(
793 cellIt, countNeg, countPos, negMask, posMask, nodes,
794 faceNodes, nullptr, valueIt);
795 }
796 }
797
798 // If sharp corners were generated, check if they should snap to
799 // existing corners or split edges of previous materials.
800 // Identify the "active" (single-minority sign) grid corner so the
801 // layer thickness check inside snapSharpCorners can use grid-level LS
802 // values instead of unreliable sub-grid interpolation.
803 if (D == 2 && perfectCornerFound && !matSharpCornerNodes.empty()) {
804 int activeBit = -1;
805 if (countNeg == 1) {
806 for (int i = 0; i < (1 << D); ++i)
807 if ((negMask >> i) & 1) {
808 activeBit = i;
809 break;
810 }
811 } else if (countPos == 1) {
812 for (int i = 0; i < (1 << D); ++i)
813 if ((posMask >> i) & 1) {
814 activeBit = i;
815 break;
816 }
817 }
818 hrleIndex activeGridIdx = cellIt.getIndices();
819 NumericType lsLAtActive = NumericType(0);
820 if (activeBit >= 0) {
821 activeGridIdx += viennahrle::BitMaskToIndex<D>(activeBit);
822 lsLAtActive = cellIt.getCorner(activeBit).getValue();
823 }
824 snapSharpCorners(l, meshSizeBefore, sharpCornerNodes, activeGridIdx,
825 lsLAtActive, cellIt.getIndices());
826 }
827 }
828
829 if (perfectCornerFound)
830 continue;
831
832 if constexpr (D == 3) {
833 // Stitch to perfect corners/edges
834 for (int axis = 0; axis < 3; ++axis) {
835 for (int d = 0; d < 2; ++d) {
836 viennahrle::Index<D> faceKey(cellIt.getIndices());
837 if (d == 1)
838 faceKey[axis]++;
839
840 auto it = faceNodes[axis].find(faceKey);
841 if (it != faceNodes[axis].end()) {
842 const unsigned faceNodeId = it->second;
843 const int *Triangles =
845
846 for (; Triangles[0] != -1; Triangles += 3) {
847 std::vector<unsigned> face_edge_nodes;
848 for (int i = 0; i < 3; ++i) {
849 int edge = Triangles[i];
850 int c0 = this->corner0[edge];
851 int c1 = this->corner1[edge];
852 bool onFace =
853 (((c0 >> axis) & 1) == d) && (((c1 >> axis) & 1) == d);
854 if (onFace) {
855 face_edge_nodes.push_back(
856 this->getNode(cellIt, edge, nodes, nullptr));
857 }
858 }
859 if (face_edge_nodes.size() == 2) {
860 this->insertElement(std::array<unsigned, 3>{
861 face_edge_nodes[0], face_edge_nodes[1], faceNodeId});
862 }
863 }
864 }
865 }
866 }
867 }
868
869 // for each element
870 const int *Triangles;
871 if constexpr (D == 2) {
873 } else {
875 }
876
877 for (; Triangles[0] != -1; Triangles += D) {
878 std::array<unsigned, D> nodeNumbers;
879
880 // for each node
881 for (int n = 0; n < D; n++) {
882 const int edge = Triangles[n];
883
884 unsigned p0 = this->corner0[edge];
885
886 // determine direction of edge
887 unsigned dir = this->direction[edge];
888
889 // look for existing surface node
890 viennahrle::Index<D> d(cellIt.getIndices());
891 auto p0B = viennahrle::BitMaskToIndex<D>(p0);
892 d += p0B;
893
894 auto nodeIt = nodes[dir].find(d);
895 if (nodeIt != nodes[dir].end()) {
896 nodeNumbers[n] = nodeIt->second;
897
898 // Check if this cached node is a sharp corner that should be
899 // inherited
900 if (sharpCorners && l > 0 && atMaterialBoundary) {
901 unsigned cachedNodeId = nodeIt->second;
902 // Check if this is a corner from the material below
903 auto it = sharpCornerNodes.find(touchingMaterial);
904 if (it != sharpCornerNodes.end()) {
905 for (const auto &scn : it->second) {
906 if (scn.nodeId == cachedNodeId) {
907 // Only inherit if not already inherited
908 auto &lCorners = sharpCornerNodes[l];
909 bool alreadyInherited = false;
910 for (const auto &existing : lCorners) {
911 if (existing.nodeId == cachedNodeId) {
912 alreadyInherited = true;
913 break;
914 }
915 }
916 if (!alreadyInherited)
917 lCorners.push_back({cachedNodeId, scn.position});
918 break;
919 }
920 }
921 }
922 }
923 } else {
924 // Node does not exist yet - try snapping to a sharp corner
925 // from the touching material at material boundaries
926 int snapNodeId = -1;
927 Vec3D<NumericType> snapPos{};
928 if (sharpCorners && l > 0 && atMaterialBoundary) {
929 auto tmIt = sharpCornerNodes.find(touchingMaterial);
930 if (tmIt != sharpCornerNodes.end() && !tmIt->second.empty()) {
931 auto [nodePos, pointId] =
932 this->computeNodePosition(cellIt, edge);
933
934 NumericType minDist2 =
936 for (const auto &scn : tmIt->second) {
937 NumericType dist2 = NumericType(0);
938 for (int i = 0; i < D; ++i) {
939 NumericType dd = scn.position[i] - nodePos[i];
940 dist2 += dd * dd;
941 }
942 if (dist2 < minDist2) {
943 minDist2 = dist2;
944 snapNodeId = scn.nodeId;
945 snapPos = scn.position;
946 }
947 }
948 }
949 }
950
951 if (snapNodeId >= 0) {
952 nodeNumbers[n] = snapNodeId;
953 nodes[dir][d] = snapNodeId; // Cache it
954 // Inherit this corner so upper materials can find it
955 sharpCornerNodes[l].push_back(
956 {static_cast<unsigned>(snapNodeId), snapPos});
957 } else {
958 nodeNumbers[n] = this->getNode(cellIt, edge, nodes, nullptr);
959 }
960 }
961 }
962
963 // Check for duplicates before inserting (standard Marching Cubes)
964 if (this->insertElement(nodeNumbers)) {
965 // element was inserted
966 if (sharpCorners && l > 0) {
967 handleEdgeCornerInteractions(mesh->lines.size() - 1,
968 sharpCornerNodes, l,
969 cellIt.getIndices());
970 }
971 }
972 }
973 }
974 }
975
976 this->scaleMesh(levelSets.front()->getGrid().getGridDelta());
977
978 mesh->cellData.insertNextScalarData(currentMaterials,
980 mesh->cellData.insertNextVectorData(currentNormals,
982 mesh->triangles.shrink_to_fit();
983 mesh->nodes.shrink_to_fit();
984 }
985};
986
987PRECOMPILE_PRECISION_DIMENSION(ToMultiSurfaceMesh)
988
989} // namespace viennals
float NumericType
Definition AirGapDeposition.cpp:24
constexpr int D
Definition Epitaxy.cpp:12
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< NumericType, D > DomainType
Definition lsDomain.hpp:32
This class holds an explicit mesh, which is always given in 3 dimensions. If it describes a 2D mesh,...
Definition lsMesh.hpp:21
static constexpr const char * normalsLabel
Definition lsMesh.hpp:35
static constexpr const char * materialIdsLabel
Definition lsMesh.hpp:34
ToMultiSurfaceMesh(SmartPointer< lsDomainType > passedLevelSet, SmartPointer< viennals::Mesh< NumericType > > passedMesh, double minNodeDistFactor=0.01, double eps=1e-12)
Definition lsToMultiSurfaceMesh.hpp:591
ToMultiSurfaceMesh(std::vector< SmartPointer< lsDomainType > > const &passedLevelSets, SmartPointer< viennals::Mesh< NumericType > > passedMesh, double minNodeDistFactor=0.01, double eps=1e-12)
Definition lsToMultiSurfaceMesh.hpp:597
void apply() override
Definition lsToMultiSurfaceMesh.hpp:622
void setMaterialMap(SmartPointer< MaterialMap > passedMaterialMap)
Definition lsToMultiSurfaceMesh.hpp:618
ToMultiSurfaceMesh(double minNodeDistFactor=0.01, double eps=1e-12)
Definition lsToMultiSurfaceMesh.hpp:588
void clearLevelSets()
Definition lsToMultiSurfaceMesh.hpp:616
void insertNextLevelSet(SmartPointer< lsDomainType > passedLevelSet)
Definition lsToMultiSurfaceMesh.hpp:612
ToMultiSurfaceMesh(SmartPointer< viennals::Mesh< NumericType > > passedMesh, double minNodeDistFactor=0.01, double eps=1e-12)
Definition lsToMultiSurfaceMesh.hpp:606
SmartPointer< Mesh< NumericType > > mesh
Definition lsToSurfaceMesh.hpp:35
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
SmartPointer< lsDomainType > currentLevelSet
Definition lsToSurfaceMesh.hpp:68
bool insertElement(const std::array< unsigned, D > &nodeNumbers)
Definition lsToSurfaceMesh.hpp:1600
bool generateSharpCorners
Definition lsToSurfaceMesh.hpp:40
std::vector< SmartPointer< lsDomainType > > levelSets
Definition lsToSurfaceMesh.hpp:34
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::unordered_map< I3, unsigned, I3Hash > nodeIdByBin
Definition lsToSurfaceMesh.hpp:63
const NumericType epsilon
Definition lsToSurfaceMesh.hpp:37
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
std::unordered_set< I3, I3Hash > uniqueElements
Definition lsToSurfaceMesh.hpp:64
void scaleMesh(const NumericType gridDelta)
Definition lsToSurfaceMesh.hpp:328
std::vector< std::pair< unsigned, Vec3D< NumericType > > > 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
const double minNodeDistanceFactor
Definition lsToSurfaceMesh.hpp:38
ToSurfaceMesh(double mnd=0.05, double eps=1e-12)
Definition lsToSurfaceMesh.hpp:82
unsigned insertNode(Vec3D< NumericType > const &pos)
Definition lsToSurfaceMesh.hpp:1631
std::tuple< Vec3D< NumericType >, std::size_t > computeNodePosition(const ConstSparseCellIterator &cellIt, int edge) const
Definition lsToSurfaceMesh.hpp:367
PointData< NumericType >::VectorDataType * normalVectorData
Definition lsToSurfaceMesh.hpp:69
std::vector< Vec3D< NumericType > > currentNormals
Definition lsToSurfaceMesh.hpp:65
std::vector< NumericType > currentMaterials
Definition lsToSurfaceMesh.hpp:66
NumericType currentMaterialId
Definition lsToSurfaceMesh.hpp:67
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:42