Skip to content

Commit 95e5e6d

Browse files
Reduce diff
Signed-off-by: Shameek Ganguly <shameek@intrinsic.ai>
1 parent ee1d45e commit 95e5e6d

1 file changed

Lines changed: 183 additions & 181 deletions

File tree

mujoco/src/SDFFeatures.cc

Lines changed: 183 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -310,212 +310,214 @@ struct ModelKinematicStructure
310310
}
311311
return;
312312
}
313-
314-
mjsJoint *joint{nullptr};
315-
mjsJoint *joint2{nullptr};
316-
// It is possible to apply joint forces using `qfrc_applied`, but this
317-
// makes it harder to retrieve the last applied forces on a joint when
318-
// implementing GetJoint. Instead, we use actuators and `mjData::ctrl`.
319-
// This allows us to use the same interface for setting velocity servo
320-
// commands as well.
321-
mjsActuator *actuator{nullptr};
322-
if (sdfJoint->Type() == ::sdf::JointType::PRISMATIC)
323-
{
324-
joint = mjs_addJoint(child, nullptr);
325-
joint->type = mjJNT_SLIDE;
326-
const auto *sdfAxis = sdfJoint->Axis(0);
327-
convertJointAxis(sdfAxis, joint->axis);
328-
copyStandardJointAxisProperties(joint, sdfAxis);
329-
}
330-
else if (sdfJoint->Type() == ::sdf::JointType::REVOLUTE)
331-
{
332-
joint = mjs_addJoint(child, nullptr);
333-
joint->type = mjJNT_HINGE;
334-
const auto *sdfAxis = sdfJoint->Axis(0);
335-
convertJointAxis(sdfAxis, joint->axis);
336-
copyStandardJointAxisProperties(joint, sdfAxis);
337-
}
338-
else if (sdfJoint->Type() == ::sdf::JointType::BALL)
313+
else
339314
{
340-
joint = mjs_addJoint(child, nullptr);
341-
joint->type = mjJNT_BALL;
342-
const auto *sdfAxis = sdfJoint->Axis(0);
343-
if (sdfAxis)
315+
mjsJoint *joint{nullptr};
316+
mjsJoint *joint2{nullptr};
317+
// It is possible to apply joint forces using `qfrc_applied`, but this
318+
// makes it harder to retrieve the last applied forces on a joint when
319+
// implementing GetJoint. Instead, we use actuators and `mjData::ctrl`.
320+
// This allows us to use the same interface for setting velocity servo
321+
// commands as well.
322+
mjsActuator *actuator{nullptr};
323+
if (sdfJoint->Type() == ::sdf::JointType::PRISMATIC)
344324
{
325+
joint = mjs_addJoint(child, nullptr);
326+
joint->type = mjJNT_SLIDE;
327+
const auto *sdfAxis = sdfJoint->Axis(0);
345328
convertJointAxis(sdfAxis, joint->axis);
346329
copyStandardJointAxisProperties(joint, sdfAxis);
347-
// For ball joints, the first range parameter should always be set to
348-
// zero.
349-
if (joint->limited && std::abs(joint->range[0]) > 0.0)
350-
{
351-
gzwarn << "MuJoCo requires the lower joint position limit of ball "
352-
"joints to be zero.\n";
353-
joint->range[0] = 0;
354-
}
355330
}
356-
}
357-
else if (sdfJoint->Type() == ::sdf::JointType::SCREW)
358-
{
359-
// Screw joints in MuJoCo are modeled by coupling a hinge joint
360-
// (rotation) and a slide joint (translation) along the same axis on
361-
// the child body using a joint equality constraint (mjEQ_JOINT).
362-
// We store the hinge joint (`joint`) as the primary joint in
363-
// JointInfo. This matches DART's choice of using the rotational DOF
364-
// as the primary, ensuring both physics plugins expose consistent
365-
// angular units (radians and rad/s) for screw joints across the
366-
// gz-physics API.
367-
// Like the universal joint, `joint` and `joint2` are compiled
368-
// contiguously on the same child body.
369-
joint = mjs_addJoint(child, nullptr);
370-
joint->type = mjJNT_HINGE;
371-
const auto *sdfAxis1 = sdfJoint->Axis(0);
372-
if (sdfAxis1)
331+
else if (sdfJoint->Type() == ::sdf::JointType::REVOLUTE)
373332
{
374-
convertJointAxis(sdfAxis1, joint->axis);
375-
copyStandardJointAxisProperties(joint, sdfAxis1);
376-
// Disable independent position limits on the primary rotational
377-
// hinge joint. In MuJoCo's soft constraint solver, enforcing limits
378-
// on the hinge joint when coupled with a small thread pitch causes
379-
// premature solver clamping and massive numerical damping. By mapping
380-
// position limits purely onto the translational slide joint, we
381-
// ensure exact kinematic limit enforcement without solver resistance.
382-
joint->limited = false;
333+
joint = mjs_addJoint(child, nullptr);
334+
joint->type = mjJNT_HINGE;
335+
const auto *sdfAxis = sdfJoint->Axis(0);
336+
convertJointAxis(sdfAxis, joint->axis);
337+
copyStandardJointAxisProperties(joint, sdfAxis);
383338
}
384-
385-
joint2 = mjs_addJoint(child, nullptr);
386-
joint2->type = mjJNT_SLIDE;
387-
if (sdfAxis1)
339+
else if (sdfJoint->Type() == ::sdf::JointType::BALL)
388340
{
389-
convertJointAxis(sdfAxis1, joint2->axis);
390-
// We only copy position limits and range to the secondary slide
391-
// joint. All passive dynamics (damping, frictionloss, stiffness) and
392-
// actuator effort limits are enforced purely on the primary
393-
// rotational hinge joint to avoid double-counting and physical unit
394-
// mismatches.
395-
joint2->limited = static_cast<int>(!std::isinf(sdfAxis1->Lower()) &&
396-
!std::isinf(sdfAxis1->Upper()));
397-
if (joint2->limited)
341+
joint = mjs_addJoint(child, nullptr);
342+
joint->type = mjJNT_BALL;
343+
const auto *sdfAxis = sdfJoint->Axis(0);
344+
if (sdfAxis)
398345
{
399-
const double pitch =
400-
convertScrewThreadPitch(sdfJoint->ScrewThreadPitch());
401-
joint2->range[0] = sdfAxis1->Lower() * pitch;
402-
joint2->range[1] = sdfAxis1->Upper() * pitch;
346+
convertJointAxis(sdfAxis, joint->axis);
347+
copyStandardJointAxisProperties(joint, sdfAxis);
348+
// For ball joints, the first range parameter should always be set to
349+
// zero.
350+
if (joint->limited && std::abs(joint->range[0]) > 0.0)
351+
{
352+
gzwarn << "MuJoCo requires the lower joint position limit of ball "
353+
"joints to be zero.\n";
354+
joint->range[0] = 0;
355+
}
403356
}
404357
}
405-
}
406-
else if (sdfJoint->Type() == ::sdf::JointType::UNIVERSAL)
407-
{
408-
// Universal joints in MuJoCo are modeled as two hinge joints in
409-
// series on the child body. We only need to keep a pointer to the
410-
// first hinge joint (`joint`) in JointInfo. Since MuJoCo compiles
411-
// joints on the same body contiguously in memory, all getters/setters
412-
// can safely access the second joint (`joint2`)'s state data using
413-
// `nq_index + 1` and `nv_index + 1` without needing to store it
414-
// separately in JointInfo.
415-
joint = mjs_addJoint(child, nullptr);
416-
joint->type = mjJNT_HINGE;
417-
const auto *sdfAxis1 = sdfJoint->Axis(0);
418-
if (sdfAxis1)
358+
else if (sdfJoint->Type() == ::sdf::JointType::SCREW)
419359
{
420-
convertJointAxis(sdfAxis1, joint->axis);
421-
copyStandardJointAxisProperties(joint, sdfAxis1);
360+
// Screw joints in MuJoCo are modeled by coupling a hinge joint
361+
// (rotation) and a slide joint (translation) along the same axis on
362+
// the child body using a joint equality constraint (mjEQ_JOINT).
363+
// We store the hinge joint (`joint`) as the primary joint in
364+
// JointInfo. This matches DART's choice of using the rotational DOF
365+
// as the primary, ensuring both physics plugins expose consistent
366+
// angular units (radians and rad/s) for screw joints across the
367+
// gz-physics API.
368+
// Like the universal joint, `joint` and `joint2` are compiled
369+
// contiguously on the same child body.
370+
joint = mjs_addJoint(child, nullptr);
371+
joint->type = mjJNT_HINGE;
372+
const auto *sdfAxis1 = sdfJoint->Axis(0);
373+
if (sdfAxis1)
374+
{
375+
convertJointAxis(sdfAxis1, joint->axis);
376+
copyStandardJointAxisProperties(joint, sdfAxis1);
377+
// Disable independent position limits on the primary rotational
378+
// hinge joint. In MuJoCo's soft constraint solver, enforcing limits
379+
// on the hinge joint when coupled with a small thread pitch causes
380+
// premature solver clamping and massive numerical damping. By mapping
381+
// position limits purely onto the translational slide joint, we
382+
// ensure exact kinematic limit enforcement without solver resistance.
383+
joint->limited = false;
384+
}
385+
386+
joint2 = mjs_addJoint(child, nullptr);
387+
joint2->type = mjJNT_SLIDE;
388+
if (sdfAxis1)
389+
{
390+
convertJointAxis(sdfAxis1, joint2->axis);
391+
// We only copy position limits and range to the secondary slide
392+
// joint. All passive dynamics (damping, frictionloss, stiffness) and
393+
// actuator effort limits are enforced purely on the primary
394+
// rotational hinge joint to avoid double-counting and physical unit
395+
// mismatches.
396+
joint2->limited = static_cast<int>(!std::isinf(sdfAxis1->Lower()) &&
397+
!std::isinf(sdfAxis1->Upper()));
398+
if (joint2->limited)
399+
{
400+
const double pitch =
401+
convertScrewThreadPitch(sdfJoint->ScrewThreadPitch());
402+
joint2->range[0] = sdfAxis1->Lower() * pitch;
403+
joint2->range[1] = sdfAxis1->Upper() * pitch;
404+
}
405+
}
422406
}
407+
else if (sdfJoint->Type() == ::sdf::JointType::UNIVERSAL)
408+
{
409+
// Universal joints in MuJoCo are modeled as two hinge joints in
410+
// series on the child body. We only need to keep a pointer to the
411+
// first hinge joint (`joint`) in JointInfo. Since MuJoCo compiles
412+
// joints on the same body contiguously in memory, all getters/setters
413+
// can safely access the second joint (`joint2`)'s state data using
414+
// `nq_index + 1` and `nv_index + 1` without needing to store it
415+
// separately in JointInfo.
416+
joint = mjs_addJoint(child, nullptr);
417+
joint->type = mjJNT_HINGE;
418+
const auto *sdfAxis1 = sdfJoint->Axis(0);
419+
if (sdfAxis1)
420+
{
421+
convertJointAxis(sdfAxis1, joint->axis);
422+
copyStandardJointAxisProperties(joint, sdfAxis1);
423+
}
423424

424-
joint2 = mjs_addJoint(child, nullptr);
425-
joint2->type = mjJNT_HINGE;
426-
const auto *sdfAxis2 = sdfJoint->Axis(1);
427-
if (sdfAxis2)
425+
joint2 = mjs_addJoint(child, nullptr);
426+
joint2->type = mjJNT_HINGE;
427+
const auto *sdfAxis2 = sdfJoint->Axis(1);
428+
if (sdfAxis2)
429+
{
430+
convertJointAxis(sdfAxis2, joint2->axis);
431+
copyStandardJointAxisProperties(joint2, sdfAxis2);
432+
}
433+
}
434+
else if (sdfJoint->Type() != ::sdf::JointType::FIXED)
428435
{
429-
convertJointAxis(sdfAxis2, joint2->axis);
430-
copyStandardJointAxisProperties(joint2, sdfAxis2);
436+
gzwarn << "Joint type " << static_cast<int>(sdfJoint->Type())
437+
<< " in joint [" << sdfJoint->Name() << "] not supported\n";
438+
return;
431439
}
432-
}
433-
else if (sdfJoint->Type() != ::sdf::JointType::FIXED)
434-
{
435-
gzwarn << "Joint type " << static_cast<int>(sdfJoint->Type())
436-
<< " in joint [" << sdfJoint->Name() << "] not supported\n";
437-
return;
438-
}
439440

