Non-determinism example
Note, not present with ["enhanced-determinism"] so more potentially an interesting counter example to docs that say is locally deterministic. Enhanced-determinism fixed this for my use case.
I noticed that my animations of a simple 2x2x2 cube of balls were not stable, the balls were finishing the animation in differing places.
I read the page here about this and believe it should be deterministic
By default, Rapier is locally deterministic, meaning that running the exact same simulation (with the same initial conditions) twice with the same machine, using the same version of Rapier, and the same version of the Rust compiler, will result in the exact same simulation results.
So I went back to your Basic Simulation Example and was able to reproduce the same scenario:
use rapier3d::prelude::*;
fn run() {
let mut indexes = vec![];
let mut rigid_body_set = RigidBodySet::new();
let mut collider_set = ColliderSet::new();
/* Create the ground. */
let collider = ColliderBuilder::cuboid(100.0, 0.1, 100.0).build();
collider_set.insert(collider);
/* Create the bounding balls. */
for x in 0..2 {
for y in 0..2 {
for z in 0..2 {
let rigid_body = RigidBodyBuilder::dynamic()
// Note the slight variance in x, this is so the objects don't just land in a stack
.translation(vector![x as f32 * 0.51 + 3.0, y as f32 * 0.5 + 3.5, z as f32 * 0.5 + 3.0])
.build();
let collider = ColliderBuilder::ball(0.5).restitution(0.7).build();
let ball_body_handle = rigid_body_set.insert(rigid_body);
collider_set.insert_with_parent(collider, ball_body_handle, &mut rigid_body_set);
indexes.push(ball_body_handle);
}
}
}
let gravity = vector![0.0, -9.81, 0.0];
let integration_parameters = IntegrationParameters::default();
let mut physics_pipeline = PhysicsPipeline::new();
let mut island_manager = IslandManager::new();
let mut broad_phase = DefaultBroadPhase::new();
let mut narrow_phase = NarrowPhase::new();
let mut impulse_joint_set = ImpulseJointSet::new();
let mut multibody_joint_set = MultibodyJointSet::new();
let mut ccd_solver = CCDSolver::new();
let physics_hooks = ();
let event_handler = ();
// Somewhere between 100 and 200 the simulations diverge
for _ in 0..200 {
physics_pipeline.step(
&gravity,
&integration_parameters,
&mut island_manager,
&mut broad_phase,
&mut narrow_phase,
&mut rigid_body_set,
&mut collider_set,
&mut impulse_joint_set,
&mut multibody_joint_set,
&mut ccd_solver,
&physics_hooks,
&event_handler,
);
}
let heights:Vec<_> = indexes.iter().map(|i|rigid_body_set[i.clone()].translation().y).collect();
println!("Final Ball altitudes: {:?}", heights);
}
fn main() {
for i in 0..10 {
run()
}
}
For the 10 loops, it produces different results:
Run 0: Final Ball altitudes: [0.5987226, 0.59869987, 0.6007092, 0.5985746, 0.598717, 0.5987229, 0.5991764, 0.59840524]
Run 1: Final Ball altitudes: [0.5987226, 0.5986998, 0.6006796, 0.59857607, 0.59871674, 0.59872305, 0.5991825, 0.5984055]
Run 2: Final Ball altitudes: [0.5987226, 0.59869987, 0.6007092, 0.5985746, 0.598717, 0.5987229, 0.5991764, 0.59840524]
Run 3: Final Ball altitudes: [0.5987226, 0.5986998, 0.6006796, 0.59857607, 0.59871674, 0.59872305, 0.5991825, 0.5984055]
Run 4: Final Ball altitudes: [0.5987226, 0.5986998, 0.6006796, 0.59857607, 0.59871674, 0.59872305, 0.5991825, 0.5984055]
Run 5: Final Ball altitudes: [0.5987226, 0.5986998, 0.6006796, 0.59857607, 0.59871674, 0.59872305, 0.5991825, 0.5984055]
Run 6: Final Ball altitudes: [0.5987226, 0.5986998, 0.6006796, 0.59857607, 0.59871674, 0.59872305, 0.5991825, 0.5984055]
Run 7: Final Ball altitudes: [0.5987226, 0.59869987, 0.6007092, 0.5985746, 0.598717, 0.5987229, 0.5991764, 0.59840524]
Run 8: Final Ball altitudes: [0.5987226, 0.59869987, 0.6007092, 0.5985746, 0.598717, 0.5987229, 0.5991764, 0.59840524]
Run 9: Final Ball altitudes: [0.5987226, 0.5986998, 0.6006796, 0.59857607, 0.59871674, 0.59872305, 0.5991825, 0.5984055]
A second run will produce different again.