Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
## Unreleased

- `InteractionGroups` struct now contains `InteractionTestMode`. Continues [rapier/pull/170](https://github.com/dimforge/rapier/pull/170) for [rapier/issues/622](https://github.com/dimforge/rapier/issues/622)
- `InteractionGroups` constructor now requires an `InteractionTestMode` parameter. If you want same behaviour as before, use `InteractionTestMode::And` (eg. `InteractionGroups::new(Group::GROUP_1, Group::GROUP_1, InteractionTestMode::And)`)
- `InteractionGroups` struct now contains `InteractionTestMode`.
Continues [rapier/pull/170](https://github.com/dimforge/rapier/pull/170)
for [rapier/issues/622](https://github.com/dimforge/rapier/issues/622)
- `InteractionGroups` constructor now requires an `InteractionTestMode` parameter. If you want same behaviour as before,
use `InteractionTestMode::And` (eg.
`InteractionGroups::new(Group::GROUP_1, Group::GROUP_1, InteractionTestMode::And)`)
- `CoefficientCombineRule::Min` - now makes sure it uses a non zero value as result by using `coeff1.min(coeff2).abs()`
- `InteractionTestMode`: Specifies which method should be used to test interactions. Supports `AND` and `OR`.
- `CoefficientCombineRule::ClampedSum` - Adds the two coefficients and does a clamp to have at most 1.
- Rename `ColliderChanges::CHANGED` to `::IN_CHANGED_SET` to make its meaning more precise.
- Rename `RigidBodyChanges::CHANGED` to `::IN_CHANGED_SET` to make its meaning more precise.
- Fix colliders ignoring user-changes after the first simulation step.
- Fix broad-phase incorrectly taking into account disabled colliders attached to an enabled dynamic rigid-body.

## v0.30.1 (17 Oct. 2025)

Expand Down
4 changes: 4 additions & 0 deletions examples3d/all_examples3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ mod joints3;
mod character_controller3;
mod debug_chain_high_mass_ratio3;
mod debug_cube_high_mass_ratio3;
mod debug_disabled3;
mod debug_internal_edges3;
mod debug_long_chain3;
mod debug_multibody_ang_motor_pos3;
mod debug_sleeping_kinematic3;
mod debug_two_cubes3;
mod gyroscopic3;
mod inverse_kinematics3;
mod joint_motor_position3;
Expand Down Expand Up @@ -106,6 +108,8 @@ pub fn main() {
("(Debug) big colliders", debug_big_colliders3::init_world),
("(Debug) boxes", debug_boxes3::init_world),
("(Debug) balls", debug_balls3::init_world),
("(Debug) disabled", debug_disabled3::init_world),
("(Debug) two cubes", debug_two_cubes3::init_world),
("(Debug) pop", debug_pop3::init_world),
(
"(Debug) dyn. coll. add",
Expand Down
59 changes: 59 additions & 0 deletions examples3d/debug_disabled3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;

pub fn init_world(testbed: &mut Testbed) {
let mut bodies = RigidBodySet::new();
let mut colliders = ColliderSet::new();
let impulse_joints = ImpulseJointSet::new();
let multibody_joints = MultibodyJointSet::new();

let rad = 0.5;

/*
* Ground
*/
let ground_size = 10.1;
let ground_height = 2.1;

let rigid_body = RigidBodyBuilder::fixed().translation(vector![0.0, -ground_height, 0.0]);
let handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::cuboid(ground_size, ground_height, ground_size);
colliders.insert_with_parent(collider, handle, &mut bodies);

/*
* Platform that will be enabled/disabled.
*/
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![0.0, 5.0, 0.0]);
let handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::cuboid(5.0, 1.0, 5.0);
let handle_to_disable = colliders.insert_with_parent(collider, handle, &mut bodies);

// Callback that will be executed on the main loop to handle proximities.
testbed.add_callback(move |mut graphics, physics, _, run_state| {
if run_state.timestep_id % 250 == 0 {
let co = &mut physics.colliders[handle_to_disable];
let enabled = co.is_enabled();
co.set_enabled(!enabled);
println!("Platform is now enabled: {}", co.is_enabled());
}

if run_state.timestep_id % 25 == 0 {
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![0.0, 20.0, 0.0]);
let handle = physics.bodies.insert(rigid_body);
let collider = ColliderBuilder::cuboid(rad, rad, rad);
physics
.colliders
.insert_with_parent(collider, handle, &mut physics.bodies);

if let Some(graphics) = &mut graphics {
graphics.add_body(handle, &physics.bodies, &physics.colliders);
}
}
});

/*
* Set up the testbed.
*/
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
testbed.look_at(point![-30.0, 4.0, -30.0], point![0.0, 1.0, 0.0]);
}
29 changes: 29 additions & 0 deletions examples3d/debug_two_cubes3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use rapier_testbed3d::Testbed;
use rapier3d::prelude::*;

pub fn init_world(testbed: &mut Testbed) {
/*
* World
*/
let mut bodies = RigidBodySet::new();
let mut colliders = ColliderSet::new();
let impulse_joints = ImpulseJointSet::new();
let multibody_joints = MultibodyJointSet::new();

// Dynamic box rigid body.
let rigid_body = RigidBodyBuilder::dynamic().translation(vector![0.0, 2.0, 0.0]);
let handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::cuboid(0.5, 0.5, 0.5);
colliders.insert_with_parent(collider, handle, &mut bodies);

let rigid_body = RigidBodyBuilder::fixed();
let handle = bodies.insert(rigid_body);
let collider = ColliderBuilder::cuboid(0.5, 0.5, 0.5);
colliders.insert_with_parent(collider, handle, &mut bodies);

/*
* Set up the testbed.
*/
testbed.set_world(bodies, colliders, impulse_joints, multibody_joints);
testbed.look_at(point![10.0, 10.0, 10.0], Point::origin());
}
16 changes: 6 additions & 10 deletions src/dynamics/rigid_body_components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ bitflags::bitflags! {
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
/// Flags describing how the rigid-body has been modified by the user.
pub struct RigidBodyChanges: u32 {
/// Flag indicating that any component of this rigid-body has been modified.
const MODIFIED = 1 << 0;
/// Flag indicating that this rigid-body is in the modified rigid-body set.
const IN_MODIFIED_SET = 1 << 0;
/// Flag indicating that the `RigidBodyPosition` component of this rigid-body has been modified.
const POSITION = 1 << 1;
/// Flag indicating that the `RigidBodyActivation` component of this rigid-body has been modified.
Expand Down Expand Up @@ -1061,10 +1061,7 @@ impl RigidBodyColliders {
co_handle: ColliderHandle,
) {
if let Some(i) = self.0.iter().position(|e| *e == co_handle) {
rb_changes.set(
RigidBodyChanges::MODIFIED | RigidBodyChanges::COLLIDERS,
true,
);
rb_changes.set(RigidBodyChanges::COLLIDERS, true);
self.0.swap_remove(i);
}
}
Expand All @@ -1083,10 +1080,7 @@ impl RigidBodyColliders {
co_shape: &ColliderShape,
co_mprops: &ColliderMassProps,
) {
rb_changes.set(
RigidBodyChanges::MODIFIED | RigidBodyChanges::COLLIDERS,
true,
);
rb_changes.set(RigidBodyChanges::COLLIDERS, true);

co_pos.0 = rb_pos.position * co_parent.pos_wrt_parent;
rb_ccd.ccd_thickness = rb_ccd.ccd_thickness.min(co_shape.ccd_thickness());
Expand All @@ -1113,6 +1107,8 @@ impl RigidBodyColliders {
) {
for handle in &self.0 {
// NOTE: the ColliderParent component must exist if we enter this method.
// NOTE: currently, we are propagating the position even if the collider is disabled.
// Is that the best behavior?
let co = colliders.index_mut_internal(*handle);
let new_pos = parent_pos * co.parent.as_ref().unwrap().pos_wrt_parent;

Expand Down
4 changes: 2 additions & 2 deletions src/dynamics/rigid_body_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ pub(crate) type ModifiedRigidBodies = ModifiedObjects<RigidBodyHandle, RigidBody
impl HasModifiedFlag for RigidBody {
#[inline]
fn has_modified_flag(&self) -> bool {
self.changes.contains(RigidBodyChanges::MODIFIED)
self.changes.contains(RigidBodyChanges::IN_MODIFIED_SET)
}

#[inline]
fn set_modified_flag(&mut self) {
self.changes |= RigidBodyChanges::MODIFIED;
self.changes |= RigidBodyChanges::IN_MODIFIED_SET;
}
}

Expand Down
4 changes: 0 additions & 4 deletions src/geometry/broad_phase_bvh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,6 @@ impl BroadPhaseBvh {
/// sent previously and no `RemovePair` happened since then). Sending redundant events is allowed
/// but can result in a slight computational overhead.
///
/// The `colliders` set is mutable only to provide access to
/// [`collider.set_internal_broad_phase_proxy_index`]. Other properties of the collider should
/// **not** be modified during the broad-phase update.
///
/// # Parameters
/// - `params`: the integration parameters governing the simulation.
/// - `colliders`: the set of colliders. Change detection with `collider.needs_broad_phase_update()`
Expand Down
4 changes: 2 additions & 2 deletions src/geometry/collider_components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ bitflags::bitflags! {
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
/// Flags describing how the collider has been modified by the user.
pub struct ColliderChanges: u32 {
/// Flag indicating that any component of the collider has been modified.
const MODIFIED = 1 << 0;
/// Flag indicating that the collider handle is in the changed collider set.
const IN_MODIFIED_SET = 1 << 0;
/// Flag indicating that the density or mass-properties of this collider was changed.
const LOCAL_MASS_PROPERTIES = 1 << 1; // => RigidBody local mass-properties update.
/// Flag indicating that the `ColliderParent` component of the collider has been modified.
Expand Down
4 changes: 2 additions & 2 deletions src/geometry/collider_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ pub type ModifiedColliders = ModifiedObjects<ColliderHandle, Collider>;
impl HasModifiedFlag for Collider {
#[inline]
fn has_modified_flag(&self) -> bool {
self.changes.contains(ColliderChanges::MODIFIED)
self.changes.contains(ColliderChanges::IN_MODIFIED_SET)
}

#[inline]
fn set_modified_flag(&mut self) {
self.changes |= ColliderChanges::MODIFIED;
self.changes |= ColliderChanges::IN_MODIFIED_SET;
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/pipeline/debug_render_pipeline/debug_render_pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,8 @@ impl DebugRenderPipeline {
c[2] * coeff[2],
c[3] * coeff[3],
]
} else if !co.is_enabled() {
self.style.disabled_color_multiplier
} else {
self.style.collider_parentless_color
};
Expand Down
34 changes: 30 additions & 4 deletions src/pipeline/physics_pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ impl PhysicsPipeline {
colliders: &mut ColliderSet,
modified_colliders: &mut ModifiedColliders,
) {
// TODO: we can’t just iterate on `modified_colliders` here to clear the
// flags because the last substep will leave some colliders with
// changes flags set after solving, but without the collider being
// part of the `ModifiedColliders` set. This is a bit error-prone but
// is necessary for the modified information to carry on to the
// next frame’s narrow-phase for updating.
for co in colliders.colliders.iter_mut() {
co.1.changes = ColliderChanges::empty();
}
Expand Down Expand Up @@ -700,12 +706,32 @@ impl PhysicsPipeline {
// If we ran the last substep, just update the broad-phase bvh instead
// of a full collision-detection step.
for handle in modified_colliders.iter() {
let co = &colliders[*handle];
let aabb = co.compute_broad_phase_aabb(&integration_parameters, bodies);
broad_phase.set_aabb(&integration_parameters, *handle, aabb);
let co = colliders.index_mut_internal(*handle);
// NOTE: `advance_to_final_positions` might have added disabled colliders to
// `modified_colliders`. This raises the question: do we want
// rigid-body transform propagation to happen on disabled colliders if
// their parent rigid-body is enabled? For now, we are propagating as
// it feels less surprising to the user and makes handling collider
// re-enable less awkward.
if co.is_enabled() {
let aabb = co.compute_broad_phase_aabb(&integration_parameters, bodies);
broad_phase.set_aabb(&integration_parameters, *handle, aabb);
}

// Clear the modified collider set, but keep the other collider changes flags.
// This is needed so that the narrow-phase at the next timestep knows it must
// not skip these colliders for its update.
// TODO: this doesn’t feel very clean, but leaving the collider in the modified
// set would be expensive as this will be traversed by all the user-changes
// functions. An alternative would be to maintain a second modified set,
// one for user changes, and one for changes applied by the solver but that
// feels a bit too much. Let’s keep it simple for now and we’ll see how it
// goes after the persistent island rework.
co.changes.remove(ColliderChanges::IN_MODIFIED_SET);
}

// Empty the modified colliders set. See comment for `co.change.remove(..)` above.
modified_colliders.clear();
// self.clear_modified_colliders(colliders, &mut modified_colliders);
}
}

Expand Down