Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions include/educelab/core/io/MeshIO_PLY.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) + "'");
}
Expand Down
83 changes: 83 additions & 0 deletions tests/src/TestMeshIO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Color::U8C3>()[0], 255u);
EXPECT_EQ(dst.vertex(1).color.value<Color::U8C3>()[1], 255u);
EXPECT_EQ(dst.vertex(2).color.value<Color::U8C3>()[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<const char*>(verts), sizeof(verts));
const uint8_t cnt = 3;
const uint32_t idx[3] = {0, 1, 2};
f.write(reinterpret_cast<const char*>(&cnt), 1);
f.write(reinterpret_cast<const char*>(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.
Expand Down
Loading