440-
// Resolve the pose of the joint relative to the body with which
441-
// it's associated. Note that this body is the child link of the joint
442-
// in SDF terms.
443-
auto jointPose = resolveSdfPose(sdfJoint->SemanticPose());
444-
mjsEquality *eq = nullptr;
445-
// Note that no joints will be created when processing a fixed joint.
446-
if (joint)
447-
{
448-
const std::string mjJointName =
449-
::sdf::JoinName(_modelInfo->name, sdfJoint->Name());
450-
mjs_setName(joint->element, mjJointName.c_str());
451-
actuator = mjs_addActuator(_spec, nullptr);
452-
actuator->trntype = mjtTrn::mjTRN_JOINT;
441+
// Resolve the pose of the joint relative to the body with which
442+
// it's associated. Note that this body is the child link of the joint
443+
// in SDF terms.
444+
auto jointPose = resolveSdfPose(sdfJoint->SemanticPose());
445+
mjsEquality *eq = nullptr;
446+
// Note that no joints will be created when processing a fixed joint.
447+
if (joint)
448+
{
449+
const std::string mjJointName =
450+
::sdf::JoinName(_modelInfo->name, sdfJoint->Name());
451+
mjs_setName(joint->element, mjJointName.c_str());
452+
actuator = mjs_addActuator(_spec, nullptr);
453+
actuator->trntype = mjtTrn::mjTRN_JOINT;
453454

454-
mjs_setString(actuator->target, mjJointName.c_str());
455+
mjs_setString(actuator->target, mjJointName.c_str());
455456

456-
copyPos(jointPose.Pos(), joint->pos);
457+
copyPos(jointPose.Pos(), joint->pos);
457458

458-
if (joint2)
459-
{
460-
// We uniquely name the second axis using getJointAxisName helper
461-
// with a flat suffix. This flat name avoids indicating any
462-
// Kinematic/SDF nesting (`::axis2`) while still satisfying
463-
// MuJoCo's requirement that joints must be uniquely named.
464-
const std::string mjJointName2 = getJointAxisName(mjJointName, 1);
465-
mjs_setName(joint2->element, mjJointName2.c_str());
466-
mjsActuator *actuator2 = mjs_addActuator(_spec, nullptr);
467-
actuator2->trntype = mjtTrn::mjTRN_JOINT;
468-
mjs_setString(actuator2->target, mjJointName2.c_str());
469-
470-
copyPos(jointPose.Pos(), joint2->pos);
471-
472-
// If this is a screw joint, couple the slide and hinge axes using
473-
// a joint equality constraint (mjEQ_JOINT) with the specified pitch.
474-
if (sdfJoint->Type() == ::sdf::JointType::SCREW)
459+
if (joint2)
475460
{
476-
eq = mjs_addEquality(_spec, nullptr);
477-
eq->type = mjEQ_JOINT;
478-
eq->active = 1;
479-
mjs_setString(eq->name1, mjJointName2.c_str());
480-
mjs_setString(eq->name2, mjJointName.c_str());
481-
482-
// dif = pos[1] - ref[1] = hinge_pos - hinge_ref
483-
// cpos = pos[0] - ref[0] - data[0] - data[1]*dif = 0
484-
// enforces: slide_pos - slide_ref =
485-
// data[1] * (hinge_pos - hinge_ref)
486-
// where data[1] = pitch (meters/rad) = ScrewThreadPitch/2pi
487-
std::fill(std::begin(eq->data), std::end(eq->data), 0.0);
488-
eq->data[1] =
489-
convertScrewThreadPitch(sdfJoint->ScrewThreadPitch());
461+
// We uniquely name the second axis using getJointAxisName helper
462+
// with a flat suffix. This flat name avoids indicating any
463+
// Kinematic/SDF nesting (`::axis2`) while still satisfying
464+
// MuJoCo's requirement that joints must be uniquely named.
465+
const std::string mjJointName2 = getJointAxisName(mjJointName, 1);
466+
mjs_setName(joint2->element, mjJointName2.c_str());
467+
mjsActuator *actuator2 = mjs_addActuator(_spec, nullptr);
468+
actuator2->trntype = mjtTrn::mjTRN_JOINT;
469+
mjs_setString(actuator2->target, mjJointName2.c_str());
470+
471+
copyPos(jointPose.Pos(), joint2->pos);
472+
473+
// If this is a screw joint, couple the slide and hinge axes using
474+
// a joint equality constraint (mjEQ_JOINT) with the specified pitch.
475+
if (sdfJoint->Type() == ::sdf::JointType::SCREW)
476+
{
477+
eq = mjs_addEquality(_spec, nullptr);
478+
eq->type = mjEQ_JOINT;
479+
eq->active = 1;
480+
mjs_setString(eq->name1, mjJointName2.c_str());
481+
mjs_setString(eq->name2, mjJointName.c_str());
482+
483+
// dif = pos[1] - ref[1] = hinge_pos - hinge_ref
484+
// cpos = pos[0] - ref[0] - data[0] - data[1]*dif = 0
485+
// enforces: slide_pos - slide_ref =
486+
// data[1] * (hinge_pos - hinge_ref)
487+
// where data[1] = pitch (meters/rad) = ScrewThreadPitch/2pi
488+
std::fill(std::begin(eq->data), std::end(eq->data), 0.0);
489+
eq->data[1] =
490+
convertScrewThreadPitch(sdfJoint->ScrewThreadPitch());
491+
}
490492
}
491493
}
492-
}
493-
auto jointInfo =
494-
std::make_shared<JointInfo>(_base.GetNextEntity(), _modelInfo);
495-
jointInfo->name = sdfJoint->Name();
496-
jointInfo->joint = joint;
497-
jointInfo->childBody = child;
498-
jointInfo->actuator = actuator;
499-
jointInfo->worldInfo = worldInfo;
500-
if (sdfJoint->Type() == ::sdf::JointType::SCREW)
501-
{
502-
jointInfo->screwConstraintSpec = eq;
503-
}
504-
if (sdfJoint->Type() == ::sdf::JointType::BALL)
505-
{
506-
jointInfo->worldInfo->ballJointPositionsCache.push_back(std::nullopt);
507-
jointInfo->ballJointCacheIndex =
508-
jointInfo->worldInfo->ballJointPositionsCache.size() - 1;
509-
}
494+
auto jointInfo =
495+
std::make_shared<JointInfo>(_base.GetNextEntity(), _modelInfo);
496+
jointInfo->name = sdfJoint->Name();
497+
jointInfo->joint = joint;
498+
jointInfo->childBody = child;
499+
jointInfo->actuator = actuator;
500+
jointInfo->worldInfo = worldInfo;
501+
if (sdfJoint->Type() == ::sdf::JointType::SCREW)
502+
{
503+
jointInfo->screwConstraintSpec = eq;
504+
}
505+
if (sdfJoint->Type() == ::sdf::JointType::BALL)
506+
{
507+
jointInfo->worldInfo->ballJointPositionsCache.push_back(std::nullopt);
508+
jointInfo->ballJointCacheIndex =
509+
jointInfo->worldInfo->ballJointPositionsCache.size() - 1;
510+
}
510511

511-
auto jointSite = mjs_addSite(child, nullptr);
512-
copyPos(jointPose.Pos(), jointSite->pos);
513-
copyQuat(jointPose.Rot(), jointSite->quat);
514-
_base.frames[jointInfo->entityId] =
515-
std::make_shared<FrameInfo>(jointSite, worldInfo);
512+
auto jointSite = mjs_addSite(child, nullptr);
513+
copyPos(jointPose.Pos(), jointSite->pos);
514+
copyQuat(jointPose.Rot(), jointSite->quat);
515+
_base.frames[jointInfo->entityId] =
516+
std::make_shared<FrameInfo>(jointSite, worldInfo);
516517

517-
_modelInfo->joints.AddEntity(jointInfo->entityId, jointInfo,
518-
jointInfo->name, _modelInfo->entityId);
518+
_modelInfo->joints.AddEntity(jointInfo->entityId, jointInfo,
519+
jointInfo->name, _modelInfo->entityId);
520+
}
519521
}
520522

521523
/// \brief Resolve the pose of a target link (specified by its model name and

0 commit comments

Comments
 (0)