diff --git a/include/educelab/core/io/MeshIO_PLY.hpp b/include/educelab/core/io/MeshIO_PLY.hpp index 6f15e5e..e2228f2 100644 --- a/include/educelab/core/io/MeshIO_PLY.hpp +++ b/include/educelab/core/io/MeshIO_PLY.hpp @@ -142,17 +142,26 @@ enum class PLYType { UChar, ///< Unsigned 8-bit integer }; -/** @brief Parse a PLY type token into a @c PLYType enum value */ +/** @brief Parse a PLY type token into a @c PLYType enum value + * + * Accepts two spellings for each type. The eight names in Greg Turk's original + * PLY description (`char`, `uchar`, ... `float`, `double`) are the only ones + * the format defines, and they are what write_ply emits. The sized aliases + * (`int8`, `uint8`, ... `float32`, `float64`) are not in that description, but + * they are in wide circulation: vcglib (and therefore MeshLab) has parsed both + * sets for two decades, and OpenMVS writes the sized ones. Rejecting them + * means being unable to read files we have to read. + */ inline auto parse_ply_type(std::string_view t) -> PLYType { - if (t == "float") return PLYType::Float; - if (t == "double") return PLYType::Double; - if (t == "int") return PLYType::Int; - if (t == "uint") return PLYType::UInt; - if (t == "short") return PLYType::Short; - if (t == "ushort") return PLYType::UShort; - if (t == "char") return PLYType::Char; - if (t == "uchar") return PLYType::UChar; + if (t == "float" or t == "float32") return PLYType::Float; + if (t == "double" or t == "float64") return PLYType::Double; + if (t == "int" or t == "int32") return PLYType::Int; + if (t == "uint" or t == "uint32") return PLYType::UInt; + if (t == "short" or t == "int16") return PLYType::Short; + if (t == "ushort" or t == "uint16") return PLYType::UShort; + if (t == "char" or t == "int8") return PLYType::Char; + if (t == "uchar" or t == "uint8") return PLYType::UChar; throw std::runtime_error( "read_ply: unrecognized property type '" + std::string(t) + "'"); } diff --git a/tests/src/TestMeshIO.cpp b/tests/src/TestMeshIO.cpp index edf61ad..1994eaa 100644 --- a/tests/src/TestMeshIO.cpp +++ b/tests/src/TestMeshIO.cpp @@ -1346,6 +1346,89 @@ TEST_F(PLYTest, BinaryLittleEndian_Read) EXPECT_EQ(dst.face(0), (Mesh3f::Face{0, 1, 2})); } +TEST_F(PLYTest, SizedTypeAliases_ASCII_Read) +{ + // Many third-party writers spell property types with the sized aliases + // (float32, uint8, ...) rather than the names in the original PLY spec. + // These files are never produced by write_ply, so only a hand-crafted + // header exercises the alias path. + const auto path = ply("sized_ascii"); + { + std::ofstream f(path); + f << "ply\n" + << "format ascii 1.0\n" + << "element vertex 3\n" + << "property float32 x\n" + << "property float32 y\n" + << "property float32 z\n" + << "property float64 nx\n" + << "property float64 ny\n" + << "property float64 nz\n" + << "property uint8 red\n" + << "property uint8 green\n" + << "property uint8 blue\n" + << "element face 1\n" + << "property list uint8 uint32 vertex_indices\n" + << "end_header\n" + << "0 0 0 0 0 1 255 0 0\n" + << "1 0 0 0 0 1 0 255 0\n" + << "0 1 0 0 0 1 0 0 255\n" + << "3 0 1 2\n"; + } + + NCMesh dst; + read_ply(path, dst); + + ASSERT_EQ(dst.num_vertices(), 3u); + ASSERT_EQ(dst.num_faces(), 1u); + EXPECT_NEAR(dst.vertex(1)[0], 1.f, 1e-5f); + EXPECT_NEAR(dst.vertex(2)[1], 1.f, 1e-5f); + ASSERT_TRUE(dst.vertex(0).normal.has_value()); + EXPECT_NEAR((*dst.vertex(0).normal)[2], 1.f, 1e-5f); + ASSERT_TRUE(dst.vertex(0).color.has_value()); + EXPECT_EQ(dst.vertex(0).color.value()[0], 255u); + EXPECT_EQ(dst.vertex(1).color.value()[1], 255u); + EXPECT_EQ(dst.vertex(2).color.value()[2], 255u); + EXPECT_EQ(dst.face(0), (NCMesh::Face{0, 1, 2})); +} + +TEST_F(PLYTest, SizedTypeAliases_BinaryLittleEndian_Read) +{ + // Header shape emitted by OpenMVS. The alias names must also resolve to + // the correct byte widths, or the binary reader desynchronizes. + const auto path = ply("sized_binary"); + { + std::ofstream f(path, std::ios::binary); + f << "ply\n" + << "format binary_little_endian 1.0\n" + << "element vertex 3\n" + << "property float32 x\n" + << "property float32 y\n" + << "property float32 z\n" + << "element face 1\n" + << "property list uint8 uint32 vertex_indices\n" + << "end_header\n"; + const float verts[9] = { + 0.f, 0.f, 0.f, + 1.f, 0.f, 0.f, + 0.f, 1.f, 0.f}; + f.write(reinterpret_cast(verts), sizeof(verts)); + const uint8_t cnt = 3; + const uint32_t idx[3] = {0, 1, 2}; + f.write(reinterpret_cast(&cnt), 1); + f.write(reinterpret_cast(idx), sizeof(idx)); + } + + Mesh3f dst; + read_ply(path, dst); + + ASSERT_EQ(dst.num_vertices(), 3u); + ASSERT_EQ(dst.num_faces(), 1u); + EXPECT_NEAR(dst.vertex(1)[0], 1.f, 1e-5f); + EXPECT_NEAR(dst.vertex(2)[1], 1.f, 1e-5f); + EXPECT_EQ(dst.face(0), (Mesh3f::Face{0, 1, 2})); +} + TEST_F(PLYTest, WriteWithUVMap_PerWedgeTexcoord) { // Two triangles sharing an edge with a UV seam.