flow 1.3.0
Kokkos cut-cell IBM incompressible Navier-Stokes solver + pnm pore extraction
Loading...
Searching...
No Matches
marching_cubes.hpp
Go to the documentation of this file.
1
87#ifndef PECLET_FLOW_VOF_MARCHING_CUBES_HPP
88#define PECLET_FLOW_VOF_MARCHING_CUBES_HPP
89
90#include <Kokkos_Core.hpp>
91#include <Kokkos_MathematicalFunctions.hpp>
92
93namespace peclet::flow::vof {
94
96enum McSource : int {
99};
100
106
117struct McVertex {
118 double psi = 0.0;
119 double d = 0.0;
120 double n[3] = {0.0, 0.0, 0.0};
121 bool has = false;
122};
123
125KOKKOS_INLINE_FUNCTION void mcCornerPos(int k, double p[3]) {
126 p[0] = static_cast<double>(k & 1);
127 p[1] = static_cast<double>((k >> 1) & 1);
128 p[2] = static_cast<double>((k >> 2) & 1);
129}
130
139KOKKOS_INLINE_FUNCTION double mcEdgeT(const McVertex& a, const McVertex& b, const double dv[3],
140 int src) {
141 if (src == kMcSrcPlic) {
142 double acc = 0.0;
143 int cnt = 0;
144 if (a.has) {
145 const double g = a.n[0] * dv[0] + a.n[1] * dv[1] + a.n[2] * dv[2];
146 if (Kokkos::fabs(g) > 1e-12) {
147 const double t = -a.d / g;
148 if (t > -0.5 && t < 1.5) {
149 acc += t;
150 ++cnt;
151 }
152 }
153 }
154 if (b.has) {
155 const double g = b.n[0] * dv[0] + b.n[1] * dv[1] + b.n[2] * dv[2];
156 if (Kokkos::fabs(g) > 1e-12) {
157 const double t = 1.0 - b.d / g;
158 if (t > -0.5 && t < 1.5) {
159 acc += t;
160 ++cnt;
161 }
162 }
163 }
164 if (cnt > 0) {
165 double t = acc / static_cast<double>(cnt);
166 if (a.has && b.has) {
167 // Phi(s) = (1-s) phi_a(s) + s phi_b(s): the linear BLEND of the two cells' own signed
168 // distance functions, which is exact on a plane and weights each cell's plane by how near
169 // the crossing is to it (a quadratic in s).
170 const double ga = a.n[0] * dv[0] + a.n[1] * dv[1] + a.n[2] * dv[2];
171 const double gb = b.n[0] * dv[0] + b.n[1] * dv[1] + b.n[2] * dv[2];
172 const double A = gb - ga, B = (ga - gb) + (b.d - a.d), C0 = a.d;
173 double s2 = -1.0;
174 if (Kokkos::fabs(A) < 1e-12 * (Kokkos::fabs(B) + 1e-30)) {
175 if (Kokkos::fabs(B) > 0.0)
176 s2 = -C0 / B;
177 } else {
178 const double disc = B * B - 4.0 * A * C0;
179 if (disc >= 0.0) {
180 const double sq = Kokkos::sqrt(disc);
181 const double r1 = (-B + sq) / (2.0 * A), r2 = (-B - sq) / (2.0 * A);
182 const bool o1 = (r1 >= 0.0 && r1 <= 1.0), o2 = (r2 >= 0.0 && r2 <= 1.0);
183 if (o1 && !o2)
184 s2 = r1;
185 else if (o2 && !o1)
186 s2 = r2;
187 else if (o1 && o2)
188 s2 = (Kokkos::fabs(r1 - t) < Kokkos::fabs(r2 - t)) ? r1 : r2;
189 }
190 }
191 if (s2 >= 0.0 && s2 <= 1.0)
192 t = s2;
193 }
194 return t < 0.0 ? 0.0 : (t > 1.0 ? 1.0 : t);
195 }
196 }
197 const double den = a.psi - b.psi;
198 if (!(Kokkos::fabs(den) > 0.0))
199 return 0.5;
200 const double t = a.psi / den;
201 return t < 0.0 ? 0.0 : (t > 1.0 ? 1.0 : t);
202}
203
205KOKKOS_INLINE_FUNCTION double mcTwiceArea(const double p0[3], const double p1[3],
206 const double p2[3], const VofMetric& g) {
207 // Phase 3: the sheet is CUT in index space (every vertex sits on a lattice edge, which is what
208 // makes it watertight) and MEASURED in physical space — the two edge vectors are mapped through
209 // H before the cross product. `g.h = {1,1,1}` multiplies each by 1.0: today's numbers.
210 const double ux = (p1[0] - p0[0]) * g.h[0], uy = (p1[1] - p0[1]) * g.h[1],
211 uz = (p1[2] - p0[2]) * g.h[2];
212 const double vx = (p2[0] - p0[0]) * g.h[0], vy = (p2[1] - p0[1]) * g.h[1],
213 vz = (p2[2] - p0[2]) * g.h[2];
214 const double cx = uy * vz - uz * vy, cy = uz * vx - ux * vz, cz = ux * vy - uy * vx;
215 return Kokkos::sqrt(cx * cx + cy * cy + cz * cz);
216}
217
219KOKKOS_INLINE_FUNCTION double mcPolygonArea(const double p[8][3], int n, const VofMetric& g) {
220 if (n < 3)
221 return 0.0;
222 // As `mcTwiceArea`: clipped in index space, measured in physical space. The shoelace vector of
223 // the mapped polygon is `det(H) H^-1` applied to the index one (the cofactor map), which for a
224 // diagonal H is the per-component product below — and exactly the identity at g.h = {1,1,1}.
225 const double c0 = g.h[1] * g.h[2], c1 = g.h[2] * g.h[0], c2 = g.h[0] * g.h[1];
226 double sx = 0.0, sy = 0.0, sz = 0.0;
227 for (int i = 0; i < n; ++i) {
228 const int j = (i + 1 == n) ? 0 : i + 1;
229 sx += p[i][1] * p[j][2] - p[i][2] * p[j][1];
230 sy += p[i][2] * p[j][0] - p[i][0] * p[j][2];
231 sz += p[i][0] * p[j][1] - p[i][1] * p[j][0];
232 }
233 sx *= c0;
234 sy *= c1;
235 sz *= c2;
236 return 0.5 * Kokkos::sqrt(sx * sx + sy * sy + sz * sz);
237}
238
241KOKKOS_INLINE_FUNCTION int mcClipHalf(double p[8][3], int n, int axis, double c, bool keepAbove) {
242 double q[8][3];
243 int m = 0;
244 for (int i = 0; i < n; ++i) {
245 const int j = (i + 1 == n) ? 0 : i + 1;
246 const double si = keepAbove ? (p[i][axis] - c) : (c - p[i][axis]);
247 const double sj = keepAbove ? (p[j][axis] - c) : (c - p[j][axis]);
248 const bool ini = si >= 0.0, inj = sj >= 0.0;
249 if (ini && m < 8) {
250 q[m][0] = p[i][0];
251 q[m][1] = p[i][1];
252 q[m][2] = p[i][2];
253 ++m;
254 }
255 if (ini != inj && m < 8) {
256 const double den = si - sj;
257 const double t = (Kokkos::fabs(den) > 0.0) ? si / den : 0.0;
258 q[m][0] = p[i][0] + t * (p[j][0] - p[i][0]);
259 q[m][1] = p[i][1] + t * (p[j][1] - p[i][1]);
260 q[m][2] = p[i][2] + t * (p[j][2] - p[i][2]);
261 ++m;
262 }
263 }
264 for (int i = 0; i < m; ++i) {
265 p[i][0] = q[i][0];
266 p[i][1] = q[i][1];
267 p[i][2] = q[i][2];
268 }
269 return m;
270}
271
273KOKKOS_INLINE_FUNCTION double mcClipToCorner(const double p0[3], const double p1[3],
274 const double p2[3], int k, const VofMetric& g) {
275 double poly[8][3];
276 for (int d = 0; d < 3; ++d) {
277 poly[0][d] = p0[d];
278 poly[1][d] = p1[d];
279 poly[2][d] = p2[d];
280 }
281 int n = 3;
282 for (int d = 0; d < 3 && n >= 3; ++d)
283 n = mcClipHalf(poly, n, d, 0.5, ((k >> d) & 1) != 0);
284 return mcPolygonArea(poly, n, g);
285}
286
289KOKKOS_INLINE_FUNCTION int mcNearestMasked(const double p[3], int mask) {
290 int best = -1;
291 double bd = 0.0;
292 for (int k = 0; k < 8; ++k) {
293 if (!((mask >> k) & 1))
294 continue;
295 double q[3];
296 mcCornerPos(k, q);
297 const double dx = p[0] - q[0], dy = p[1] - q[1], dz = p[2] - q[2];
298 const double d2 = dx * dx + dy * dy + dz * dz;
299 if (best < 0 || d2 < bd) {
300 best = k;
301 bd = d2;
302 }
303 }
304 return best;
305}
306
317KOKKOS_INLINE_FUNCTION int mcRetarget(int k, int mask) {
318 if (mask == 0 || ((mask >> k) & 1))
319 return k;
320 double q[3];
321 mcCornerPos(k, q);
322 const int t = mcNearestMasked(q, mask);
323 return t < 0 ? k : t;
324}
325
333KOKKOS_INLINE_FUNCTION double mcTriangleToCorner(const double p0[3], const double p1[3],
334 const double p2[3], int lc, int deposit, int mask,
335 const VofMetric& g) {
337 double ctr[3];
338 for (int d = 0; d < 3; ++d)
339 ctr[d] = (p0[d] + p1[d] + p2[d]) * (1.0 / 3.0);
340 int corner = 0;
341 for (int d = 0; d < 3; ++d)
342 if (ctr[d] >= 0.5)
343 corner |= 1 << d;
344 if (mask != 0 && !((mask >> corner) & 1)) {
345 const int t = mcNearestMasked(ctr, mask);
346 if (t >= 0)
347 corner = t;
348 }
349 return (corner == lc) ? 0.5 * mcTwiceArea(p0, p1, p2, g) : 0.0;
350 }
351 double acc = 0.0;
352 if (mask == 0 || ((mask >> lc) & 1))
353 acc += mcClipToCorner(p0, p1, p2, lc, g);
354 if (mask != 0 && mask != 255)
355 for (int k = 0; k < 8; ++k)
356 if (!((mask >> k) & 1) && mcRetarget(k, mask) == lc)
357 acc += mcClipToCorner(p0, p1, p2, k, g);
358 return acc;
359}
360
367KOKKOS_INLINE_FUNCTION double mcCubeCornerArea(const McVertex v[8], int lc, int src, int deposit,
368 const VofMetric& g) {
369 // Kuhn's decomposition along the body diagonal 0 -> 7. Corner index = x + 2y + 4z.
370 const int TET[6][4] = {{0, 1, 3, 7}, {0, 1, 5, 7}, {0, 2, 3, 7},
371 {0, 2, 6, 7}, {0, 4, 5, 7}, {0, 4, 6, 7}};
372 int mask = 0;
373 for (int k = 0; k < 8; ++k)
374 if (v[k].has)
375 mask |= 1 << k;
376 // No mixed cell in the cube = no reconstructible interface, and nothing this rung's consumer
377 // could carry an `mdot` on. This is the same statement the rest of the rung makes (a grid-
378 // aligned sharp interface has no interfacial cell and therefore no PLIC area either), and it is
379 // what keeps a PERIODIC SEAM — the C = 1 | C = 0 jump a half-space wraps into — from being
380 // reported as interface.
381 if (mask == 0)
382 return 0.0;
383 double acc = 0.0;
384 for (int t = 0; t < 6; ++t) {
385 int code = 0;
386 for (int j = 0; j < 4; ++j)
387 if (v[TET[t][j]].psi < 0.0) // liquid
388 code |= 1 << j;
389 if (code == 0 || code == 15)
390 continue;
391 int nneg = 0;
392 for (int j = 0; j < 4; ++j)
393 if (code & (1 << j))
394 ++nneg;
395 double tri[4][3]; // up to 4 crossing points (the 2-vs-2 quad)
396 int ntri = 0;
397 if (nneg == 1 || nneg == 3) {
398 // the lone corner, and the three edges leaving it
399 const bool lonesign = (nneg == 1);
400 int lone = -1;
401 for (int j = 0; j < 4; ++j)
402 if (((code & (1 << j)) != 0) == lonesign) {
403 lone = j;
404 break;
405 }
406 for (int j = 0; j < 4; ++j) {
407 if (j == lone)
408 continue;
409 const int ca = TET[t][lone], cb = TET[t][j];
410 double pa[3], pb[3];
411 mcCornerPos(ca, pa);
412 mcCornerPos(cb, pb);
413 const double dv[3] = {pb[0] - pa[0], pb[1] - pa[1], pb[2] - pa[2]};
414 const double s = mcEdgeT(v[ca], v[cb], dv, src);
415 for (int d = 0; d < 3; ++d)
416 tri[ntri][d] = pa[d] + s * dv[d];
417 ++ntri;
418 }
419 acc += mcTriangleToCorner(tri[0], tri[1], tri[2], lc, deposit, mask, g);
420 } else {
421 // 2 vs 2: the quad through the four edges that join the two pairs, in cyclic order
422 int neg[2], pos[2], nn = 0, np = 0;
423 for (int j = 0; j < 4; ++j)
424 ((code & (1 << j)) ? neg[nn++] : pos[np++]) = j;
425 const int pair[4][2] = {
426 {neg[0], pos[0]}, {neg[0], pos[1]}, {neg[1], pos[1]}, {neg[1], pos[0]}};
427 for (int q = 0; q < 4; ++q) {
428 const int ca = TET[t][pair[q][0]], cb = TET[t][pair[q][1]];
429 double pa[3], pb[3];
430 mcCornerPos(ca, pa);
431 mcCornerPos(cb, pb);
432 const double dv[3] = {pb[0] - pa[0], pb[1] - pa[1], pb[2] - pa[2]};
433 const double s = mcEdgeT(v[ca], v[cb], dv, src);
434 for (int d = 0; d < 3; ++d)
435 tri[q][d] = pa[d] + s * dv[d];
436 }
437 acc += mcTriangleToCorner(tri[0], tri[1], tri[2], lc, deposit, mask, g);
438 acc += mcTriangleToCorner(tri[0], tri[2], tri[3], lc, deposit, mask, g);
439 }
440 }
441 return acc;
442}
443
445KOKKOS_INLINE_FUNCTION double mcCubeCornerArea(const McVertex v[8], int lc, int src, int deposit) {
446 return mcCubeCornerArea(v, lc, src, deposit, VofMetric{});
447}
448
449} // namespace peclet::flow::vof
450
451#endif // PECLET_FLOW_VOF_MARCHING_CUBES_HPP
McDeposit
Which cell a triangle's area is booked to.
@ kMcDepositSplit
clipped to each cell's cube, each cell gets its own piece
@ kMcDepositCentroid
the whole triangle to the cell holding its centroid
int mcClipHalf(double p[8][3], int n, int axis, double c, bool keepAbove)
One Sutherland-Hodgman clip of a convex polygon against the half-space keepAbove ?...
int mcNearestMasked(const double p[3], int mask)
The corner in mask nearest the point p; the lowest index breaks a tie.
double mcTwiceArea(const double p0[3], const double p1[3], const double p2[3], const VofMetric &g)
Twice the area of the triangle (p0, p1, p2) — i.e. |(p1-p0) x (p2-p0)|.
double mcPolygonArea(const double p[8][3], int n, const VofMetric &g)
Area of a PLANAR polygon of n vertices, 1/2 |sum p_i x p_{i+1}|.
double mcTriangleToCorner(const double p0[3], const double p1[3], const double p2[3], int lc, int deposit, int mask, const VofMetric &g)
The part of one triangle that belongs to local corner lc of the dual cube.
McSource
What the edge crossing interpolates.
@ kMcSrcColour
the C = 1/2 level set (the WO's design)
@ kMcSrcPlic
the zero of the PLIC-reconstructed signed distance (the smooth variant)
double mcClipToCorner(const double p0[3], const double p1[3], const double p2[3], int k, const VofMetric &g)
The area of the part of one triangle inside corner k's octant of the dual cube.
void mcCornerPos(int k, double p[3])
Local coordinates of dual-cube corner k (bit 0 = x, bit 1 = y, bit 2 = z).
int mcRetarget(int k, int mask)
The retarget rule.
double mcEdgeT(const McVertex &a, const McVertex &b, const double dv[3], int src)
The parameter t in [0,1] at which the level set cuts the edge from corner a to corner b.
double mcCubeCornerArea(const McVertex v[8], int lc, int src, int deposit, const VofMetric &g)
The interfacial area inside one dual cube that belongs to its local corner lc.
One dual-cube corner = one cell centre.