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.
The URDF and MJCF loaders are included in the C library when it is built with the robotics feature (see
building the C bindings), which is only available for the 3D
library with 32-bits floats. Their types and functions are only declared if RAPIER_ROBOTICS is defined (which the
Rapier::rapier CMake target does automatically). The loaded models are owned by the application: they must be freed
with their dedicated Free function once they are no longer needed, and they can be inserted as many times as needed
before that.
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 inserted into the world either with impulse joints or with multibody joints.
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 R3UrdfRobot by r3UrdfRobotFromFile, then inserted into the world with
r3UrdfRobot_InsertUsingImpulseJoints or r3UrdfRobot_InsertUsingMultibodyJoints. Each link becomes a rigid-body
(with its colliders), and each joint an impulse joint or a multibody joint. The returned R3UrdfRobotHandles gives
the handles of the rigid-bodies created for the links with r3UrdfRobotHandles_Bodies, in the order of the links of
the URDF file:
R3World *world = r3NewWorld();
// Read the robot (`path` is the path of the URDF file), then insert its links and joints into the world.
R3UrdfLoaderOptions options = r3DefaultUrdfLoaderOptions();
R3UrdfRobot *robot = r3UrdfRobotFromFile(path, &options);
R3UrdfRobotHandles *handles = r3UrdfRobot_InsertUsingMultibodyJoints(world, robot, 0);
printf("The robot has %zu links.\n", r3UrdfRobotHandles_Bodies(handles, NULL, 0));
// The loaded robot and the handles are owned by the application.
r3FreeUrdfRobotHandles(handles);
r3FreeUrdfRobot(robot);
The R3UrdfLoaderOptions (initialized by r3DefaultUrdfLoaderOptions) control the conversion of the robot. The
insertion of the same loaded robot can be repeated, e.g., after moving it with r3UrdfRobot_AppendTransform. When
inserting with multibody joints, the last argument is a bitmask of options:
R3_MULTIBODY_JOINTS_ARE_KINEMATIC makes the multibody joints kinematic (they are then entirely controlled by the
application, e.g., through inverse kinematics), and R3_MULTIBODY_DISABLE_SELF_CONTACTS ignores the contacts between
the links of the robot:
R3UrdfLoaderOptions options = r3DefaultUrdfLoaderOptions();
// Whether colliders are created from the collision shapes of the links.
// Default: 1
options.createCollidersFromCollisionShapes = 1;
// Whether colliders are created from the visual shapes of the links.
// Default: 0
options.createCollidersFromVisualShapes = 0;
// Whether the mass properties declared by the links are applied to their rigid-bodies.
// Default: 1
options.applyImportedMassProps = 1;
// Whether the colliders of two links attached by a joint can collide.
// Default: 0
options.enableJointCollisions = 0;
// Whether the root links are fixed rigid-bodies.
// Default: 0
options.makeRootsFixed = 1;
// The pose applied to the whole robot, e.g., to convert its Z-up convention to Y-up.
// Default: the identity pose.
options.shift = r3Pose(r3Vector(0.0, 0.0, 0.0), r3RotationFromAxisAngle(r3Vector(1.0, 0.0, 0.0), R3_PI / 2.0));
// The description every rigid-body is created from (before the URDF data is applied).
// Default: a dynamic rigid-body.
options.rigidBodyBlueprint = r3DynamicRigidBodyDesc();
R3UrdfRobot *robot = r3UrdfRobotFromFile(path, &options);
// Insert the same robot twice: once with impulse joints, then 10 units further with multibody joints.
R3UrdfRobotHandles *impulse_robot = r3UrdfRobot_InsertUsingImpulseJoints(world, robot);
r3UrdfRobot_AppendTransform(robot, r3TranslationPose(r3Vector(10.0, 0.0, 0.0)));
R3UrdfRobotHandles *multibody_robot =
r3UrdfRobot_InsertUsingMultibodyJoints(world, robot, R3_MULTIBODY_DISABLE_SELF_CONTACTS);
A robot can also be read from a string containing its URDF description with r3UrdfRobotFromString, in which case the
relative paths of its meshes are resolved from the directory given as its second argument (or from the current
directory if it is NULL):
// Read the robot from a string containing its URDF description. The relative paths of its meshes are resolved
// from `mesh_dir` (or from the current directory if it is NULL).
R3UrdfLoaderOptions options = r3DefaultUrdfLoaderOptions();
R3UrdfRobot *robot = r3UrdfRobotFromString(urdf_xml, mesh_dir, &options);
The robotics feature 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 R3MjcfRobot by r3MjcfRobotFromFile (with the R3MjcfLoaderOptions initialized by
r3DefaultMjcfLoaderOptions), then inserted into the world with r3MjcfRobot_InsertUsingImpulseJoints or
r3MjcfRobot_InsertUsingMultibodyJoints. The returned R3MjcfRobotHandles gives the handles of the rigid-bodies
created for the bodies of the model with r3MjcfRobotHandles_Bodies (in the order of the model file, with an invalid
handle for the MJCF bodies that don't have a rigid-body):
R3World *world = r3NewWorld();
// Read the model (`path` is the path of the MJCF file), then insert its bodies and joints into the world.
R3MjcfLoaderOptions options = r3DefaultMjcfLoaderOptions();
R3MjcfRobot *robot = r3MjcfRobotFromFile(path, &options);
R3MjcfRobotHandles *handles = r3MjcfRobot_InsertUsingImpulseJoints(world, robot);
// The loaded model and the handles are owned by the application.
r3FreeMjcfRobotHandles(handles);
r3FreeMjcfRobot(robot);
On top of the fields shared with R3UrdfLoaderOptions, the R3MjcfLoaderOptions can skip the plane geometries of
the model (skipPlaneGeoms), and disable the motors of its joints (disableJointMotors). The collision groups of the
colliders of the loaded model can also be modified before its insertion with
r3MjcfRobot_SetBodyColliderCollisionGroups.
The bitmask of options given to r3MjcfRobot_InsertUsingMultibodyJoints accepts the same flags as for URDF, as well
as R3_MULTIBODY_SKIP_LOOP_CLOSURES (the <equality> constraints closing loops aren't inserted as impulse joints),
R3_MULTIBODY_SKIP_JOINT_MOTORS, R3_MULTIBODY_SKIP_JOINT_LIMITS, and R3_MULTIBODY_SKIP_JOINT_SPRINGS. Note that
the gravity declared by the model isn't applied automatically: it is given by r3MjcfRobot_Gravity. Each actuator of
the model drives the motor of its joint: r3MjcfRobotHandles_ApplyControlsScaled sets the control inputs of all the
actuators at once, and r3MjcfRobotHandles_ApplyKeyframe resets the robot to one of the keyframes of the model:
// Unlike the URDF loader, joints are generally inserted as multibody joints (like in MuJoCo).
R3MjcfRobotHandles *handles = r3MjcfRobot_InsertUsingMultibodyJoints(
world, robot, R3_MULTIBODY_SKIP_LOOP_CLOSURES | R3_MULTIBODY_DISABLE_SELF_CONTACTS);
// The gravity declared by the model isn't applied automatically. It is expressed in the frame of the
// model file, so we rotate it like `options.shift` rotated the model.
R3Vector gravity = r3MjcfRobot_Gravity(robot);
r3SetGravity(world, r3RotationTransformVector(options.shift.rotation, gravity));
// Drive the actuators of the model: one control input per actuator.
size_t actuator_count = r3MjcfRobotHandles_ActuatorCount(handles);
R3Real *controls = calloc(actuator_count, sizeof(R3Real));
controls[0] = 0.5;
r3MjcfRobotHandles_ApplyControlsScaled(handles, controls, actuator_count, 1.0);
free(controls);
// Reset the robot to the first keyframe declared by the model (if any).
if (r3MjcfRobot_KeyframeCount(robot) > 0) {
r3MjcfRobotHandles_ApplyKeyframe(handles, robot, 0);
}
The contact rules of the model (<contact><exclude>, and the friction of <contact><pair>) are applied by
physics hooks created by r3MjcfRobotHandles_ContactHooks, which
must be given to each simulation step (the inserted colliders already enable the required hooks). These hooks are owned
by the application, and must outlive every step using them:
// The contact rules of the model, applied by physics hooks given to every step.
R3MjcfContactHooks *contact_hooks = r3MjcfRobotHandles_ContactHooks(handles, robot);
R3PhysicsHooks hooks = r3MjcfContactHooks_PhysicsHooks(contact_hooks);
r3Step(world, &hooks, NULL);
// The hooks must be freed once they are no longer used by the steps.
r3FreeMjcfContactHooks(contact_hooks);
Finally, the visual meshes of each body of the model can be read with r3MjcfRobot_BodyVisualCount and
r3MjcfRobot_BodyVisual, in order to render the model with your own engine.
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.
It reads the .stl, .dae, and .obj files.
The mesh loader is included in the C library by the robotics feature too. A mesh file is read by
r3LoadedMeshesFromFile into an R3LoadedMeshes owned by the application, which gives the shape and the pose of
each of its meshes (for an .obj file, each group and each change of material starts a new mesh). The conversion is
selected by one of the R3_MESH_CONVERTER_* constants (along with the R3_TRIMESH_* flags applied to the triangle
meshes, which are only accepted when converting with R3_MESH_CONVERTER_TRIMESH). A mesh that failed to convert
doesn't fail the whole load: r3LoadedMeshes_CloneShape then reports an R3_INVALID_ARGUMENT error for that mesh
only:
R3World *world = r3NewWorld();
// Every mesh of the file (`path`) becomes one shape, converted here into its convex hull.
R3LoadedMeshes *meshes = r3LoadedMeshesFromFile(path, R3_MESH_CONVERTER_CONVEX_HULL, 0, r3Vector(1.0, 1.0, 1.0));
for (size_t i = 0; i < r3LoadedMeshes_Count(meshes); i++) {
R3SharedShape *shape = r3LoadedMeshes_CloneShape(meshes, i);
R3ColliderDesc collider = r3DefaultColliderDesc();
collider.shape.kind = R3_SHAPE_DESC_SHARED;
collider.shape.sharedShape = shape;
collider.position = r3LoadedMeshes_Pose(meshes, i);
r3InsertColliderWithoutParent(world, &collider);
// The collider keeps its own reference to the shape.
r3FreeSharedShape(shape);
}
r3FreeLoadedMeshes(meshes);