From 076db5d366a087d1741b51b20f53934076cfeda9 Mon Sep 17 00:00:00 2001 From: Alessio Quaglino Date: Fri, 26 Jan 2024 04:53:37 -0800 Subject: [PATCH] Copy face_ into facetexcoord_ if not specified and needed. PiperOrigin-RevId: 601732448 Change-Id: Iffe01076db22201dba2611ea36174f808ae00c1e --- src/user/user_mesh.cc | 12 +++++ test/user/user_mesh_test.cc | 88 +++++++++++++++++++++++++++++++------ 2 files changed, 87 insertions(+), 13 deletions(-) diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index d3a15e2d..827f8174 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -463,6 +463,12 @@ void mjCMesh::Compile(const mjVFS* vfs) { throw mjCError(this, "texcoord must be a multiple of 2"); } + // check size if no face texcoord indices are given + if (usertexcoord_.size() != 2*nvert_ && userfacetexcoord_.empty()) { + throw mjCError(this, + "texcoord must be 2*nv if face texcoord indices are not provided in an OBJ file"); + } + // copy from user ntexcoord_ = (int)usertexcoord_.size()/2; texcoord_ = VecToArray(usertexcoord_, !file_.empty()); @@ -570,6 +576,12 @@ void mjCMesh::Compile(const mjVFS* vfs) { facetexcoord_ = VecToArray(userfacetexcoord_, !file_.empty()); } + // no facetexcoord: copy from faces + if (!facetexcoord_ && texcoord_) { + facetexcoord_ = (int*) mju_malloc(3*nface_*sizeof(int)); + memcpy(facetexcoord_, face_, 3*nface_*sizeof(int)); + } + // facenormal might not exist if usernormal was specified if (!facenormal_) { facenormal_ = (int*) mju_malloc(3*nface_*sizeof(int)); diff --git a/test/user/user_mesh_test.cc b/test/user/user_mesh_test.cc index c218e0c5..497d25d0 100644 --- a/test/user/user_mesh_test.cc +++ b/test/user/user_mesh_test.cc @@ -60,6 +60,7 @@ static const char* const kMalformedFaceOBJPath = using ::testing::HasSubstr; using ::testing::IsNull; +using ::testing::NotNull; // ------------- test invalid filenames ---------------------------------------- @@ -315,7 +316,7 @@ TEST_F(MjCMeshTest, LoadMSH) { char error[1024]; size_t error_sz = 1024; mjModel* model = mj_loadXML(xml_path.c_str(), 0, error, error_sz); - ASSERT_THAT(model, testing::NotNull()) << error; + ASSERT_THAT(model, NotNull()) << error; ASSERT_EQ(model->nmeshvert, 36); mj_deleteModel(model); } @@ -395,7 +396,7 @@ TEST_F(MjCMeshTest, TinyMeshLoads) { )"; mjModel* model = LoadModelFromString(xml); - ASSERT_THAT(model, testing::NotNull()); + ASSERT_THAT(model, NotNull()); mj_deleteModel(model); } @@ -415,7 +416,7 @@ TEST_F(MjCMeshTest, FaceNormalAutogenerated) { )"; mjModel* model = LoadModelFromString(xml); - ASSERT_THAT(model, testing::NotNull()); + ASSERT_THAT(model, NotNull()); mj_deleteModel(model); } @@ -437,7 +438,7 @@ TEST_F(MjCMeshTest, SmallInertiaLoads) { )"; mjModel* model = LoadModelFromString(xml); - ASSERT_THAT(model, testing::NotNull()); + ASSERT_THAT(model, NotNull()); mj_deleteModel(model); } @@ -526,7 +527,7 @@ TEST_F(MjCMeshTest, FlippedFaceAllowedWorld) { )"; std::array error; mjModel* model = LoadModelFromString(xml, error.data(), error.size()); - EXPECT_THAT(model, testing::NotNull()); + EXPECT_THAT(model, NotNull()); CheckTetrahedronWasRescaled(model); mj_deleteModel(model); } @@ -548,7 +549,7 @@ TEST_F(MjCMeshTest, FlippedFaceAllowedNoMass) { )"; std::array error; mjModel* model = LoadModelFromString(xml, error.data(), error.size()); - EXPECT_THAT(model, testing::NotNull()); + EXPECT_THAT(model, NotNull()); CheckTetrahedronWasRescaled(model); mj_deleteModel(model); } @@ -571,7 +572,7 @@ TEST_F(MjCMeshTest, FlippedFaceAllowedInertial) { )"; std::array error; mjModel* model = LoadModelFromString(xml, error.data(), error.size()); - EXPECT_THAT(model, testing::NotNull()); + EXPECT_THAT(model, NotNull()); CheckTetrahedronWasRescaled(model); mj_deleteModel(model); } @@ -593,7 +594,7 @@ TEST_F(MjCMeshTest, FlippedFaceAllowedNegligibleArea) { )"; std::array error; mjModel* model = LoadModelFromString(xml, error.data(), error.size()); - EXPECT_THAT(model, testing::NotNull()); + EXPECT_THAT(model, NotNull()); CheckTetrahedronWasRescaled(model); mj_deleteModel(model); } @@ -649,7 +650,7 @@ TEST_F(MjCMeshTest, AreaTooSmallAllowedWorld) { )"; std::array error; mjModel* model = LoadModelFromString(xml, error.data(), error.size()); - EXPECT_THAT(model, testing::NotNull()); + EXPECT_THAT(model, NotNull()); mj_deleteModel(model); } @@ -692,7 +693,7 @@ TEST_F(MjCMeshTest, VolumeSmallAllowedShell) { )"; std::array error; mjModel* model = LoadModelFromString(xml, error.data(), error.size()); - ASSERT_THAT(model, testing::NotNull()); + ASSERT_THAT(model, NotNull()); EXPECT_LE(mju_abs(model->geom_size[0]), 1); EXPECT_LE(mju_abs(model->geom_size[1]), 1); EXPECT_LE(mju_abs(model->geom_size[2]), 1); @@ -717,7 +718,7 @@ TEST_F(MjCMeshTest, VolumeNegativeDefaultsLegacy) { )"; std::array error; mjModel* model = LoadModelFromString(xml, error.data(), error.size()); - EXPECT_THAT(model, testing::NotNull()); + EXPECT_THAT(model, NotNull()); EXPECT_LE(mju_abs(model->geom_size[0]), 1); EXPECT_LE(mju_abs(model->geom_size[1]), 1); EXPECT_LE(mju_abs(model->geom_size[2]), 1); @@ -740,7 +741,7 @@ TEST_F(MjCMeshTest, VolumeTooSmallAllowedWorld) { )"; std::array error; mjModel* model = LoadModelFromString(xml, error.data(), error.size()); - EXPECT_THAT(model, testing::NotNull()); + EXPECT_THAT(model, NotNull()); mj_deleteModel(model); } @@ -818,7 +819,7 @@ TEST_F(MjCMeshTest, MeshPosQuat) { )"; mjModel* model = LoadModelFromString(xml); - ASSERT_THAT(model, testing::NotNull()); + ASSERT_THAT(model, NotNull()); // Loading the mesh results in an offset of the geom's pos and quat due to the // fact that the geom's center is not the volumetric center of the mesh. To // recover the geom's originally specified pose, the offset used is stored in @@ -862,5 +863,66 @@ TEST_F(MjCMeshTest, MeshPosQuat) { mj_deleteModel(model); } +// ----------------------------- texcoord ------------------------------------- + +TEST_F(MjCMeshTest, CreateFaceTexCoord) { + static constexpr char xml[] = R"( + + + + + + )"; + std::array error; + mjModel* model = LoadModelFromString(xml, error.data(), error.size()); + EXPECT_THAT(model, NotNull()) << error.data(); + mj_deleteModel(model); +} + +TEST_F(MjCMeshTest, UseFaceTexCoord) { + static constexpr char xml[] = R"( + + + + + + )"; + std::array error; + mjModel* model = LoadModelFromString(xml, error.data(), error.size()); + EXPECT_THAT(model, NotNull()) << error.data(); + EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 0]], .0); + EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 1]], .2); + EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 2]], .1); + EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 3]], .0); + EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 4]], .1); + EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 5]], .3); + EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 6]], .2); + EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 7]], .0); + EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 8]], .3); + EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[ 9]], .1); + EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[10]], .2); + EXPECT_FLOAT_EQ(model->mesh_texcoord[2*model->mesh_facetexcoord[11]], .3); + mj_deleteModel(model); +} + +TEST_F(MjCMeshTest, MissingTexCoord) { + static constexpr char xml[] = R"( + + + + + + )"; + std::array error; + mjModel* model = LoadModelFromString(xml, error.data(), error.size()); + EXPECT_THAT(model, testing::IsNull()); + EXPECT_THAT(error.data(), HasSubstr("texcoord must be 2*nv")); +} + + } // namespace } // namespace mujoco