Skip to main content

Boundaries and rigid-body coupling

The RbdCoupling argument of state.insert_rigid_body lets MPM particles interact with a rigid-body collider. This is typically used for a container holding sand, or a moving blade pushing an elastic object. Use BoundaryCondition to choose how the material moves wrt. the collider's surface.

Coupling modes​

There are three RbdCoupling modes:

  • RbdCoupling.NONE selects ordinary rigid-body dynamics, without an MPM boundary.
  • RbdCoupling.mpm_one_way(boundary) lets the body affect particles without receiving their reaction. This is typically used for fixed walls or prescribed motion.
  • RbdCoupling.mpm_two_way(boundary) also transfers particle impulses to the body.

With RbdCoupling.mpm_two_way, pipeline.simulate runs the rigid-body step, then MPM integrates the coupled body's pose and writes it back. These separate updates don't resolve all interactions simultaneously. Keep this limitation in mind when combining coupled bodies with articulated mechanisms or ordinary rigid-body contacts.

Boundary conditions​

Import BoundaryCondition from nexus3d. Its constructors select the surface response:

  • BoundaryCondition.stick() removes relative velocity at the surface. The material follows the surface without sliding.
  • BoundaryCondition.slip() removes the normal component of relative velocity and preserves the tangential component.
  • BoundaryCondition.separate(friction) prevents motion into the surface while allowing the material to move away. The friction argument opposes sliding.
sticky = BoundaryCondition.stick()
sliding = BoundaryCondition.slip()
separating = BoundaryCondition.separate(0.5)

The BoundaryCondition response is independent of ColliderBuilder.friction and ColliderBuilder.restitution, which configure ordinary rigid-body contacts. Changing those coefficients won't change BoundaryCondition.

Creating a ground​

The following passes BoundaryCondition.separate(0.5) to one-way coupling. It uses RigidBodyBuilder.fixed and ColliderBuilder.cuboid to describe the ground:

coupling = RbdCoupling.mpm_one_way(BoundaryCondition.separate(0.5))
body = RigidBodyBuilder.fixed().build()
collider = ColliderBuilder.cuboid(5.0, 0.1, 5.0).build()
state.insert_rigid_body(body, collider, coupling)

Call state.insert_rigid_body during scene construction, before state.finalize. Also call state.add_particles, so the scene contains the material to simulate. The viewer.insert_shape method can display the ground, but registering graphics alone doesn't create a boundary.

Compatible particle-in-cell​

Nexus uses CPIC (Compatible Particle-In-Cell) by default. This lets transfers account for boundaries separating particles from grid nodes. Call state.set_mpm_use_cpic(enabled) to change this setting. For a first simulation, it is recommended to keep enabled = True.

Environment and timestep​

The state.finalize method collects coupled boundaries from rigid-body environment 0. Calling state.add_environment creates more rigid-body environments, without duplicating the MPM material or its boundaries. Insert an MPM boundary with state.insert_rigid_body, or with state.insert_rigid_body_in(0, ...).

The rigid-body and MPM solvers have independent timesteps. The MPM SimulationParams.dt must match the total rigid-body time advanced by pipeline.simulate if the results need to stay synchronized.

The default rigid-body timestep is 1 / 60, so leave state.set_rbd_steps_per_frame(1) when using SimulationParams(..., 1 / 60). The state.set_mpm_substeps method subdivides that MPM time; it doesn't change the total SimulationParams.dt.

Call state.set_rbd_gravity after state.finalize. For the material, supply gravity in SimulationParams(gravity, dt), or call state.set_mpm_gravity. Keep the two values consistent when gravity should affect both parts of the scene equally.

info

If particles pass through the ground, check its RbdCoupling value and environment index first. Then check cell_width, SimulationParams.dt, and state.set_mpm_substeps as described in MPM timestep and gravity. A visible shape from viewer.insert_shape isn't evidence that state.finalize registered an MPM boundary.