diff --git a/src/ModelReader.cpp b/src/ModelReader.cpp index 4723ff3fc..554af7ed3 100644 --- a/src/ModelReader.cpp +++ b/src/ModelReader.cpp @@ -1,7 +1,9 @@ #define BIORBD_API_EXPORTS #include "ModelReader.h" +#include #include +#include #include "BiorbdModel.h" #include "RigidBody/GeneralizedCoordinates.h" @@ -293,20 +295,21 @@ void Reader::readModelFile(const utils::Path &path, Model *model) { utils::String filePathInString; file.read(filePathInString); utils::Path filePath(meshFolderPrefix + filePathInString); - if (!filePath.extension().compare("bioMesh")) { + utils::String extension(filePath.extension().tolower()); + if (!extension.compare("biomesh")) { mesh = readMeshFileBiorbdSegments( path.folder() + filePath.relativePath()); - } else if (!filePath.extension().compare("ply")) { + } else if (!extension.compare("ply")) { mesh = readMeshFilePly(path.folder() + filePath.relativePath()); - } else if (!filePath.extension().compare("obj")) { + } else if (!extension.compare("obj")) { mesh = readMeshFileObj(path.folder() + filePath.relativePath()); } #ifdef MODULE_VTP_FILES_READER - else if (!filePath.extension().compare("vtp")) { + else if (!extension.compare("vtp")) { mesh = readMeshFileVtp(path.folder() + filePath.relativePath()); } #endif - else if (!filePath.extension().tolower().compare("stl")) { + else if (!extension.compare("stl")) { mesh = readMeshFileStl(path.folder() + filePath.relativePath()); } else { utils::Error::raise( @@ -2183,68 +2186,169 @@ rigidbody::Mesh Reader::readMeshFileBiorbdSegments(const utils::Path &path) { } rigidbody::Mesh Reader::readMeshFilePly(const utils::Path &path) { - // Read a bone file - - // Open file - // std::cout << "Loading marker file: " << path << std::endl; + // Read a polygon file format mesh file #ifdef _WIN32 - utils::IfStream file( - utils::Path::toWindowsFormat(path.absolutePath()).c_str(), std::ios::in); + utils::String filepath( + utils::Path::toWindowsFormat(path.absolutePath()).c_str()); #else - utils::IfStream file(path.absolutePath().c_str(), std::ios::in); + utils::String filepath(path.absolutePath().c_str()); #endif - // Read file - utils::String tp; - - // Know the number of points - file.reachSpecificTag("element"); - file.readSpecificTag("vertex", tp); - size_t nVertex(static_cast(atoi(tp.c_str()))); - int nVertexProperties(file.countTagsInAConsecutiveLines("property")); - - // Find the number of columns for the vertex - file.reachSpecificTag("element"); - file.readSpecificTag("face", tp); - size_t nFaces(static_cast(atoi(tp.c_str()))); - int nFacesProperties(file.countTagsInAConsecutiveLines("property")); + std::ifstream file(filepath.c_str(), std::ios::in); + utils::Error::check(file.is_open(), "Failed to load file " + filepath); + + struct PlyProperty { + PlyProperty(const utils::String &name, bool isList) + : name(name), isList(isList) {} + utils::String name; + bool isList; + }; + + struct PlyElement { + PlyElement(const utils::String &name, size_t count) + : name(name), count(count) {} + utils::String name; + size_t count; + std::vector properties; + }; + + utils::String line; + std::getline(file, line); + std::stringstream firstLine(line); + utils::String plyTag; + firstLine >> plyTag; + utils::Error::check( + !plyTag.tolower().compare("ply"), + filepath + " is not a valid PLY file"); + + std::vector elements; + bool isAscii(false); + int currentElement(-1); + while (std::getline(file, line)) { + std::stringstream ss(line); + utils::String tag; + ss >> tag; + tag = tag.tolower(); + if (!tag.compare("comment") || tag.empty()) { + continue; + } else if (!tag.compare("format")) { + utils::String format; + ss >> format; + isAscii = !format.tolower().compare("ascii"); + } else if (!tag.compare("element")) { + utils::String name; + size_t count; + ss >> name >> count; + elements.push_back(PlyElement(name.tolower(), count)); + currentElement = static_cast(elements.size()) - 1; + } else if (!tag.compare("property")) { + utils::Error::check( + currentElement >= 0, "PLY property found before any element"); + utils::String propertyType; + ss >> propertyType; + if (!propertyType.tolower().compare("list")) { + utils::String countType, valueType, name; + ss >> countType >> valueType >> name; + elements[static_cast(currentElement)].properties.push_back( + PlyProperty(name.tolower(), true)); + } else { + utils::String name; + ss >> name; + elements[static_cast(currentElement)].properties.push_back( + PlyProperty(name.tolower(), false)); + } + } else if (!tag.compare("end_header")) { + break; + } + } - // Trouver le nombre de ?? - file.reachSpecificTag("end_header"); + utils::Error::check( + isAscii, + utils::String("Only ASCII PLY files are supported for meshfile: ") + + filepath); rigidbody::Mesh mesh; mesh.setPath(path); - std::map variable = - std::map(); - // Get all the points - for (size_t iPoints = 0; iPoints < nVertex; ++iPoints) { - utils::Vector3d nodeTp(0, 0, 0); - readVector3d(file, variable, nodeTp); - mesh.addPoint(nodeTp); - // Ignore the columns post XYZ - for (int i = 0; i < nVertexProperties - 3; ++i) { - double dump; - file.read(dump); - } - } - for (size_t iPoints = 0; iPoints < nFaces; ++iPoints) { - rigidbody::MeshFace patchTp; - size_t nVertices; - file.read(nVertices); - if (nVertices != 3) { - utils::Error::raise("Patches must be 3 vertices!"); - } - for (size_t i = 0; i < nVertices; ++i) { - file.read(patchTp(i)); - } - int dump; - // Remove if there are too many columns - for (int i = 0; i < nFacesProperties - 1; ++i) { - file.read(dump); + for (size_t iElement = 0; iElement < elements.size(); ++iElement) { + const PlyElement &element(elements[iElement]); + + for (size_t iRecord = 0; iRecord < element.count; ++iRecord) { + if (!element.name.compare("vertex")) { + utils::Vector3d vertex(0, 0, 0); + for (size_t iProperty = 0; iProperty < element.properties.size(); + ++iProperty) { + const PlyProperty &property(element.properties[iProperty]); + if (property.isList) { + size_t nValues; + double dump; + file >> nValues; + for (size_t i = 0; i < nValues; ++i) { + file >> dump; + } + } else { + double value; + file >> value; + if (!property.name.compare("x")) { + vertex[0] = value; + } else if (!property.name.compare("y")) { + vertex[1] = value; + } else if (!property.name.compare("z")) { + vertex[2] = value; + } + } + } + mesh.addPoint(vertex); + } else if (!element.name.compare("face")) { + std::vector vertexIndices; + for (size_t iProperty = 0; iProperty < element.properties.size(); + ++iProperty) { + const PlyProperty &property(element.properties[iProperty]); + if (property.isList) { + size_t nValues; + file >> nValues; + for (size_t i = 0; i < nValues; ++i) { + int value; + file >> value; + if ( + !property.name.compare("vertex_indices") || + !property.name.compare("vertex_index")) { + vertexIndices.push_back(value); + } + } + } else { + double dump; + file >> dump; + } + } + + utils::Error::check( + vertexIndices.size() >= 3, + "PLY faces must have at least 3 vertices"); + for (size_t i = 1; i + 1 < vertexIndices.size(); ++i) { + mesh.addFace( + {vertexIndices[0], vertexIndices[i], vertexIndices[i + 1]}); + } + } else { + for (size_t iProperty = 0; iProperty < element.properties.size(); + ++iProperty) { + const PlyProperty &property(element.properties[iProperty]); + if (property.isList) { + size_t nValues; + double dump; + file >> nValues; + for (size_t i = 0; i < nValues; ++i) { + file >> dump; + } + } else { + double dump; + file >> dump; + } + } + } } - mesh.addFace(patchTp); } + return mesh; } diff --git a/test/models/meshFiles/cube.PLY b/test/models/meshFiles/cube.PLY new file mode 100644 index 000000000..3a5b199af --- /dev/null +++ b/test/models/meshFiles/cube.PLY @@ -0,0 +1,27 @@ +ply +format ascii 1.0 +comment cube with quad faces to validate PLY triangulation +element vertex 8 +property float x +property float y +property float z +property uchar red +property uchar green +property uchar blue +element face 6 +property list uchar int vertex_indices +end_header +-1 -1 -1 255 255 255 +-1 -1 1 255 255 255 +-1 1 1 255 255 255 +-1 1 -1 255 255 255 +1 -1 -1 255 255 255 +1 -1 1 255 255 255 +1 1 1 255 255 255 +1 1 -1 255 255 255 +4 0 1 2 3 +4 0 4 5 1 +4 1 5 6 2 +4 2 6 7 3 +4 3 7 4 0 +4 4 7 6 5 diff --git a/test/models/simpleWithPlyMeshFile.bioMod b/test/models/simpleWithPlyMeshFile.bioMod new file mode 100644 index 000000000..f133cf754 --- /dev/null +++ b/test/models/simpleWithPlyMeshFile.bioMod @@ -0,0 +1,5 @@ +version 4 + +segment Segment1 + meshfile meshFiles/cube.PLY +endsegment diff --git a/test/test_biorbd.cpp b/test/test_biorbd.cpp index 5772d1217..e021b0b89 100644 --- a/test/test_biorbd.cpp +++ b/test/test_biorbd.cpp @@ -36,6 +36,7 @@ static std::string modelPathWithObj("models/violin.bioMod"); static std::string modelPathWithVtp("models/thoraxWithVtp.bioMod"); #endif static std::string modelPathWithStl("models/pendulum.bioMod"); +static std::string modelPathWithPly("models/simpleWithPlyMeshFile.bioMod"); TEST(FileIO, OpenModel) { EXPECT_NO_THROW(Model model(modelPathForGeneralTesting)); @@ -105,6 +106,13 @@ TEST(MeshFile, FileIO) { Model model(modelPathWithMeshFile); } +TEST(MeshFile, FileIoPly) { + EXPECT_NO_THROW(Model model(modelPathWithPly)); + Model model(modelPathWithPly); + EXPECT_EQ(model.mesh(0).nbVertex(), 8); + EXPECT_EQ(model.mesh(0).faces().size(), 12); +} + #ifndef SKIP_LONG_TESTS TEST(MeshFile, FileIoObj) { EXPECT_NO_THROW(Model model(modelPathWithObj));