peclet-dem 0.4.0
Performance-portable XPBD Discrete Element Method (Kokkos + ArborX)
Loading...
Searching...
No Matches
narrowphase.hpp
Go to the documentation of this file.
1
11#ifndef DEM_NARROWPHASE_HPP
12#define DEM_NARROWPHASE_HPP
13
14#include <Kokkos_Core.hpp>
15
16#include "contact_preprocessing.hpp" // ContactC, CpExec/CpMem
17#include "dem_portable.hpp"
18
19namespace peclet::dem {
20
29struct ShapeDesc {
30 int type; // peclet::dem::ShapeKind
31 F4 params; // analytic parameters (see sdf_analytic); grid: params.x = bounding radius
32 int shellOffset; // start index into the flat shell-points View
33 int numPoints; // shell size; 0 => analytic single-probe (sphere center)
34 // --- grid-SDF fields (type == SHAPE_GRID_SDF); zero for analytic shapes ---
35 int gridOffset = 0; // start index into the shared sdfGrid View
36 int nx = 0, ny = 0, nz = 0; // lattice dimensions
37 F3 gridOrigin{0, 0, 0}; // canonical coord of node (0,0,0)
38 F3 gridInvSpacing{0, 0, 0}; // 1 / node spacing, per axis
39};
40
41struct PlaneP {
44};
45
59struct WallSdf {
60 // world-space grid SDF samples live in Particles::wallGrid at [gridOffset, gridOffset+nx*ny*nz),
61 // x-fastest (idx = x + y*nx + z*nx*ny), at nodes q = origin + (x,y,z)/invSpacing.
62 int nx = 0, ny = 0, nz = 0;
63 int gridOffset = 0;
64 F3 origin{0, 0, 0};
65 F3 invSpacing{0, 0, 0};
66 // rigid-body surface velocity field v(x) = linVel + angVel × (x − center) (set from the host).
67 F3 linVel{0, 0, 0};
68 F3 angVel{0, 0, 0};
69 F3 center{0, 0, 0};
70 // binary particle–wall material (independent of the body-body material).
71 float restitution = 0.0f;
72 float friction = 0.0f;
73 // Optional material id: >= 0 and a pair table set -> particle-wall (e, mu) comes from the
74 // pair table row (materialId(particle), materialId) instead of the binary values above.
75 int materialId = -1;
76};
77
79constexpr int kMaxMaterials = 8;
80using PairTableView = Kokkos::View<const float*, CpMem>;
81using MatIdView = Kokkos::View<const unsigned char*, CpMem>;
82
83// Convenience aliases for the SoA particle views the narrow-phase reads.
84using PosView = Kokkos::View<const float* [3], CpMem>;
85using QuatView = Kokkos::View<const float* [4], CpMem>;
86using ScalarF = Kokkos::View<const float*, CpMem>;
87using ScalarI = Kokkos::View<const int*, CpMem>;
88using ShellView = Kokkos::View<const float* [3], CpMem>;
89using GridView = Kokkos::View<const float*, CpMem>;
90
91KOKKOS_INLINE_FUNCTION F3 loadF3(PosView v, int i) {
92 return F3{v(i, 0), v(i, 1), v(i, 2)};
93}
94KOKKOS_INLINE_FUNCTION F4 loadF4(QuatView v, int i) {
95 return F4{v(i, 0), v(i, 1), v(i, 2), v(i, 3)};
96}
97
104KOKKOS_INLINE_FUNCTION float sampleGridSdf(F3 p, const ShapeDesc& d, GridView grid) {
105 const float fx = (p.x - d.gridOrigin.x) * d.gridInvSpacing.x;
106 const float fy = (p.y - d.gridOrigin.y) * d.gridInvSpacing.y;
107 const float fz = (p.z - d.gridOrigin.z) * d.gridInvSpacing.z;
108 const float cx = Kokkos::fmin(Kokkos::fmax(fx, 0.0f), (float)(d.nx - 1));
109 const float cy = Kokkos::fmin(Kokkos::fmax(fy, 0.0f), (float)(d.ny - 1));
110 const float cz = Kokkos::fmin(Kokkos::fmax(fz, 0.0f), (float)(d.nz - 1));
111 const int ix = (int)cx, iy = (int)cy, iz = (int)cz;
112 const int ix1 = ix < d.nx - 1 ? ix + 1 : ix;
113 const int iy1 = iy < d.ny - 1 ? iy + 1 : iy;
114 const int iz1 = iz < d.nz - 1 ? iz + 1 : iz;
115 const float tx = cx - ix, ty = cy - iy, tz = cz - iz;
116 const long nxny = (long)d.nx * d.ny;
117 const int off = d.gridOffset;
118 auto at = [&](int x, int y, int z) { return grid(off + (long)z * nxny + (long)y * d.nx + x); };
119 const float c00 = at(ix, iy, iz) * (1 - tx) + at(ix1, iy, iz) * tx;
120 const float c10 = at(ix, iy1, iz) * (1 - tx) + at(ix1, iy1, iz) * tx;
121 const float c01 = at(ix, iy, iz1) * (1 - tx) + at(ix1, iy, iz1) * tx;
122 const float c11 = at(ix, iy1, iz1) * (1 - tx) + at(ix1, iy1, iz1) * tx;
123 const float c0 = c00 * (1 - ty) + c10 * ty;
124 const float c1 = c01 * (1 - ty) + c11 * ty;
125 const float val = c0 * (1 - tz) + c1 * tz;
126 // residual distance from p to the clamped lattice point (voxel units -> canonical units).
127 const float rx = (d.gridInvSpacing.x > 0.0f) ? (fx - cx) / d.gridInvSpacing.x : 0.0f;
128 const float ry = (d.gridInvSpacing.y > 0.0f) ? (fy - cy) / d.gridInvSpacing.y : 0.0f;
129 const float rz = (d.gridInvSpacing.z > 0.0f) ? (fz - cz) / d.gridInvSpacing.z : 0.0f;
130 return val + Kokkos::sqrt(rx * rx + ry * ry + rz * rz);
131}
132
141KOKKOS_INLINE_FUNCTION float sampleWallSdf(F3 p, const WallSdf& w, GridView grid) {
142 const float fx = (p.x - w.origin.x) * w.invSpacing.x;
143 const float fy = (p.y - w.origin.y) * w.invSpacing.y;
144 const float fz = (p.z - w.origin.z) * w.invSpacing.z;
145 const float cx = Kokkos::fmin(Kokkos::fmax(fx, 0.0f), (float)(w.nx - 1));
146 const float cy = Kokkos::fmin(Kokkos::fmax(fy, 0.0f), (float)(w.ny - 1));
147 const float cz = Kokkos::fmin(Kokkos::fmax(fz, 0.0f), (float)(w.nz - 1));
148 const int ix = (int)cx, iy = (int)cy, iz = (int)cz;
149 const int ix1 = ix < w.nx - 1 ? ix + 1 : ix;
150 const int iy1 = iy < w.ny - 1 ? iy + 1 : iy;
151 const int iz1 = iz < w.nz - 1 ? iz + 1 : iz;
152 const float tx = cx - ix, ty = cy - iy, tz = cz - iz;
153 const long nxny = (long)w.nx * w.ny;
154 const int off = w.gridOffset;
155 auto at = [&](int x, int y, int z) { return grid(off + (long)z * nxny + (long)y * w.nx + x); };
156 const float c00 = at(ix, iy, iz) * (1 - tx) + at(ix1, iy, iz) * tx;
157 const float c10 = at(ix, iy1, iz) * (1 - tx) + at(ix1, iy1, iz) * tx;
158 const float c01 = at(ix, iy, iz1) * (1 - tx) + at(ix1, iy, iz1) * tx;
159 const float c11 = at(ix, iy1, iz1) * (1 - tx) + at(ix1, iy1, iz1) * tx;
160 const float c0 = c00 * (1 - ty) + c10 * ty;
161 const float c1 = c01 * (1 - ty) + c11 * ty;
162 const float val = c0 * (1 - tz) + c1 * tz;
163 const float rx = (w.invSpacing.x > 0.0f) ? (fx - cx) / w.invSpacing.x : 0.0f;
164 const float ry = (w.invSpacing.y > 0.0f) ? (fy - cy) / w.invSpacing.y : 0.0f;
165 const float rz = (w.invSpacing.z > 0.0f) ? (fz - cz) / w.invSpacing.z : 0.0f;
166 return val - Kokkos::sqrt(rx * rx + ry * ry + rz * rz);
167}
168
171KOKKOS_INLINE_FUNCTION float sdfEvalShape(F3 p, const ShapeDesc& d, GridView grid) {
172 if (d.type == SHAPE_GRID_SDF)
173 return sampleGridSdf(p, d, grid);
174 return sdfEval(p, d.type, d.params);
175}
176
179inline void detectContactsKokkos(Kokkos::View<const int* [2], CpMem> pairs, int numPairs,
180 PosView pos, QuatView quat, ScalarF scale, ScalarI shapeId,
181 Kokkos::View<const ShapeDesc*, CpMem> shapes, ShellView shell,
182 float globalScale, float margin,
183 Kokkos::View<ContactC*, CpMem> outContacts,
184 Kokkos::View<int, CpMem> outCount,
185 Kokkos::View<float, CpMem> maxOverlap, GridView sdfGrid = GridView{},
186 MatIdView matId = MatIdView{}, PairTableView pairTable = PairTableView{}) {
187 CpExec space;
188 const int maxContacts = static_cast<int>(outContacts.extent(0));
189 Kokkos::parallel_for(
190 "peclet::dem::np::contacts", Kokkos::RangePolicy<CpExec>(space, 0, numPairs),
191 KOKKOS_LAMBDA(int idx) {
192 const int idA = pairs(idx, 0), idB = pairs(idx, 1);
193 const ShapeDesc dA = shapes(shapeId(idA));
194 const ShapeDesc dB = shapes(shapeId(idB));
195 const F3 posA = loadF3(pos, idA), posB = loadF3(pos, idB);
196 const F4 qA = loadF4(quat, idA), qB = loadF4(quat, idB);
197 // Canonical->world size = per-particle scale * global scale. globalScale must appear in the
198 // shell placement, the canonical remap of B, and the distance rescale exactly as it does in
199 // the sphere probe radius below — else a non-unit global_scale makes A's and B's radii
200 // disagree (grains a real diameter apart read a huge penetration and the solver explodes).
201 // The plane/wall boundary kernels already fold globalScale into their `s = scale*globalScale`.
202 const float effScaleA = scale(idA) * globalScale, effScaleB = scale(idB) * globalScale;
203
204 const int countA = dA.numPoints;
205 const bool sphereA = (dA.type == SPHERE);
206 const int iter = (countA > 0) ? countA : 1;
207
208 for (int k = 0; k < iter; ++k) {
209 F3 pLocalA{0, 0, 0};
210 float pointRadius = 0.0f;
211 if (countA > 0) {
212 const int s = dA.shellOffset + k;
213 pLocalA = F3{shell(s, 0), shell(s, 1), shell(s, 2)};
214 } else if (sphereA) {
215 pointRadius = dA.params.x * effScaleA;
216 }
217
218 const F3 pWorld = add3(posA, rotateVector(qA, scale3(pLocalA, effScaleA)));
219 const F3 pLocalB = invRotateVector(qB, sub3(pWorld, posB));
220 const F3 pCanB = scale3(pLocalB, 1.0f / effScaleB);
221
222 const float dist = sdfEvalShape(pCanB, dB, sdfGrid) * effScaleB;
223 const float effDist = dist - pointRadius;
224 if (effDist >= margin)
225 continue;
226
227 if (effDist < 0.0f)
228 Kokkos::atomic_max(&maxOverlap(), -effDist);
229 const int slot = Kokkos::atomic_fetch_add(&outCount(), 1);
230 if (slot >= maxContacts) {
231 Kokkos::atomic_add(&outCount(), -1);
232 continue;
233 }
234
235 const float eps = 1e-4f;
236 F3 nLoc{sdfEvalShape(F3{pCanB.x + eps, pCanB.y, pCanB.z}, dB, sdfGrid) -
237 sdfEvalShape(F3{pCanB.x - eps, pCanB.y, pCanB.z}, dB, sdfGrid),
238 sdfEvalShape(F3{pCanB.x, pCanB.y + eps, pCanB.z}, dB, sdfGrid) -
239 sdfEvalShape(F3{pCanB.x, pCanB.y - eps, pCanB.z}, dB, sdfGrid),
240 sdfEvalShape(F3{pCanB.x, pCanB.y, pCanB.z + eps}, dB, sdfGrid) -
241 sdfEvalShape(F3{pCanB.x, pCanB.y, pCanB.z - eps}, dB, sdfGrid)};
242 const float len = len3(nLoc);
243 nLoc = (len > 1e-9f) ? scale3(nLoc, 1.0f / len) : F3{0, 1, 0};
244
245 const F3 nWorld = rotateVector(qB, nLoc);
246 const F3 pSurfA = sub3(pWorld, scale3(nWorld, pointRadius));
247 const F3 rA = sub3(pSurfA, posA);
248 const F3 rB = sub3(sub3(pSurfA, scale3(nWorld, effDist)), posB);
249
250 ContactC c{};
251 c.bodyA = idA;
252 c.bodyB = idB;
253 c.normal = F4{nWorld.x, nWorld.y, nWorld.z, 0.0f};
254 c.rA = F4{rA.x, rA.y, rA.z, 0.0f};
255 c.rB = F4{rB.x, rB.y, rB.z, 0.0f};
256 c.dist = effDist;
257 c.friction_lambda_n = 0.0f;
258 c.weight = 0.0f;
259 if (pairTable.extent(0) > 0) { // per-pair body-body material
260 const int t = (int(matId(idA)) * kMaxMaterials + int(matId(idB))) * 2;
261 c.boundaryRestitution = pairTable(t);
262 c.boundaryFriction = pairTable(t + 1);
263 }
264 outContacts(slot) = c;
265 }
266 });
267 space.fence();
268}
269
278inline void detectWallSdfKokkos(int numReal, int numWalls, PosView pos, QuatView quat, ScalarF scale,
279 ScalarI shapeId, Kokkos::View<const ShapeDesc*, CpMem> shapes,
280 ShellView shell, Kokkos::View<const WallSdf*, CpMem> walls,
281 GridView wallGrid, float globalScale, float margin,
282 Kokkos::View<ContactC*, CpMem> outContacts,
283 Kokkos::View<int, CpMem> outCount,
284 Kokkos::View<float, CpMem> maxOverlap,
285 MatIdView matId = MatIdView{}, PairTableView pairTable = PairTableView{}) {
286 CpExec space;
287 const int maxContacts = static_cast<int>(outContacts.extent(0));
288 Kokkos::parallel_for(
289 "peclet::dem::np::wallsdf", Kokkos::RangePolicy<CpExec>(space, 0, numReal),
290 KOKKOS_LAMBDA(int i) {
291 const F3 posA = loadF3(pos, i);
292 const float s = scale(i) * globalScale;
293 const ShapeDesc d = shapes(shapeId(i));
294 float baseR = d.params.x;
295 if (baseR == 0.0f)
296 baseR = 1.0f;
297 const float radius = baseR * s; // analytic sphere radius (numPts == 0)
298 const int numPts = d.numPoints;
299 const F4 qA = loadF4(quat, i);
300 const float eps = 1e-4f;
301
302 for (int wi = 0; wi < numWalls; ++wi) {
303 const WallSdf w = walls(wi);
304 const int iter = (numPts > 0) ? numPts : 1;
305 for (int k = 0; k < iter; ++k) {
306 // World surface probe: a shell point (rotated + scaled) or, for a sphere, the centre.
307 F3 rA{0, 0, 0};
308 if (numPts > 0) {
309 const int si = d.shellOffset + k;
310 rA = rotateVector(qA, scale3(F3{shell(si, 0), shell(si, 1), shell(si, 2)}, s));
311 }
312 const F3 pw = add3(posA, rA);
313 const float sdf = sampleWallSdf(pw, w, wallGrid);
314 // Outward SDF gradient (central difference) = push-out normal (wall -> void).
315 F3 n{sampleWallSdf(F3{pw.x + eps, pw.y, pw.z}, w, wallGrid) -
316 sampleWallSdf(F3{pw.x - eps, pw.y, pw.z}, w, wallGrid),
317 sampleWallSdf(F3{pw.x, pw.y + eps, pw.z}, w, wallGrid) -
318 sampleWallSdf(F3{pw.x, pw.y - eps, pw.z}, w, wallGrid),
319 sampleWallSdf(F3{pw.x, pw.y, pw.z + eps}, w, wallGrid) -
320 sampleWallSdf(F3{pw.x, pw.y, pw.z - eps}, w, wallGrid)};
321 const float ln = len3(n);
322 n = (ln > 1e-9f) ? scale3(n, 1.0f / ln) : F3{0, 1, 0};
323
324 // For a sphere the nearest surface point is one radius toward the wall (−normal); its
325 // signed gap to the wall is sdf − radius.
326 const float dist = (numPts > 0) ? sdf : sdf - radius;
327 if (dist >= margin)
328 continue;
329 const F3 rAeff = (numPts > 0) ? rA : scale3(n, -radius);
330 const F3 pSurfA = add3(posA, rAeff); // particle surface point
331 const F3 pWall = sub3(pSurfA, scale3(n, dist)); // point on the wall along the normal
332
333 if (dist < 0.0f)
334 Kokkos::atomic_max(&maxOverlap(), -dist);
335 const int slot = Kokkos::atomic_fetch_add(&outCount(), 1);
336 if (slot >= maxContacts) {
337 Kokkos::atomic_add(&outCount(), -1);
338 continue;
339 }
340 // Rigid-body wall surface velocity at the contact point: linVel + angVel × (r − center).
341 const F3 r = sub3(pWall, w.center);
342 const F3 vWall = add3(w.linVel, cross3v(w.angVel, r));
343
344 ContactC c{};
345 c.bodyA = i;
346 c.bodyB = -1;
347 c.normal = F4{n.x, n.y, n.z, 0.0f};
348 c.rA = F4{rAeff.x, rAeff.y, rAeff.z, 0.0f};
349 c.rB = F4{pWall.x, pWall.y, pWall.z, 0.0f};
350 c.dist = dist;
351 c.friction_lambda_n = 0.0f;
352 c.weight = 0.0f;
353 c.boundaryVel = F4{vWall.x, vWall.y, vWall.z, 0.0f};
354 if (w.materialId >= 0 && pairTable.extent(0) > 0) {
355 const int t = (int(matId(i)) * kMaxMaterials + w.materialId) * 2;
356 c.boundaryRestitution = pairTable(t);
357 c.boundaryFriction = pairTable(t + 1);
358 } else {
359 c.boundaryRestitution = w.restitution;
360 c.boundaryFriction = w.friction;
361 }
362 outContacts(slot) = c;
363 }
364 }
365 });
366 space.fence();
367}
368
371inline void detectBoundaryKokkos(int numReal, int numPlanes, PosView pos, QuatView quat,
372 ScalarF scale, ScalarI shapeId,
373 Kokkos::View<const ShapeDesc*, CpMem> shapes, ShellView shell,
374 Kokkos::View<const PlaneP*, CpMem> planes, float globalScale,
375 float margin, Kokkos::View<ContactC*, CpMem> outContacts,
376 Kokkos::View<int, CpMem> outCount,
377 Kokkos::View<float, CpMem> maxOverlap) {
378 CpExec space;
379 const int maxContacts = static_cast<int>(outContacts.extent(0));
380 Kokkos::parallel_for(
381 "peclet::dem::np::boundary", Kokkos::RangePolicy<CpExec>(space, 0, numReal),
382 KOKKOS_LAMBDA(int i) {
383 const F3 posA = loadF3(pos, i);
384 const float s = scale(i) * globalScale;
385 const ShapeDesc d = shapes(shapeId(i));
386 float baseR = d.params.x;
387 if (baseR == 0.0f)
388 baseR = 1.0f;
389 const float radius = baseR * s;
390 const int numPts = d.numPoints;
391 const F4 qA = loadF4(quat, i);
392
393 for (int pi = 0; pi < numPlanes; ++pi) {
394 const PlaneP pl = planes(pi);
395 // emit helper, inlined (no lambda capture of mutable counters across backends).
396 if (numPts > 0) {
397 for (int k = 0; k < numPts; ++k) {
398 const int si = d.shellOffset + k;
399 const F3 rA =
400 rotateVector(qA, scale3(F3{shell(si, 0), shell(si, 1), shell(si, 2)}, s));
401 const F3 pwk = add3(posA, rA);
402 const float dist = dot3(sub3(pwk, pl.point), pl.normal);
403 if (dist >= margin)
404 continue;
405 if (dist < 0.0f)
406 Kokkos::atomic_max(&maxOverlap(), -dist);
407 const int slot = Kokkos::atomic_fetch_add(&outCount(), 1);
408 if (slot >= maxContacts) {
409 Kokkos::atomic_add(&outCount(), -1);
410 continue;
411 }
412 ContactC c{};
413 c.bodyA = i;
414 c.bodyB = -1;
415 c.normal = F4{pl.normal.x, pl.normal.y, pl.normal.z, 0.0f};
416 c.rA = F4{rA.x, rA.y, rA.z, 0.0f};
417 c.rB = F4{pl.point.x, pl.point.y, pl.point.z, 0.0f};
418 c.dist = dist;
419 c.friction_lambda_n = 0.0f;
420 c.weight = 0.0f;
421 outContacts(slot) = c;
422 }
423 } else {
424 const float dist = dot3(sub3(posA, pl.point), pl.normal) - radius;
425 if (dist >= margin)
426 continue;
427 if (dist < 0.0f)
428 Kokkos::atomic_max(&maxOverlap(), -dist);
429 const int slot = Kokkos::atomic_fetch_add(&outCount(), 1);
430 if (slot >= maxContacts) {
431 Kokkos::atomic_add(&outCount(), -1);
432 continue;
433 }
434 const F3 rA = scale3(pl.normal, -radius);
435 ContactC c{};
436 c.bodyA = i;
437 c.bodyB = -1;
438 c.normal = F4{pl.normal.x, pl.normal.y, pl.normal.z, 0.0f};
439 c.rA = F4{rA.x, rA.y, rA.z, 0.0f};
440 c.rB = F4{pl.point.x, pl.point.y, pl.point.z, 0.0f};
441 c.dist = dist;
442 c.friction_lambda_n = 0.0f;
443 c.weight = 0.0f;
444 outContacts(slot) = c;
445 }
446 }
447 });
448 space.fence();
449}
450
451} // namespace peclet::dem
452
453#endif // DEM_NARROWPHASE_HPP
dem — portable (Kokkos) contact->manifold reduction, replacing the thrust-based reduce_contacts_to_ma...
dem — portable POD types + math + analytic SDFs shared by the Kokkos kernel ports.
void detectContactsKokkos(Kokkos::View< const int *[2], CpMem > pairs, int numPairs, PosView pos, QuatView quat, ScalarF scale, ScalarI shapeId, Kokkos::View< const ShapeDesc *, CpMem > shapes, ShellView shell, float globalScale, float margin, Kokkos::View< ContactC *, CpMem > outContacts, Kokkos::View< int, CpMem > outCount, Kokkos::View< float, CpMem > maxOverlap, GridView sdfGrid=GridView{}, MatIdView matId=MatIdView{}, PairTableView pairTable=PairTableView{})
Pair point-shell vs SDF contacts.
float sdfEval(F3 p, int type, F4 params)
Kokkos::View< const float *[3], CpMem > ShellView
F3 cross3v(F3 a, F3 b)
Kokkos::View< const float *, CpMem > GridView
Kokkos::View< const unsigned char *, CpMem > MatIdView
float sampleWallSdf(F3 p, const WallSdf &w, GridView grid)
Trilinearly sample a static world-space wall SDF at world point p.
Kokkos::View< const float *, CpMem > PairTableView
F3 invRotateVector(F4 q, F3 v)
Kokkos::View< const int *, CpMem > ScalarI
void detectBoundaryKokkos(int numReal, int numPlanes, PosView pos, QuatView quat, ScalarF scale, ScalarI shapeId, Kokkos::View< const ShapeDesc *, CpMem > shapes, ShellView shell, Kokkos::View< const PlaneP *, CpMem > planes, float globalScale, float margin, Kokkos::View< ContactC *, CpMem > outContacts, Kokkos::View< int, CpMem > outCount, Kokkos::View< float, CpMem > maxOverlap)
Per-real-particle contacts against explicit planes (point-shell shapes test each surface point; analy...
void detectWallSdfKokkos(int numReal, int numWalls, PosView pos, QuatView quat, ScalarF scale, ScalarI shapeId, Kokkos::View< const ShapeDesc *, CpMem > shapes, ShellView shell, Kokkos::View< const WallSdf *, CpMem > walls, GridView wallGrid, float globalScale, float margin, Kokkos::View< ContactC *, CpMem > outContacts, Kokkos::View< int, CpMem > outCount, Kokkos::View< float, CpMem > maxOverlap, MatIdView matId=MatIdView{}, PairTableView pairTable=PairTableView{})
Per-real-particle contacts against a static world-space wall SDF set (a drum barrel,...
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 rotateVector(F4 q, F3 v)
float dot3(F3 a, F3 b)
F3 sub3(F3 a, F3 b)
CpExec::memory_space CpMem
F4 loadF4(QuatView v, int i)
F3 scale3(F3 a, float s)
float len3(F3 v)
F3 add3(F3 a, F3 b)
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
constexpr int kMaxMaterials
Pair-material lookup: flat [K*K*2] table, entry ((a*K + b)*2) = restitution, +1 = friction.
Kokkos::View< const float *, CpMem > ScalarF
Kokkos::DefaultExecutionSpace CpExec
Portable mirror of ParticleSystem.cuh ContactConstraint (the fields this reduction touches).
Portable mirror of ShapeDescriptor (analytic fields + a flat-array point shell).
Static, world-space SDF container/geometry the particles collide against (a drum barrel,...