#[cfg(feature = "dim3")]
use crate::alloc_prelude::*;
use crate::prelude::*;
#[test]
#[cfg(feature = "dim3")]
fn issue_927_remove_isolating_single_link_keeps_iter_valid() {
let mut world = PhysicsWorld::new();
let a = world.insert_body(
RigidBodyBuilder::dynamic()
.translation(Vector::new(0.0, 5.0, 0.0))
.additional_mass(1.0),
);
let b = world.insert_body(
RigidBodyBuilder::dynamic()
.translation(Vector::new(1.0, 5.0, 0.0))
.additional_mass(1.0),
);
let c = world.insert_body(
RigidBodyBuilder::dynamic()
.translation(Vector::new(2.0, 5.0, 0.0))
.additional_mass(1.0),
);
let joint = || RevoluteJointBuilder::new(Vector::new(0.0, 0.0, 1.0));
world.insert_multibody_joint(a, b, joint());
let handle_bc = world.insert_multibody_joint(b, c, joint()).unwrap();
world.remove_multibody_joint(handle_bc);
let count = world.multibody_joints.iter().count();
assert_eq!(count, 1, "only the A->B joint should remain");
}
#[test]
#[cfg(feature = "dim3")]
fn issue_927_branching_multibody_tree_steps_without_panic() {
let mut world = PhysicsWorld::new();
world.insert(
RigidBodyBuilder::fixed().translation(Vector::new(0.0, -0.5, 0.0)),
ColliderBuilder::cuboid(50.0, 0.5, 50.0),
);
let make_body = |world: &mut PhysicsWorld, pos: Vector| {
world
.insert(
RigidBodyBuilder::dynamic()
.translation(pos)
.additional_mass(5.0),
ColliderBuilder::ball(0.3),
)
.0
};
let (chassis, _) = world.insert(
RigidBodyBuilder::dynamic()
.translation(Vector::new(0.0, 1.5, 0.0))
.additional_mass(50.0),
ColliderBuilder::cuboid(2.0, 0.3, 1.0),
);
let joint = || RevoluteJointBuilder::new(Vector::new(1.0, 0.0, 0.0));
let axle_fl = make_body(&mut world, Vector::new(-1.5, 1.2, 1.2));
let mangueta_fl = make_body(&mut world, Vector::new(-1.5, 1.0, 1.5));
let wheel_fl = make_body(&mut world, Vector::new(-1.5, 0.5, 1.8));
world.insert_multibody_joint(axle_fl, mangueta_fl, joint());
world.insert_multibody_joint(mangueta_fl, wheel_fl, joint());
let axle_fr = make_body(&mut world, Vector::new(1.5, 1.2, 1.2));
let mangueta_fr = make_body(&mut world, Vector::new(1.5, 1.0, 1.5));
let wheel_fr = make_body(&mut world, Vector::new(1.5, 0.5, 1.8));
world.insert_multibody_joint(axle_fr, mangueta_fr, joint());
world.insert_multibody_joint(mangueta_fr, wheel_fr, joint());
let axle_rl = make_body(&mut world, Vector::new(-1.5, 1.2, -1.2));
let wheel_rl = make_body(&mut world, Vector::new(-1.5, 0.5, -1.5));
world.insert_multibody_joint(axle_rl, wheel_rl, joint());
let axle_rr = make_body(&mut world, Vector::new(1.5, 1.2, -1.2));
let wheel_rr = make_body(&mut world, Vector::new(1.5, 0.5, -1.5));
world.insert_multibody_joint(axle_rr, wheel_rr, joint());
world.insert_multibody_joint(chassis, axle_fl, joint());
world.insert_multibody_joint(chassis, axle_fr, joint());
world.insert_multibody_joint(chassis, axle_rl, joint());
world.insert_multibody_joint(chassis, axle_rr, joint());
for _ in 0..30 {
world.step();
}
assert!(world.bodies[chassis].translation().y.is_finite());
}
#[test]
#[cfg(feature = "dim3")]
fn issue_906_insert_multibody_joint_between_steps() {
const SHIFT: Real = 1.15;
let mut world = PhysicsWorld::new();
let (root, _) = world.insert(
RigidBodyBuilder::fixed(),
ColliderBuilder::cuboid(0.5, 0.5, 0.5),
);
let joint = || {
RevoluteJointBuilder::new(Vector::new(0.0, 1.0, 0.0))
.local_anchor1(Vector::new(0.0, -SHIFT, 0.0))
};
let mut last = root;
for i in 1..4 {
let (body, _) = world.insert(
RigidBodyBuilder::dynamic().translation(Vector::new(0.0, -SHIFT * i as Real, 0.0)),
ColliderBuilder::cuboid(0.5, 0.5, 0.5),
);
world.insert_multibody_joint(last, body, joint());
last = body;
}
world.step();
for i in 4..10 {
let (body, _) = world.insert(
RigidBodyBuilder::dynamic().translation(Vector::new(0.0, -SHIFT * i as Real, 0.0)),
ColliderBuilder::cuboid(0.5, 0.5, 0.5),
);
world.insert_multibody_joint(last, body, joint());
last = body;
world.step();
}
assert!(world.bodies[last].translation().y.is_finite());
}
#[test]
#[cfg(feature = "dim3")]
fn issue_907_body_colliding_with_multibody() {
let mut world = PhysicsWorld::new();
let (ground, _) = world.insert(
RigidBodyBuilder::fixed(),
ColliderBuilder::cuboid(50.0, 0.5, 50.0),
);
let (attached, _) = world.insert(
RigidBodyBuilder::dynamic()
.translation(Vector::new(0.0, 3.0, 0.0))
.angvel(Vector::new(0.0, 1.0, 0.0)),
ColliderBuilder::cuboid(0.5, 0.5, 0.5),
);
world.insert_multibody_joint(
ground,
attached,
RevoluteJointBuilder::new(Vector::new(0.0, 1.0, 0.0))
.local_anchor1(Vector::new(0.0, 3.0, 0.0)),
);
let (free, _) = world.insert(
RigidBodyBuilder::dynamic().translation(Vector::new(3.0, 3.0, 0.0)),
ColliderBuilder::cuboid(0.5, 0.5, 0.5),
);
for _ in 0..400 {
world.step();
}
assert!(world.bodies[free].translation().y.is_finite());
}
#[test]
#[cfg(feature = "dim3")]
fn issue_908_remove_body_from_multibody_chain() {
let mut world = PhysicsWorld::new();
let (root, _) = world.insert(
RigidBodyBuilder::fixed(),
ColliderBuilder::cuboid(0.5, 0.5, 0.5),
);
let mut chain = vec![root];
for i in 1..4 {
let (body, _) = world.insert(
RigidBodyBuilder::dynamic().translation(Vector::new(0.0, -2.0 * i as Real, 0.0)),
ColliderBuilder::cuboid(0.5, 0.5, 0.5),
);
world.insert_multibody_joint(
*chain.last().unwrap(),
body,
SphericalJointBuilder::new().local_anchor1(Vector::new(0.0, -2.0, 0.0)),
);
chain.push(body);
}
let (free, _) = world.insert(
RigidBodyBuilder::dynamic().translation(Vector::new(0.0, 2.0, 0.0)),
ColliderBuilder::cuboid(0.5, 0.5, 0.5),
);
for _ in 0..32 {
world.step();
}
while chain.len() > 1 {
let body = chain.pop().unwrap();
world.remove_body(body);
for _ in 0..32 {
world.step();
}
}
assert!(world.bodies[free].translation().y.is_finite());
}
#[test]
#[cfg(feature = "dim3")]
fn issue_400_multibody_joint_fixed_parent_dynamic_collision() {
let mut world = PhysicsWorld::new();
let (table, _) = world.insert(
RigidBodyBuilder::fixed(),
ColliderBuilder::cuboid(1.0, 0.1, 1.0),
);
let (flipper, _) = world.insert(
RigidBodyBuilder::dynamic().translation(Vector::new(-0.5, 0.3, -0.5)),
ColliderBuilder::cuboid(0.1, 0.1, 0.1),
);
world.insert_multibody_joint(
table,
flipper,
RevoluteJointBuilder::new(Vector::new(0.0, 1.0, 0.0))
.local_anchor1(Vector::new(-0.5, 0.3, -0.5))
.motor_velocity(-1.0, 1.0),
);
let (ball, _) = world.insert(
RigidBodyBuilder::dynamic().translation(Vector::new(0.0, 1.0, 0.0)),
ColliderBuilder::ball(0.01).friction(0.0),
);
for _ in 0..200 {
world.step();
}
assert!(world.bodies[ball].translation().y.is_finite());
}
#[test]
#[cfg(feature = "dim2")]
fn issue_656_multibody_root_reacts_to_collision_torque() {
let impulse_angvel = run_issue_656_scenario(false);
let multibody_angvel = run_issue_656_scenario(true);
assert!(
impulse_angvel > 0.1,
"scenario should induce rotation; impulse-joint baseline = {impulse_angvel}"
);
assert!(
multibody_angvel > impulse_angvel * 0.25,
"multibody root barely rotated: multibody = {multibody_angvel}, impulse = {impulse_angvel}"
);
}
#[cfg(feature = "dim2")]
fn run_issue_656_scenario(use_multibody: bool) -> Real {
let mut world = PhysicsWorld::new();
world.insert(
RigidBodyBuilder::fixed().translation(Vector::new(0.0, -2.0)),
ColliderBuilder::cuboid(0.5, 0.5),
);
let start = Vector::new(0.70, 0.0);
let (root, _) = world.insert(
RigidBodyBuilder::dynamic().translation(start),
ColliderBuilder::cuboid(0.3, 0.1).mass(0.1),
);
let (child, _) = world.insert(
RigidBodyBuilder::dynamic().translation(start),
ColliderBuilder::cuboid(0.05, 0.05).mass(0.1),
);
let joint = RevoluteJointBuilder::new()
.local_anchor1(Vector::new(0.0, 0.0))
.local_anchor2(Vector::new(0.0, 0.0))
.contacts_enabled(false);
if use_multibody {
world.insert_multibody_joint(root, child, joint);
} else {
world.insert_impulse_joint(root, child, joint);
}
let mut max_angvel: Real = 0.0;
for _ in 0..600 {
world.step();
max_angvel = max_angvel.max(world.bodies[root].angvel().abs());
}
max_angvel
}
#[test]
#[cfg(feature = "dim3")]
fn issue_379_multibody_ball_joint_respects_angular_limits() {
const LIMIT: Real = 0.3;
let mut world = PhysicsWorld::new();
let root = world.insert_body(RigidBodyBuilder::fixed());
let (child, _) = world.insert(
RigidBodyBuilder::dynamic()
.translation(Vector::new(1.0, 0.0, 0.0))
.additional_mass(1.0),
ColliderBuilder::cuboid(1.0, 0.1, 0.1),
);
world.insert_multibody_joint(
root,
child,
SphericalJointBuilder::new()
.local_anchor1(Vector::new(0.0, 0.0, 0.0))
.local_anchor2(Vector::new(-1.0, 0.0, 0.0))
.limits(JointAxis::AngZ, [-LIMIT, LIMIT]),
);
let mut lowest: Real = 0.0;
for _ in 0..600 {
world.step();
lowest = lowest.min(world.bodies[child].translation().y);
}
let max_drop = -LIMIT.sin();
assert!(
lowest > max_drop - 0.15,
"ball joint ignored its angular limit: child dropped to y = {lowest}, \
but the {LIMIT} rad limit should keep it above {max_drop}"
);
assert!(
lowest < max_drop + 0.1,
"child never reached the joint limit (y = {lowest}); the test is not \
exercising the limit"
);
}
#[test]
#[cfg(feature = "dim3")]
fn issue_906_append_fixed_joint_to_fixed_multibody_root() {
let mut world = PhysicsWorld::new();
let (root, _) = world.insert(
RigidBodyBuilder::fixed(),
ColliderBuilder::cuboid(0.5, 0.5, 0.5),
);
let (child, _) = world.insert(
RigidBodyBuilder::dynamic()
.translation(Vector::new(0.0, -2.0, 0.0))
.additional_mass(1.0),
ColliderBuilder::cuboid(0.5, 0.5, 0.5),
);
world.insert_multibody_joint(
root,
child,
RevoluteJointBuilder::new(Vector::new(0.0, 0.0, 1.0))
.local_anchor1(Vector::new(0.0, -2.0, 0.0)),
);
world.step();
let (attached, _) = world.insert(
RigidBodyBuilder::dynamic()
.translation(Vector::new(2.0, 0.0, 0.0))
.additional_mass(1.0),
ColliderBuilder::cuboid(0.5, 0.5, 0.5),
);
let handle = world.insert_multibody_joint(
root,
attached,
FixedJointBuilder::new().local_anchor1(Vector::new(2.0, 0.0, 0.0)),
);
assert!(
handle.is_some(),
"attaching a fixed joint to a fixed multibody root should be allowed"
);
let start = world.bodies[attached].translation();
for _ in 0..60 {
world.step();
}
let end = world.bodies[attached].translation();
let drift = (end.x - start.x).abs() + (end.y - start.y).abs() + (end.z - start.z).abs();
assert!(
drift < 1.0e-3,
"body fixed-jointed to a fixed root should not move, but drifted by {drift}"
);
assert!(world.bodies[child].translation().y.is_finite());
assert_eq!(world.multibody_joints.iter().count(), 2);
}
#[test]
#[cfg(feature = "dim3")]
fn issue_907_contact_with_branch_off_fixed_root() {
let mut world = PhysicsWorld::new();
let (root, _) = world.insert(
RigidBodyBuilder::fixed(),
ColliderBuilder::cuboid(0.5, 0.5, 0.5),
);
let (child, _) = world.insert(
RigidBodyBuilder::dynamic()
.translation(Vector::new(0.0, -3.0, 0.0))
.additional_mass(1.0),
ColliderBuilder::ball(0.5),
);
world.insert_multibody_joint(
root,
child,
RevoluteJointBuilder::new(Vector::new(0.0, 0.0, 1.0))
.local_anchor1(Vector::new(0.0, -3.0, 0.0)),
);
world.step();
let (branch, _) = world.insert(
RigidBodyBuilder::dynamic()
.translation(Vector::new(5.0, 0.0, 0.0))
.additional_mass(1.0),
ColliderBuilder::cuboid(0.5, 0.5, 0.5),
);
world.insert_multibody_joint(
root,
branch,
FixedJointBuilder::new().local_anchor1(Vector::new(5.0, 0.0, 0.0)),
);
let (free, _) = world.insert(
RigidBodyBuilder::dynamic()
.translation(Vector::new(5.0, 3.0, 0.0))
.additional_mass(1.0),
ColliderBuilder::cuboid(0.3, 0.3, 0.3),
);
for _ in 0..200 {
world.step();
}
let free_y = world.bodies[free].translation().y;
assert!(
(free_y - 0.8).abs() < 0.15,
"free body should rest on the fixed-jointed branch, but settled at y = {free_y}"
);
assert!(world.bodies[child].translation().y.is_finite());
}