Skip to content

Commit 8aac3a7

Browse files
authored
mujoco: support joint mimic constraints (#990)
Implement joint mimic features in the MuJoCo physics engine plugin, supporting coupling for standard 1-DOF and multi-DOF joints. Because MuJoCo solves joint equality constraints as soft force constraints, dynamic keyframe state-teleportation (e.g. SetJointPosition) without coordinate alignment generates severe physical spring-force solver spikes. We resolve this by performing automated C++ coordinate state propagation to pre-align downstream follower states in the active state arrays at the keyframing boundary. Generated-by: Gemini 3.0 Pro --------- Signed-off-by: Addisu Z. Taddese <addisuzt@intrinsic.ai>
1 parent 18d4cc8 commit 8aac3a7

6 files changed

Lines changed: 578 additions & 13 deletions

File tree

mujoco/src/Base.cc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,20 @@ void resolveJointIndices(WorldInfo &_worldInfo)
8787
jointInfo->screwEqIndex = eqId;
8888
}
8989
}
90+
91+
// Resolve mimic constraints compiled IDs
92+
for (auto &constraint : jointInfo->mimicConstraints)
93+
{
94+
int eqId = mjs_getId(constraint.spec->element);
95+
if (eqId >= 0 && eqId < m->neq)
96+
{
97+
constraint.eqId = eqId;
98+
}
99+
else
100+
{
101+
constraint.eqId = -1;
102+
}
103+
}
90104
}
91105
}
92106

mujoco/src/Base.hh

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ namespace physics
4444
namespace mujoco
4545
{
4646

47+
/// \brief Format the scoped name of a joint axis, appending a suffix
48+
/// for secondary degrees of freedom if the axis index is greater than 0.
49+
/// \param[in] _scopedJointName The standard pre-scoped joint name string.
50+
/// \param[in] _dof The specific degree-of-freedom (axis index) of the joint.
51+
/// \return The formatted joint/axis name string.
52+
inline std::string getJointAxisName(const std::string &_scopedJointName,
53+
std::size_t _dof)
54+
{
55+
std::string name = _scopedJointName;
56+
if (_dof > 0)
57+
{
58+
name += "_axis" + std::to_string(_dof + 1);
59+
}
60+
return name;
61+
}
62+
4763
inline void copyPos(const math::Vector3d &_src, mjtNum *_dst)
4864
{
4965
_dst[0] = _src.X();
@@ -155,6 +171,33 @@ struct LinkInfo
155171
detail::EntityStorage<std::shared_ptr<ShapeInfo>, const mjsGeom *> shapes{};
156172
};
157173

174+
struct JointInfo;
175+
176+
/// \brief Metadata for a mimic joint constraint relationship.
177+
/// Couples the state of a follower joint/axis to a leader joint/axis
178+
/// via a linear relationship (multiplier, offset, reference).
179+
struct MimicConstraintInfo
180+
{
181+
/// \brief Pointer to the equality constraint spec element in mjSpec.
182+
mjsEquality* spec{nullptr};
183+
184+
/// \brief The compiled equality constraint index in the active mjModel.
185+
/// Set to -1 if the constraint is not currently compiled/active.
186+
int eqId{-1};
187+
188+
/// \brief The specific degree-of-freedom (axis index) of the leader joint
189+
/// that acts as the driver for this constraint.
190+
std::size_t leaderDof{0};
191+
192+
/// \brief Safe weak reference targeting the follower joint's metadata
193+
/// to avoid strong circular dependency cycles and memory leaks.
194+
std::weak_ptr<JointInfo> followerJointInfo;
195+
196+
/// \brief The specific degree-of-freedom (axis index) of the follower joint
197+
/// that is constrained by this relationship.
198+
std::size_t followerDof{0};
199+
};
200+
158201
struct JointInfo
159202
{
160203
JointInfo(std::size_t _entityId,
@@ -179,6 +222,12 @@ struct JointInfo
179222
mjsEquality* screwConstraintSpec{nullptr};
180223
// Compiled screw joint equality constraint index in mjModel (max 1)
181224
std::optional<int> screwEqIndex{std::nullopt};
225+
// Mimic constraints where this joint is the leader.
226+
// Storing constraints on the leader (rather than the followers) supports:
227+
// 1. One-to-many cases where one leader drives multiple followers.
228+
// 2. O(num_followers) keyframe updates, avoiding expensive
229+
// O(total_joints) global searches across the entire model.
230+
std::vector<MimicConstraintInfo> mimicConstraints;
182231
};
183232

184233

@@ -250,6 +299,8 @@ struct WorldInfo
250299

251300
class Base
252301
{
302+
public: virtual ~Base() = default;
303+
253304
// Note: Entity ID 0 is reserved for the "engine"
254305
public: std::size_t entityCount = 1;
255306

0 commit comments

Comments
 (0)