Skip to main content

soft_body_events

The tears applied during a step, whether they were generated by the simulation itself or requested with SoftBody::tear_edgeRapierSoftBody::tear_edge (as well as the ones applied by RapierContextMut::tear_soft_body and cut_soft_body)SoftBody.tearEdger3SoftBody_TearEdgeSoftBody.tear_edge, are reported the same way as the collision events: by giving an event handler (EventHandler::handle_soft_body_tear_event) to the step: by giving an event queue (EventQueue.drainSoftBodyTearEvents) to the step, i.e., as messages: by giving an event collector (R3EventCollector) to r3Step: by the event handler of the world (PhysicsWorld.event_handler). Each event (a SoftBodyTearEventa SoftBodyTearEvent, read with a MessageReadera SoftBodyTearEventan R3SoftBodyTearEventa SoftBodyTearEvent) identifies the soft-body that tore and gives the pieces it was split into, so the rendering of the scene can be updated accordingly:

// Tears applied during a step are reported through the event handler.
let (collision_send, _collision_recv) = std::sync::mpsc::channel();
let (contact_force_send, _contact_force_recv) = std::sync::mpsc::channel();
let (soft_body_tear_send, soft_body_tear_recv) = std::sync::mpsc::channel();
let event_handler =
ChannelEventCollector::new(collision_send, contact_force_send, soft_body_tear_send);
world.step_with_events(&(), &event_handler);
while let Ok(tear_event) = soft_body_tear_recv.try_recv() {
println!("Soft body {:?} tore", tear_event.soft_body);
}
// Tears applied during a step are reported through the event queue.
let eventQueue = new RAPIER.EventQueue(true);
world.step(eventQueue);
eventQueue.drainSoftBodyTearEvents((event) => {
console.log("Soft body", event.softBody(), "tore:", event.numPieces(), "pieces split off");
});

The SoftBodyTearEvent message gives the entities of the torn soft-body and of its pieces (the first piece being the torn entity itself, the other ones being the entities already returned by tear_soft_body and cut_soft_body for the tears they applied), the pieces of the clusters the tear split (cluster_splits), and the joints it moved from a cluster proxy to another (moved_joints). Its raw field is the event of Rapier, with every detail of the topology change in terms of handles and particle indices:

fn read_tear_events(context: ReadRapierContext, mut tears: MessageReader<SoftBodyTearEvent>) {
let Ok(context) = context.single() else {
return;
};
for tear in tears.read() {
// The first piece is the torn entity itself, the others are the entities spawned for
// the soft-bodies split off it.
for piece in &tear.pieces {
println!(
"Soft body {} tore: piece {} has {} particles",
tear.soft_body,
piece.soft_body,
piece.particles.len()
);
}
// Where a particle of the torn body went.
if let Some((handle, index)) = tear.raw.particle_destination(399) {
let entity = context.soft_body_entity(handle);
println!("particle 399 is now particle {index} of {entity:?}");
}
}
}

The collector keeps the events of every step it is given to, until it is emptied with r3EventCollector_Clear. The tear events are counted by r3EventCollector_TearEventCount, and r3EventCollector_TearEvent returns an owned copy of one of them, to be freed with r3FreeSoftBodyTearEvent. This is the same R3SoftBodyTearEvent as the one returned by r3SoftBody_Tear and r3CutSoftBody, so all the accessors described in the previous section apply to it as well:

// Tears applied during a step are reported through the event collector.
R2EventCollector *events = r2NewEventCollector();
r2Step(world, NULL, events);
size_t num_tear_events = r2EventCollector_TearEventCount(events);
for (size_t i = 0; i < num_tear_events; i++) {
// An owned copy of the event.
R2SoftBodyTearEvent *tear_event = r2EventCollector_TearEvent(events, i);
R2SoftBodyHandle torn = r2SoftBodyTearEvent_SoftBody(tear_event);
printf("Soft body %u tore\n", torn.index);
r2FreeSoftBodyTearEvent(tear_event);
}
// The collector keeps its events until it is cleared.
r2EventCollector_Clear(events);

A ChannelEventCollector keeps the tear events of every step until they are drained with its drain_soft_body_tear_events method. Any other object following the EventHandler protocol can be used as the event handler as well (the methods it doesn't need can be omitted): its handle_soft_body_tear_event(soft_bodies, event) method is called at the end of the step for every soft-body that tore, soft_bodies being the SoftBodySet of the world. The soft-bodies can be read during that call, but not modified. This is the same SoftBodyTearEvent as the one returned by PhysicsWorld.tear_soft_body and PhysicsWorld.cut_soft_body, so everything described in the previous section applies to it as well:

# Tears applied during a step are reported to the event handler of the world.
collector = rp.ChannelEventCollector()
world.event_handler = collector
world.step()
for tear_event in collector.drain_soft_body_tear_events():
print(f"Soft body {tear_event.soft_body} tore")