peclet.voro 1.0.4
Device-native moving-particle Voronoi dynamics
Loading...
Searching...
No Matches
convex_cell.hpp
Go to the documentation of this file.
1
24#ifndef PECLET_VORO_CONVEX_CELL_HPP
25#define PECLET_VORO_CONVEX_CELL_HPP
26
27#include <Kokkos_Core.hpp>
28
29#include "peclet/voro/params.hpp" // kMaxPlanes / kMaxTriangles: the capacity defaults
30#include "peclet/voro/plane_policy.hpp" // Voronoi/Power plane-from-DOF policies (leaf: Kokkos only)
31
32// Loop unroll hint, applied ONLY in the CUDA/HIP device passes (where it lets the compiler
33// scalarize small dynamically-indexed local arrays into registers); a no-op on the host so gcc sees
34// no unknown pragma.
35#if defined(__CUDA_ARCH__) || defined(__HIP_DEVICE_COMPILE__)
36#define VOR_UNROLL _Pragma("unroll")
37#else
38#define VOR_UNROLL
39#endif
40
41namespace peclet::voro {
42
43namespace detail {
44// Minimal forward-mode dual number (value + K partials) used by ConvexCell::geomVolumeAreaGrad (the
45// geomFull area-Jacobian). Templated on Real so it routes through nvcc/hipcc like the rest of the
46// header.
47template <class Real, int K>
48struct Dual {
49 Real v;
50 Real d[K];
51};
52template <class Real, int K>
53KOKKOS_INLINE_FUNCTION Dual<Real, K> dnum(Real c) {
55 r.v = c;
56 for (int i = 0; i < K; ++i)
57 r.d[i] = Real(0);
58 return r;
59}
60template <class Real, int K>
61KOKKOS_INLINE_FUNCTION Dual<Real, K> dseed(Real c, int s) {
63 r.d[s] = Real(1);
64 return r;
65}
66template <class Real, int K>
69 r.v = a.v + b.v;
70 for (int i = 0; i < K; ++i)
71 r.d[i] = a.d[i] + b.d[i];
72 return r;
73}
74template <class Real, int K>
77 r.v = a.v - b.v;
78 for (int i = 0; i < K; ++i)
79 r.d[i] = a.d[i] - b.d[i];
80 return r;
81}
82template <class Real, int K>
85 r.v = a.v * b.v;
86 for (int i = 0; i < K; ++i)
87 r.d[i] = a.d[i] * b.v + a.v * b.d[i];
88 return r;
89}
90template <class Real, int K>
93 const Real inv = Real(1) / b.v;
94 r.v = a.v * inv;
95 for (int i = 0; i < K; ++i)
96 r.d[i] = (a.d[i] - r.v * b.d[i]) * inv;
97 return r;
98}
99template <class Real, int K>
102 r.v = Kokkos::sqrt(a.v);
103 const Real inv = (r.v > Real(0)) ? Real(1) / (Real(2) * r.v) : Real(0);
104 for (int i = 0; i < K; ++i)
105 r.d[i] = a.d[i] * inv;
106 return r;
107}
108template <class Real, int K>
110 return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
111}
112template <class Real, int K>
114 Dual<Real, K> o[3]) {
115 o[0] = a[1] * b[2] - a[2] * b[1];
116 o[1] = a[2] * b[0] - a[0] * b[2];
117 o[2] = a[0] * b[1] - a[1] * b[0];
118}
119template <class Real, int K>
121 const Dual<Real, K> c[3]) {
122 Dual<Real, K> bc[3];
123 dcross(b, c, bc);
124 return ddot(a, bc);
125}
126template <class Real, int K>
128 Dual<Real, K> f[3]) {
129 const Dual<Real, K> s = ddot(v, c) / ddot(c, c);
130 f[0] = v[0] - s * c[0];
131 f[1] = v[1] - s * c[1];
132 f[2] = v[2] - s * c[2];
133}
134} // namespace detail
135
139template <class Real, int MAXP = kMaxPlanes, int MAXT = kMaxTriangles, bool TrackAdj = false>
141 static_assert(MAXP <= 255, "plane index must fit in unsigned char");
142 static constexpr int kMaxP = MAXP;
143 static constexpr int kMaxT = MAXT;
144 static constexpr bool kTrackAdj =
145 TrackAdj;
146 Real n[MAXP]
147 [3];
150 Real nn[MAXP];
151 int pnbr[MAXP];
152 int np;
153 unsigned char t0[MAXT], t1[MAXT], t2[MAXT];
154 Real vx[MAXT], vy[MAXT], vz[MAXT];
155 bool alive[MAXT];
156 int nt;
157 bool overflow;
158
172 Kokkos::Array<int, (TrackAdj ? MAXT : 0) * 3> adj;
173
176 KOKKOS_INLINE_FUNCTION void initBox(Real L0, Real L1, Real L2) {
177 const Real h[3] = {Real(0.5) * L0, Real(0.5) * L1, Real(0.5) * L2};
178 for (int ax = 0; ax < 3; ++ax) {
179 for (int s = 0; s < 2; ++s) {
180 const int k = 2 * ax + s;
181 n[k][0] = n[k][1] = n[k][2] = 0;
182 n[k][ax] = (s == 0) ? h[ax] : -h[ax]; // foot point = (±h) e_ax
183 nn[k] = h[ax] * h[ax];
184 pnbr[k] = -1;
185 }
186 }
187 np = 6;
188 nt = 0;
189 overflow = false;
190 // 8 corners: x-plane (0|1), y-plane (2|3), z-plane (4|5)
191 for (int sx = 0; sx < 2; ++sx)
192 for (int sy = 0; sy < 2; ++sy)
193 for (int sz = 0; sz < 2; ++sz) {
194 t0[nt] = (unsigned char)(0 + sx);
195 t1[nt] = (unsigned char)(2 + sy);
196 t2[nt] = (unsigned char)(4 + sz);
197 alive[nt] = true;
199 ++nt;
200 }
201 if constexpr (TrackAdj)
202 rebuildAdjacency(); // one-time bootstrap (~12 box triangles)
203 }
204
209 KOKKOS_INLINE_FUNCTION void computeVertex(int t) {
210 const Real* a = n[t0[t]];
211 const Real* b = n[t1[t]];
212 const Real* c = n[t2[t]];
213 const Real da = nn[t0[t]], db = nn[t1[t]], dc = nn[t2[t]];
214 const Real bc[3] = {b[1] * c[2] - b[2] * c[1], b[2] * c[0] - b[0] * c[2],
215 b[0] * c[1] - b[1] * c[0]};
216 const Real ca[3] = {c[1] * a[2] - c[2] * a[1], c[2] * a[0] - c[0] * a[2],
217 c[0] * a[1] - c[1] * a[0]};
218 const Real ab[3] = {a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2],
219 a[0] * b[1] - a[1] * b[0]};
220 const Real det = a[0] * bc[0] + a[1] * bc[1] + a[2] * bc[2];
221 const Real inv = det != Real(0) ? Real(1) / det : Real(0);
222 vx[t] = (da * bc[0] + db * ca[0] + dc * ab[0]) * inv;
223 vy[t] = (da * bc[1] + db * ca[1] + dc * ab[1]) * inv;
224 vz[t] = (da * bc[2] + db * ca[2] + dc * ab[2]) * inv;
225 }
226
231 KOKKOS_INLINE_FUNCTION void initBoxPlanes(Real L0, Real L1, Real L2) {
232 const Real h[3] = {Real(0.5) * L0, Real(0.5) * L1, Real(0.5) * L2};
233 for (int ax = 0; ax < 3; ++ax)
234 for (int s = 0; s < 2; ++s) {
235 const int k = 2 * ax + s;
236 n[k][0] = n[k][1] = n[k][2] = 0;
237 n[k][ax] = (s == 0) ? h[ax] : -h[ax]; // foot point = (±h) e_ax
238 nn[k] = h[ax] * h[ax];
239 pnbr[k] = -1;
240 }
241 np = 6;
242 overflow = false;
243 }
244
257 template <class Policy = Voronoi>
258 KOKKOS_INLINE_FUNCTION void reevalGeometry(Real sx, Real sy, Real sz, const Real* pos, Real L,
259 Real wSelf = Real(0), const Real* weight = nullptr) {
260 const Real Lh = Real(0.5) * L;
261 for (int k = 0; k < np; ++k) {
262 if (pnbr[k] < 0)
263 continue; // bounding-box / SDF-wall plane — does not move
264 const int g = pnbr[k];
265 Real rx = pos[3 * g] - sx, ry = pos[3 * g + 1] - sy, rz = pos[3 * g + 2] - sz;
266 rx = rx > Lh ? rx - L : (rx < -Lh ? rx + L : rx);
267 ry = ry > Lh ? ry - L : (ry < -Lh ? ry + L : ry);
268 rz = rz > Lh ? rz - L : (rz < -Lh ? rz + L : rz);
269 if constexpr (Policy::kHasWeightDof) {
270 // radical foot point: n = (d/|r|²) r, d = ½(|r|² + w_self − w_nbr)
271 const Real r[3] = {rx, ry, rz};
272 const Real rho = rx * rx + ry * ry + rz * rz;
273 const Real off = Policy::template offsetFromRel<Real>(r, wSelf, weight[g]);
274 const Real a = rho > Real(0) ? off / rho : Real(0);
275 n[k][0] = a * rx;
276 n[k][1] = a * ry;
277 n[k][2] = a * rz;
278 nn[k] = n[k][0] * n[k][0] + n[k][1] * n[k][1] + n[k][2] * n[k][2];
279 } else {
280 // foot point of the Voronoi bisector: n = ½r, offset nn = |n|² = ¼|r|²
281 const Real hx = Real(0.5) * rx, hy = Real(0.5) * ry, hz = Real(0.5) * rz;
282 n[k][0] = hx;
283 n[k][1] = hy;
284 n[k][2] = hz;
285 nn[k] = hx * hx + hy * hy + hz * hz;
286 }
287 }
288 for (int t = 0; t < nt; ++t)
289 if (alive[t])
290 computeVertex(t);
291 }
292
303 KOKKOS_INLINE_FUNCTION bool isSelfConsistent(Real tol) const {
304 // Branch-free max-slack accumulation (no per-thread early-out divergence): the whole-cell worst
305 // perpendicular poke is compared to `tol` once at the end. The brute scan is the complete
306 // certificate — it catches far-pokes by NON-adjacent planes too, which the local (Lawson) form
307 // deliberately does not. (An early-out variant would only help when np is large AND fail-fast
308 // rate is high; measure first.)
309 Real maxd = Real(0);
310 for (int t = 0; t < nt; ++t) {
311 if (!alive[t])
312 continue;
313 const Real v[3] = {vx[t], vy[t], vz[t]};
314 for (int k = 0; k < np; ++k) {
315 if (t0[t] == k || t1[t] == k || t2[t] == k)
316 continue; // a plane that defines this vertex
317 const Real s = n[k][0] * v[0] + n[k][1] * v[1] + n[k][2] * v[2] - nn[k];
318 if (s >
319 Real(0)) { // v outside plane k; convert to a perpendicular distance for the tol test
320 const Real invlen = (nn[k] > Real(0)) ? Real(1) / Kokkos::sqrt(nn[k]) : Real(0);
321 maxd = Kokkos::max(maxd, s * invlen);
322 }
323 }
324 }
325 return maxd <= tol;
326 }
327
345 KOKKOS_INLINE_FUNCTION bool isSelfConsistentPartners(Real tol, int* outPartners, int maxPartners,
346 int& nPartners) const {
347 nPartners = 0;
348 bool consistent = true;
349 for (int t = 0; t < nt; ++t) {
350 if (!alive[t])
351 continue;
352 const Real v[3] = {vx[t], vy[t], vz[t]};
353 for (int k = 0; k < np; ++k) {
354 if (t0[t] == k || t1[t] == k || t2[t] == k)
355 continue; // a plane that defines this vertex
356 const Real s = n[k][0] * v[0] + n[k][1] * v[1] + n[k][2] * v[2] - nn[k];
357 if (s > Real(0)) { // v outside plane k; perpendicular distance vs tol
358 const Real invlen = (nn[k] > Real(0)) ? Real(1) / Kokkos::sqrt(nn[k]) : Real(0);
359 if (s * invlen > tol) {
360 consistent = false;
361 const int part = pnbr[k];
362 if (part >= 0) { // box/SDF planes (pnbr<0) have no partner cell
363 bool seen = false;
364 for (int q = 0; q < nPartners; ++q)
365 if (outPartners[q] == part) {
366 seen = true;
367 break;
368 }
369 if (!seen && nPartners < maxPartners)
370 outPartners[nPartners++] = part;
371 }
372 }
373 }
374 }
375 }
376 return consistent;
377 }
378
381 KOKKOS_INLINE_FUNCTION int oppositePlane(int s, int x, int y) const {
382 const int a = t0[s], b = t1[s], c = t2[s];
383 return (a != x && a != y) ? a : (b != x && b != y) ? b : c;
384 }
385
394 KOKKOS_INLINE_FUNCTION void computePoke4(unsigned char* out) const {
395 static_assert(TrackAdj, "computePoke4 requires TrackAdj");
396 for (int t = 0; t < nt; ++t) {
397 if (!alive[t]) {
398 out[t * 4 + 0] = out[t * 4 + 1] = out[t * 4 + 2] = out[t * 4 + 3] = 0xff;
399 continue;
400 }
401 const int vt[3] = {t0[t], t1[t], t2[t]};
402 int eo[3];
403 for (int e = 0; e < 3; ++e) {
404 const int s = adj[t * 3 + e];
405 eo[e] = (s >= 0 && s < nt && alive[s]) ? oppositePlane(s, vt[e], vt[(e + 1) % 3]) : 0xff;
406 out[t * 4 + e] = (unsigned char)(eo[e] & 0xff);
407 }
408 const Real v[3] = {vx[t], vy[t], vz[t]};
409 Real best = Real(3.4e38);
410 int g = 0xff;
411 for (int k = 0; k < np; ++k) {
412 if (k == vt[0] || k == vt[1] || k == vt[2])
413 continue; // defining plane
414 if (k == eo[0] || k == eo[1] || k == eo[2])
415 continue; // already an edge-opposite
416 const Real slack = nn[k] - (n[k][0] * v[0] + n[k][1] * v[1] + n[k][2] * v[2]);
417 if (slack >= Real(0) && slack < best) {
418 best = slack;
419 g = k;
420 }
421 }
422 out[t * 4 + 3] = (unsigned char)(g & 0xff);
423 }
424 }
425
432 KOKKOS_INLINE_FUNCTION bool isLocallyConvex(const unsigned char* poke4, Real tol) const {
433 Real maxd = Real(0);
434 for (int t = 0; t < nt; ++t) {
435 if (!alive[t])
436 continue;
437 const Real v[3] = {vx[t], vy[t], vz[t]};
438 for (int j = 0; j < 4; ++j) {
439 const int k = (int)poke4[t * 4 + j];
440 if (k == 0xff)
441 continue;
442 const Real sl = n[k][0] * v[0] + n[k][1] * v[1] + n[k][2] * v[2] - nn[k];
443 if (sl > Real(0)) {
444 const Real invlen = (nn[k] > Real(0)) ? Real(1) / Kokkos::sqrt(nn[k]) : Real(0);
445 maxd = Kokkos::max(maxd, sl * invlen);
446 }
447 }
448 }
449 return maxd <= tol;
450 }
451
456 KOKKOS_INLINE_FUNCTION bool isLocallyConvexPartners(const unsigned char* poke4, Real tol,
457 int* outPartners, int maxPartners,
458 int& nPartners) const {
459 nPartners = 0;
460 bool consistent = true;
461 for (int t = 0; t < nt; ++t) {
462 if (!alive[t])
463 continue;
464 const Real v[3] = {vx[t], vy[t], vz[t]};
465 for (int j = 0; j < 4; ++j) {
466 const int k = (int)poke4[t * 4 + j];
467 if (k == 0xff)
468 continue;
469 const Real sl = n[k][0] * v[0] + n[k][1] * v[1] + n[k][2] * v[2] - nn[k];
470 if (sl > Real(0)) {
471 const Real invlen = (nn[k] > Real(0)) ? Real(1) / Kokkos::sqrt(nn[k]) : Real(0);
472 if (sl * invlen > tol) {
473 consistent = false;
474 const int part = pnbr[k];
475 if (part >= 0) {
476 bool seen = false;
477 for (int q = 0; q < nPartners; ++q)
478 if (outPartners[q] == part) {
479 seen = true;
480 break;
481 }
482 if (!seen && nPartners < maxPartners)
483 outPartners[nPartners++] = part;
484 }
485 }
486 }
487 }
488 }
489 return consistent;
490 }
491
495 KOKKOS_INLINE_FUNCTION Real faceMoment2(int k) const {
496 Real fx[MAXFV], fy[MAXFV], fz[MAXFV];
497 const int m = faceOrdered(k, fx, fy, fz);
498 if (m < 3)
499 return Real(0);
500 const Real nx = n[k][0], ny = n[k][1], nz = n[k][2];
501 const Real nl = Kokkos::sqrt(nn[k]);
502 if (!(nl > Real(0)))
503 return Real(0);
504 const Real ux = nx / nl, uy = ny / nl, uz = nz / nl;
505 Real M2 = Real(0);
506 for (int q = 0; q < m; ++q) {
507 const int r = (q + 1) % m;
508 const Real a[3] = {fx[q] - nx, fy[q] - ny, fz[q] - nz};
509 const Real b[3] = {fx[r] - nx, fy[r] - ny, fz[r] - nz};
510 const Real cr[3] = {a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2],
511 a[0] * b[1] - a[1] * b[0]};
512 const Real At = Real(0.5) * (cr[0] * ux + cr[1] * uy + cr[2] * uz);
513 const Real aa = a[0] * a[0] + a[1] * a[1] + a[2] * a[2];
514 const Real bb = b[0] * b[0] + b[1] * b[1] + b[2] * b[2];
515 const Real ab = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
516 M2 += At / Real(6) * (aa + bb + ab); // (A/12)(|a|²+|b|²+|a+b|²)
517 }
518 return M2;
519 }
520
526 KOKKOS_INLINE_FUNCTION Real planeGap(const Real pdir[3], Real d) const {
527 const Real l2 = pdir[0] * pdir[0] + pdir[1] * pdir[1] + pdir[2] * pdir[2];
528 if (!(l2 > Real(0)))
529 return Real(-1e30);
530 Real m = Real(-1e30);
531 for (int t = 0; t < nt; ++t) {
532 if (!alive[t])
533 continue;
534 const Real s = pdir[0] * vx[t] + pdir[1] * vy[t] + pdir[2] * vz[t] - d;
535 if (s > m)
536 m = s;
537 }
538 return m / Kokkos::sqrt(l2);
539 }
540
548 KOKKOS_INLINE_FUNCTION int compactPlanes() {
549 unsigned char used[MAXP];
550 if (np > MAXP) // never true (clip caps np at MAXP); bounds the loops for the compiler
551 np = MAXP;
552 for (int k = 0; k < np; ++k)
553 used[k] = (k < 6) ? 1 : 0;
554 for (int t = 0; t < nt; ++t)
555 if (alive[t]) {
556 used[t0[t]] = 1;
557 used[t1[t]] = 1;
558 used[t2[t]] = 1;
559 }
560 unsigned char map[MAXP];
561 int m = 0;
562 for (int k = 0; k < np; ++k) {
563 if (!used[k]) {
564 map[k] = 0;
565 continue;
566 }
567 map[k] = (unsigned char)m;
568 if (m != k) {
569 n[m][0] = n[k][0];
570 n[m][1] = n[k][1];
571 n[m][2] = n[k][2];
572 nn[m] = nn[k];
573 pnbr[m] = pnbr[k];
574 }
575 ++m;
576 }
577 const int removed = np - m;
578 if (removed > 0) {
579 for (int t = 0; t < nt; ++t)
580 if (alive[t]) {
581 t0[t] = map[t0[t]];
582 t1[t] = map[t1[t]];
583 t2[t] = map[t2[t]];
584 }
585 np = m;
586 }
587 return removed;
588 }
589
591 KOKKOS_INLINE_FUNCTION Real maxVertexRsq() const {
592 Real m = 0;
593 for (int t = 0; t < nt; ++t) {
594 if (!alive[t])
595 continue;
596 const Real r = vx[t] * vx[t] + vy[t] * vy[t] + vz[t] * vz[t];
597 if (r > m)
598 m = r;
599 }
600 return m;
601 }
602
609 KOKKOS_INLINE_FUNCTION int findSharing(int self, int x, int y) const {
610 for (int s = 0; s < nt; ++s) {
611 if (s == self || !alive[s])
612 continue;
613 const int a = t0[s], b = t1[s], c = t2[s];
614 const bool hasX = (a == x || b == x || c == x);
615 const bool hasY = (a == y || b == y || c == y);
616 if (hasX && hasY)
617 return s;
618 }
619 return -1;
620 }
621
624 KOKKOS_INLINE_FUNCTION void rebuildAdjacency() {
625 if constexpr (TrackAdj) {
626 for (int t = 0; t < nt; ++t) {
627 if (!alive[t])
628 continue;
629 const int vt[3] = {t0[t], t1[t], t2[t]};
630 for (int e = 0; e < 3; ++e)
631 adj[t * 3 + e] = findSharing(t, vt[e], vt[(e + 1) % 3]);
632 }
633 }
634 }
635
640 KOKKOS_INLINE_FUNCTION bool checkAdjacencyInvariant() const {
641 if constexpr (!TrackAdj) {
642 return true;
643 } else {
644 for (int t = 0; t < nt; ++t) {
645 if (!alive[t])
646 continue;
647 const int vt[3] = {t0[t], t1[t], t2[t]};
648 for (int e = 0; e < 3; ++e) {
649 const int x = vt[e], y = vt[(e + 1) % 3];
650 const int s = adj[t * 3 + e];
651 if (s < 0 || s >= nt || !alive[s])
652 return false; // live target
653 const int vs[3] = {t0[s], t1[s], t2[s]};
654 const bool hasX = (vs[0] == x || vs[1] == x || vs[2] == x);
655 const bool hasY = (vs[0] == y || vs[1] == y || vs[2] == y);
656 if (!(hasX && hasY))
657 return false; // shares the edge
658 bool back = false; // symmetry
659 for (int f = 0; f < 3; ++f)
660 if (adj[s * 3 + f] == t)
661 back = true;
662 if (!back)
663 return false;
664 }
665 }
666 return true;
667 }
668 }
669
670 KOKKOS_INLINE_FUNCTION int allocTri() {
671 for (int s = 0; s < nt; ++s)
672 if (!alive[s])
673 return s;
674 if (nt < MAXT)
675 return nt++;
676 overflow = true;
677 return -1;
678 }
679
688 KOKKOS_INLINE_FUNCTION bool clip(const Real pdir[3], Real d, int nbr, Real* gapOut = nullptr) {
689 // The plane list keeps every plane that cut when it was committed, including those a later
690 // plane made redundant (no live triangle any more) — typically 2–3× the face count. At the
691 // cap, drop those first (compactPlanes: geometry and topology unchanged, indices renumbered)
692 // and overflow only if the LIVE planes fill MAXP. Without this the overflow depended on the
693 // insertion order: a 29-face cell committed 59 planes in one gather order and 64 in another
694 // (thread-scheduling of the counting-sort scatter), and was silently published with zero
695 // volume in the latter. Callers holding plane indices across a clip must keep headroom
696 // (clipCellAgainstSdf compacts up front for exactly that reason).
697 if (np >= MAXP && compactPlanes() == 0) {
698 overflow = true;
699 if (gapOut)
700 *gapOut = Real(-1e30);
701 return false;
702 }
703 const int pi = np; // tentative index
704 const Real l2 = pdir[0] * pdir[0] + pdir[1] * pdir[1] + pdir[2] * pdir[2];
705 const Real a = (l2 > Real(0)) ? d / l2 : Real(0); // foot scale: nf = a·pdir
706 n[pi][0] = a * pdir[0];
707 n[pi][1] = a * pdir[1];
708 n[pi][2] = a * pdir[2];
709 nn[pi] = n[pi][0] * n[pi][0] + n[pi][1] * n[pi][1] + n[pi][2] * n[pi][2];
710 pnbr[pi] = nbr;
711
712 // Mark triangles whose dual vertex is outside the new half-space (nf·v > nf·nf).
713 bool kill[MAXT];
714 bool any = false;
715 Real smax = Real(-1e30);
716 for (int t = 0; t < nt; ++t) {
717 kill[t] = false;
718 if (!alive[t])
719 continue;
720 const Real s = n[pi][0] * vx[t] + n[pi][1] * vy[t] + n[pi][2] * vz[t] - nn[pi];
721 if (s > smax)
722 smax = s;
723 if (s > 0) {
724 kill[t] = true;
725 any = true;
726 }
727 }
728 if (gapOut) { // s = n·v − n·n is |n| times the distance beyond the plane
729 const Real nl = Kokkos::sqrt(nn[pi]);
730 *gapOut = nl > Real(0) ? smax / nl : Real(-1e30);
731 }
732 if (!any)
733 return false; // candidate does not cut -> no-op, plane not committed
734
735 np = pi + 1; // commit the plane
736
737 if constexpr (!TrackAdj) {
738 // ---- Lean path (unchanged): collect the horizon by findSharing, then retriangulate. ----
739 // For each killed triangle edge whose sharing triangle is alive, the new plane gets a
740 // triangle (x, y, pi). Collect first, then mutate.
741 unsigned char nA[MAXT], nB[MAXT];
742 int nnew = 0;
743 for (int t = 0; t < nt; ++t) {
744 if (!kill[t])
745 continue;
746 const int vtx[3] = {t0[t], t1[t], t2[t]};
747 for (int e = 0; e < 3; ++e) {
748 const int x = vtx[e], y = vtx[(e + 1) % 3];
749 const int other = findSharing(t, x, y);
750 if (other >= 0 && !kill[other]) {
751 if (nnew < MAXT) {
752 nA[nnew] = (unsigned char)x;
753 nB[nnew] = (unsigned char)y;
754 ++nnew;
755 } else {
756 overflow = true;
757 }
758 }
759 }
760 }
761 // Remove killed triangles.
762 for (int t = 0; t < nt; ++t)
763 if (kill[t])
764 alive[t] = false;
765 // Add the new fan around plane pi.
766 for (int i = 0; i < nnew; ++i) {
767 const int slot = allocTri();
768 if (slot < 0)
769 break;
770 t0[slot] = nA[i];
771 t1[slot] = nB[i];
772 t2[slot] = (unsigned char)pi;
773 alive[slot] = true;
774 computeVertex(slot);
775 }
776 } else {
777 // ---- TrackAdj path: walk the conflict-region boundary as ONE oriented cycle via `adj`
778 // (O(conflict), no findSharing), then stitch the new fan's adjacency in O(1) per edge. The
779 // conflict region (vertices outside the new half-space) is connected and its boundary is a
780 // single cycle for a convex clip. ----
781 int hA[MAXT], hB[MAXT], hSurv[MAXT],
782 hBack[MAXT]; // horizon edge {A,B}, survivor across it, slot back
783 int H = 0;
784 // Start edge: a killed triangle with a surviving neighbour across some slot.
785 int t0k = -1, e0 = -1;
786 for (int t = 0; t < nt && t0k < 0; ++t) {
787 if (!kill[t])
788 continue;
789 for (int e = 0; e < 3; ++e) {
790 const int s = adj[t * 3 + e];
791 if (s >= 0 && alive[s] && !kill[s]) {
792 t0k = t;
793 e0 = e;
794 break;
795 }
796 }
797 }
798 if (t0k < 0) {
799 overflow = true;
800 return true;
801 } // no surviving boundary (cannot happen for a real cut)
802 const int vstart[3] = {t0[t0k], t1[t0k], t2[t0k]};
803 int A = vstart[e0], B = vstart[(e0 + 1) % 3];
804 int tt = t0k, ee = e0;
805 const int guardMax = 6 * nt + 12;
806 int guard = 0;
807 do {
808 const int surv = adj[tt * 3 + ee];
809 int back = 0;
810 for (int f = 0; f < 3; ++f)
811 if (adj[surv * 3 + f] == tt) {
812 back = f;
813 break;
814 }
815 if (H < MAXT) {
816 hA[H] = A;
817 hB[H] = B;
818 hSurv[H] = surv;
819 hBack[H] = back;
820 ++H;
821 } else {
822 overflow = true;
823 break;
824 }
825 // Pivot around vertex B, rotating through killed triangles, until we exit to the next
826 // survivor.
827 const int pivot = B;
828 int rt = tt, arrival = ee, rotGuard = 0;
829 while (true) {
830 const int rv[3] = {t0[rt], t1[rt], t2[rt]};
831 const int lv = (rv[0] == pivot) ? 0
832 : (rv[1] == pivot) ? 1
833 : 2; // local index of pivot in rt
834 const int crossSlot = (arrival == lv) ? (lv + 2) % 3 : lv; // the OTHER B-edge of rt
835 const int rn = adj[rt * 3 + crossSlot];
836 if (rn >= 0 && kill[rn]) { // still inside the conflict region: keep rotating around B
837 int f = (adj[rn * 3 + 0] == rt) ? 0 : (adj[rn * 3 + 1] == rt) ? 1 : 2;
838 rt = rn;
839 arrival = f;
840 if (++rotGuard > guardMax) {
841 overflow = true;
842 break;
843 }
844 continue;
845 }
846 // rn survives -> edge crossSlot of rt is the next horizon edge, oriented out of the
847 // pivot.
848 const int u = rv[crossSlot], w = rv[(crossSlot + 1) % 3];
849 A = pivot;
850 B = (u == pivot) ? w : u;
851 tt = rt;
852 ee = crossSlot;
853 break;
854 }
855 if (overflow)
856 break;
857 if (++guard > guardMax) {
858 overflow = true;
859 break;
860 }
861 } while (!(tt == t0k && ee == e0));
862
863 // Remove killed triangles (frees their slots for the fan via allocTri).
864 for (int t = 0; t < nt; ++t)
865 if (kill[t])
866 alive[t] = false;
867 // Allocate the whole fan first (so τ_{i±1} cross-references resolve), then set topology +
868 // adjacency.
869 int tau[MAXT];
870 bool ok = !overflow;
871 for (int i = 0; i < H && ok; ++i) {
872 tau[i] = allocTri();
873 if (tau[i] < 0)
874 ok = false;
875 else
876 alive[tau[i]] = true; // reserve the slot so allocTri doesn't hand it out again
877 }
878 if (ok) {
879 for (int i = 0; i < H; ++i) {
880 const int s = tau[i];
881 t0[s] = (unsigned char)hA[i];
882 t1[s] = (unsigned char)hB[i];
883 t2[s] = (unsigned char)pi;
884 alive[s] = true;
885 computeVertex(s);
886 adj[s * 3 + 0] = hSurv[i]; // slot 0 = edge {A_i,B_i} -> survivor across the horizon
887 adj[s * 3 + 1] = tau[(i + 1) % H]; // slot 1 = edge {B_i,P} -> next fan triangle
888 adj[s * 3 + 2] = tau[(i - 1 + H) % H]; // slot 2 = edge {P,A_i} -> prev fan triangle
889 adj[hSurv[i] * 3 + hBack[i]] = s; // back-patch the survivor's slot to this fan triangle
890 }
891 }
892 }
893 return true;
894 }
895
896 KOKKOS_INLINE_FUNCTION bool empty() const {
897 for (int t = 0; t < nt; ++t)
898 if (alive[t])
899 return false;
900 return true;
901 }
902
903 KOKKOS_INLINE_FUNCTION int countFaces() const {
904 int nf = 0;
905 for (int k = 0; k < np; ++k) {
906 bool used = false;
907 for (int t = 0; t < nt && !used; ++t)
908 if (alive[t] && (t0[t] == k || t1[t] == k || t2[t] == k))
909 used = true;
910 if (used)
911 ++nf;
912 }
913 return nf;
914 }
915
916 static constexpr int MAXFV = 28; // max vertices on one face polygon
917
923 KOKKOS_INLINE_FUNCTION int faceOrderedIdx(int k, int out[MAXFV]) const {
924 int m = 0;
925 Real fx[MAXFV], fy[MAXFV], fz[MAXFV];
926 for (int t = 0; t < nt; ++t) {
927 if (!alive[t])
928 continue;
929 if (t0[t] != k && t1[t] != k && t2[t] != k)
930 continue;
931 if (m < MAXFV) {
932 out[m] = t;
933 fx[m] = vx[t];
934 fy[m] = vy[t];
935 fz[m] = vz[t];
936 ++m;
937 }
938 }
939 if (m < 3)
940 return m;
941 const Real nx = n[k][0], ny = n[k][1], nz = n[k][2];
942 const Real nlen = Kokkos::sqrt(nx * nx + ny * ny + nz * nz);
943 if (nlen == Real(0))
944 return 0;
945 const Real un[3] = {nx / nlen, ny / nlen, nz / nlen};
946 Real e1[3];
947 if (Kokkos::fabs(un[0]) <= Kokkos::fabs(un[1]) && Kokkos::fabs(un[0]) <= Kokkos::fabs(un[2])) {
948 e1[0] = 0;
949 e1[1] = -un[2];
950 e1[2] = un[1];
951 } else if (Kokkos::fabs(un[1]) <= Kokkos::fabs(un[2])) {
952 e1[0] = -un[2];
953 e1[1] = 0;
954 e1[2] = un[0];
955 } else {
956 e1[0] = -un[1];
957 e1[1] = un[0];
958 e1[2] = 0;
959 }
960 const Real e1l = Kokkos::sqrt(e1[0] * e1[0] + e1[1] * e1[1] + e1[2] * e1[2]);
961 e1[0] /= e1l;
962 e1[1] /= e1l;
963 e1[2] /= e1l;
964 const Real e2[3] = {un[1] * e1[2] - un[2] * e1[1], un[2] * e1[0] - un[0] * e1[2],
965 un[0] * e1[1] - un[1] * e1[0]};
966 Real cx = 0, cy = 0, cz = 0;
967 for (int i = 0; i < m; ++i) {
968 cx += fx[i];
969 cy += fy[i];
970 cz += fz[i];
971 }
972 cx /= m;
973 cy /= m;
974 cz /= m;
975 Real ang[MAXFV];
976 for (int i = 0; i < m; ++i) {
977 const Real dx = fx[i] - cx, dy = fy[i] - cy, dz = fz[i] - cz;
978 const Real px = dx * e1[0] + dy * e1[1] + dz * e1[2]; // in-plane coords
979 const Real py = dx * e2[0] + dy * e2[1] + dz * e2[2];
980 // diamond pseudo-angle: monotonic in true angle, in [0,4), NO atan2 (transcendental)
981 const Real s = Kokkos::fabs(px) + Kokkos::fabs(py);
982 const Real t = (s > Real(0)) ? py / s : Real(0); // [-1,1]
983 ang[i] = (px < Real(0)) ? (Real(2) - t) : (py < Real(0) ? Real(4) + t : t);
984 }
985 for (int i = 1; i < m; ++i) {
986 const Real ka = ang[i];
987 const int ki = out[i];
988 int j = i - 1;
989 while (j >= 0 && ang[j] > ka) {
990 ang[j + 1] = ang[j];
991 out[j + 1] = out[j];
992 --j;
993 }
994 ang[j + 1] = ka;
995 out[j + 1] = ki;
996 }
997 return m;
998 }
999
1004 KOKKOS_INLINE_FUNCTION int faceOrdered(int k, Real fx[MAXFV], Real fy[MAXFV],
1005 Real fz[MAXFV]) const {
1006 int idx[MAXFV];
1007 const int m = faceOrderedIdx(k, idx);
1008 for (int i = 0; i < m; ++i) {
1009 fx[i] = vx[idx[i]];
1010 fy[i] = vy[idx[i]];
1011 fz[i] = vz[idx[i]];
1012 }
1013 return m;
1014 }
1015
1017 KOKKOS_INLINE_FUNCTION static void polyAreaVec(const Real fx[], const Real fy[], const Real fz[],
1018 int m, Real A[3]) {
1019 Real ax = 0, ay = 0, az = 0;
1020 for (int i = 0; i < m; ++i) {
1021 const int j = (i + 1 == m) ? 0 : i + 1;
1022 ax += fy[i] * fz[j] - fz[i] * fy[j];
1023 ay += fz[i] * fx[j] - fx[i] * fz[j];
1024 az += fx[i] * fy[j] - fy[i] * fx[j];
1025 }
1026 A[0] = Real(0.5) * ax;
1027 A[1] = Real(0.5) * ay;
1028 A[2] = Real(0.5) * az;
1029 }
1030
1031 static constexpr int MAXSV = 2 * MAXFV;
1032
1042 KOKKOS_INLINE_FUNCTION int sectionPolygon(const Real p0[3], const Real u[3], Real px[MAXSV],
1043 Real py[MAXSV], Real pz[MAXSV]) const {
1044 int m = 0;
1045 for (int i = 0; i < nt; ++i) {
1046 if (!alive[i])
1047 continue;
1048 const Real di = (vx[i] - p0[0]) * u[0] + (vy[i] - p0[1]) * u[1] + (vz[i] - p0[2]) * u[2];
1049 const unsigned char a0 = t0[i], a1 = t1[i], a2 = t2[i];
1050 for (int j = i + 1; j < nt; ++j) {
1051 if (!alive[j])
1052 continue;
1053 const unsigned char b0 = t0[j], b1 = t1[j], b2 = t2[j];
1054 const int common = (a0 == b0) + (a0 == b1) + (a0 == b2) + (a1 == b0) + (a1 == b1) +
1055 (a1 == b2) + (a2 == b0) + (a2 == b1) + (a2 == b2);
1056 if (common != 2)
1057 continue; // not an edge (a primal edge = two triangles sharing exactly two planes)
1058 const Real dj = (vx[j] - p0[0]) * u[0] + (vy[j] - p0[1]) * u[1] + (vz[j] - p0[2]) * u[2];
1059 if ((di > Real(0)) == (dj > Real(0)))
1060 continue; // both endpoints on the same side -> edge doesn't cross the plane
1061 const Real denom = di - dj;
1062 if (denom == Real(0))
1063 continue;
1064 const Real tt = di / denom;
1065 if (m >= MAXSV)
1066 return -1;
1067 px[m] = vx[i] + tt * (vx[j] - vx[i]);
1068 py[m] = vy[i] + tt * (vy[j] - vy[i]);
1069 pz[m] = vz[i] + tt * (vz[j] - vz[i]);
1070 ++m;
1071 }
1072 }
1073 if (m < 3)
1074 return m;
1075 const Real ulen = Kokkos::sqrt(u[0] * u[0] + u[1] * u[1] + u[2] * u[2]);
1076 if (ulen == Real(0))
1077 return 0;
1078 const Real un[3] = {u[0] / ulen, u[1] / ulen, u[2] / ulen};
1079 Real e1[3];
1080 if (Kokkos::fabs(un[0]) <= Kokkos::fabs(un[1]) && Kokkos::fabs(un[0]) <= Kokkos::fabs(un[2])) {
1081 e1[0] = 0;
1082 e1[1] = -un[2];
1083 e1[2] = un[1];
1084 } else if (Kokkos::fabs(un[1]) <= Kokkos::fabs(un[2])) {
1085 e1[0] = -un[2];
1086 e1[1] = 0;
1087 e1[2] = un[0];
1088 } else {
1089 e1[0] = -un[1];
1090 e1[1] = un[0];
1091 e1[2] = 0;
1092 }
1093 const Real e1l = Kokkos::sqrt(e1[0] * e1[0] + e1[1] * e1[1] + e1[2] * e1[2]);
1094 e1[0] /= e1l;
1095 e1[1] /= e1l;
1096 e1[2] /= e1l;
1097 const Real e2[3] = {un[1] * e1[2] - un[2] * e1[1], un[2] * e1[0] - un[0] * e1[2],
1098 un[0] * e1[1] - un[1] * e1[0]};
1099 Real cx = 0, cy = 0, cz = 0;
1100 for (int i = 0; i < m; ++i) {
1101 cx += px[i];
1102 cy += py[i];
1103 cz += pz[i];
1104 }
1105 cx /= m;
1106 cy /= m;
1107 cz /= m;
1108 Real ang[MAXSV];
1109 for (int i = 0; i < m; ++i) {
1110 const Real dx = px[i] - cx, dy = py[i] - cy, dz = pz[i] - cz;
1111 const Real qx = dx * e1[0] + dy * e1[1] + dz * e1[2];
1112 const Real qy = dx * e2[0] + dy * e2[1] + dz * e2[2];
1113 const Real s = Kokkos::fabs(qx) + Kokkos::fabs(qy);
1114 const Real tt = (s > Real(0)) ? qy / s : Real(0);
1115 ang[i] = (qx < Real(0)) ? (Real(2) - tt) : (qy < Real(0) ? Real(4) + tt : tt);
1116 }
1117 for (int i = 1; i < m; ++i) {
1118 Real ka = ang[i], kx = px[i], ky = py[i], kz = pz[i];
1119 int j = i - 1;
1120 while (j >= 0 && ang[j] > ka) {
1121 ang[j + 1] = ang[j];
1122 px[j + 1] = px[j];
1123 py[j + 1] = py[j];
1124 pz[j + 1] = pz[j];
1125 --j;
1126 }
1127 ang[j + 1] = ka;
1128 px[j + 1] = kx;
1129 py[j + 1] = ky;
1130 pz[j + 1] = kz;
1131 }
1132 return m;
1133 }
1134
1137 KOKKOS_INLINE_FUNCTION Real volume() const {
1138 Real vol = 0;
1139 Real fx[MAXFV], fy[MAXFV], fz[MAXFV];
1140 for (int k = 0; k < np; ++k) {
1141 const int m = faceOrdered(k, fx, fy, fz);
1142 if (m < 3)
1143 continue;
1144 Real A[3];
1145 polyAreaVec(fx, fy, fz, m, A);
1146 const Real area = Kokkos::sqrt(A[0] * A[0] + A[1] * A[1] + A[2] * A[2]);
1147 const Real support = Kokkos::sqrt(nn[k]); // |n_k| = seed->plane distance
1148 vol += support * area;
1149 }
1150 return vol * (Real(1) / Real(3));
1151 }
1152
1153 // ---- Vertex-local, SORT-FREE geometry (Ray/Sokolov/Lefebvre/Lévy TOG 2018; geogram ConvexCell)
1154 // ---- Plane stored as the (non-unit) foot-point normal n with interior {x : n·x ≤ n·n}; then x=n
1155 // is the foot of the perpendicular from the origin onto the plane (the facet point), and |n| is
1156 // the origin→ plane distance. The cell now stores n directly (member `n`, with `nn = n·n`), so
1157 // this is just a read. Volume by the divergence theorem, coning every boundary flag (facet n_i,
1158 // edge foot f, vertex v) to the origin: each tetra (0,n_i,f,v) is LOCAL to one vertex + its 3
1159 // planes, and the signed sum is exact even when feet fall outside their faces. No vertex
1160 // ordering, no adjacency — a pure per-vertex scatter.
1161
1164 KOKKOS_INLINE_FUNCTION void planeN(int k, Real out[3]) const {
1165 out[0] = n[k][0];
1166 out[1] = n[k][1];
1167 out[2] = n[k][2];
1168 }
1169 KOKKOS_INLINE_FUNCTION static void xprod(const Real a[3], const Real b[3], Real o[3]) {
1170 o[0] = a[1] * b[2] - a[2] * b[1];
1171 o[1] = a[2] * b[0] - a[0] * b[2];
1172 o[2] = a[0] * b[1] - a[1] * b[0];
1173 }
1174 KOKKOS_INLINE_FUNCTION static Real dot3(const Real a[3], const Real b[3]) {
1175 return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
1176 }
1177 KOKKOS_INLINE_FUNCTION static Real det3(const Real a[3], const Real b[3], const Real c[3]) {
1178 Real bc[3];
1179 xprod(b, c, bc);
1180 return dot3(a, bc);
1181 }
1183 KOKKOS_INLINE_FUNCTION static void edgeFoot(const Real v[3], const Real ck[3], Real f[3]) {
1184 const Real c2 = dot3(ck, ck), s = (c2 > Real(0)) ? dot3(v, ck) / c2 : Real(0);
1185 f[0] = v[0] - s * ck[0];
1186 f[1] = v[1] - s * ck[1];
1187 f[2] = v[2] - s * ck[2];
1188 }
1189
1192 KOKKOS_INLINE_FUNCTION Real volumePerVertex() const {
1193 Real vol = 0;
1194 for (int t = 0; t < nt; ++t) {
1195 if (!alive[t])
1196 continue;
1197 Real n1[3], n2[3], n3[3];
1198 planeN(t0[t], n1);
1199 planeN(t1[t], n2);
1200 planeN(t2[t], n3);
1201 Real c1[3], c2[3], c3[3];
1202 xprod(n2, n3, c1);
1203 xprod(n3, n1, c2);
1204 xprod(n1, n2, c3);
1205 Real D = dot3(n1, c1);
1206 if (D < Real(0)) // canonical order D>0: swap (n2,n3) and (c2,c3); v is unchanged
1207 for (int a = 0; a < 3; ++a) {
1208 Real tmp = n2[a];
1209 n2[a] = n3[a];
1210 n3[a] = tmp;
1211 tmp = c2[a];
1212 c2[a] = c3[a];
1213 c3[a] = tmp;
1214 }
1215 const Real v[3] = {vx[t], vy[t], vz[t]};
1216 Real f12[3], f23[3], f31[3];
1217 edgeFoot(v, c3, f12);
1218 edgeFoot(v, c1, f23);
1219 edgeFoot(v, c2, f31);
1220 Real e[3], d = 0;
1221 e[0] = n1[0] - n2[0];
1222 e[1] = n1[1] - n2[1];
1223 e[2] = n1[2] - n2[2];
1224 d += det3(e, f12, v);
1225 e[0] = n2[0] - n3[0];
1226 e[1] = n2[1] - n3[1];
1227 e[2] = n2[2] - n3[2];
1228 d += det3(e, f23, v);
1229 e[0] = n3[0] - n1[0];
1230 e[1] = n3[1] - n1[1];
1231 e[2] = n3[2] - n1[2];
1232 d += det3(e, f31, v);
1233 vol += d;
1234 }
1235 return vol * (Real(1) / Real(6));
1236 }
1237
1241 KOKKOS_INLINE_FUNCTION void facetAreasPerVertex(Real* area) const {
1242 for (int t = 0; t < nt; ++t) {
1243 if (!alive[t])
1244 continue;
1245 int k1 = t0[t], k2 = t1[t], k3 = t2[t];
1246 Real n1[3], n2[3], n3[3];
1247 planeN(k1, n1);
1248 planeN(k2, n2);
1249 planeN(k3, n3);
1250 Real c1[3], c2[3], c3[3];
1251 xprod(n2, n3, c1);
1252 xprod(n3, n1, c2);
1253 xprod(n1, n2, c3);
1254 Real D = dot3(n1, c1);
1255 if (D < Real(0)) { // canonical: swap planes 2,3 (normals, cross, AND indices)
1256 for (int a = 0; a < 3; ++a) {
1257 Real tm = n2[a];
1258 n2[a] = n3[a];
1259 n3[a] = tm;
1260 tm = c2[a];
1261 c2[a] = c3[a];
1262 c3[a] = tm;
1263 }
1264 int tk = k2;
1265 k2 = k3;
1266 k3 = tk;
1267 }
1268 const Real v[3] = {vx[t], vy[t], vz[t]};
1269 Real f12[3], f23[3], f31[3];
1270 edgeFoot(v, c3, f12);
1271 edgeFoot(v, c1, f23);
1272 edgeFoot(v, c2, f31);
1273 Real g[3];
1274 g[0] = f12[0] - f31[0];
1275 g[1] = f12[1] - f31[1];
1276 g[2] = f12[2] - f31[2];
1277 area[k1] += det3(n1, g, v) / (Real(2) * Kokkos::sqrt(dot3(n1, n1)));
1278 g[0] = f23[0] - f12[0];
1279 g[1] = f23[1] - f12[1];
1280 g[2] = f23[2] - f12[2];
1281 area[k2] += det3(n2, g, v) / (Real(2) * Kokkos::sqrt(dot3(n2, n2)));
1282 g[0] = f31[0] - f23[0];
1283 g[1] = f31[1] - f23[1];
1284 g[2] = f31[2] - f23[2];
1285 area[k3] += det3(n3, g, v) / (Real(2) * Kokkos::sqrt(dot3(n3, n3)));
1286 }
1287 }
1288
1292 KOKKOS_INLINE_FUNCTION static void scatterFacetMoment(int ki, const Real ni[3], const Real ff[3],
1293 const Real fl[3], const Real v[3],
1294 Real* area, Real* mx, Real* my, Real* mz) {
1295 const Real inv2n = Real(0.5) / Kokkos::sqrt(dot3(ni, ni));
1296 const Real sa = det3(ni, ff, v) * inv2n; // triangle (ni, ff, v)
1297 const Real sb = -det3(ni, fl, v) * inv2n; // triangle (ni, v, fl)
1298 area[ki] += sa + sb;
1299 mx[ki] += sa * (ni[0] + ff[0] + v[0]) / Real(3) + sb * (ni[0] + v[0] + fl[0]) / Real(3);
1300 my[ki] += sa * (ni[1] + ff[1] + v[1]) / Real(3) + sb * (ni[1] + v[1] + fl[1]) / Real(3);
1301 mz[ki] += sa * (ni[2] + ff[2] + v[2]) / Real(3) + sb * (ni[2] + v[2] + fl[2]) / Real(3);
1302 }
1303
1308 KOKKOS_INLINE_FUNCTION void facetMomentsPerVertex(Real* area, Real* mx, Real* my,
1309 Real* mz) const {
1310 for (int t = 0; t < nt; ++t) {
1311 if (!alive[t])
1312 continue;
1313 int k1 = t0[t], k2 = t1[t], k3 = t2[t];
1314 Real n1[3], n2[3], n3[3];
1315 planeN(k1, n1);
1316 planeN(k2, n2);
1317 planeN(k3, n3);
1318 Real c1[3], c2[3], c3[3];
1319 xprod(n2, n3, c1);
1320 xprod(n3, n1, c2);
1321 xprod(n1, n2, c3);
1322 if (dot3(n1, c1) < Real(0)) { // canonical: swap planes 2,3 (normals, cross, indices)
1323 for (int a = 0; a < 3; ++a) {
1324 Real tm = n2[a];
1325 n2[a] = n3[a];
1326 n3[a] = tm;
1327 tm = c2[a];
1328 c2[a] = c3[a];
1329 c3[a] = tm;
1330 }
1331 int tk = k2;
1332 k2 = k3;
1333 k3 = tk;
1334 }
1335 const Real v[3] = {vx[t], vy[t], vz[t]};
1336 Real f12[3], f23[3], f31[3];
1337 edgeFoot(v, c3, f12);
1338 edgeFoot(v, c1, f23);
1339 edgeFoot(v, c2, f31);
1340 scatterFacetMoment(k1, n1, f12, f31, v, area, mx, my, mz);
1341 scatterFacetMoment(k2, n2, f23, f12, v, area, mx, my, mz);
1342 scatterFacetMoment(k3, n3, f31, f23, v, area, mx, my, mz);
1343 }
1344 }
1345
1352 KOKKOS_INLINE_FUNCTION static void scatterGradRaw(int ki, const Real ni[3], const Real ff[3],
1353 const Real fl[3], const Real v[3], Real* dgx,
1354 Real* dgy, Real* dgz) {
1355 const Real sa = det3(ni, ff, v); // raw signed area of triangle (ni, ff, v)
1356 const Real sb = -det3(ni, fl, v); // raw signed area of triangle (ni, v, fl)
1357 const Real da = sa + sb;
1358 const Real momx = sa * (ni[0] + ff[0] + v[0]) / Real(3) + sb * (ni[0] + v[0] + fl[0]) / Real(3);
1359 const Real momy = sa * (ni[1] + ff[1] + v[1]) / Real(3) + sb * (ni[1] + v[1] + fl[1]) / Real(3);
1360 const Real momz = sa * (ni[2] + ff[2] + v[2]) / Real(3) + sb * (ni[2] + v[2] + fl[2]) / Real(3);
1361 dgx[ki] += Real(2) * da * ni[0] - momx; // 2·da·n_k − mom (numerator piece)
1362 dgy[ki] += Real(2) * da * ni[1] - momy;
1363 dgz[ki] += Real(2) * da * ni[2] - momz;
1364 }
1365
1374 KOKKOS_INLINE_FUNCTION void geomVolumeGrad(Real& vol, Real* dgx, Real* dgy, Real* dgz) const {
1375 Real V = 0;
1376 for (int t = 0; t < nt; ++t) {
1377 if (!alive[t])
1378 continue;
1379 int k1 = t0[t], k2 = t1[t], k3 = t2[t];
1380 Real n1[3], n2[3], n3[3];
1381 planeN(k1, n1);
1382 planeN(k2, n2);
1383 planeN(k3, n3);
1384 Real c1[3], c2[3], c3[3];
1385 xprod(n2, n3, c1);
1386 xprod(n3, n1, c2);
1387 xprod(n1, n2, c3);
1388 if (dot3(n1, c1) < Real(0)) { // canonical D>0: swap planes 2,3 (normals, cross, indices)
1389 for (int a = 0; a < 3; ++a) {
1390 Real tm = n2[a];
1391 n2[a] = n3[a];
1392 n3[a] = tm;
1393 tm = c2[a];
1394 c2[a] = c3[a];
1395 c3[a] = tm;
1396 }
1397 int tk = k2;
1398 k2 = k3;
1399 k3 = tk;
1400 }
1401 const Real v[3] = {vx[t], vy[t], vz[t]};
1402 Real f12[3], f23[3], f31[3];
1403 edgeFoot(v, c3, f12);
1404 edgeFoot(v, c1, f23);
1405 edgeFoot(v, c2, f31);
1406 Real e[3];
1407 e[0] = n1[0] - n2[0];
1408 e[1] = n1[1] - n2[1];
1409 e[2] = n1[2] - n2[2];
1410 V += det3(e, f12, v);
1411 e[0] = n2[0] - n3[0];
1412 e[1] = n2[1] - n3[1];
1413 e[2] = n2[2] - n3[2];
1414 V += det3(e, f23, v);
1415 e[0] = n3[0] - n1[0];
1416 e[1] = n3[1] - n1[1];
1417 e[2] = n3[2] - n1[2];
1418 V += det3(e, f31, v);
1419 scatterGradRaw(k1, n1, f12, f31, v, dgx, dgy, dgz);
1420 scatterGradRaw(k2, n2, f23, f12, v, dgx, dgy, dgz);
1421 scatterGradRaw(k3, n3, f31, f23, v, dgx, dgy, dgz);
1422 }
1423 for (int k = 0; k < np; ++k) { // per-facet fold, sqrt-free: numerator/(2·nn)
1424 const Real inv = (nn[k] > Real(0)) ? Real(1) / (Real(2) * nn[k]) : Real(0);
1425 dgx[k] *= inv;
1426 dgy[k] *= inv;
1427 dgz[k] *= inv;
1428 }
1429 vol = V * (Real(1) / Real(6));
1430 }
1431
1436 KOKKOS_INLINE_FUNCTION static void scatterAreaMomentRaw(int ki, const Real ni[3],
1437 const Real ff[3], const Real fl[3],
1438 const Real v[3], Real* sa_acc, Real* smx,
1439 Real* smy, Real* smz) {
1440 const Real sa = det3(ni, ff, v); // raw signed area of triangle (ni, ff, v)
1441 const Real sb = -det3(ni, fl, v); // raw signed area of triangle (ni, v, fl)
1442 sa_acc[ki] += sa + sb;
1443 smx[ki] += sa * (ni[0] + ff[0] + v[0]) / Real(3) + sb * (ni[0] + v[0] + fl[0]) / Real(3);
1444 smy[ki] += sa * (ni[1] + ff[1] + v[1]) / Real(3) + sb * (ni[1] + v[1] + fl[1]) / Real(3);
1445 smz[ki] += sa * (ni[2] + ff[2] + v[2]) / Real(3) + sb * (ni[2] + v[2] + fl[2]) / Real(3);
1446 }
1447
1457 KOKKOS_INLINE_FUNCTION void geomVolumeArea(Real& vol, Real* avx, Real* avy, Real* avz, Real* dgx,
1458 Real* dgy, Real* dgz) const {
1459 Real V = 0;
1460 for (int t = 0; t < nt; ++t) {
1461 if (!alive[t])
1462 continue;
1463 int k1 = t0[t], k2 = t1[t], k3 = t2[t];
1464 Real n1[3], n2[3], n3[3];
1465 planeN(k1, n1);
1466 planeN(k2, n2);
1467 planeN(k3, n3);
1468 Real c1[3], c2[3], c3[3];
1469 xprod(n2, n3, c1);
1470 xprod(n3, n1, c2);
1471 xprod(n1, n2, c3);
1472 if (dot3(n1, c1) < Real(0)) { // canonical D>0: swap planes 2,3 (normals, cross, indices)
1473 for (int a = 0; a < 3; ++a) {
1474 Real tm = n2[a];
1475 n2[a] = n3[a];
1476 n3[a] = tm;
1477 tm = c2[a];
1478 c2[a] = c3[a];
1479 c3[a] = tm;
1480 }
1481 int tk = k2;
1482 k2 = k3;
1483 k3 = tk;
1484 }
1485 const Real v[3] = {vx[t], vy[t], vz[t]};
1486 Real f12[3], f23[3], f31[3];
1487 edgeFoot(v, c3, f12);
1488 edgeFoot(v, c1, f23);
1489 edgeFoot(v, c2, f31);
1490 Real e[3];
1491 e[0] = n1[0] - n2[0];
1492 e[1] = n1[1] - n2[1];
1493 e[2] = n1[2] - n2[2];
1494 V += det3(e, f12, v);
1495 e[0] = n2[0] - n3[0];
1496 e[1] = n2[1] - n3[1];
1497 e[2] = n2[2] - n3[2];
1498 V += det3(e, f23, v);
1499 e[0] = n3[0] - n1[0];
1500 e[1] = n3[1] - n1[1];
1501 e[2] = n3[2] - n1[2];
1502 V += det3(e, f31, v);
1503 scatterAreaMomentRaw(k1, n1, f12, f31, v, avx, dgx, dgy,
1504 dgz); // avx <- S_a, (dgx,dgy,dgz) <- S_m
1505 scatterAreaMomentRaw(k2, n2, f23, f12, v, avx, dgx, dgy, dgz);
1506 scatterAreaMomentRaw(k3, n3, f31, f23, v, avx, dgx, dgy, dgz);
1507 }
1508 for (int k = 0; k < np; ++k) { // per-facet fold, sqrt-free
1509 const Real inv = (nn[k] > Real(0)) ? Real(1) / (Real(2) * nn[k]) : Real(0);
1510 const Real sa = avx[k]; // raw area S_a (accumulated here during the scatter)
1511 const Real smx = dgx[k], smy = dgy[k], smz = dgz[k];
1512 dgx[k] = (Real(2) * sa * n[k][0] - smx) * inv; // dV/dn_k
1513 dgy[k] = (Real(2) * sa * n[k][1] - smy) * inv;
1514 dgz[k] = (Real(2) * sa * n[k][2] - smz) * inv;
1515 avx[k] = sa * n[k][0] * inv; // outward area-vector A_k·n_k/|n_k|
1516 avy[k] = sa * n[k][1] * inv;
1517 avz[k] = sa * n[k][2] * inv;
1518 }
1519 vol = V * (Real(1) / Real(6));
1520 }
1521
1524 KOKKOS_INLINE_FUNCTION static void edgeCrossAdj(int j, int p, int q, const Real Nn[3][3],
1525 const Real w[3], Real out[3]) {
1526 if (j == p) {
1527 xprod(Nn[q], w, out);
1528 } else if (j == q) {
1529 Real tm[3];
1530 xprod(Nn[p], w, tm);
1531 out[0] = -tm[0];
1532 out[1] = -tm[1];
1533 out[2] = -tm[2];
1534 } else {
1535 out[0] = out[1] = out[2] = Real(0);
1536 }
1537 }
1538
1550 KOKKOS_INLINE_FUNCTION void geomVolumeAreaGrad(int t, int pl[3], Real contrib[3],
1551 Real grad[3][3][3]) const {
1552 // planes + canonical swap (matches facetAreasPerVertex), reusing the cached vertex — nothing
1553 // recomputed. Every loop is VOR_UNROLL'd so the small local arrays scalarize into registers on
1554 // GPU (no local-memory spill from runtime indices); on CPU the macro is empty.
1555 int kk[3] = {t0[t], t1[t], t2[t]};
1556 Real Nn[3][3];
1557 VOR_UNROLL for (int a = 0; a < 3; ++a) {
1558 Nn[a][0] = n[kk[a]][0];
1559 Nn[a][1] = n[kk[a]][1];
1560 Nn[a][2] = n[kk[a]][2];
1561 }
1562 {
1563 Real c01[3];
1564 xprod(Nn[1], Nn[2], c01);
1565 if (dot3(Nn[0], c01) < Real(0)) {
1566 VOR_UNROLL for (int d = 0; d < 3; ++d) {
1567 Real tm = Nn[1][d];
1568 Nn[1][d] = Nn[2][d];
1569 Nn[2][d] = tm;
1570 }
1571 int tk = kk[1];
1572 kk[1] = kk[2];
1573 kk[2] = tk;
1574 }
1575 }
1576 pl[0] = kk[0];
1577 pl[1] = kk[1];
1578 pl[2] = kk[2];
1579 const Real v[3] = {vx[t], vy[t], vz[t]};
1580 Real nn3[3], u[3][3]; // u[a] = 2 n_a − v
1581 VOR_UNROLL for (int a = 0; a < 3; ++a) {
1582 nn3[a] = dot3(Nn[a], Nn[a]);
1583 VOR_UNROLL for (int d = 0; d < 3; ++d) u[a][d] = Real(2) * Nn[a][d] - v[d];
1584 }
1585 // edge directions e[m] = N[eP]×N[eQ] for edges (0,1),(1,2),(2,0); cofactor of plane j is c_j =
1586 // e[(j+1)%3]
1587 const int eP[3] = {0, 1, 2}, eQ[3] = {1, 2, 0};
1588 Real e[3][3], ecc[3], es[3], F[3][3];
1589 VOR_UNROLL for (int m = 0; m < 3; ++m) {
1590 xprod(Nn[eP[m]], Nn[eQ[m]], e[m]);
1591 ecc[m] = dot3(e[m], e[m]);
1592 const Real evc = dot3(v, e[m]);
1593 es[m] = (ecc[m] > Real(0)) ? evc / ecc[m] : Real(0);
1594 VOR_UNROLL for (int d = 0; d < 3; ++d) F[m][d] =
1595 v[d] - es[m] * e[m][d]; // foot of v on edge line m
1596 }
1597 const Real D = dot3(Nn[0], e[1]); // e[1] = N1×N2 = cofactor c_0; D = det(n0,n1,n2) > 0
1598 Real g[3][3], den[3];
1599 VOR_UNROLL for (int i = 0; i < 3; ++i) { // facet i: g_i = F[i] − F[(i+2)%3]
1600 const int mb = (i + 2) % 3;
1601 VOR_UNROLL for (int d = 0; d < 3; ++d) g[i][d] = F[i][d] - F[mb][d];
1602 const Real Ni = det3(Nn[i], g[i], v);
1603 den[i] = Real(2) * Kokkos::sqrt(nn3[i]);
1604 contrib[i] = (den[i] > Real(0)) ? Ni / den[i] : Real(0);
1605 }
1606 VOR_UNROLL for (int i = 0; i < 3; ++i) {
1607 Real wi[3];
1608 xprod(v, Nn[i], wi); // w_i = v×n_i (∂det3(n_i,·,v)/∂g)
1609 Real nixg[3];
1610 xprod(Nn[i], g[i], nixg); // n_i×g_i (∂det3(n_i,g_i,·)/∂v)
1611 const int mb = (i + 2) % 3;
1612 VOR_UNROLL for (int j = 0; j < 3; ++j) {
1613 const Real* cj = e[(j + 1) % 3]; // cofactor of plane j
1614 Real gN[3] = {0, 0, 0};
1615 if (i == j) {
1616 Real gxv[3];
1617 xprod(g[i], v, gxv);
1618 VOR_UNROLL for (int d = 0; d < 3; ++d) gN[d] += gxv[d];
1619 } // ∂/∂n_i
1620 const Real alpha = dot3(nixg, cj) / D; // ∂/∂v (rank-1)
1621 VOR_UNROLL for (int d = 0; d < 3; ++d) gN[d] += alpha * u[j][d];
1622 // ∂/∂g = (∂F[i]/∂n_j − ∂F[(i+2)%3]/∂n_j)^T w_i — the two foot adjoints
1623 VOR_UNROLL for (int s = 0; s < 2; ++s) {
1624 const int m = (s == 0) ? i : mb;
1625 const Real sgn = (s == 0) ? Real(1) : Real(-1);
1626 const int p = eP[m], q = eQ[m];
1627 const Real cjw = dot3(cj, wi); // for vAdj
1628 const Real cje = dot3(cj, e[m]); // for dS
1629 const Real emw = dot3(e[m], wi);
1630 Real eaW[3], eaV[3], eaE[3];
1631 edgeCrossAdj(j, p, q, Nn, wi, eaW);
1632 edgeCrossAdj(j, p, q, Nn, v, eaV);
1633 edgeCrossAdj(j, p, q, Nn, e[m], eaE);
1634 const Real inv_ecc = (ecc[m] > Real(0)) ? Real(1) / ecc[m] : Real(0);
1635 Real dS[3]; // ∂s_m/∂n_j
1636 VOR_UNROLL for (int d = 0; d < 3; ++d) dS[d] =
1637 ((cje / D) * u[j][d] + eaV[d]) * inv_ecc - (es[m] * inv_ecc) * Real(2) * eaE[d];
1638 // (∂F[m]/∂n_j)^T w_i = (c_j·w_i/D) u_j − (e_m·w_i) dS − s_m (∂e_m/∂n_j)^T w_i
1639 VOR_UNROLL for (int d = 0; d < 3; ++d) gN[d] +=
1640 sgn * ((cjw / D) * u[j][d] - emw * dS[d] - es[m] * eaW[d]);
1641 }
1642 const Real invden = (den[i] > Real(0)) ? Real(1) / den[i] : Real(0);
1643 VOR_UNROLL for (int d = 0; d < 3; ++d) grad[i][j][d] = gN[d] * invden;
1644 if (i == j) { // 1/(2|n_i|) factor: −A_i n_i/nn_i
1645 const Real f = (nn3[i] > Real(0)) ? contrib[i] / nn3[i] : Real(0);
1646 VOR_UNROLL for (int d = 0; d < 3; ++d) grad[i][j][d] -= f * Nn[i][d];
1647 }
1648 }
1649 }
1650 }
1651
1656 KOKKOS_INLINE_FUNCTION void geomVolumeAreaGradAD(int t, int pl[3], Real contrib[3],
1657 Real grad[3][3][3]) const {
1658 using D = detail::Dual<Real, 9>;
1659 int k1 = t0[t], k2 = t1[t], k3 = t2[t];
1660 Real rn1[3] = {n[k1][0], n[k1][1], n[k1][2]};
1661 Real rn2[3] = {n[k2][0], n[k2][1], n[k2][2]};
1662 Real rn3[3] = {n[k3][0], n[k3][1], n[k3][2]};
1663 { // canonical D>0 swap on the real values (same as facetAreasPerVertex) so contributions land
1664 // right
1665 Real cc[3];
1666 xprod(rn2, rn3, cc);
1667 if (dot3(rn1, cc) < Real(0)) {
1668 for (int a = 0; a < 3; ++a) {
1669 Real tm = rn2[a];
1670 rn2[a] = rn3[a];
1671 rn3[a] = tm;
1672 }
1673 int tk = k2;
1674 k2 = k3;
1675 k3 = tk;
1676 }
1677 }
1678 pl[0] = k1;
1679 pl[1] = k2;
1680 pl[2] = k3;
1681 D n1[3], n2[3], n3[3];
1682 for (int a = 0; a < 3; ++a) { // seed: n1 <- slots 0..2, n2 <- 3..5, n3 <- 6..8
1683 n1[a] = detail::dseed<Real, 9>(rn1[a], a);
1684 n2[a] = detail::dseed<Real, 9>(rn2[a], 3 + a);
1685 n3[a] = detail::dseed<Real, 9>(rn3[a], 6 + a);
1686 }
1687 const D nn1 = detail::ddot<Real, 9>(n1, n1), nn2 = detail::ddot<Real, 9>(n2, n2),
1688 nn3 = detail::ddot<Real, 9>(n3, n3);
1689 D c1[3], c2[3], c3[3];
1690 detail::dcross<Real, 9>(n2, n3, c1);
1691 detail::dcross<Real, 9>(n3, n1, c2);
1692 detail::dcross<Real, 9>(n1, n2, c3);
1693 const D Dd = detail::ddot<Real, 9>(n1, c1); // det
1694 D v[3];
1695 for (int a = 0; a < 3; ++a)
1696 v[a] = (nn1 * c1[a] + nn2 * c2[a] + nn3 * c3[a]) / Dd; // Cramer vertex
1697 D f12[3], f23[3], f31[3];
1698 detail::dedgeFoot<Real, 9>(v, c3, f12);
1699 detail::dedgeFoot<Real, 9>(v, c1, f23);
1700 detail::dedgeFoot<Real, 9>(v, c2, f31);
1701 const D two = detail::dnum<Real, 9>(Real(2));
1702 D g[3], aa[3];
1703 for (int a = 0; a < 3; ++a)
1704 g[a] = f12[a] - f31[a];
1705 aa[0] = detail::ddet3<Real, 9>(n1, g, v) / (two * detail::dsqrt<Real, 9>(nn1));
1706 for (int a = 0; a < 3; ++a)
1707 g[a] = f23[a] - f12[a];
1708 aa[1] = detail::ddet3<Real, 9>(n2, g, v) / (two * detail::dsqrt<Real, 9>(nn2));
1709 for (int a = 0; a < 3; ++a)
1710 g[a] = f31[a] - f23[a];
1711 aa[2] = detail::ddet3<Real, 9>(n3, g, v) / (two * detail::dsqrt<Real, 9>(nn3));
1712 for (int i = 0; i < 3; ++i) {
1713 contrib[i] = aa[i].v;
1714 for (int j = 0; j < 3; ++j)
1715 for (int c = 0; c < 3; ++c)
1716 grad[i][j][c] = aa[i].d[3 * j + c];
1717 }
1718 }
1719
1727 KOKKOS_INLINE_FUNCTION void geometryPerVertex(Real& vol, Real* area, Real* mx, Real* my,
1728 Real* mz) const {
1729 Real V = 0;
1730 for (int t = 0; t < nt; ++t) {
1731 if (!alive[t])
1732 continue;
1733 int k1 = t0[t], k2 = t1[t], k3 = t2[t];
1734 Real n1[3], n2[3], n3[3];
1735 planeN(k1, n1);
1736 planeN(k2, n2);
1737 planeN(k3, n3);
1738 Real c1[3], c2[3], c3[3];
1739 xprod(n2, n3, c1);
1740 xprod(n3, n1, c2);
1741 xprod(n1, n2, c3);
1742 if (dot3(n1, c1) < Real(0)) { // canonical D>0: swap planes 2,3 (normals, cross, indices)
1743 for (int a = 0; a < 3; ++a) {
1744 Real tm = n2[a];
1745 n2[a] = n3[a];
1746 n3[a] = tm;
1747 tm = c2[a];
1748 c2[a] = c3[a];
1749 c3[a] = tm;
1750 }
1751 int tk = k2;
1752 k2 = k3;
1753 k3 = tk;
1754 }
1755 const Real v[3] = {vx[t], vy[t], vz[t]};
1756 Real f12[3], f23[3], f31[3];
1757 edgeFoot(v, c3, f12);
1758 edgeFoot(v, c1, f23);
1759 edgeFoot(v, c2, f31);
1760 Real e[3];
1761 e[0] = n1[0] - n2[0];
1762 e[1] = n1[1] - n2[1];
1763 e[2] = n1[2] - n2[2];
1764 V += det3(e, f12, v);
1765 e[0] = n2[0] - n3[0];
1766 e[1] = n2[1] - n3[1];
1767 e[2] = n2[2] - n3[2];
1768 V += det3(e, f23, v);
1769 e[0] = n3[0] - n1[0];
1770 e[1] = n3[1] - n1[1];
1771 e[2] = n3[2] - n1[2];
1772 V += det3(e, f31, v);
1773 scatterFacetMoment(k1, n1, f12, f31, v, area, mx, my, mz);
1774 scatterFacetMoment(k2, n2, f23, f12, v, area, mx, my, mz);
1775 scatterFacetMoment(k3, n3, f31, f23, v, area, mx, my, mz);
1776 }
1777 vol = V * (Real(1) / Real(6));
1778 }
1779
1789 KOKKOS_INLINE_FUNCTION bool facetGeometry(int k, Real areaVec[3], Real dv[3],
1790 Real conn[3]) const {
1791 Real fx[MAXFV], fy[MAXFV], fz[MAXFV];
1792 const int m = faceOrdered(k, fx, fy, fz);
1793 if (m < 3)
1794 return false;
1795 Real A[3];
1796 polyAreaVec(fx, fy, fz, m, A);
1797 const Real r[3] = {Real(2) * n[k][0], Real(2) * n[k][1],
1798 Real(2) * n[k][2]}; // connector r = 2·foot
1799 if (A[0] * r[0] + A[1] * r[1] + A[2] * r[2] < Real(0)) { // orient outward (toward neighbour)
1800 A[0] = -A[0];
1801 A[1] = -A[1];
1802 A[2] = -A[2];
1803 }
1804 const Real area = Kokkos::sqrt(A[0] * A[0] + A[1] * A[1] + A[2] * A[2]);
1805 // area-weighted centroid (fan-triangulate from v0)
1806 Real cx = 0, cy = 0, cz = 0, asum = 0;
1807 for (int i = 1; i + 1 < m; ++i) {
1808 const Real ux = fx[i] - fx[0], uy = fy[i] - fy[0], uz = fz[i] - fz[0];
1809 const Real wx = fx[i + 1] - fx[0], wy = fy[i + 1] - fy[0], wz = fz[i + 1] - fz[0];
1810 const Real tx = uy * wz - uz * wy, ty = uz * wx - ux * wz, tz = ux * wy - uy * wx;
1811 const Real at = Real(0.5) * Kokkos::sqrt(tx * tx + ty * ty + tz * tz);
1812 cx += at * (fx[0] + fx[i] + fx[i + 1]);
1813 cy += at * (fy[0] + fy[i] + fy[i + 1]);
1814 cz += at * (fz[0] + fz[i] + fz[i + 1]);
1815 asum += at;
1816 }
1817 const Real inv = asum > Real(0) ? Real(1) / (Real(3) * asum) : Real(0);
1818 cx *= inv;
1819 cy *= inv;
1820 cz *= inv;
1821 const Real s = area / Kokkos::sqrt(r[0] * r[0] + r[1] * r[1] + r[2] * r[2]);
1822 areaVec[0] = A[0];
1823 areaVec[1] = A[1];
1824 areaVec[2] = A[2];
1825 dv[0] = s * (r[0] - cx);
1826 dv[1] = s * (r[1] - cy);
1827 dv[2] = s * (r[2] - cz);
1828 conn[0] = r[0];
1829 conn[1] = r[1];
1830 conn[2] = r[2];
1831 return true;
1832 }
1833};
1834
1842template <class Real, int MAXP, int MAXT, bool TrackAdj, class Policy = Voronoi>
1844 const Real L[3], const Real* relx, const Real* rely,
1845 const Real* relz, const int* ids, int nNbr,
1846 Real wSelf = Real(0), const Real* weights = nullptr) {
1847 c.initBox(L[0], L[1], L[2]);
1848 for (int i = 0; i < nNbr; ++i) {
1849 const Real n[3] = {relx[i], rely[i], relz[i]};
1850 Real wNbr = Real(0);
1851 if constexpr (Policy::kHasWeightDof)
1852 wNbr = weights[i];
1853 const Real off = Policy::template offsetFromRel<Real>(n, wSelf, wNbr);
1854 if constexpr (!Policy::kHasWeightDof) {
1855 if (!(off < Real(2) * c.maxVertexRsq()))
1856 break; // security radius (bisector certificate; Voronoi only)
1857 }
1858 c.clip(n, off, ids[i]);
1859 if (c.overflow)
1860 return;
1861 }
1862}
1863
1864} // namespace peclet::voro
1865
1866#endif // PECLET_VORO_CONVEX_CELL_HPP
#define VOR_UNROLL
Definition convex_cell.hpp:38
KOKKOS_INLINE_FUNCTION Dual< Real, K > dnum(Real c)
Definition convex_cell.hpp:53
KOKKOS_INLINE_FUNCTION void dedgeFoot(const Dual< Real, K > v[3], const Dual< Real, K > c[3], Dual< Real, K > f[3])
Definition convex_cell.hpp:127
KOKKOS_INLINE_FUNCTION Dual< Real, K > ddet3(const Dual< Real, K > a[3], const Dual< Real, K > b[3], const Dual< Real, K > c[3])
Definition convex_cell.hpp:120
KOKKOS_INLINE_FUNCTION Dual< Real, K > operator-(const Dual< Real, K > &a, const Dual< Real, K > &b)
Definition convex_cell.hpp:75
KOKKOS_INLINE_FUNCTION Dual< Real, K > operator*(const Dual< Real, K > &a, const Dual< Real, K > &b)
Definition convex_cell.hpp:83
KOKKOS_INLINE_FUNCTION Dual< Real, K > operator+(const Dual< Real, K > &a, const Dual< Real, K > &b)
Definition convex_cell.hpp:67
KOKKOS_INLINE_FUNCTION Dual< Real, K > dsqrt(const Dual< Real, K > &a)
Definition convex_cell.hpp:100
KOKKOS_INLINE_FUNCTION Dual< Real, K > dseed(Real c, int s)
Definition convex_cell.hpp:61
KOKKOS_INLINE_FUNCTION Dual< Real, K > ddot(const Dual< Real, K > a[3], const Dual< Real, K > b[3])
Definition convex_cell.hpp:109
KOKKOS_INLINE_FUNCTION void dcross(const Dual< Real, K > a[3], const Dual< Real, K > b[3], Dual< Real, K > o[3])
Definition convex_cell.hpp:113
KOKKOS_INLINE_FUNCTION Dual< Real, K > operator/(const Dual< Real, K > &a, const Dual< Real, K > &b)
Definition convex_cell.hpp:91
Device-native Voronoi tessellation, geometry and finite-volume physics.
Definition convex_cell.hpp:41
KOKKOS_INLINE_FUNCTION void buildConvexCell(ConvexCell< Real, MAXP, MAXT, TrackAdj > &c, const Real L[3], const Real *relx, const Real *rely, const Real *relz, const int *ids, int nNbr, Real wSelf=Real(0), const Real *weights=nullptr)
Definition convex_cell.hpp:1843
The named engine defaults of peclet.voro — one header-level home for every capacity and default the l...
Definition convex_cell.hpp:140
KOKKOS_INLINE_FUNCTION Real volumePerVertex() const
Definition convex_cell.hpp:1192
static constexpr int MAXFV
Definition convex_cell.hpp:916
KOKKOS_INLINE_FUNCTION int findSharing(int self, int x, int y) const
Definition convex_cell.hpp:609
Kokkos::Array< int,(TrackAdj ? MAXT :0) *3 > adj
Definition convex_cell.hpp:172
KOKKOS_INLINE_FUNCTION int compactPlanes()
Definition convex_cell.hpp:548
KOKKOS_INLINE_FUNCTION Real planeGap(const Real pdir[3], Real d) const
Definition convex_cell.hpp:526
KOKKOS_INLINE_FUNCTION bool isLocallyConvexPartners(const unsigned char *poke4, Real tol, int *outPartners, int maxPartners, int &nPartners) const
Definition convex_cell.hpp:456
KOKKOS_INLINE_FUNCTION int faceOrderedIdx(int k, int out[MAXFV]) const
Definition convex_cell.hpp:923
KOKKOS_INLINE_FUNCTION int sectionPolygon(const Real p0[3], const Real u[3], Real px[MAXSV], Real py[MAXSV], Real pz[MAXSV]) const
Definition convex_cell.hpp:1042
KOKKOS_INLINE_FUNCTION void facetMomentsPerVertex(Real *area, Real *mx, Real *my, Real *mz) const
Definition convex_cell.hpp:1308
static KOKKOS_INLINE_FUNCTION void scatterFacetMoment(int ki, const Real ni[3], const Real ff[3], const Real fl[3], const Real v[3], Real *area, Real *mx, Real *my, Real *mz)
Definition convex_cell.hpp:1292
KOKKOS_INLINE_FUNCTION int oppositePlane(int s, int x, int y) const
Definition convex_cell.hpp:381
unsigned char t1[MAXT]
Definition convex_cell.hpp:153
KOKKOS_INLINE_FUNCTION void facetAreasPerVertex(Real *area) const
Definition convex_cell.hpp:1241
static KOKKOS_INLINE_FUNCTION void edgeFoot(const Real v[3], const Real ck[3], Real f[3])
foot of the perpendicular from v onto the edge line of direction ck: v − (v·ck/ck·ck) ck.
Definition convex_cell.hpp:1183
KOKKOS_INLINE_FUNCTION Real faceMoment2(int k) const
Definition convex_cell.hpp:495
static KOKKOS_INLINE_FUNCTION Real dot3(const Real a[3], const Real b[3])
Definition convex_cell.hpp:1174
KOKKOS_INLINE_FUNCTION void initBoxPlanes(Real L0, Real L1, Real L2)
Definition convex_cell.hpp:231
static constexpr bool kTrackAdj
lets templated consumers (store save/load) branch at compile time
Definition convex_cell.hpp:144
KOKKOS_INLINE_FUNCTION void initBox(Real L0, Real L1, Real L2)
Definition convex_cell.hpp:176
static constexpr int kMaxT
dual-triangle (vertex) capacity
Definition convex_cell.hpp:143
bool overflow
set if MAXP/MAXT exceeded -> cell invalid, caller falls back
Definition convex_cell.hpp:157
int nt
triangle high-water mark
Definition convex_cell.hpp:156
Real nn[MAXP]
cached n·n, i.e. the half-space offset (so the cull test is n·x <= nn)
Definition convex_cell.hpp:150
Real n[MAXP][3]
Definition convex_cell.hpp:147
static KOKKOS_INLINE_FUNCTION void polyAreaVec(const Real fx[], const Real fy[], const Real fz[], int m, Real A[3])
Polygon area vector (0.5 Σ vi × v(i+1)) of an ordered face.
Definition convex_cell.hpp:1017
KOKKOS_INLINE_FUNCTION bool isLocallyConvex(const unsigned char *poke4, Real tol) const
Definition convex_cell.hpp:432
KOKKOS_INLINE_FUNCTION void rebuildAdjacency()
Definition convex_cell.hpp:624
KOKKOS_INLINE_FUNCTION int faceOrdered(int k, Real fx[MAXFV], Real fy[MAXFV], Real fz[MAXFV]) const
Definition convex_cell.hpp:1004
Real vy[MAXT]
Definition convex_cell.hpp:154
KOKKOS_INLINE_FUNCTION bool isSelfConsistentPartners(Real tol, int *outPartners, int maxPartners, int &nPartners) const
Definition convex_cell.hpp:345
KOKKOS_INLINE_FUNCTION void geomVolumeAreaGrad(int t, int pl[3], Real contrib[3], Real grad[3][3][3]) const
Definition convex_cell.hpp:1550
KOKKOS_INLINE_FUNCTION bool empty() const
Definition convex_cell.hpp:896
int pnbr[MAXP]
neighbour seed id per plane (<0 => bounding box)
Definition convex_cell.hpp:151
static constexpr int MAXSV
max vertices on a cross-section polygon
Definition convex_cell.hpp:1031
KOKKOS_INLINE_FUNCTION void geomVolumeGrad(Real &vol, Real *dgx, Real *dgy, Real *dgz) const
Definition convex_cell.hpp:1374
KOKKOS_INLINE_FUNCTION Real maxVertexRsq() const
Largest squared dual-vertex radius over live triangles (drives the security radius).
Definition convex_cell.hpp:591
static KOKKOS_INLINE_FUNCTION void edgeCrossAdj(int j, int p, int q, const Real Nn[3][3], const Real w[3], Real out[3])
Definition convex_cell.hpp:1524
KOKKOS_INLINE_FUNCTION bool isSelfConsistent(Real tol) const
Definition convex_cell.hpp:303
KOKKOS_INLINE_FUNCTION bool checkAdjacencyInvariant() const
Definition convex_cell.hpp:640
Real vx[MAXT]
Definition convex_cell.hpp:154
KOKKOS_INLINE_FUNCTION void geometryPerVertex(Real &vol, Real *area, Real *mx, Real *my, Real *mz) const
Definition convex_cell.hpp:1727
KOKKOS_INLINE_FUNCTION int countFaces() const
Definition convex_cell.hpp:903
unsigned char t2[MAXT]
triangle = triple of plane indices
Definition convex_cell.hpp:153
KOKKOS_INLINE_FUNCTION void geomVolumeArea(Real &vol, Real *avx, Real *avy, Real *avz, Real *dgx, Real *dgy, Real *dgz) const
Definition convex_cell.hpp:1457
KOKKOS_INLINE_FUNCTION void computeVertex(int t)
Definition convex_cell.hpp:209
int np
number of planes
Definition convex_cell.hpp:152
KOKKOS_INLINE_FUNCTION bool clip(const Real pdir[3], Real d, int nbr, Real *gapOut=nullptr)
Definition convex_cell.hpp:688
static constexpr int kMaxP
plane capacity (consumers size scratch by these)
Definition convex_cell.hpp:142
KOKKOS_INLINE_FUNCTION Real volume() const
Definition convex_cell.hpp:1137
KOKKOS_INLINE_FUNCTION void geomVolumeAreaGradAD(int t, int pl[3], Real contrib[3], Real grad[3][3][3]) const
Definition convex_cell.hpp:1656
static KOKKOS_INLINE_FUNCTION void scatterAreaMomentRaw(int ki, const Real ni[3], const Real ff[3], const Real fl[3], const Real v[3], Real *sa_acc, Real *smx, Real *smy, Real *smz)
Definition convex_cell.hpp:1436
KOKKOS_INLINE_FUNCTION void planeN(int k, Real out[3]) const
Definition convex_cell.hpp:1164
unsigned char t0[MAXT]
Definition convex_cell.hpp:153
KOKKOS_INLINE_FUNCTION void reevalGeometry(Real sx, Real sy, Real sz, const Real *pos, Real L, Real wSelf=Real(0), const Real *weight=nullptr)
Definition convex_cell.hpp:258
KOKKOS_INLINE_FUNCTION int allocTri()
Definition convex_cell.hpp:670
KOKKOS_INLINE_FUNCTION void computePoke4(unsigned char *out) const
Definition convex_cell.hpp:394
bool alive[MAXT]
triangle live flag
Definition convex_cell.hpp:155
static KOKKOS_INLINE_FUNCTION void scatterGradRaw(int ki, const Real ni[3], const Real ff[3], const Real fl[3], const Real v[3], Real *dgx, Real *dgy, Real *dgz)
Definition convex_cell.hpp:1352
KOKKOS_INLINE_FUNCTION bool facetGeometry(int k, Real areaVec[3], Real dv[3], Real conn[3]) const
Definition convex_cell.hpp:1789
Real vz[MAXT]
cached dual-vertex position per triangle
Definition convex_cell.hpp:154
static KOKKOS_INLINE_FUNCTION Real det3(const Real a[3], const Real b[3], const Real c[3])
Definition convex_cell.hpp:1177
static KOKKOS_INLINE_FUNCTION void xprod(const Real a[3], const Real b[3], Real o[3])
Definition convex_cell.hpp:1169
Definition convex_cell.hpp:48
Real d[K]
Definition convex_cell.hpp:50
Real v
Definition convex_cell.hpp:49