11#ifndef DEM_NARROWPHASE_HPP
12#define DEM_NARROWPHASE_HPP
14#include <Kokkos_Core.hpp>
18#include "peclet/core/geom/scene.hpp"
40 peclet::core::geom::GridDesc<float>
grid;
45 const peclet::core::geom::ShapeNode<float>*
nodes =
nullptr;
74 peclet::core::geom::GridDesc<float>
grid;
82 const peclet::core::geom::ShapeNode<float>*
nodes =
nullptr;
101using MatIdView = Kokkos::View<const unsigned char*, CpMem>;
104using PosView = Kokkos::View<const float* [3], CpMem>;
105using QuatView = Kokkos::View<const float* [4], CpMem>;
106using ScalarF = Kokkos::View<const float*, CpMem>;
107using ScalarI = Kokkos::View<const int*, CpMem>;
112 return F3{v(i, 0), v(i, 1), v(i, 2)};
115 return F4{v(i, 0), v(i, 1), v(i, 2), v(i, 3)};
130 return peclet::core::geom::sampleGrid(
toCoreVec(p), d.
grid, grid);
145 return w.
sign * peclet::core::geom::evalTree<float>(
146 peclet::core::geom::TablePtr<peclet::core::geom::ShapeNode<float>>{w.
nodes},
148 peclet::core::geom::TablePtr<peclet::core::geom::GridDesc<float>>{
nullptr},
149 peclet::core::geom::PoolPtr<float>{
nullptr});
150 return peclet::core::geom::sampleGrid(
toCoreVec(p), w.
grid, grid);
159 return peclet::core::geom::evalTree<float>(
160 peclet::core::geom::TablePtr<peclet::core::geom::ShapeNode<float>>{d.
nodes}, d.
nodeCount,
162 peclet::core::geom::TablePtr<peclet::core::geom::GridDesc<float>>{
nullptr},
163 peclet::core::geom::PoolPtr<float>{
nullptr});
176 namespace prim = peclet::core::geom::prim;
177 const peclet::core::Vec3<float> q =
toCoreVec(p);
178 peclet::core::Vec3<float> g;
181 g = prim::Sphere<float>{d.
params.
x}.grad(q);
193 peclet::core::Vec3<float> gt;
194 (void)peclet::core::geom::evalTreeGrad<float>(
195 peclet::core::geom::TablePtr<peclet::core::geom::ShapeNode<float>>{d.
nodes}, d.
nodeCount,
197 peclet::core::geom::TablePtr<peclet::core::geom::GridDesc<float>>{
nullptr},
198 peclet::core::geom::PoolPtr<float>{
nullptr}, gt);
199 return F3{gt.
x, gt.y, gt.z};
202 const float eps = 1e-4f;
211 return F3{g.
x, g.y, g.z};
237 Kokkos::View<const ShapeDesc*, CpMem> shapes,
ShellView shell,
238 float globalScale,
float margin,
239 Kokkos::View<ContactC*, CpMem> outContacts,
240 Kokkos::View<int, CpMem> outCount,
241 Kokkos::View<float, CpMem> maxOverlap,
245 const int maxContacts =
static_cast<int>(outContacts.extent(0));
246 Kokkos::parallel_for(
247 "peclet::dem::np::contacts", Kokkos::RangePolicy<CpExec>(space, 0, numPairs),
248 KOKKOS_LAMBDA(
int idx) {
249 const int idA = pairs(idx, 0), idB = pairs(idx, 1);
250 const ShapeDesc dA = shapes(shapeId(idA));
251 const ShapeDesc dB = shapes(shapeId(idB));
252 const F3 posA =
loadF3(pos, idA), posB =
loadF3(pos, idB);
253 const F4 qA =
loadF4(quat, idA), qB =
loadF4(quat, idB);
260 const float effScaleA = scale(idA) * globalScale, effScaleB = scale(idB) * globalScale;
262 const int countA = dA.numPoints;
263 const bool sphereA = (dA.type ==
SPHERE);
264 const int iter = (countA > 0) ? countA : 1;
267 const int countB = dB.numPoints;
268 const bool sphereProbeB = twoWay && countA > 0 && countB == 0 && dB.type ==
SPHERE;
269 const bool shellProbeB = twoWay && countA > 0 && countB > 0;
271 for (
int k = 0; k < (sphereProbeB ? 0 : iter); ++k) {
273 float pointRadius = 0.0f;
275 const int s = dA.shellOffset + k;
276 pLocalA = F3{shell(s, 0), shell(s, 1), shell(s, 2)};
277 }
else if (sphereA) {
278 pointRadius = dA.params.x * effScaleA;
283 const F3 pCanB =
scale3(pLocalB, 1.0f / effScaleB);
285 const float dist =
sdfEvalShape(pCanB, dB, sdfGrid) * effScaleB;
286 const float effDist = dist - pointRadius;
287 if (effDist >= margin)
291 Kokkos::atomic_max(&maxOverlap(), -effDist);
292 const int slot = Kokkos::atomic_fetch_add(&outCount(), 1);
293 if (slot >= maxContacts) {
294 Kokkos::atomic_add(&outCount(), -1);
299 const float len =
len3(nLoc);
300 nLoc = (len > 1e-9f) ?
scale3(nLoc, 1.0f / len) : F3{0, 1, 0};
303 const F3 pSurfA =
sub3(pWorld,
scale3(nWorld, pointRadius));
304 const F3 rA =
sub3(pSurfA, posA);
310 c.normal = F4{nWorld.x, nWorld.y, nWorld.z, 0.0f};
311 c.rA = F4{rA.x, rA.y, rA.z, 0.0f};
312 c.rB = F4{rB.x, rB.y, rB.z, 0.0f};
314 c.friction_lambda_n = 0.0f;
316 if (pairTable.extent(0) > 0) {
317 const int t = (int(matId(idA)) *
kMaxMaterials + int(matId(idB))) * 2;
318 c.boundaryRestitution = pairTable(t);
319 c.boundaryFriction = pairTable(t + 1);
321 outContacts(slot) = c;
327 const int iterB = sphereProbeB ? 1 : (shellProbeB ? countB : 0);
328 for (
int k = 0; k < iterB; ++k) {
330 float pointRadius = 0.0f;
332 const int s = dB.shellOffset + k;
333 pLocalB = F3{shell(s, 0), shell(s, 1), shell(s, 2)};
335 pointRadius = dB.params.x * effScaleB;
340 const F3 pCanA =
scale3(pLocalA, 1.0f / effScaleA);
342 const float dist =
sdfEvalShape(pCanA, dA, sdfGrid) * effScaleA;
343 const float effDist = dist - pointRadius;
344 if (effDist >= margin)
348 Kokkos::atomic_max(&maxOverlap(), -effDist);
349 const int slot = Kokkos::atomic_fetch_add(&outCount(), 1);
350 if (slot >= maxContacts) {
351 Kokkos::atomic_add(&outCount(), -1);
356 const float len =
len3(mLoc);
357 mLoc = (len > 1e-9f) ?
scale3(mLoc, 1.0f / len) : F3{0, 1, 0};
360 const F3 pSurfB =
sub3(pWorld,
scale3(mWorld, pointRadius));
361 const F3 pSurfA =
sub3(pSurfB,
scale3(mWorld, effDist));
362 const F3 rA =
sub3(pSurfA, posA);
363 const F3 rB =
sub3(pSurfB, posB);
371 c.normal = F4{-mWorld.x, -mWorld.y, -mWorld.z, 1.0f};
372 c.rA = F4{rA.x, rA.y, rA.z, 0.0f};
373 c.rB = F4{rB.x, rB.y, rB.z, 0.0f};
375 c.friction_lambda_n = 0.0f;
377 if (pairTable.extent(0) > 0) {
378 const int t = (int(matId(idA)) *
kMaxMaterials + int(matId(idB))) * 2;
379 c.boundaryRestitution = pairTable(t);
380 c.boundaryFriction = pairTable(t + 1);
382 outContacts(slot) = c;
399 Kokkos::View<const ShapeDesc*, CpMem> shapes,
ShellView shell,
400 Kokkos::View<const WallSdf*, CpMem> walls,
GridView wallGrid,
float globalScale,
float margin,
401 Kokkos::View<ContactC*, CpMem> outContacts, Kokkos::View<int, CpMem> outCount,
405 const int maxContacts =
static_cast<int>(outContacts.extent(0));
406 Kokkos::parallel_for(
407 "peclet::dem::np::wallsdf", Kokkos::RangePolicy<CpExec>(space, 0, numReal),
408 KOKKOS_LAMBDA(
int i) {
409 const F3 posA =
loadF3(pos, i);
410 const float s = scale(i) * globalScale;
411 const ShapeDesc d = shapes(shapeId(i));
412 float baseR = d.params.x;
415 const float radius = baseR * s;
416 const int numPts = d.numPoints;
417 const F4 qA =
loadF4(quat, i);
418 const float eps = 1e-4f;
420 for (
int wi = 0; wi < numWalls; ++wi) {
421 const WallSdf w = walls(wi);
422 const int iter = (numPts > 0) ? numPts : 1;
423 for (
int k = 0; k < iter; ++k) {
427 const int si = d.shellOffset + k;
430 const F3 pw =
add3(posA, rA);
437 if (w.shapeRoot >= 0) {
438 peclet::core::Vec3<float> gt;
439 (void)peclet::core::geom::evalTreeGrad<float>(
440 peclet::core::geom::TablePtr<peclet::core::geom::ShapeNode<float>>{w.nodes},
442 peclet::core::geom::TablePtr<peclet::core::geom::GridDesc<float>>{
nullptr},
443 peclet::core::geom::PoolPtr<float>{
nullptr}, gt);
444 n = F3{w.sign * gt.x, w.sign * gt.y, w.sign * gt.z};
446 n = F3{
sampleWallSdf(F3{pw.x + eps, pw.y, pw.z}, w, wallGrid) -
453 const float ln =
len3(n);
454 n = (ln > 1e-9f) ?
scale3(n, 1.0f / ln) : F3{0, 1, 0};
458 const float dist = (numPts > 0) ? sdf : sdf - radius;
461 const F3 rAeff = (numPts > 0) ? rA :
scale3(n, -radius);
462 const F3 pSurfA =
add3(posA, rAeff);
463 const F3 pWall =
sub3(pSurfA,
scale3(n, dist));
466 Kokkos::atomic_max(&maxOverlap(), -dist);
467 const int slot = Kokkos::atomic_fetch_add(&outCount(), 1);
468 if (slot >= maxContacts) {
469 Kokkos::atomic_add(&outCount(), -1);
474 const F3 r =
sub3(pWall, w.center);
475 const F3 vWall =
add3(w.linVel,
cross3v(w.angVel, r));
480 c.normal = F4{n.x, n.y, n.z, 0.0f};
481 c.rA = F4{rAeff.x, rAeff.y, rAeff.z, 0.0f};
482 c.rB = F4{pWall.x, pWall.y, pWall.z, 0.0f};
484 c.friction_lambda_n = 0.0f;
486 c.boundaryVel = F4{vWall.x, vWall.y, vWall.z, 0.0f};
487 if (w.materialId >= 0 && pairTable.extent(0) > 0) {
488 const int t = (int(matId(i)) *
kMaxMaterials + w.materialId) * 2;
489 c.boundaryRestitution = pairTable(t);
490 c.boundaryFriction = pairTable(t + 1);
492 c.boundaryRestitution = w.restitution;
493 c.boundaryFriction = w.friction;
495 outContacts(slot) = c;
507 Kokkos::View<const ShapeDesc*, CpMem> shapes,
ShellView shell,
508 Kokkos::View<const PlaneP*, CpMem> planes,
float globalScale,
509 float margin, Kokkos::View<ContactC*, CpMem> outContacts,
510 Kokkos::View<int, CpMem> outCount,
511 Kokkos::View<float, CpMem> maxOverlap) {
513 const int maxContacts =
static_cast<int>(outContacts.extent(0));
514 Kokkos::parallel_for(
515 "peclet::dem::np::boundary", Kokkos::RangePolicy<CpExec>(space, 0, numReal),
516 KOKKOS_LAMBDA(
int i) {
518 const float s = scale(i) * globalScale;
523 const float radius = baseR * s;
527 for (
int pi = 0; pi < numPlanes; ++pi) {
528 const PlaneP pl = planes(pi);
531 for (
int k = 0; k < numPts; ++k) {
535 const F3 pwk =
add3(posA, rA);
540 Kokkos::atomic_max(&maxOverlap(), -dist);
541 const int slot = Kokkos::atomic_fetch_add(&outCount(), 1);
542 if (slot >= maxContacts) {
543 Kokkos::atomic_add(&outCount(), -1);
550 c.rA =
F4{rA.
x, rA.
y, rA.
z, 0.0f};
553 c.friction_lambda_n = 0.0f;
555 outContacts(slot) = c;
562 Kokkos::atomic_max(&maxOverlap(), -dist);
563 const int slot = Kokkos::atomic_fetch_add(&outCount(), 1);
564 if (slot >= maxContacts) {
565 Kokkos::atomic_add(&outCount(), -1);
573 c.rA =
F4{rA.
x, rA.
y, rA.
z, 0.0f};
576 c.friction_lambda_n = 0.0f;
578 outContacts(slot) = c;
dem — portable POD types + math + analytic SDFs shared by the Kokkos kernel ports.
F3 sdfGradShape(F3 p, const ShapeDesc &d, GridView grid)
Outward gradient of a shape's SDF at canonical point p, NOT normalised (callers normalise,...
float sdfEval(F3 p, int type, F4 params)
Kokkos::View< const float *[3], CpMem > ShellView
Kokkos::View< const float *, CpMem > GridView
constexpr int wallContactId(int wallIndex)
Wall identity of a boundary contact (docs/contact_solve_framework.md §12 S6): a contact with a wall c...
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...
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)
peclet::core::Vec3< float > toCoreVec(F3 p)
F3 -> core's Real-templated point type. Layout-identical; this is a type change, not a copy.
CpExec::memory_space CpMem
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{}, int wallIdBase=0)
Per-real-particle contacts against a static world-space wall SDF set (a drum barrel,...
F4 loadF4(QuatView v, int i)
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{}, bool twoWay=false)
Pair point-shell vs SDF contacts.
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 ShapeDescriptor (analytic fields + a flat-array point shell).
const peclet::core::geom::ShapeNode< float > * nodes
peclet::core::geom::GridDesc< float > grid
Static, world-space SDF container/geometry the particles collide against (a drum barrel,...
const peclet::core::geom::ShapeNode< float > * nodes
peclet::core::geom::GridDesc< float > grid