Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix occasional crashes when inserting or removing multibody links #672

Merged
merged 4 commits into from
Jul 7, 2024
Merged
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
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
## Unreleased

### Fix

- Fix crash when removing a multibody joint, or a rigid-body with a multipody-joint attached to it.
- Fix crash when inserting multibody joints in an arbitrary order (instead of incrementally from root to leaf).

### Added
- Implement rotation gizmo for Ball 2D shape (as radius line) in Debug renderer if `DebugRenderMode::COLLIDER_SHAPES` enabled

- Implement rotation gizmo for Ball 2D shape (as radius line) in Debug renderer if `DebugRenderMode::COLLIDER_SHAPES`
enabled

## v0.21.0 (23 June 2024)

Expand Down
1 change: 1 addition & 0 deletions crates/rapier2d-f64/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,4 @@ thiserror = "1"
[dev-dependencies]
bincode = "1"
serde = { version = "1", features = ["derive"] }
oorandom = { version = "11", default-features = false }
1 change: 1 addition & 0 deletions crates/rapier2d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,4 @@ thiserror = "1"
[dev-dependencies]
bincode = "1"
serde = { version = "1", features = ["derive"] }
oorandom = { version = "11", default-features = false }
1 change: 1 addition & 0 deletions crates/rapier3d-f64/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,4 @@ thiserror = "1"
[dev-dependencies]
bincode = "1"
serde = { version = "1", features = ["derive"] }
oorandom = { version = "11", default-features = false }
1 change: 1 addition & 0 deletions crates/rapier3d/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,4 @@ thiserror = "1"
[dev-dependencies]
bincode = "1"
serde = { version = "1", features = ["derive"] }
oorandom = { version = "11", default-features = false }
154 changes: 150 additions & 4 deletions src/dynamics/joint/multibody_joint/multibody.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ impl Multibody {
let mut link2mb = vec![usize::MAX; self.links.len()];
let mut link_id2new_id = vec![usize::MAX; self.links.len()];

// Split multibody and update the set of links and ndofs.
for (i, mut link) in self.links.0.into_iter().enumerate() {
let is_new_root = i == 0
|| !joint_only && link.parent_internal_id == to_remove
Expand Down Expand Up @@ -192,7 +193,14 @@ impl Multibody {

link.internal_id = i;
link.assembly_id = assembly_id;
link.parent_internal_id = link_id2new_id[link.parent_internal_id];

// NOTE: for the root, the current`link.parent_internal_id` is invalid since that
// parent lies in a different multibody now.
link.parent_internal_id = if i != 0 {
link_id2new_id[link.parent_internal_id]
} else {
0
};
assembly_id += link_ndofs;
}
}
Expand All @@ -202,7 +210,10 @@ impl Multibody {

pub(crate) fn append(&mut self, mut rhs: Multibody, parent: usize, joint: MultibodyJoint) {
let rhs_root_ndofs = rhs.links[0].joint.ndofs();
let rhs_copy_shift = self.ndofs + rhs_root_ndofs;
// Values for rhs will be copied into the buffers of `self` starting at this index.
let rhs_copy_shift = self.ndofs + joint.ndofs();
// Number of dofs to copy from rhs. The root’s dofs isn’t included because it will be
// replaced by `joint.
let rhs_copy_ndofs = rhs.ndofs - rhs_root_ndofs;

// Adjust the ids of all the rhs links except the first one.
Expand All @@ -224,7 +235,7 @@ impl Multibody {
rhs.links[0].parent_internal_id = parent;
}

// Grow buffers and append data from rhs.
// Grow buffers then append data from rhs.
self.grow_buffers(rhs_copy_ndofs + rhs.links[0].joint.ndofs(), rhs.links.len());

if rhs_copy_ndofs > 0 {
Expand Down Expand Up @@ -1360,9 +1371,144 @@ impl IndexSequence {
#[cfg(test)]
mod test {
use super::IndexSequence;
use crate::math::Real;
use crate::dynamics::{ImpulseJointSet, IslandManager};
use crate::math::{Real, SPATIAL_DIM};
use crate::prelude::{
ColliderSet, MultibodyJointHandle, MultibodyJointSet, RevoluteJoint, RigidBodyBuilder,
RigidBodySet,
};
use na::{DVector, RowDVector};

#[test]
fn test_multibody_append() {
let mut bodies = RigidBodySet::new();
let mut joints = MultibodyJointSet::new();

let a = bodies.insert(RigidBodyBuilder::dynamic());
let b = bodies.insert(RigidBodyBuilder::dynamic());
let c = bodies.insert(RigidBodyBuilder::dynamic());
let d = bodies.insert(RigidBodyBuilder::dynamic());

#[cfg(feature = "dim2")]
let joint = RevoluteJoint::new();
#[cfg(feature = "dim3")]
let joint = RevoluteJoint::new(na::Vector::x_axis());

let mb_handle = joints.insert(a, b, joint, true).unwrap();
joints.insert(c, d, joint, true).unwrap();
joints.insert(b, c, joint, true).unwrap();

assert_eq!(joints.get(mb_handle).unwrap().0.ndofs, SPATIAL_DIM + 3);
}

#[test]
fn test_multibody_insert() {
let mut rnd = oorandom::Rand32::new(1234);

for k in 0..10 {
let mut bodies = RigidBodySet::new();
let mut multibody_joints = MultibodyJointSet::new();

let num_links = 100;
let mut handles = vec![];

for _ in 0..num_links {
handles.push(bodies.insert(RigidBodyBuilder::dynamic()));
}

let mut insertion_id: Vec<_> = (0..num_links - 1).collect();

#[cfg(feature = "dim2")]
let joint = RevoluteJoint::new();
#[cfg(feature = "dim3")]
let joint = RevoluteJoint::new(na::Vector::x_axis());

match k {
0 => {} // Remove in insertion order.
1 => {
// Remove from leaf to root.
insertion_id.reverse();
}
_ => {
// Shuffle the vector a bit.
// (This test checks multiple shuffle arrangements due to k > 2).
for l in 0..num_links - 1 {
insertion_id.swap(l, rnd.rand_range(0..num_links as u32 - 1) as usize);
}
}
}

let mut mb_handle = MultibodyJointHandle::invalid();
for i in insertion_id {
mb_handle = multibody_joints
.insert(handles[i], handles[i + 1], joint, true)
.unwrap();
}

assert_eq!(
multibody_joints.get(mb_handle).unwrap().0.ndofs,
SPATIAL_DIM + num_links - 1
);
}
}

#[test]
fn test_multibody_remove() {
let mut rnd = oorandom::Rand32::new(1234);

for k in 0..10 {
let mut bodies = RigidBodySet::new();
let mut multibody_joints = MultibodyJointSet::new();
let mut colliders = ColliderSet::new();
let mut impulse_joints = ImpulseJointSet::new();
let mut islands = IslandManager::new();

let num_links = 100;
let mut handles = vec![];

for _ in 0..num_links {
handles.push(bodies.insert(RigidBodyBuilder::dynamic()));
}

#[cfg(feature = "dim2")]
let joint = RevoluteJoint::new();
#[cfg(feature = "dim3")]
let joint = RevoluteJoint::new(na::Vector::x_axis());

for i in 0..num_links - 1 {
multibody_joints
.insert(handles[i], handles[i + 1], joint, true)
.unwrap();
}

match k {
0 => {} // Remove in insertion order.
1 => {
// Remove from leaf to root.
handles.reverse();
}
_ => {
// Shuffle the vector a bit.
// (This test checks multiple shuffle arrangements due to k > 2).
for l in 0..num_links {
handles.swap(l, rnd.rand_range(0..num_links as u32) as usize);
}
}
}

for handle in handles {
bodies.remove(
handle,
&mut islands,
&mut colliders,
&mut impulse_joints,
&mut multibody_joints,
true,
);
}
}
}

fn test_sequence() -> IndexSequence {
let mut seq = IndexSequence::new();
seq.remove(2);
Expand Down
17 changes: 10 additions & 7 deletions src/dynamics/joint/multibody_joint/multibody_joint_set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,18 +209,17 @@ impl MultibodyJointSet {
Some(MultibodyJointHandle(body2.0))
}

/// Removes an multibody_joint from this set.
/// Removes a multibody_joint from this set.
pub fn remove(&mut self, handle: MultibodyJointHandle, wake_up: bool) {
if let Some(removed) = self.rb2mb.get(handle.0).copied() {
let multibody = self.multibodies.remove(removed.multibody.0).unwrap();

// Remove the edge from the connectivity graph.
if let Some(parent_link) = multibody.link(removed.id).unwrap().parent_id() {
let parent_rb = multibody.link(parent_link).unwrap().rigid_body;
self.connectivity_graph.remove_edge(
self.rb2mb.get(parent_rb.0).unwrap().graph_id,
removed.graph_id,
);
let parent_graph_id = self.rb2mb.get(parent_rb.0).unwrap().graph_id;
self.connectivity_graph
.remove_edge(parent_graph_id, removed.graph_id);

if wake_up {
self.to_wake_up.push(RigidBodyHandle(handle.0));
Expand All @@ -236,8 +235,12 @@ impl MultibodyJointSet {
for multibody in multibodies {
if multibody.num_links() == 1 {
// We don’t have any multibody_joint attached to this body, remove it.
if let Some(other) = self.connectivity_graph.remove_node(removed.graph_id) {
self.rb2mb.get_mut(other.0).unwrap().graph_id = removed.graph_id;
let isolated_link = multibody.link(0).unwrap();
let isolated_graph_id =
self.rb2mb.get(isolated_link.rigid_body.0).unwrap().graph_id;
if let Some(other) = self.connectivity_graph.remove_node(isolated_graph_id)
{
self.rb2mb.get_mut(other.0).unwrap().graph_id = isolated_graph_id;
}
} else {
let mb_id = self.multibodies.insert(multibody);
Expand Down