Particles and materials
Nexus provides the Material Point Method (MPM) for materials that deform, like elastic objects, sand, snow, or fluids.
The Particle values carry the material state, and an auxiliary grid computes their interaction.
The ParticleModel determines the material's response to deformation.
Unlike rigid-bodies with ball colliders, MPM particles interact through the grid. A particle's radius determines its initial volume and visualization size.
Creation and insertion
Import Particle, ParticleModel, and SimulationParams from nexus3d.
Construct Particle(position, radius, density, model),
then call state.add_particles to upload a group of particles. Its return value identifies a chunk.
The following adds a block of elastic material to an existing simulation, using
a viewer initialized with viewer.init_backend():
cell_width = 0.5
params = SimulationParams(Vec3(0.0, -9.81, 0.0), 1.0 / 60.0)
state.set_mpm_params(viewer, params, cell_width)
state.set_mpm_substeps(20)
model = ParticleModel.elastic(1.0e4, 0.2)
particles = []
for x in range(4):
for y in range(4):
for z in range(4):
position = Vec3(x * cell_width / 2.0,
2.0 + y * cell_width / 2.0,
z * cell_width / 2.0)
particles.append(Particle(position, cell_width / 4.0, 1000.0, model))
chunk = state.add_particles(viewer, particles)
Here, cell_width is the grid cell width, the particle spacing is cell_width / 2, and the particle radius is cell_width / 4.
This gives two particles per cell along each axis, which is a useful starting point for regular blocks.
The initial volume is the particle diameter raised to the dimension, and mass is this volume multiplied by density.
Call state.set_mpm_params before state.add_particles. Calling state.set_mpm_params again after upload resets
the grid, so it is preferable to choose the cell width at setup. pipeline.simulate advances the uploaded material,
and viewer.sync updates its graphics. The simulation loop is the
same as for the getting started example.
Material models
The young_modulus argument controls stiffness. Increasing it makes deformation more difficult, but also requires
smaller substeps for stability. The poisson_ratio argument controls lateral deformation during compression or
stretching. The following shows ParticleModel.elastic
and ParticleModel.sand:
elastic = ParticleModel.elastic(1.0e4, 0.2)
sand = ParticleModel.sand(1.0e5, 0.2)
An elastic material tends to recover its initial shape. Sand also has a plastic response, so a pile can
retain a new shape.
ParticleModel.elastic_neo_hookean and ParticleModel.sand_neo_hookean use Neo-Hookean elasticity.
The Python ParticleModel exposes the elastic and sand constructors above. The Rust ParticleModel::snow,
ParticleModel::cohesive_sand, and ParticleModel::water_for_depth constructors are not exposed by these bindings.
Initial state
Before state.add_particles, set particle.velocity for an
initial velocity. Use particle.set_damping
and particle.set_fixed to configure damping or hold particles in place. This is typically used to attach
part of an elastic object to a fixed support.
Keep in mind that modifying the original particle after state.add_particles doesn't modify its uploaded copy.
For an emitter, create new Particle values and call state.extend_chunk as shown below.
Timestep and gravity
The params.dt value in SimulationParams is the total MPM time advanced by one pipeline.simulate call.
The state.set_mpm_substeps method divides this time into substeps; its default is 20, and counts smaller than
one are clamped to one. With params.dt = 1 / 60 and state.set_mpm_substeps(20), each substep advances 1 / 1200
seconds. Increasing state.set_mpm_substeps improves stability at the expense of computations.
The params.gravity value defines initial MPM gravity. After state.set_mpm_params, call state.set_mpm_gravity
to change it without resetting the grid. It is separate from state.set_rbd_gravity; setting one doesn't change the
other. See coupling when both solvers interact.
Reducing cell_width or increasing young_modulus can require shorter substeps. If particles gain large velocities
or disappear, increase state.set_mpm_substeps, reduce params.dt, or reduce the ParticleModel constructor's
young_modulus. Increasing the number of particles alone doesn't fix an unstable timestep.
Also check that radius and density passed to Particle are positive,
and that the initial particle positions don't lie inside an unintended boundary.
Chunks and emitters
The handle returned by state.add_particles identifies the uploaded group. Call state.extend_chunk to append
particles, and state.remove_chunk to remove the group. Given chunk from the creation example:
model = ParticleModel.sand(1.0e5, 0.2)
particle = Particle(Vec3(0.0, 3.0, 0.0), 0.125, 1000.0, model)
state.extend_chunk(viewer, chunk, [particle])
state.remove_chunk(viewer, chunk)
Removing particles can move other particles inside the compute buffers. Use the chunk handle for chunk operations;
a raw particle buffer index is not a persistent identifier. Don't reuse the handle after state.remove_chunk.
Boundaries
The RbdCoupling.NONE value leaves particles unaffected by a
rigid-body collider. To hold the material above a ground, supply
RbdCoupling.mpm_one_way to state.insert_rigid_body.
The MPM coupling page shows the full setup and BoundaryCondition choices.