soft_body_cohesion
Rapier supports three ways of holding the particles of a soft-body together: shape matching, constraints, and the Finite Elements Method (FEM). They can be combined, e.g., shape matching on top of edge constraints. Note that shape matching works on the particles alone, whereas the constraints need edges or cells, and the FEM solver needs cells.
Shape matching
Shape matching doesn't need any element. At each timestep, the rest shape of the body is placed where it best fits its current shape, i.e., both shapes are given the same center of mass, and the rest shape is given the rotation bringing its particles the closest to the current ones. Each particle is then pulled toward its twin in that matched rest shape by a spring:
This is cheap, and a body always recovers its original shape whatever the deformation it went through. On the other
hand, the particles don't interact with their neighbors: pushing on one particle doesn't pull the ones around it, which
makes the deformations feel very local. Shape matching is enabled by
SoftBodyBuilder::shape_matchingSoftBodyDesc.setShapeMatchingshapeMatching field of the descriptionSoftBodyBuilder.shape_matchingshape_matching_softnessshapeMatchingSoftnessshapeMatchingSoftnessshape_matching_softness
- Example 2D
- Example 3D
// A cloud of particles without any element: shape matching alone pulls them back toward
// their rest shape, placed where it best fits the current one.
let points: Vec<Vector> = (0..9)
.map(|i| Vector::new((i % 3) as f32, (i / 3) as f32 + 4.0) * 0.3)
.collect();
let blob = SoftBodyBuilder::new(points)
.shape_matching(true)
.material(SoftBodyMaterial {
// How fast the particles are pulled back toward their rest shape.
shape_matching_softness: SpringCoefficients::new(5.0, 1.0),
..Default::default()
})
.particle_radius(0.1);
let _blob_handle = world.insert_soft_body(blob);
// A cloud of particles without any element: shape matching alone pulls them back toward
// their rest shape, placed where it best fits the current one.
let points: Vec<Vector> = (0..27)
.map(|i| Vector::new((i % 3) as f32, (i / 3 % 3) as f32 + 4.0, (i / 9) as f32) * 0.3)
.collect();
let blob = SoftBodyBuilder::new(points)
.shape_matching(true)
.material(SoftBodyMaterial {
// How fast the particles are pulled back toward their rest shape.
shape_matching_softness: SpringCoefficients::new(5.0, 1.0),
..Default::default()
})
.particle_radius(0.1);
let _blob_handle = world.insert_soft_body(blob);
- Example 2D
- Example 3D
// A cloud of particles without any element: shape matching alone pulls them back toward
// their rest shape, placed where it best fits the current one.
let points = [];
for (let i = 0; i < 9; ++i) {
points.push((i % 3) * 0.3, Math.floor(i / 3) * 0.3 + 4.0);
}
let shapeMaterial = new RAPIER.SoftBodyMaterial();
// How fast the particles are pulled back toward their rest shape.
shapeMaterial.shapeMatchingSoftness = { naturalFrequency: 5.0, dampingRatio: 1.0 };
let pointCloudDesc = new RAPIER.SoftBodyDesc(points)
.setShapeMatching(true)
.setMaterial(shapeMaterial)
.setParticleRadius(0.1);
let pointCloud = world.createSoftBody(pointCloudDesc);
// A cloud of particles without any element: shape matching alone pulls them back toward
// their rest shape, placed where it best fits the current one.
let points = [];
for (let i = 0; i < 27; ++i) {
points.push((i % 3) * 0.3, (Math.floor(i / 3) % 3) * 0.3 + 4.0, Math.floor(i / 9) * 0.3);
}
let shapeMaterial = new RAPIER.SoftBodyMaterial();
// How fast the particles are pulled back toward their rest shape.
shapeMaterial.shapeMatchingSoftness = { naturalFrequency: 5.0, dampingRatio: 1.0 };
let pointCloudDesc = new RAPIER.SoftBodyDesc(points)
.setShapeMatching(true)
.setMaterial(shapeMaterial)
.setParticleRadius(0.1);
let pointCloud = world.createSoftBody(pointCloudDesc);
- Example 2D
- Example 3D
// A cloud of particles without any element: shape matching alone pulls them back toward
// their rest shape, placed where it best fits the current one.
let points: Vec<Vec2> = (0..9)
.map(|i| Vec2::new((i % 3) as f32, (i / 3) as f32) * 0.3)
.collect();
commands.spawn((
Transform::from_xyz(0.0, 4.0, 0.0),
SoftBody::new(
SoftBodyBuilder::new(points)
.shape_matching(true)
.particle_radius(0.1),
),
SoftBodyMaterial(RapierSoftBodyMaterial {
// How fast the particles are pulled back toward their rest shape.
shape_matching_softness: SpringCoefficients::new(5.0, 1.0),
..default()
}),
));
// A cloud of particles without any element: shape matching alone pulls them back toward
// their rest shape, placed where it best fits the current one.
let points: Vec<Vec3> = (0..27)
.map(|i| Vec3::new((i % 3) as f32, (i / 3 % 3) as f32, (i / 9) as f32) * 0.3)
.collect();
commands.spawn((
Transform::from_xyz(0.0, 4.0, 0.0),
SoftBody::new(
SoftBodyBuilder::new(points)
.shape_matching(true)
.particle_radius(0.1),
),
SoftBodyMaterial(RapierSoftBodyMaterial {
// How fast the particles are pulled back toward their rest shape.
shape_matching_softness: SpringCoefficients::new(5.0, 1.0),
..default()
}),
));
- Example 2D
- Example 3D
// A cloud of particles without any element: shape matching alone pulls them back toward
// their rest shape, placed where it best fits the current one.
R2Vector points[9];
for (int i = 0; i < 9; i++) {
points[i] = r2VectorScale(r2Vector(i % 3, i / 3 + 4.0), 0.3);
}
R2SoftBodyDesc cloud = r2DefaultSoftBodyDesc();
r2SoftBodyDesc_SetParticles(&cloud, (R2VectorView){points, 9});
cloud.shapeMatching = (R2OptionalBool){1, 1};
// How fast the particles are pulled back toward their rest shape.
cloud.material.shapeMatchingSoftness = (R2SpringCoefficients){5.0, 1.0};
cloud.particleRadius = (R2OptionalReal){1, 0.1};
R2SoftBodyHandle cloud_handle = r2InsertSoftBody(world, &cloud);
// A cloud of particles without any element: shape matching alone pulls them back toward
// their rest shape, placed where it best fits the current one.
R3Vector points[27];
for (int i = 0; i < 27; i++) {
points[i] = r3VectorScale(r3Vector(i % 3, i / 3 % 3 + 4.0, i / 9), 0.3);
}
R3SoftBodyDesc cloud = r3DefaultSoftBodyDesc();
r3SoftBodyDesc_SetParticles(&cloud, (R3VectorView){points, 27});
cloud.shapeMatching = (R3OptionalBool){1, 1};
// How fast the particles are pulled back toward their rest shape.
cloud.material.shapeMatchingSoftness = (R3SpringCoefficients){5.0, 1.0};
cloud.particleRadius = (R3OptionalReal){1, 0.1};
R3SoftBodyHandle cloud_handle = r3InsertSoftBody(world, &cloud);
# A cloud of particles without any element: shape matching alone pulls them back toward
# their rest shape, placed where it best fits the current one.
points = 0.3 * np.array([(i % 3, i // 3 % 3 + 4.0, i // 9) for i in range(27)])
blob = (
rp.SoftBodyBuilder(points)
.shape_matching(True)
.material(
rp.SoftBodyMaterial(
# How fast the particles are pulled back toward their rest shape.
shape_matching_softness=rp.SpringCoefficients(5.0, 1.0),
)
)
.particle_radius(0.1)
)
blob_handle = world.add_soft_body(blob)
Use shape matching for low-detail deformations, or whenever computing a topology (edges, cells) isn't desired. It is
enabled by default by the trimesh and polyline constructorstrimesh and polyline constructorsr3SoftBodyDesc_SetSurfaceMesh and r2SoftBodyDesc_SetTrimesh constructors (unless the shapeMatching override disables it)trimesh constructor
Constraints
The constraints-based soft-body solver is the default solverSoftBodySolver::Constraints)R3_SOFT_SOLVER_CONSTRAINTS)SoftBodySolver.CONSTRAINTS)
An edge is a spring-damper pulling its two particles back toward its rest length. A cell is either a volume constraint
keeping its area or its volume, the shape itself being held by the edges, or an elastic element resisting any
deformation. This is selected by the cell model of the body
(cell_modelsetCellModelcellModelcell_model, which takes a SoftBodyCellModel
VolumeVolumeR3_SOFT_CELL_VOLUME : one constraint per cell keeping its area (2D) or its volume (3D) at its rest value. This is the cheapest model, and combined with the edges it is often enough to obtain a convincing jelly.VOLUMECorotationalCorotationalR3_SOFT_CELL_COROTATIONAL : linear elasticity expressed in the rotation-free frame of the cell. It is stable at any stiffness and recovers from inverted cells.COROTATIONALNeoHookeanNeoHookeanR3_SOFT_CELL_NEO_HOOKEAN : stable Neo-Hookean hyperelasticity. It feels stiffer than linear elasticity on compression, but softer on tension.NEO_HOOKEAN
The stiffness of every element is configured by the SoftBodyMaterialR3SoftBodyMaterialSoftBody::set_materialRapierSoftBodyMaterial) and overrides the one of the
builderSoftBody.setMaterialr3SoftBody_SetMaterial (the current one being given by r3SoftBody_Material)material property of SoftBodyr3DefaultSoftBodyMaterial, or by r3UniformSoftBodyMaterial which gives the same softness to every constraint.SoftBodyMaterial.copy gives a detached copy. A material is created with the SoftBodyMaterial constructor, which takes any of its fields as keyword arguments, or with SoftBodyMaterial.uniform which gives the same softness to every constraint.
- The edge softness (
edge_softnessedgeSoftnessedgeSoftness ) for the structural edges;edge_softness - The bend softness (
bend_softnessbendSoftnessbendSoftness ) for the bending edges and the dihedral constraints;bend_softness - The volume softness (
volume_softnessvolumeSoftnessvolumeSoftness ) for the volume constraints.volume_softness
The elastic cells are given a Young modulus (young_modulusyoungModulusyoungModulusyoung_moduluspoisson_ratiopoissonRatiopoissonRatiopoisson_ratioelastic_damping_ratioelasticDampingRatioelasticDampingRatioelastic_damping_ratio
Finally, a body with a closed surface can preserve the area (2D) or the volume (3D) it encloses
(volume_preservationsetVolumePreservationvolumePreservationvolume_preservation, or enable_volume_preservation after the insertionvolume_factorvolume_factor (or a SoftBodyVolumeFactor component)setVolumeFactorvolumeFactor (or r3SoftBody_SetVolumeFactor after the insertion)volume_factor (or the volume_factor property of SoftBody after the insertion)
- Example 2D
- Example 3D
// Elastic cells: a jelly square with corotational linear elasticity.
let jelly = SoftBodyBuilder::grid(Vector::new(3.0, 1.2), Vector::splat(1.0), 6, 6)
// The constitutive model of the cells: `Volume` (per-cell area constraints,
// the shape is held by the edges), `Corotational` or `NeoHookean`.
.cell_model(SoftBodyCellModel::Corotational)
.material(SoftBodyMaterial {
// Stiffness of the elastic cells.
young_modulus: 3.0e3,
poisson_ratio: 0.35,
elastic_damping_ratio: 0.5,
// Plasticity: the rest shape flows past 5% strain, at 20 per second.
plastic_yield: 0.05,
plastic_creep: 20.0,
// Tearing: an element past 40% strain tears.
tear_strain: Some(0.4),
..Default::default()
})
.particle_mass(0.2);
let jelly_handle = world.insert_soft_body(jelly);
// A pressurized blob: a ring of particles inflated by area preservation.
let blob = SoftBodyBuilder::disk(Vector::new(0.0, 3.0), 0.8, 24)
.softness(SpringCoefficients::new(20.0, 1.0))
// Target area multiplier (`> 1` inflates the body); enables area preservation.
.volume_factor(1.1)
.self_contacts(true);
let blob_handle = world.insert_soft_body(blob);
// Elastic cells: a jelly cube with corotational linear elasticity.
let jelly = SoftBodyBuilder::cuboid(Vector::new(3.0, 1.0, 0.0), Vector::splat(0.5), 4, 4, 4)
// The constitutive model of the cells: `Volume` (per-cell volume constraints,
// the shape is held by the edges), `Corotational` or `NeoHookean`.
.cell_model(SoftBodyCellModel::Corotational)
.material(SoftBodyMaterial {
// Stiffness of the elastic cells.
young_modulus: 2.0e3,
poisson_ratio: 0.35,
elastic_damping_ratio: 0.5,
// Plasticity: the rest shape flows past 5% strain, at 20 per second.
plastic_yield: 0.05,
plastic_creep: 20.0,
// Tearing: an element past 40% strain tears.
tear_strain: Some(0.4),
..Default::default()
})
.particle_mass(0.2);
let jelly_handle = world.insert_soft_body(jelly);
// A material shared by the edges, bending constraints and volume constraints.
let material = SoftBodyMaterial {
// Softness of the bending constraints, on top of a uniform 30 Hz softness.
bend_softness: SpringCoefficients::new(3.0, 1.0),
..SoftBodyMaterial::uniform(SpringCoefficients::new(30.0, 1.0))
};
world.soft_bodies[cloth_handle].set_material(material);
- Example 2D
- Example 3D
// Elastic cells: a jelly square with corotational linear elasticity.
let material = new RAPIER.SoftBodyMaterial();
// Stiffness of the elastic cells.
material.youngModulus = 3.0e3;
material.poissonRatio = 0.35;
material.elasticDampingRatio = 0.5;
// Plasticity: the rest shape flows past 5% strain, at 20 per second.
material.plasticYield = 0.05;
material.plasticCreep = 20.0;
// Tearing: an element past 40% strain tears.
material.tearStrain = 0.4;
let jellyDesc = RAPIER.SoftBodyDesc.grid({ x: 3.0, y: 1.2 }, { x: 1.0, y: 1.0 }, 6, 6)
// The constitutive model of the cells: `Volume` (per-cell area constraints,
// the shape is held by the edges), `Corotational` or `NeoHookean`.
.setCellModel(RAPIER.SoftBodyCellModel.Corotational)
.setMaterial(material)
.setParticleMass(0.2);
let jelly = world.createSoftBody(jellyDesc);
// A pressurized blob: a ring of particles inflated by area preservation.
let blobDesc = RAPIER.SoftBodyDesc.disk({ x: 0.0, y: 3.0 }, 0.8, 24)
.setSoftness(20.0, 1.0)
// Target area multiplier (`> 1` inflates the body); enables area preservation.
.setVolumeFactor(1.1)
.setSelfContacts(true);
let blob = world.createSoftBody(blobDesc);
// Elastic cells: a jelly cube with corotational linear elasticity.
let material = new RAPIER.SoftBodyMaterial();
// Stiffness of the elastic cells.
material.youngModulus = 2.0e3;
material.poissonRatio = 0.35;
material.elasticDampingRatio = 0.5;
// Plasticity: the rest shape flows past 5% strain, at 20 per second.
material.plasticYield = 0.05;
material.plasticCreep = 20.0;
// Tearing: an element past 40% strain tears.
material.tearStrain = 0.4;
let jellyDesc = RAPIER.SoftBodyDesc.cuboid({ x: 3.0, y: 1.0, z: 0.0 }, { x: 0.5, y: 0.5, z: 0.5 }, 4, 4, 4)
// The constitutive model of the cells: `Volume` (per-cell volume constraints,
// the shape is held by the edges), `Corotational` or `NeoHookean`.
.setCellModel(RAPIER.SoftBodyCellModel.Corotational)
.setMaterial(material)
.setParticleMass(0.2);
let jelly = world.createSoftBody(jellyDesc);
// A material shared by the edges, bending constraints and volume constraints.
let clothMaterial = RAPIER.SoftBodyMaterial.uniform(30.0, 1.0);
// Softness of the bending constraints, on top of the uniform 30 Hz softness.
clothMaterial.bendSoftness = { naturalFrequency: 3.0, dampingRatio: 1.0 };
cloth.setMaterial(clothMaterial);
- Example 2D
- Example 3D
// Elastic cells: a jelly square with corotational linear elasticity.
let jelly_body = SoftBody::grid(Vec2::splat(1.0), 6, 6).map(|builder| {
// The constitutive model of the cells: `Volume` (per-cell area constraints,
// the shape is held by the edges), `Corotational` or `NeoHookean`.
builder
.cell_model(SoftBodyCellModel::Corotational)
.particle_mass(0.2)
});
let jelly = commands
.spawn((
Jelly,
Transform::from_xyz(3.0, 1.2, 0.0),
jelly_body.clone(),
// The material of the soft-body: modifying this component updates the soft-body.
SoftBodyMaterial(RapierSoftBodyMaterial {
// Stiffness of the elastic cells.
young_modulus: 3.0e3,
poisson_ratio: 0.35,
elastic_damping_ratio: 0.5,
// Plasticity: the rest shape flows past 5% strain, at 20 per second.
plastic_yield: 0.05,
plastic_creep: 20.0,
// Tearing: an element past 40% strain tears.
tear_strain: Some(0.4),
..default()
}),
))
.id();
// A pressurized blob: a ring of particles inflated by area preservation.
let blob_body = SoftBody::disk(0.8, 24).map(|builder| builder.self_contacts(true));
let blob_transform = Transform::from_xyz(0.0, 3.0, 0.0);
let blob = commands
.spawn((
blob_transform,
blob_body.clone(),
SoftBodyMaterial::uniform(20.0, 1.0),
// Target area multiplier (`> 1` inflates the body).
SoftBodyVolumeFactor(1.1),
))
.id();
// Elastic cells: a jelly cube with corotational linear elasticity.
let jelly_body = SoftBody::cuboid(Vec3::splat(0.5), 4, 4, 4).map(|builder| {
// The constitutive model of the cells: `Volume` (per-cell volume constraints,
// the shape is held by the edges), `Corotational` or `NeoHookean`.
builder
.cell_model(SoftBodyCellModel::Corotational)
.particle_mass(0.2)
});
let jelly = commands
.spawn((
Jelly,
Transform::from_xyz(3.0, 1.0, 0.0),
jelly_body.clone(),
// The material of the soft-body: modifying this component updates the soft-body.
SoftBodyMaterial(RapierSoftBodyMaterial {
// Stiffness of the elastic cells.
young_modulus: 2.0e3,
poisson_ratio: 0.35,
elastic_damping_ratio: 0.5,
// Plasticity: the rest shape flows past 5% strain, at 20 per second.
plastic_yield: 0.05,
plastic_creep: 20.0,
// Tearing: an element past 40% strain tears.
tear_strain: Some(0.4),
..default()
}),
))
.id();
// A balloon inflated by volume preservation.
commands.spawn((
Transform::from_xyz(0.0, 3.0, 3.0),
SoftBody::sphere(0.8, 2),
SoftBodyMaterial::uniform(20.0, 1.0),
// Target volume multiplier (`> 1` inflates the body).
SoftBodyVolumeFactor(1.1),
));
// A material shared by the edges, bending constraints and volume constraints.
commands
.entity(cloth)
.insert(SoftBodyMaterial(RapierSoftBodyMaterial {
// Softness of the bending constraints, on top of a uniform 30 Hz softness.
bend_softness: SpringCoefficients::new(3.0, 1.0),
..RapierSoftBodyMaterial::uniform(SpringCoefficients::new(30.0, 1.0))
}));
- Example 2D
- Example 3D
// Elastic cells: a jelly square with corotational linear elasticity.
R2SoftBodyDesc jelly = r2GridSoftBodyDesc(r2Vector(3.0, 1.2), r2Vector(1.0, 1.0), 6, 6);
// The constitutive model of the cells: R2_SOFT_CELL_VOLUME (per-cell area constraints,
// the shape is held by the edges), R2_SOFT_CELL_COROTATIONAL or R2_SOFT_CELL_NEO_HOOKEAN.
jelly.cellModel = R2_SOFT_CELL_COROTATIONAL;
// Stiffness of the elastic cells.
jelly.material.youngModulus = 3.0e3;
jelly.material.poissonRatio = 0.35;
jelly.material.elasticDampingRatio = 0.5;
// Plasticity: the rest shape flows past 5% strain, at 20 per second.
jelly.material.plasticYield = 0.05;
jelly.material.plasticCreep = 20.0;
// Tearing: an element past 40% strain tears.
jelly.material.tearStrain = (R2OptionalReal){1, 0.4};
jelly.particleMass = 0.2;
R2SoftBodyHandle jelly_handle = r2InsertSoftBody(world, &jelly);
// A pressurized blob: a ring of particles inflated by area preservation.
R2SoftBodyDesc blob = r2DiskSoftBodyDesc(r2Vector(0.0, 3.0), 0.8, 24);
blob.material = r2UniformSoftBodyMaterial((R2SpringCoefficients){20.0, 1.0});
// Target area multiplier (`> 1` inflates the body), for the area preservation enabled by
// the disk constructor (`volumePreservation`).
blob.volumeFactor = 1.1;
blob.selfContacts = 1;
R2SoftBodyHandle blob_handle = r2InsertSoftBody(world, &blob);
// Elastic cells: a jelly cube with corotational linear elasticity.
R3SoftBodyDesc jelly = r3CuboidSoftBodyDesc(r3Vector(3.0, 1.0, 0.0), r3Vector(0.5, 0.5, 0.5), 4, 4, 4);
// The constitutive model of the cells: R3_SOFT_CELL_VOLUME (per-cell volume constraints,
// the shape is held by the edges), R3_SOFT_CELL_COROTATIONAL or R3_SOFT_CELL_NEO_HOOKEAN.
jelly.cellModel = R3_SOFT_CELL_COROTATIONAL;
// Stiffness of the elastic cells.
jelly.material.youngModulus = 2.0e3;
jelly.material.poissonRatio = 0.35;
jelly.material.elasticDampingRatio = 0.5;
// Plasticity: the rest shape flows past 5% strain, at 20 per second.
jelly.material.plasticYield = 0.05;
jelly.material.plasticCreep = 20.0;
// Tearing: an element past 40% strain tears.
jelly.material.tearStrain = (R3OptionalReal){1, 0.4};
jelly.particleMass = 0.2;
R3SoftBodyHandle jelly_handle = r3InsertSoftBody(world, &jelly);
// A material shared by the edges, bending constraints and volume constraints.
R3SoftBodyMaterial material = r3UniformSoftBodyMaterial((R3SpringCoefficients){30.0, 1.0});
// Softness of the bending constraints, on top of a uniform 30 Hz softness.
material.bendSoftness = (R3SpringCoefficients){3.0, 1.0};
r3SoftBody_SetMaterial(cloth_handle, &material);
# Elastic cells: a jelly cube with corotational linear elasticity.
jelly = (
rp.SoftBody.cuboid((3.0, 1.0, 0.0), (0.5, 0.5, 0.5), 4, 4, 4)
# The constitutive model of the cells: `VOLUME` (per-cell volume constraints,
# the shape is held by the edges), `COROTATIONAL` or `NEO_HOOKEAN`.
.cell_model(rp.SoftBodyCellModel.COROTATIONAL)
.material(
rp.SoftBodyMaterial(
# Stiffness of the elastic cells.
young_modulus=2.0e3,
poisson_ratio=0.35,
elastic_damping_ratio=0.5,
# Plasticity: the rest shape flows past 5% strain, at 20 per second.
plastic_yield=0.05,
plastic_creep=20.0,
# Tearing: an element past 40% strain tears.
tear_strain=0.4,
)
)
.particle_mass(0.2)
)
jelly_handle = world.add_soft_body(jelly)
# A material shared by the edges, bending constraints and volume constraints.
material = rp.SoftBodyMaterial.uniform(rp.SpringCoefficients(30.0, 1.0))
# Softness of the bending constraints, on top of a uniform 30 Hz softness.
material.bend_softness = rp.SpringCoefficients(3.0, 1.0)
world.soft_bodies[cloth_handle].material = material
# The `material` property is also a live view: its fields can be modified in place.
world.soft_bodies[cloth_handle].material.deformation_damping = 0.1
The stiffness effectively simulated by the constraints solver depends on its convergence: with too few iterations, a stiff
body looks softer than its material says. This is why soft-bodies are configured with 3 additional internal PGS solver iterations by default, which can
be modified with
additional_pgs_iterationssetAdditionalPgsIterationsadditionalPgsIterationsadditional_pgs_iterationsadditional_solver_iterationssetAdditionalSolverIterationsadditionalSolverIterationsadditional_solver_iterations
The following table gathers the settings to look at for the most common problems:
| Problem | What to change |
|---|---|
| The body is too soft, or stretches too much. | Raise the natural frequency of the material's edge_softnessedgeSoftnessedgeSoftnessedge_softnessyoung_modulusyoungModulusyoungModulusyoung_modulusadditional_pgs_iterationssetAdditionalPgsIterationsadditionalPgsIterationsadditional_pgs_iterationssolversetSolversolversolver |
| A cloth stretches, but should still fold easily. | Keep a stiff edge_softnessedgeSoftnessedgeSoftnessedge_softnessbend_softnessbendSoftnessbendSoftnessbend_softness |
| A rope compresses like a spring. | Make its edges resist stretching only with tension_onlysetTensionOnlytensionOnlyEdgestension_only |
| The body keeps wobbling after an impact. | Raise the damping_ratiodampingRatiodamping_ratiodamping_ratioelastic_damping_ratioelasticDampingRatioelasticDampingRatioelastic_damping_ratiodeformation_dampingdeformationDampingdeformationDampingdeformation_damping |
| A closed body collapses, or must be inflated. | Enable volume_preservationsetVolumePreservationvolumePreservationvolume_preservationvolume_factorsetVolumeFactorvolumeFactorvolume_factor |
| The deformations are too local. | Combine shape_matchingsetShapeMatchingshapeMatchingshape_matching |
FEM solver
The FEM solver (SoftBodySolver::Fem, behind the fem cargo featureSoftBodySolver::Fem,
behind the fem feature of bevy_rapier, given to the builder or with the SoftBodyElasticitySolver
componentSoftBodySolver.FemR3_SOFT_SOLVER_FEM, given to the solver field of the description or to r3SoftBody_SetSolver, and requiring the fem feature of the librarySoftBodySolver.FEM, given to the builder with solver, or to the solver property of SoftBody after the insertion
This comes at a price: the system is factorized at each timestep, and every constraint touching the body (contacts,
joints) needs a solve against it. Note that the FEM solver requires cells, so it only applies to the bodies built with
cells, e.g., with the grid, cuboid, or volumetricgrid, cuboid, or volumetricr2GridSoftBodyDesc, r3CuboidSoftBodyDesc, or r3VolumetricSoftBodyDesccuboid, volumetric, or volumetric_with
- Example 2D
- Example 3D
// A stiff beam simulated by the FEM solver (requires the `fem` cargo feature): its stiffness
// doesn't depend on the number of solver iterations.
let beam = SoftBodyBuilder::grid(Vector::new(0.0, 2.0), Vector::new(1.0, 0.1), 21, 3)
.solver(SoftBodySolver::Fem)
.cell_model(SoftBodyCellModel::NeoHookean)
.material(SoftBodyMaterial {
young_modulus: 1.0e5,
poisson_ratio: 0.3,
..Default::default()
})
// The particles of the side at `x = -1` are the first 3 ones.
.pinned_particles(0..3);
let _beam_handle = world.insert_soft_body(beam);
// The tuning of the linear solves of the FEM solver, shared by every body using it.
let fem = &mut world.integration_parameters.soft_bodies.fem;
fem.linear_tolerance = 1.0e-5;
fem.max_linear_iterations = 20;
// A stiff beam simulated by the FEM solver (requires the `fem` cargo feature): its stiffness
// doesn't depend on the number of solver iterations.
let beam = SoftBodyBuilder::cuboid(Vector::new(0.0, 2.0, -3.0), Vector::new(1.0, 0.1, 0.1), 11, 3, 3)
.solver(SoftBodySolver::Fem)
.cell_model(SoftBodyCellModel::NeoHookean)
.material(SoftBodyMaterial {
young_modulus: 1.0e5,
poisson_ratio: 0.3,
..Default::default()
})
// The particles of the face at `x = -1` are the first 3 × 3 ones.
.pinned_particles(0..9);
let _beam_handle = world.insert_soft_body(beam);
// The tuning of the linear solves of the FEM solver, shared by every body using it.
let fem = &mut world.integration_parameters.soft_bodies.fem;
fem.linear_tolerance = 1.0e-5;
fem.max_linear_iterations = 20;
- Example 2D
- Example 3D
// A stiff beam simulated by the FEM solver: its stiffness doesn't depend on the number of
// solver iterations.
let beamMaterial = new RAPIER.SoftBodyMaterial();
beamMaterial.youngModulus = 1.0e5;
beamMaterial.poissonRatio = 0.3;
let beamDesc = RAPIER.SoftBodyDesc.grid({ x: 0.0, y: 2.0 }, { x: 1.0, y: 0.1 }, 21, 3)
.setSolver(RAPIER.SoftBodySolver.Fem)
.setCellModel(RAPIER.SoftBodyCellModel.NeoHookean)
.setMaterial(beamMaterial)
// The particles of the side at `x = -1` are the first 3 ones.
.setPinnedParticles(new Uint32Array([0, 1, 2]));
let beam = world.createSoftBody(beamDesc);
// The tuning of the linear solves of the FEM solver, shared by every body using it.
world.integrationParameters.softBodiesFemLinearTolerance = 1.0e-5;
world.integrationParameters.softBodiesFemMaxLinearIterations = 20;
// A stiff beam simulated by the FEM solver: its stiffness doesn't depend on the number of
// solver iterations.
let beamMaterial = new RAPIER.SoftBodyMaterial();
beamMaterial.youngModulus = 1.0e5;
beamMaterial.poissonRatio = 0.3;
let beamDesc = RAPIER.SoftBodyDesc.cuboid(
{ x: 0.0, y: 2.0, z: -3.0 }, { x: 1.0, y: 0.1, z: 0.1 }, 11, 3, 3,
)
.setSolver(RAPIER.SoftBodySolver.Fem)
.setCellModel(RAPIER.SoftBodyCellModel.NeoHookean)
.setMaterial(beamMaterial)
// The particles of the face at `x = -1` are the first 3 × 3 ones.
.setPinnedParticles(new Uint32Array([0, 1, 2, 3, 4, 5, 6, 7, 8]));
let beam = world.createSoftBody(beamDesc);
// The tuning of the linear solves of the FEM solver, shared by every body using it.
world.integrationParameters.softBodiesFemLinearTolerance = 1.0e-5;
world.integrationParameters.softBodiesFemMaxLinearIterations = 20;
- Example 2D
- Example 3D
fn configure_fem(
mut commands: Commands,
mut simulation: Single<&mut RapierContextSimulation, With<DefaultRapierContext>>,
) {
// A stiff beam simulated by the FEM solver (requires the `fem` feature): its stiffness
// doesn't depend on the number of solver iterations.
commands.spawn((
Transform::from_xyz(0.0, 2.0, 0.0),
SoftBody::grid(Vec2::new(1.0, 0.1), 21, 3).map(|builder| {
builder
.cell_model(SoftBodyCellModel::NeoHookean)
// The particles of the side at `x = -1` are the first 3 ones.
.pinned_particles(0..3)
}),
SoftBodyElasticitySolver(SoftBodySolver::Fem),
SoftBodyMaterial(RapierSoftBodyMaterial {
young_modulus: 1.0e5,
poisson_ratio: 0.3,
..default()
}),
));
// The tuning of the linear solves of the FEM solver, shared by every body using it.
let fem = &mut simulation.integration_parameters.soft_bodies.fem;
fem.linear_tolerance = 1.0e-5;
fem.max_linear_iterations = 20;
}
fn configure_fem(
mut commands: Commands,
mut simulation: Single<&mut RapierContextSimulation, With<DefaultRapierContext>>,
) {
// A stiff beam simulated by the FEM solver (requires the `fem` feature): its stiffness
// doesn't depend on the number of solver iterations.
commands.spawn((
Transform::from_xyz(0.0, 2.0, -3.0),
SoftBody::cuboid(Vec3::new(1.0, 0.1, 0.1), 11, 3, 3).map(|builder| {
builder
.cell_model(SoftBodyCellModel::NeoHookean)
// The particles of the face at `x = -1` are the first 3 × 3 ones.
.pinned_particles(0..9)
}),
SoftBodyElasticitySolver(SoftBodySolver::Fem),
SoftBodyMaterial(RapierSoftBodyMaterial {
young_modulus: 1.0e5,
poisson_ratio: 0.3,
..default()
}),
));
// The tuning of the linear solves of the FEM solver, shared by every body using it.
let fem = &mut simulation.integration_parameters.soft_bodies.fem;
fem.linear_tolerance = 1.0e-5;
fem.max_linear_iterations = 20;
}
- Example 2D
- Example 3D
// A stiff beam simulated by the FEM solver (requires the `fem` feature): its stiffness
// doesn't depend on the number of solver iterations.
R2SoftBodyDesc beam = r2GridSoftBodyDesc(r2Vector(0.0, 2.0), r2Vector(1.0, 0.1), 21, 3);
beam.solver = R2_SOFT_SOLVER_FEM;
beam.cellModel = R2_SOFT_CELL_NEO_HOOKEAN;
beam.material.youngModulus = 1.0e5;
beam.material.poissonRatio = 0.3;
// The particles of the side at `x = -1` are the first 3 ones.
const uint32_t beam_pinned[] = {0, 1, 2};
beam.pinned = (R2IndexView){beam_pinned, 3};
R2SoftBodyHandle beam_handle = r2InsertSoftBody(world, &beam);
// The tuning of the linear solves of the FEM solver, shared by every body using it.
r2FemSetLinearTolerance(world, 1.0e-5);
r2FemSetMaxLinearIterations(world, 20);
// A stiff beam simulated by the FEM solver (requires the `fem` feature): its stiffness
// doesn't depend on the number of solver iterations.
R3SoftBodyDesc beam = r3CuboidSoftBodyDesc(r3Vector(0.0, 2.0, -3.0), r3Vector(1.0, 0.1, 0.1), 11, 3, 3);
beam.solver = R3_SOFT_SOLVER_FEM;
beam.cellModel = R3_SOFT_CELL_NEO_HOOKEAN;
beam.material.youngModulus = 1.0e5;
beam.material.poissonRatio = 0.3;
// The particles of the face at `x = -1` are the first 3 × 3 ones.
const uint32_t beam_pinned[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
beam.pinned = (R3IndexView){beam_pinned, 9};
R3SoftBodyHandle beam_handle = r3InsertSoftBody(world, &beam);
// The tuning of the linear solves of the FEM solver, shared by every body using it.
r3FemSetLinearTolerance(world, 1.0e-5);
r3FemSetMaxLinearIterations(world, 20);
# A stiff beam simulated by the FEM solver: its stiffness doesn't depend on the number of
# solver iterations.
beam = (
rp.SoftBody.cuboid((0.0, 2.0, -3.0), (1.0, 0.1, 0.1), 11, 3, 3)
.solver(rp.SoftBodySolver.FEM)
.cell_model(rp.SoftBodyCellModel.NEO_HOOKEAN)
.material(rp.SoftBodyMaterial(young_modulus=1.0e5, poisson_ratio=0.3))
# The particles of the face at `x = -1` are the first 3 × 3 ones.
.pinned_particles(range(9))
)
beam_handle = world.add_soft_body(beam)
# The tuning of the linear solves of the FEM solver, shared by every body using it.
fem = world.integration_parameters.soft_bodies.fem
fem.linear_tolerance = 1.0e-5
fem.max_linear_iterations = 20
The linear solves stop at the relative residual
linear_tolerancesoftBodiesFemLinearTolerancelinearTolerance (set by r3FemSetLinearTolerance)linear_tolerancemax_linear_iterationssoftBodiesFemMaxLinearIterationsmaxLinearIterations (set by r3FemSetMaxLinearIterations)max_linear_iterationsmax_dense_dofssoftBodiesFemMaxDenseDofsmaxDenseDofs (set by r3FemSetMaxDenseDofs)max_dense_dofs600 by default) are
factorized directly, whereas the larger ones rely on the iterative conjugate gradient.
Use the FEM solver for stiff materials which simulated stiffness must not depend on the iteration count, e.g., the chassis of a car or a metal beam. This also results in more realistic plasticity and tearing.