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.

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.

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 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);
warning

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

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);