peclet-dem 1.1.0
Performance-portable XPBD Discrete Element Method (Kokkos + ArborX)
Loading...
Searching...
No Matches
output_sdf.hpp
Go to the documentation of this file.
1
12#ifndef DEM_OUTPUT_SDF_HPP
13#define DEM_OUTPUT_SDF_HPP
14
15#include <Kokkos_Core.hpp>
16#include <vector>
17
18#include "dem_portable.hpp" // F3/F4, invRotateVector, sdfSphere/sdfHollowCylinder, ShapeKind
19#include "narrowphase.hpp" // ShapeDesc, CpExec/CpMem, view aliases
20
21namespace peclet::dem {
22
23// Generate the SDF grid (flat, x-fastest: i = x + y*rx + z*rx*ry; negative inside solid) over
24// [min,max].
29inline std::vector<float> generateSdfKokkos(int rx, int ry, int rz, F3 dmin, F3 dmax, int numReal,
30 PosView pos, QuatView quat, ScalarF scale,
31 ScalarI shapeId,
32 Kokkos::View<const ShapeDesc*, CpMem> shapes, bool px,
33 bool py, bool pz, GridView sdfGrid = GridView{},
34 float globalScale = 1.0f) {
35 CpExec space;
36 const long total = (long)rx * ry * rz;
37 const F3 origin = dmin;
38 const F3 vox{(dmax.x - dmin.x) / rx, (dmax.y - dmin.y) / ry, (dmax.z - dmin.z) / rz};
39
40 Kokkos::View<float*, CpMem> grid("sdf_grid", total);
41 Kokkos::View<int*, CpMem> state("sdf_state", total);
42 Kokkos::deep_copy(grid, 100.0f);
43 Kokkos::deep_copy(state, 0);
44
45 // SPLAT: each particle writes the exact analytic signed distance over its AABB band (atomic-min).
46 Kokkos::parallel_for(
47 "peclet::dem::sdf::splat", Kokkos::RangePolicy<CpExec>(space, 0, numReal),
48 KOKKOS_LAMBDA(int i) {
49 const F3 pw = loadF3(pos, i);
50 const F4 q = loadF4(quat, i);
51 // scale * globalScale -- the SAME effective scale the narrow-phase applies. Using the
52 // bare per-particle scale here (the old behaviour) made the exported SDF disagree with the
53 // simulated geometry whenever global_scale != 1: the splat drew bodies at the wrong size
54 // and the AABB band was sized for that wrong size too.
55 const float sc = scale(i) * globalScale;
56 const ShapeDesc shp = shapes(shapeId(i));
57 float rbound = 1.0f;
58 if (shp.type == HOLLOW_CYLINDER) {
59 const float r = shp.params.x, h = shp.params.y;
60 rbound = Kokkos::sqrt(r * r + (h * 0.5f) * (h * 0.5f));
61 } else if (shp.type == SPHERE)
62 rbound = shp.params.x;
63 else if (shp.type == SHAPE_GRID_SDF || shp.type == SHAPE_SCENE)
64 rbound = shp.params.x; // canonical bounding radius stored in params.x
65 rbound *= sc;
66 rbound *= 1.2f;
67
68 int minx = (int)Kokkos::floor((pw.x - rbound - origin.x) / vox.x),
69 maxx = (int)Kokkos::ceil((pw.x + rbound - origin.x) / vox.x);
70 int miny = (int)Kokkos::floor((pw.y - rbound - origin.y) / vox.y),
71 maxy = (int)Kokkos::ceil((pw.y + rbound - origin.y) / vox.y);
72 int minz = (int)Kokkos::floor((pw.z - rbound - origin.z) / vox.z),
73 maxz = (int)Kokkos::ceil((pw.z + rbound - origin.z) / vox.z);
74 if (!px) {
75 minx = minx < 0 ? 0 : minx;
76 maxx = maxx > rx - 1 ? rx - 1 : maxx;
77 }
78 if (!py) {
79 miny = miny < 0 ? 0 : miny;
80 maxy = maxy > ry - 1 ? ry - 1 : maxy;
81 }
82 if (!pz) {
83 minz = minz < 0 ? 0 : minz;
84 maxz = maxz > rz - 1 ? rz - 1 : maxz;
85 }
86
87 for (int z = minz; z <= maxz; ++z)
88 for (int y = miny; y <= maxy; ++y)
89 for (int x = minx; x <= maxx; ++x) {
90 const F3 vp{origin.x + (x + 0.5f) * vox.x, origin.y + (y + 0.5f) * vox.y,
91 origin.z + (z + 0.5f) * vox.z};
92 const F3 prel = sub3(vp, pw);
93 const F3 plocal = scale3(invRotateVector(q, prel), 1.0f / sc);
94 float distc = 100.0f;
95 if (shp.type == HOLLOW_CYLINDER)
96 distc = sdfHollowCylinder(plocal, shp.params);
97 else if (shp.type == SPHERE)
98 distc = sdfSphere(plocal, shp.params);
99 else if (shp.type == SHAPE_GRID_SDF)
100 distc = sampleGridSdf(plocal, shp, sdfGrid);
101 else if (shp.type == SHAPE_SCENE)
102 distc = sdfEvalShape(plocal, shp, sdfGrid); // composed tree, exact
103 const float dist = distc * sc;
104 const int wx = (x % rx + rx) % rx, wy = (y % ry + ry) % ry, wz = (z % rz + rz) % rz;
105 if (!px && (x < 0 || x >= rx))
106 continue;
107 if (!py && (y < 0 || y >= ry))
108 continue;
109 if (!pz && (z < 0 || z >= rz))
110 continue;
111 const long vi = (long)wz * rx * ry + (long)wy * rx + wx;
112 Kokkos::atomic_min(&grid(vi), dist);
113 if (dist < rbound)
114 state(vi) = 1;
115 }
116 });
117 space.fence();
118
119 // EIKONAL: Jacobi fast-iterative-method (ping-pong), max(res)*4 iterations, periodic wrap.
120 Kokkos::View<float*, CpMem> grid2("sdf_grid2", total);
121 const int iters = Kokkos::max(Kokkos::max(rx, ry), rz) * 4;
122 const float h = vox.x;
123 Kokkos::View<float*, CpMem> in = grid, out = grid2;
124 for (int it = 0; it < iters; ++it) {
125 Kokkos::View<float*, CpMem> din = in, dout = out;
126 Kokkos::View<int*, CpMem> st = state;
127 Kokkos::parallel_for(
128 "peclet::dem::sdf::eikonal", Kokkos::RangePolicy<CpExec>(space, 0, total),
129 KOKKOS_LAMBDA(long idx) {
130 if (st(idx) == 1) {
131 dout(idx) = din(idx);
132 return;
133 }
134 const long sy = rx, sz = (long)rx * ry;
135 const int z = (int)(idx / sz);
136 const long rem = idx % sz;
137 const int y = (int)(rem / sy), x = (int)(rem % sy);
138 float xm = 100.0f, xp = 100.0f, ym = 100.0f, yp = 100.0f, zm = 100.0f, zp = 100.0f;
139 if (x > 0)
140 xm = din(idx - 1);
141 else if (px)
142 xm = din(idx - 1 + rx);
143 if (x < rx - 1)
144 xp = din(idx + 1);
145 else if (px)
146 xp = din(idx + 1 - rx);
147 if (y > 0)
148 ym = din(idx - sy);
149 else if (py)
150 ym = din((long)z * sz + (long)(ry - 1) * sy + x);
151 if (y < ry - 1)
152 yp = din(idx + sy);
153 else if (py)
154 yp = din((long)z * sz + x);
155 if (z > 0)
156 zm = din(idx - sz);
157 else if (pz)
158 zm = din((long)(rz - 1) * sz + (long)y * sy + x);
159 if (z < rz - 1)
160 zp = din(idx + sz);
161 else if (pz)
162 zp = din((long)y * sy + x);
163 float a = Kokkos::fmin(xm, xp), b = Kokkos::fmin(ym, yp), c = Kokkos::fmin(zm, zp);
164 if (a > b) {
165 float t = a;
166 a = b;
167 b = t;
168 }
169 if (b > c) {
170 float t = b;
171 b = c;
172 c = t;
173 }
174 if (a > b) {
175 float t = a;
176 a = b;
177 b = t;
178 }
179 float u = 100.0f, u1 = a + h;
180 if (u1 <= b)
181 u = u1;
182 else {
183 const float d2 = 2.0f * h * h - (a - b) * (a - b);
184 float u2 = (d2 < 0.0f) ? 100.0f : (a + b + Kokkos::sqrt(d2)) * 0.5f;
185 if (u2 <= c)
186 u = u2;
187 else {
188 const float s = a + b + c, qd = s * s - 3.0f * (a * a + b * b + c * c - h * h);
189 u = (qd < 0.0f) ? 100.0f : (s + Kokkos::sqrt(qd)) / 3.0f;
190 }
191 }
192 dout(idx) = u;
193 });
194 space.fence();
195 auto tmp = in;
196 in = out;
197 out = tmp;
198 }
199
200 std::vector<float> h_grid(total);
201 auto hv = Kokkos::create_mirror_view(in);
202 Kokkos::deep_copy(hv, in);
203 for (long i = 0; i < total; ++i)
204 h_grid[i] = hv(i);
205 return h_grid;
206}
207
208} // namespace peclet::dem
209
210#endif // DEM_OUTPUT_SDF_HPP
dem — portable POD types + math + analytic SDFs shared by the Kokkos kernel ports.
float sdfHollowCylinder(F3 p, F4 params)
Kokkos::View< const float *, CpMem > GridView
std::vector< float > generateSdfKokkos(int rx, int ry, int rz, F3 dmin, F3 dmax, int numReal, PosView pos, QuatView quat, ScalarF scale, ScalarI shapeId, Kokkos::View< const ShapeDesc *, CpMem > shapes, bool px, bool py, bool pz, GridView sdfGrid=GridView{}, float globalScale=1.0f)
globalScale multiplies every per-particle scale, exactly as the narrow-phase does (narrowphase....
F3 invRotateVector(F4 q, F3 v)
Kokkos::View< const int *, CpMem > ScalarI
F3 loadF3(PosView v, int i)
float sdfEvalShape(F3 p, const ShapeDesc &d, GridView grid)
Canonical-space SDF of a shape: analytic dispatch, or a trilinear grid sample for an imported grid SD...
F3 sub3(F3 a, F3 b)
F4 loadF4(QuatView v, int i)
F3 scale3(F3 a, float s)
Kokkos::View< const float *[4], CpMem > QuatView
float sampleGridSdf(F3 p, const ShapeDesc &d, GridView grid)
Trilinearly sample an imported grid SDF at canonical point p.
Kokkos::View< const float *[3], CpMem > PosView
float sdfSphere(F3 p, F4 params)
Kokkos::View< const float *, CpMem > ScalarF
Kokkos::DefaultExecutionSpace CpExec
dem — portable (Kokkos) narrow-phase: SDF point-shell collision + boundary planes.