diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index 20244116..ad350223 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -647,19 +647,6 @@ void mjCMesh::LoadFromDecoder(mjResource* resource, bool remove_repeated) { facenormal_.assign(src_mesh->userfacenormal->begin(), src_mesh->userfacenormal->end()); facetexcoord_.assign(src_mesh->userfacetexcoord->begin(), src_mesh->userfacetexcoord->end()); - // correct winding order for left-handed coordinate systems - bool righthand = scale[0] * scale[1] * scale[2] > 0; - if (!righthand) { - for (size_t i = 0; i < face_.size(); i += 3) { - std::swap(face_[i + 1], face_[i + 2]); - } - for (size_t i = 0; i < facenormal_.size(); i += 3) { - std::swap(facenormal_[i + 1], facenormal_[i + 2]); - } - for (size_t i = 0; i < facetexcoord_.size(); i += 3) { - std::swap(facetexcoord_[i + 1], facetexcoord_[i + 2]); - } - } std::vector vert(src_mesh->uservert->begin(), src_mesh->uservert->end()); mj_deleteSpec(mesh_spec); @@ -1434,6 +1421,20 @@ void mjCMesh::Process() { polygon_map_.resize(nvert()); } + // correct winding order for left-handed coordinate systems + bool righthand = scale[0] * scale[1] * scale[2] > 0; + if (!righthand) { + for (size_t i = 0; i < face_.size(); i += 3) { + std::swap(face_[i + 1], face_[i + 2]); + } + for (size_t i = 0; i < facenormal_.size(); i += 3) { + std::swap(facenormal_[i + 1], facenormal_[i + 2]); + } + for (size_t i = 0; i < facetexcoord_.size(); i += 3) { + std::swap(facetexcoord_[i + 1], facetexcoord_[i + 2]); + } + } + // user offset, rotation, scaling ApplyTransformations(); diff --git a/test/user/user_mesh_test.cc b/test/user/user_mesh_test.cc index 80eb76ef..1618b789 100644 --- a/test/user/user_mesh_test.cc +++ b/test/user/user_mesh_test.cc @@ -1086,6 +1086,71 @@ TEST_F(MjCMeshTest, MeshScale) { mj_deleteModel(model); } +TEST_F(MjCMeshTest, NegativeScaleUserMeshCompiles) { + static constexpr char xml[] = R"( + + + + + + + + + )"; + char error[1024]; + mjModel* model = LoadModelFromString(xml, error, sizeof(error)); + ASSERT_THAT(model, NotNull()) << error; + mj_deleteModel(model); +} + +TEST_F(MjCMeshTest, NegativeScaleUserMeshMatchesPositiveScale) { + static constexpr char pos_xml[] = R"( + + + + + + + + + )"; + + static constexpr char neg_xml[] = R"( + + + + + + + + + )"; + + char error[1024]; + mjModel* pos_model = LoadModelFromString(pos_xml, error, sizeof(error)); + ASSERT_THAT(pos_model, NotNull()) << error; + + mjModel* neg_model = LoadModelFromString(neg_xml, error, sizeof(error)); + ASSERT_THAT(neg_model, NotNull()) << error; + + ASSERT_EQ(pos_model->nmeshface, neg_model->nmeshface); + + for (int i = 0; i < pos_model->nmeshface; i++) { + EXPECT_EQ(pos_model->mesh_face[3*i + 0], neg_model->mesh_face[3*i + 0]); + EXPECT_EQ(pos_model->mesh_face[3*i + 1], neg_model->mesh_face[3*i + 2]); + EXPECT_EQ(pos_model->mesh_face[3*i + 2], neg_model->mesh_face[3*i + 1]); + } + + mj_deleteModel(pos_model); + mj_deleteModel(neg_model); +} + TEST_F(MjCMeshTest, ShellInertiaTest) { static constexpr char xml[] = R"(