Skip to main content

Scene loaders

Rapier provides companion crates converting some popular robotics formats into the rigid-bodies, the colliders, and the joints they contain. Note that these crates are 3D only, and that they are released separately from rapier3d itself.

These crates are integrated into bevy_rapier3d behind the urdf, mjcf, and meshloader cargo features (see the bevy_rapier3d::loaders module). Instead of inserting anything into the physics context directly, the loaders spawn regular entities with rigid-body, collider, and joint components. Therefore the spawned objects can be modified, queried, or despawned like any other entity.

info

The scene loaders are not available in bevy_rapier2d.

URDF​

The rapier3d-urdf crate loads the Unified Robot Description Format used by the ROS community. A robot is read from a URDF file (or from a string), and is then spawned as entities either with impulse joints or with multibody joints.

info

Robotics generally care a lot about joint violation. Then inserting the model using multibody joints is strongly recommended since they guarantee the absence of joint violation by encoding the locked degrees of freedom into the equations of motion directly instead of relying on the constraints solver (which might not converge).

The robot is read as an UrdfModel, and spawned with spawn_urdf_robot. The UrdfSpawnOptions select the kind of joints (impulse joints by default), the transform of the root entity every link is a child of, and the physics context the robot is added to. Each link becomes a rigid-body entity (with an UrdfLinkId component), each of its shapes a collider entity child of the link, and each joint an ImpulseJoint or a MultibodyJoint component on the entity of its child link. The returned SpawnedUrdfRobot lists all these entities, and finds them by their URDF name:

// Read the robot, then spawn its links and joints as entities.
let model = UrdfModel::from_file("robot.urdf", UrdfLoaderOptions::default(), None)?;
let robot = spawn_urdf_robot(
&mut commands,
&model,
&UrdfSpawnOptions {
multibody: true,
multibody_options: UrdfMultibodyOptions::DISABLE_SELF_CONTACTS,
// URDF files are generally Z-up, whereas Bevy is Y-up.
root_transform: Transform::from_rotation(Quat::from_rotation_x(-FRAC_PI_2)),
..default()
},
);
println!("The robot has {} links.", robot.links.len());
let _elbow = robot.joints_by_name["elbow"];

The visual elements of the links are not rendered: they are given by the UrdfLinkVisuals component so you can attach your own meshes to the link entities. On top of what rapier3d-urdf converts, the <dynamics> (damping and friction) and <mimic> elements of the joints are applied to multibody joints (as MultibodyJointDamping, MultibodyJointFriction, and MultibodyJointCouplings components), but they are ignored by impulse joints.

warning

The urdf feature of bevy_rapier3d also enables the loading of the .stl, .dae, and .obj meshes referenced by an URDF file. Note that a joint inserted as a multibody joint is reset to its neutral position, i.e., all of its coordinates are zero.

MJCF​

The rapier3d-mjcf crate loads the MJCF XML format of MuJoCo. We recommend browsing the MuJoCo Menagerie repository which contains many MJCF models.

The model is read as an MjcfRobot, and spawned with spawn_mjcf_model. Unlike the URDF loader, its joints are spawned as multibody joints by default (like in MuJoCo), which can be changed with MjcfSpawnOptions::multibody. The returned SpawnedMjcfModel lists the spawned entities, and finds them by their MJCF name. Note that the gravity declared by the model isn't applied automatically:

// Read the model, then spawn its bodies, joints, and actuators as entities.
let (robot, _model) = MjcfRobot::from_file("robot.xml", MjcfLoaderOptions::default())?;
let model = spawn_mjcf_model(
&mut commands,
&robot,
&MjcfSpawnOptions {
// MJCF files are Z-up, whereas Bevy is Y-up.
root_transform: Transform::from_rotation(Quat::from_rotation_x(-FRAC_PI_2)),
..default()
},
);
// The gravity of the model isn't applied automatically.
configurations.single_mut()?.gravity = model.gravity;

Two more elements of MJCF need your app to be set up accordingly. The contact rules of the model (<contact><exclude>, and the friction of <contact><pair>) are registered in the MjcfContactFilters resource and applied by the MjcfPhysicsHooks physics hooks (if you have your own hooks, call MjcfContactFilters::filter_contact_pair and MjcfContactFilters::modify_solver_contacts from them instead). Each actuator of the model is spawned as an entity with an MjcfActuator component, which drives the motor of its joint once the MjcfPlugin is added:

App::new()
.add_plugins((
DefaultPlugins,
// The hooks apply the `<contact>` rules of the MJCF models.
RapierPhysicsPlugin::<MjcfPhysicsHooks>::default(),
// Applies the controls of the actuators to the joints they drive.
MjcfPlugin::default(),
))

The actuators are then controlled by setting their MjcfActuator::ctrl input:

fn drive_actuators(time: Res<Time>, mut actuators: Query<(&Name, &mut MjcfActuator)>) {
for (name, mut actuator) in actuators.iter_mut() {
if name.as_str() == "cart_motor" {
actuator.ctrl = time.elapsed_secs().sin();
}
}
}
note

Note that MJCF sometimes describes a whole simulation and not only a scene, therefore some of its elements have no equivalent in Rapier, and some others are approximated.

Meshes​

The rapier3d-meshloader crate builds shapes from the usual mesh files, which is what both the MJCF and URDF loaders use internally. It is useful on its own whenever the collision geometry of a scene comes from an asset file rather than from primitive shapes. The formats it reads are selected by its cargo features: stl, collada, and wavefront. Each mesh of the file becomes one shape, converted as selected by the MeshConverter:

// All the meshes of the file are combined into a single collider, each of them being
// converted here into its convex hull.
let collider = Collider::from_mesh_file("asset.obj", &MeshConverter::ConvexHull, Vec3::ONE)?;
commands.spawn((Transform::default(), RigidBody::Dynamic, collider));

Collider::from_mesh_file combines all the meshes of the file into a single collider (with a compound shape if there are several of them). Use load_mesh_file_colliders instead to get one collider per mesh, together with its pose, the mesh itself, and its material (which can be converted into a Bevy mesh for rendering with raw_mesh_to_bevy_mesh if the to-bevy-mesh feature is enabled):

// Every mesh of the file becomes its own collider.
let parts = load_mesh_file_colliders("asset.obj", &MeshConverter::TriMesh, Vec3::ONE)?;
for part in parts {
commands.spawn((part.transform, part.collider));
}