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}