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;