8#ifndef DEM_SHAPE_REGISTRY_HPP
9#define DEM_SHAPE_REGISTRY_HPP
14#include <Kokkos_Core.hpp>
21#include "peclet/core/geom/scene_builder.hpp"
47 void initializeShape(
int shape_type,
float radius,
float height,
float thickness) {
48 F4 params{radius, 0, 0, 0};
49 std::vector<F3> shell;
53 params =
F4{radius, height, thickness, 0};
55 float min_dim = std::min(radius, thickness);
58 float spacing = std::min(radius * 0.3f, min_dim * 0.5f);
62 bound = std::sqrt(radius * radius + 0.25f * height * height);
63 }
else if (shape_type ==
BOX) {
65 params =
F4{radius, radius, radius, 0};
66 float spacing = std::max(radius * 0.5f, 1e-3f);
67 shell =
genBoxShell(radius, radius, radius, spacing);
68 bound = std::sqrt(3.0f) * radius;
71 params =
F4{radius, 0, 0, 0};
74 const int nPts =
static_cast<int>(shell.size());
77 float ix = 1.0f, iy = 1.0f, iz = 1.0f;
78 if (shape_type ==
SPHERE) {
80 float v = 2.5f / (radius * radius);
84 float r_out = radius, r_in = radius - thickness;
87 float term_r = r_out * r_out + r_in * r_in;
88 float I_zz = 0.5f * term_r;
89 float I_xx = (1.0f / 12.0f) * (3.0f * term_r + height * height);
96 }
else if (shape_type ==
BOX) {
97 float L = 2.0f * radius;
98 float I = (1.0f / 6.0f) * L * L;
100 ix = iy = iz = 1.0f / I;
106 ShapeDesc sd{shape_type, params, 0, nPts};
115 int addShape(
int shape_type,
float radius,
float height,
float thickness) {
118 throw std::runtime_error(
"addShape: call initialize_shape/set_sphere_shape first");
128 const std::vector<F3> addedShell =
shellHost_;
136 appendShape(added, addedShell, addedInvI, addedBase);
145 if (
static_cast<int>(ids.size()) !=
P_.
numReal)
146 throw std::runtime_error(
"set_shape_ids: expected one id per particle");
147 const int nShapes =
static_cast<int>(
shapesHost_.size());
149 if (v < 0 || v >= nShapes)
150 throw std::runtime_error(
"set_shape_ids: shape id out of range");
151 auto sid = Kokkos::create_mirror_view(
P_.
shapeId);
168 int addSdfShape(
const std::vector<float>& grid,
int nx,
int ny,
int nz,
F3 origin,
F3 spacing,
169 const std::vector<float>& shellFlat,
F3 invInertia,
float boundingRadius) {
171 throw std::runtime_error(
"add_sdf_shape: call initialize_shape/set_sdf_shape first");
172 if (nx < 2 || ny < 2 || nz < 2)
173 throw std::runtime_error(
"add_sdf_shape: grid dims must be >= 2 on each axis");
174 if (
static_cast<long>(nx) * ny * nz !=
static_cast<long>(grid.size()))
175 throw std::runtime_error(
"add_sdf_shape: grid.size() must equal nx*ny*nz");
176 const std::vector<float> shellUse =
177 shellFlat.empty() ?
autoShell(grid, nx, ny, nz, origin, spacing, boundingRadius)
179 const int nPts =
static_cast<int>(shellUse.size() / 3);
181 throw std::runtime_error(
182 "add_sdf_shape: surface point shell is empty and could not be "
183 "generated from the field");
186 sd.params =
F4{boundingRadius, 0, 0, 0};
192 sd.grid.invSpacing = peclet::core::Vec3<float>{spacing.
x > 0 ? 1.0f / spacing.
x : 0.0f,
193 spacing.
y > 0 ? 1.0f / spacing.
y : 0.0f,
194 spacing.
z > 0 ? 1.0f / spacing.
z : 0.0f};
195 sd.grid.extension = peclet::core::geom::GridExtension::kObject;
197 std::vector<F3> shellPts(nPts);
198 for (
int i = 0; i < nPts; ++i)
199 shellPts[i] =
F3{shellUse[3 * i], shellUse[3 * i + 1], shellUse[3 * i + 2]};
200 appendShape(sd, shellPts, invInertia, boundingRadius);
219 void setSdfShape(
const std::vector<float>& grid,
int nx,
int ny,
int nz,
F3 origin,
F3 spacing,
220 const std::vector<float>& shellFlat,
F3 invInertia,
float boundingRadius) {
221 if (nx < 2 || ny < 2 || nz < 2)
222 throw std::runtime_error(
"setSdfShape: grid dims must be >= 2 on each axis");
223 if (
static_cast<long>(nx) * ny * nz !=
static_cast<long>(grid.size()))
224 throw std::runtime_error(
"setSdfShape: grid.size() must equal nx*ny*nz");
226 const std::vector<float> shellUse =
227 shellFlat.empty() ?
autoShell(grid, nx, ny, nz, origin, spacing, boundingRadius)
229 const int nPts =
static_cast<int>(shellUse.size() / 3);
231 throw std::runtime_error(
232 "setSdfShape: surface point shell is empty and could not be "
233 "generated from the field");
236 P_.
sdfGrid = Kokkos::View<float*, CpMem>(
"sdfGrid", grid.size());
237 auto hg = Kokkos::create_mirror_view(
P_.
sdfGrid);
238 for (
size_t i = 0; i < grid.size(); ++i)
243 P_.
shell = Kokkos::View<float* [3], CpMem>(
"shell", nPts);
244 auto hs = Kokkos::create_mirror_view(
P_.
shell);
245 for (
int i = 0; i < nPts; ++i) {
246 hs(i, 0) = shellFlat[3 * i];
247 hs(i, 1) = shellFlat[3 * i + 1];
248 hs(i, 2) = shellFlat[3 * i + 2];
250 Kokkos::deep_copy(
P_.
shell, hs);
255 sd.params =
F4{boundingRadius, 0, 0, 0};
263 sd.grid.invSpacing = peclet::core::Vec3<float>{spacing.
x > 0 ? 1.0f / spacing.
x : 0.0f,
264 spacing.
y > 0 ? 1.0f / spacing.
y : 0.0f,
265 spacing.
z > 0 ? 1.0f / spacing.
z : 0.0f};
266 sd.grid.extension = peclet::core::geom::GridExtension::kObject;
268 std::vector<F3> shellPts(nPts);
269 for (
int i = 0; i < nPts; ++i)
270 shellPts[i] =
F3{shellUse[3 * i], shellUse[3 * i + 1], shellUse[3 * i + 2]};
274 appendShape(sd, shellPts, invInertia, boundingRadius);
285 int addSceneShape(
const std::vector<int>& nodeInts,
const std::vector<float>& nodeReals,
int root,
286 const std::vector<float>& shellFlat,
F3 invInertia,
float boundingRadius) {
287 namespace g = peclet::core::geom;
288 if (nodeInts.size() % g::kNodeIntStride || nodeReals.size() % g::kNodeRealStride)
289 throw std::runtime_error(
"add_scene_shape: node arrays are not a whole number of records");
290 const int n =
static_cast<int>(nodeInts.size() / g::kNodeIntStride);
291 if (n == 0 ||
static_cast<int>(nodeReals.size() / g::kNodeRealStride) != n)
292 throw std::runtime_error(
"add_scene_shape: node int/real counts disagree");
293 if (root < 0 || root >= n)
294 throw std::runtime_error(
"add_scene_shape: root out of range");
296 for (
int i = 0; i < n; ++i) {
297 g::ShapeNode<float> nd = g::decodeNode<float>(&nodeInts[i * g::kNodeIntStride],
298 &nodeReals[i * g::kNodeRealStride]);
299 if (nd.kind == g::kGrid)
300 throw std::runtime_error(
301 "add_scene_shape: grid leaves are not supported in particle trees (use set_sdf_shape "
302 "for sampled shapes)");
303 if (nd.kind >= g::kCsgBase) {
309 std::vector<F3> shellPts;
310 if (shellFlat.size() % 3)
311 throw std::runtime_error(
"add_scene_shape: shell must be (M,3)");
312 shellPts.reserve(shellFlat.size() / 3);
313 for (std::size_t i = 0; i + 2 < shellFlat.size(); i += 3)
314 shellPts.push_back(
F3{shellFlat[i], shellFlat[i + 1], shellFlat[i + 2]});
315 if (shellPts.empty())
316 throw std::runtime_error(
317 "add_scene_shape: an empty shell would make the body invisible to contacts -- bake the "
318 "tree and generate one (SceneBuilder.bake + the marching-cubes shell path)");
321 sd.params =
F4{boundingRadius, 0, 0, 0};
323 sd.shapeRoot = base + root;
324 appendShape(sd, shellPts, invInertia, boundingRadius);
334 std::vector<float>
autoShell(
const std::vector<float>& grid,
int nx,
int ny,
int nz,
F3 origin,
335 F3 spacing,
float boundingRadius)
const {
336 namespace g = peclet::core::geom;
337 g::SceneBuilder<float> b;
338 const int node = b.addGrid(
339 grid, nx, ny, nz, peclet::core::Vec3<float>{origin.
x, origin.
y, origin.
z},
340 peclet::core::Vec3<float>{spacing.
x, spacing.
y, spacing.
z}, g::GridExtension::kObject);
344 const float pitch = std::max(boundingRadius / 12.0f, 1e-4f);
345 const float r = boundingRadius * 1.05f;
346 const std::vector<peclet::core::Vec3<float>> pts =
347 g::surfacePoints<float>(b.view(), node, pitch, peclet::core::Vec3<float>{-r, -r, -r},
348 peclet::core::Vec3<float>{r, r, r});
349 std::vector<float> flat;
350 flat.reserve(pts.size() * 3);
351 for (
const auto& q : pts) {
395 sd.
numPoints =
static_cast<int>(shell.size());
404 const int nShapes =
static_cast<int>(
shapesHost_.size());
418 if (
static_cast<int>(
P_.
shapes.extent(0)) < nShapes)
419 P_.
shapes = Kokkos::View<ShapeDesc*, CpMem>(
"shapes", nShapes);
420 auto hs = Kokkos::create_mirror_view(
P_.
shapes);
421 for (
int i = 0; i < nShapes; ++i) {
425 hs(i).nodeCount =
static_cast<int>(
P_.
shapeNodes.extent(0));
430 const int nPts =
static_cast<int>(
shellHost_.size());
432 P_.
shell = Kokkos::View<float* [3], CpMem>(
"shell", nPts);
433 auto hsh = Kokkos::create_mirror_view(
P_.
shell);
434 for (
int i = 0; i < nPts; ++i) {
439 Kokkos::deep_copy(
P_.
shell, hsh);
444 auto hg = Kokkos::create_mirror_view(
P_.
sdfGrid);
454 for (
int i = 0; i < nShapes; ++i) {
456 shellMax = std::max(shellMax,
shapesHost_[i].numPoints);
Host-side shape registry (Layer 1).
void appendShape(ShapeDesc sd, const std::vector< F3 > &shell, F3 invI, float baseRadius)
Append one shape, taking ownership of its shell.
std::vector< float > baseRadiusHost_
void setSphereShape(float radius)
std::vector< ShapeDesc > shapesHost_
std::vector< F3 > shellHost_
int addSdfShape(const std::vector< float > &grid, int nx, int ny, int nz, F3 origin, F3 spacing, const std::vector< float > &shellFlat, F3 invInertia, float boundingRadius)
Append a grid-SDF shape (the general non-spherical particle) to the mixture and return its index.
void uploadShapes()
Rebuild the device Views from the host registry.
std::vector< peclet::core::geom::ShapeNode< float > > shapeNodesHost_
int addShape(int shape_type, float radius, float height, float thickness)
Append an analytic shape and return its index, for simulations with a MIXTURE of shapes.
int addSceneShape(const std::vector< int > &nodeInts, const std::vector< float > &nodeReals, int root, const std::vector< float > &shellFlat, F3 invInertia, float boundingRadius)
void setSdfShape(const std::vector< float > &grid, int nx, int ny, int nz, F3 origin, F3 spacing, const std::vector< float > &shellFlat, F3 invInertia, float boundingRadius)
std::vector< float > autoShell(const std::vector< float > &grid, int nx, int ny, int nz, F3 origin, F3 spacing, float boundingRadius) const
Generate a collision shell for a grid SDF by sampling its own zero level set, so a caller does not ha...
void ensureContactCapacity()
std::vector< float > sdfSamplesHost_
void initializeShape(int shape_type, float radius, float height, float thickness)
void setShapeIds(const std::vector< int > &ids)
Per-particle shape assignment.
std::vector< F3 > invIHost_
std::vector< F3 > genCylinderShell(float radius, float height, float thickness, float spacing)
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 growContactBuffers(Particles &P, long nBodies, long floorWant=0)
Grow every maxContacts-sized collision buffer to match the CURRENT particle capacity.
std::vector< F3 > genBoxShell(float hx, float hy, float hz, float spacing)
dem — portable (Kokkos) narrow-phase: SDF point-shell collision + boundary planes.
dem — portable (Kokkos) particle SoA container: the storage the dem flip pivots on.
dem — portable (host) surface-shell point generators for the analytic shapes.
dem — the single-rank step drivers over the particle SoA: the XPBD substep (demStep),...
Kokkos::View< ShapeDesc *, CpMem > shapes
Kokkos::View< peclet::core::geom::ShapeNode< float > *, CpMem > shapeNodes
Kokkos::View< float *, CpMem > sdfGrid
Kokkos::View< float *[3], CpMem > shell
Portable mirror of ShapeDescriptor (analytic fields + a flat-array point shell).