From 1cda1e7a8c2094fd0a87507d0c75bc2f109588d1 Mon Sep 17 00:00:00 2001 From: Yuval Tassa Date: Mon, 12 Jan 2026 07:42:18 -0800 Subject: [PATCH] Prevent degenerate body inverse weights in constraints. Fixes #2472 PiperOrigin-RevId: 855223971 Change-Id: I646207f82353b7099aab9e220888fe11a4cd3d6d --- src/engine/engine_setconst.c | 14 +++++++++-- test/engine/engine_forward_test.cc | 37 ++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/engine/engine_setconst.c b/src/engine/engine_setconst.c index af3664bb..34387de4 100644 --- a/src/engine/engine_setconst.c +++ b/src/engine/engine_setconst.c @@ -376,8 +376,18 @@ static void set0(mjModel* m, mjData* d) { } // average diagonal and assign - m->body_invweight0[2*i] = (A[0] + A[7] + A[14])/3; - m->body_invweight0[2*i+1] = (A[21] + A[28] + A[35])/3; + mjtNum tran = (A[0] + A[7] + A[14])/3; + mjtNum rot = (A[21] + A[28] + A[35])/3; + + // if one is zero, use the other to prevent degenerate constraints + if (tran < mjMINVAL && rot > mjMINVAL) { + tran = rot; // use rotation as fallback for translation + } else if (rot < mjMINVAL && tran > mjMINVAL) { + rot = tran; // use translation as fallback for rotation + } + + m->body_invweight0[2*i] = tran; + m->body_invweight0[2*i+1] = rot; } } diff --git a/test/engine/engine_forward_test.cc b/test/engine/engine_forward_test.cc index 601f1965..63f9e0d2 100644 --- a/test/engine/engine_forward_test.cc +++ b/test/engine/engine_forward_test.cc @@ -810,6 +810,43 @@ TEST_F(ForwardTest, MocapQuats) { mj_deleteModel(model); } +// model with degenerate translational inertia +TEST_F(ForwardTest, DegenerateInertia) { + static constexpr char xml[] = R"( + + + + + + + + + + + + + + )"; + char error[1024]; + mjModel* model = LoadModelFromString(xml, error, sizeof(error)); + ASSERT_THAT(model, NotNull()) << error; + mjData* data = mj_makeData(model); + + for (int i = 0; i < 1000; i++) { + mj_step(model, data); + EXPECT_EQ(data->warning[mjWARN_BADQACC].number, 0) + << "divergence at timestep " << i; + if (data->warning[mjWARN_BADQACC].number != 0) { + break; + } + } + + mj_deleteData(data); + mj_deleteModel(model); +} + // user defined 2nd-order activation dynamics: frequency-controlled oscillator // note that scalar mjcb_act_dyn callbacks are expected to return act_dot, but // since we have a vector output we write into act_dot directly