Skip to main content

rapier3d_mjcf/loader/
options.rs

1//! Loader-wide configuration (`MjcfLoaderOptions`,
2//! `MjcfMultibodyOptions`, `ContactFilterMode`).
3
4use rapier3d::dynamics::RigidBodyBuilder;
5#[cfg(feature = "__meshloader_is_enabled")]
6use rapier3d::geometry::MeshConverter;
7use rapier3d::geometry::{ColliderBuilder, TriMeshFlags};
8use rapier3d::math::{Pose, Real};
9
10bitflags::bitflags! {
11    /// Options applied to multibody joints created from the MJCF joints.
12    #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
13    pub struct MjcfMultibodyOptions: u8 {
14        /// If set, the created multibody joints are kinematic.
15        const JOINTS_ARE_KINEMATIC = 0b0001;
16        /// If set, contacts between two links of the same multibody are disabled.
17        const DISABLE_SELF_CONTACTS = 0b0010;
18        /// If set, `<equality><connect>` and `<equality><weld>` constraints
19        /// are **not** inserted as impulse joints alongside the multibody.
20        ///
21        /// MuJoCo uses equalities to close kinematic loops (e.g. Cassie's
22        /// achilles / plantar rods, four-bar mechanisms in parallel-link
23        /// arms). Rapier's multibody is tree-structured, so the loader
24        /// resorts to inserting those constraints as ordinary impulse
25        /// joints. With stiff articulated chains, that impulse joint
26        /// fights the multibody solver and can pull the chain hard
27        /// toward the constraint anchor — useful escape hatch when
28        /// debugging a chain that otherwise drifts under loop closure.
29        const SKIP_LOOP_CLOSURES = 0b0100;
30        /// If set, the motor entries baked into each joint by the loader
31        /// (`<joint frictionloss>`, and the actuator motors set at runtime)
32        /// are stripped before the joint is handed to the multibody. Only the
33        /// multibody path is affected — the impulse-joint path still sees
34        /// motors. Passive `<joint stiffness>` springs are **not** motors and
35        /// are unaffected; use [`SKIP_JOINT_SPRINGS`](Self::SKIP_JOINT_SPRINGS)
36        /// for those. Useful when comparing against a pure kinematic chain.
37        const SKIP_JOINT_MOTORS = 0b1000;
38        /// If set, the joint limits baked into each joint by the loader
39        /// (`<joint range>`) are stripped before the joint is handed to
40        /// the multibody. Only the multibody path is affected — the
41        /// impulse-joint path still sees limits. Useful when a model's
42        /// declared `range` interacts badly with the loader's resolved
43        /// rest pose (the limit then fights the joint immediately at
44        /// t=0).
45        const SKIP_JOINT_LIMITS = 0b1_0000;
46        /// If set, MJCF `<joint stiffness>` passive springs are **not**
47        /// installed as implicit springs on the multibody. Only the multibody
48        /// path is affected. The springs are integrated implicitly by default
49        /// (stable for stiff springs on low-inertia links); set this to compare
50        /// against a spring-free chain or to debug a model whose declared
51        /// `springref`/`stiffness` is unexpected.
52        const SKIP_JOINT_SPRINGS = 0b10_0000;
53    }
54}
55
56/// How the loader maps MJCF `contype` / `conaffinity` to rapier
57/// [`InteractionGroups`](rapier3d::geometry::InteractionGroups).
58#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
59pub enum ContactFilterMode {
60    /// `memberships = filter = contype | conaffinity` (default). MJCF's
61    /// "OR" rule is equivalent to rapier's "AND" rule on this encoding for
62    /// the common case where each geom has `contype == conaffinity`.
63    #[default]
64    Symmetric,
65    /// `memberships = contype`, `filter = conaffinity`. Matches MJCF only
66    /// when geoms are deliberately partitioned into types vs. affinities.
67    Asymmetric,
68}
69
70/// Configuration for [`MjcfRobot::from_model`](super::MjcfRobot::from_model).
71#[derive(Clone, Debug)]
72pub struct MjcfLoaderOptions {
73    /// Build colliders for `<geom>` elements that participate in contact
74    /// (`contype != 0` or `conaffinity != 0`). Default `true`.
75    pub create_colliders_from_collision_shapes: bool,
76    /// Build colliders for `<geom>` elements that don't participate in
77    /// contact (visual-only, `contype == 0 && conaffinity == 0`). Default
78    /// `false`.
79    pub create_colliders_from_visual_shapes: bool,
80    /// Use the model's `<inertial>` elements (or compiler-derived inertias)
81    /// to set the rigid-body's mass properties. Default `true`.
82    pub apply_imported_mass_props: bool,
83    /// Allow contacts between two bodies sharing a joint. Default `false`.
84    pub enable_joint_collisions: bool,
85    /// If `true`, the rigid-bodies at the root of the kinematic chains are
86    /// initialized as [`RigidBodyType::Fixed`](rapier3d::dynamics::RigidBodyType::Fixed). Default `false`.
87    pub make_roots_fixed: bool,
88    /// Trimesh flags applied to mesh colliders when the default mesh
89    /// converter is used.
90    pub trimesh_flags: TriMeshFlags,
91    /// Custom mesh converter. Set to (e.g.) [`MeshConverter::ConvexHull`]
92    /// to make every mesh collider a convex hull. Default `None` (use
93    /// `TriMeshWithFlags(trimesh_flags)`).
94    #[cfg(feature = "__meshloader_is_enabled")]
95    pub mesh_converter: Option<MeshConverter>,
96    /// Transform appended to every created rigid-body (default identity).
97    pub shift: Pose,
98    /// Uniform scale applied to lengths read from the MJCF (default `1.0`).
99    pub scale: Real,
100    /// Blueprint applied to every collider created by the loader (default
101    /// has density 0 — physical mass comes from `<inertial>`).
102    pub collider_blueprint: ColliderBuilder,
103    /// Blueprint applied to every rigid-body created by the loader (default
104    /// dynamic — root bodies are fixed if [`Self::make_roots_fixed`]).
105    pub rigid_body_blueprint: RigidBodyBuilder,
106    /// How to encode `contype`/`conaffinity` into rapier `InteractionGroups`.
107    pub contact_filter_mode: ContactFilterMode,
108    /// If `true`, `<geom type="plane">` elements are skipped entirely (no
109    /// collider is created, the geom doesn't contribute to derived inertia,
110    /// and the body it belonged to is unaffected).
111    ///
112    /// Default `true`.
113    pub skip_plane_geoms: bool,
114    /// If `true`, skip the per-joint **motor** setup that the loader
115    /// normally applies for `<joint stiffness>`, `<joint springdamper>`,
116    /// and `<joint frictionloss>` attributes (springs and Coulomb-friction
117    /// approximations).
118    ///
119    /// `<joint damping>` is intentionally **not** affected by this flag —
120    /// per-DoF damping is a dynamics-level friction term, not a motor, and
121    /// the multibody-joint insertion path still routes `<joint damping>`
122    /// values through the multibody's per-DoF damping vector. (The
123    /// impulse-joint path has no per-DoF damping buffer, so damping is
124    /// lost there when this flag is on.)
125    ///
126    /// The joint kinematic structure (type, axis, anchor, limits) is still
127    /// honored. Useful when:
128    ///
129    /// - you want pure kinematic dynamics for debugging,
130    /// - the model declares spring stiffness that's too high for your
131    ///   simulation timestep,
132    /// - you intend to drive every joint yourself through
133    ///   [`MjcfRobotHandles::apply_controls`](super::MjcfRobotHandles::apply_controls)
134    ///   and don't want the loader's default motors fighting your controller.
135    ///
136    /// Default `false`.
137    pub disable_joint_motors: bool,
138}
139
140impl Default for MjcfLoaderOptions {
141    fn default() -> Self {
142        Self {
143            create_colliders_from_collision_shapes: true,
144            create_colliders_from_visual_shapes: false,
145            apply_imported_mass_props: true,
146            enable_joint_collisions: false,
147            make_roots_fixed: false,
148            trimesh_flags: TriMeshFlags::all(),
149            #[cfg(feature = "__meshloader_is_enabled")]
150            mesh_converter: None,
151            shift: Pose::IDENTITY,
152            scale: 1.0,
153            collider_blueprint: ColliderBuilder::default().density(0.0),
154            rigid_body_blueprint: RigidBodyBuilder::dynamic(),
155            contact_filter_mode: ContactFilterMode::Symmetric,
156            skip_plane_geoms: true,
157            disable_joint_motors: false,
158        }
159    }
160}