diff --git a/src/user/user_model.cc b/src/user/user_model.cc index 9ff5362adce..fbfd57712e5 100644 --- a/src/user/user_model.cc +++ b/src/user/user_model.cc @@ -2791,7 +2791,8 @@ void mjCModel::CopyTree(mjModel* m) { m->geom_conaffinity[gid] = pg->conaffinity; m->geom_condim[gid] = pg->condim; m->geom_bodyid[gid] = pg->body->id; - if (pg->mesh) { + // a primitive fitted to a mesh keeps pg->mesh, but must not reference it in mjModel + if (pg->mesh && (pg->type == mjGEOM_MESH || pg->type == mjGEOM_SDF)) { m->geom_dataid[gid] = pg->mesh->id; } else if (pg->hfield) { m->geom_dataid[gid] = pg->hfield->id; diff --git a/test/engine/engine_collision_convex_test.cc b/test/engine/engine_collision_convex_test.cc index 7d4346e5e35..3ee6615770c 100644 --- a/test/engine/engine_collision_convex_test.cc +++ b/test/engine/engine_collision_convex_test.cc @@ -76,5 +76,32 @@ TEST_F(MjcConvexTest, CylinderBox) { mj_deleteModel(model); } +TEST_F(MjcConvexTest, PlaneFittedEllipsoid) { + static constexpr char xml[] = R"( + + + + + + + + + + + + + )"; + char error[1024]; + MjModelPtr model = LoadModelFromString(xml, error, sizeof(error)); + ASSERT_THAT(model.get(), NotNull()) << error; + MjDataPtr data = MakeData(model); + + // plane vs ellipsoid is a single contact, whether or not it was mesh-fitted + mj_forward(model.get(), data.get()); + EXPECT_EQ(data->ncon, 1); +} + } // namespace } // namespace mujoco diff --git a/test/user/user_objects_test.cc b/test/user/user_objects_test.cc index 857847240a3..1c6e19620a0 100644 --- a/test/user/user_objects_test.cc +++ b/test/user/user_objects_test.cc @@ -1153,6 +1153,40 @@ TEST_F(MjCGeomTest, BadMeshZeroMassDensityDoesntError) { EXPECT_EQ(model->body_mass[2], 0); } +TEST_F(MjCGeomTest, FittedPrimitiveHasNoMeshDataid) { + static constexpr char xml[] = R"( + + + + + + + + + + + + + + )"; + std::array error; + MjModelPtr model = LoadModelFromString(xml, error.data(), error.size()); + ASSERT_THAT(model.get(), NotNull()) << error.data(); + ASSERT_EQ(model->ngeom, 6); + + // fitted primitives must not reference the mesh they were fitted to + for (int i = 0; i < 5; i++) { + EXPECT_NE(model->geom_type[i], mjGEOM_MESH); + EXPECT_EQ(model->geom_dataid[i], -1) << "geom " << i; + } + + // an actual mesh geom still references its mesh + EXPECT_EQ(model->geom_type[5], mjGEOM_MESH); + EXPECT_EQ(model->geom_dataid[5], 0); +} + // ------------- test joints -------------------------------------------------- using MjCJointTest = MujocoTest;