From a6011210948252675acef5f5a15e8ad34c437fb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=9A=E6=AD=A3?= <15887509+GZ_greenLeaf@user.noreply.gitee.com> Date: Thu, 22 Jan 2026 21:49:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86ply=E8=AF=BB?= =?UTF-8?q?=E5=86=99=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 通过调用一个稍微修改过的tinyply库,实现了可以读写有混合网格的ply文件。 --- plugins/io/CMakeLists.txt | 3 +- plugins/io/PlyModelPlugin/CMakeLists.txt | 6 + plugins/io/PlyModelPlugin/PlyModelHandler.cpp | 348 ++++++ plugins/io/PlyModelPlugin/PlyModelHandler.h | 28 + plugins/io/PlyModelPlugin/PlyModelPlugin.h | 24 + plugins/io/PlyModelPlugin/PlyModelPlugin.json | 7 + .../io/PlyModelPlugin/Plytest/CMakeLists.txt | 2 + .../PlyModelPlugin/Plytest/TestPlyModelIO.cpp | 108 ++ plugins/io/PlyModelPlugin/Plytest/simple.ply | 18 + .../PlyModelPlugin/Plytest/simple_write.ply | 17 + plugins/io/PlyModelPlugin/Plytest/test.ply | 27 + .../io/PlyModelPlugin/Plytest/test_write.ply | 26 + plugins/io/PlyModelPlugin/tinyply.h | 1108 +++++++++++++++++ 13 files changed, 1721 insertions(+), 1 deletion(-) create mode 100644 plugins/io/PlyModelPlugin/CMakeLists.txt create mode 100644 plugins/io/PlyModelPlugin/PlyModelHandler.cpp create mode 100644 plugins/io/PlyModelPlugin/PlyModelHandler.h create mode 100644 plugins/io/PlyModelPlugin/PlyModelPlugin.h create mode 100644 plugins/io/PlyModelPlugin/PlyModelPlugin.json create mode 100644 plugins/io/PlyModelPlugin/Plytest/CMakeLists.txt create mode 100644 plugins/io/PlyModelPlugin/Plytest/TestPlyModelIO.cpp create mode 100644 plugins/io/PlyModelPlugin/Plytest/simple.ply create mode 100644 plugins/io/PlyModelPlugin/Plytest/simple_write.ply create mode 100644 plugins/io/PlyModelPlugin/Plytest/test.ply create mode 100644 plugins/io/PlyModelPlugin/Plytest/test_write.ply create mode 100644 plugins/io/PlyModelPlugin/tinyply.h diff --git a/plugins/io/CMakeLists.txt b/plugins/io/CMakeLists.txt index b88156c..ff3afb5 100644 --- a/plugins/io/CMakeLists.txt +++ b/plugins/io/CMakeLists.txt @@ -2,4 +2,5 @@ add_subdirectory(OBJModelPlugin) add_subdirectory(MModelPlugin) add_subdirectory(StepModelPlugin) add_subdirectory(VtkLegacyModelPlugin) -add_subdirectory(MeshMeditModelPlugin) \ No newline at end of file +add_subdirectory(MeshMeditModelPlugin) +add_subdirectory(PlyModelPlugin) \ No newline at end of file diff --git a/plugins/io/PlyModelPlugin/CMakeLists.txt b/plugins/io/PlyModelPlugin/CMakeLists.txt new file mode 100644 index 0000000..236c292 --- /dev/null +++ b/plugins/io/PlyModelPlugin/CMakeLists.txt @@ -0,0 +1,6 @@ +precess_add_io_plugin(PlyModelPlugin + SOURCES "PlyModelHandler.cpp" + PLUGIN_H "PlyModelPlugin.h" +) + +add_subdirectory(Plytest) \ No newline at end of file diff --git a/plugins/io/PlyModelPlugin/PlyModelHandler.cpp b/plugins/io/PlyModelPlugin/PlyModelHandler.cpp new file mode 100644 index 0000000..d011f48 --- /dev/null +++ b/plugins/io/PlyModelPlugin/PlyModelHandler.cpp @@ -0,0 +1,348 @@ +/** + * @file PlyModelHandler_ai.cpp + * @brief 使用tinyply库实现的PLY文件读写功能 + * 支持ASCII和二进制格式的PLY文件 + */ +#include "PlyModelHandler.h" +#include "ArgType.h" +#include "MeshData.h" +#include "ModelData.h" + +#define TINYPLY_IMPLEMENTATION +#include + +#include +#include +#include + +namespace systems::io { + +std::unique_ptr PlyModelHandler::read_model(const fs::path& path, const std::vector& args) +{ + try { + // 打开文件流 + std::ifstream ss(path, std::ios::binary); + if (!ss) { + std::fprintf(stderr, "PlyModelHandler: cannot open file %s\n", path.string().c_str()); + return {}; + } + + // 创建tinyply解析器 + tinyply::PlyFile file; + if (!file.parse_header(ss)) { + std::fprintf(stderr, "PlyModelHandler: malformed PLY header in %s\n", path.string().c_str()); + return {}; + } + + // 请求顶点数据 (x, y, z) + auto vertices = file.request_properties_from_element("vertex", { "x", "y", "z" }); + + // 请求面数据 (vertex_indices) + auto faces = file.request_properties_from_element("face", { "vertex_indices" }); + + // 读取数据 + file.read(ss); + + // 创建网格数据 + auto mesh = std::make_unique(); + mesh->init(); + + // 处理顶点数据 + if (vertices && vertices->count > 0) { + mesh->vertex_positions_.reserve(vertices->count); + + if (vertices->t == tinyply::Type::FLOAT32) { + const float* verts = reinterpret_cast(vertices->buffer.get()); + for (size_t i = 0; i < vertices->count; ++i) { + mesh->vertex_positions_.push_back({ + static_cast(verts[i * 3]), + static_cast(verts[i * 3 + 1]), + static_cast(verts[i * 3 + 2]) + }); + } + } else if (vertices->t == tinyply::Type::FLOAT64) { + const double* verts = reinterpret_cast(vertices->buffer.get()); + for (size_t i = 0; i < vertices->count; ++i) { + mesh->vertex_positions_.push_back({ + verts[i * 3], + verts[i * 3 + 1], + verts[i * 3 + 2] + }); + } + } + } + + // 处理面数据(改进:支持不同的 count 类型和 index 类型,使用 memcpy 读取) + if (faces && faces->count > 0) { + mesh->face_vertices_offset_.clear(); + mesh->face_vertices_offset_.push_back(0); + + // 从 header 中获取 face.vertex_indices 的元信息(isList / listCount / listType / propertyType) + bool prop_is_list = false; + size_t prop_list_count = 0; + tinyply::Type prop_list_count_type = tinyply::Type::INVALID; // count 的类型 + tinyply::Type prop_elem_type = faces->t; // 元素类型(索引的类型),faces->t 已由 request_properties_from_element 设置 + + { + auto elements = file.get_elements(); + for (const auto& elem : elements) { + if (elem.name == "face") { + for (const auto& prop : elem.properties) { + if (prop.name == "vertex_indices") { + prop_is_list = prop.isList; + prop_list_count = prop.listCount; + prop_list_count_type = prop.listType; + // 如果 request_properties_from_element 已经设置了类型,则以 faces->t 为准 + if (faces->t == tinyply::Type::INVALID) + prop_elem_type = prop.propertyType; + break; + } + } + break; + } + } + } + + const uint8_t* src = faces->buffer.get_const(); + const size_t total_faces = faces->count; + size_t byte_offset = 0; + const size_t bufferSize = faces->buffer.size_bytes(); + auto ensure_space_at = [&](size_t offset, size_t need) { + if (offset + need > bufferSize) + throw std::runtime_error("PlyModelHandler: unexpected EOF while parsing face buffer"); + }; + + if (prop_is_list && prop_list_count == 0) { + // 变长列表:tinyply 的 buffer 中仅包含元素数据(不包含 count), + // 所以我们使用 faces->listSizes(由 tinyply 在读取时填充)来获取每个 face 的顶点数量。 + if (faces->listSizes.size() == total_faces) { + // 正常情况:使用 tinyply 填充的 per-face listSizes + for (size_t f = 0; f < total_faces; ++f) { + size_t vertex_count = faces->listSizes[f]; + for (size_t k = 0; k < vertex_count; ++k) { + switch (prop_elem_type) { + case tinyply::Type::INT8: { + ensure_space_at(byte_offset, 1); + int8_t v = 0; std::memcpy(&v, src + byte_offset, sizeof(int8_t)); byte_offset += sizeof(int8_t); mesh->face_vertices_.push_back(static_cast(v)); } break; + case tinyply::Type::UINT8: { + ensure_space_at(byte_offset, 1); + uint8_t v = 0; std::memcpy(&v, src + byte_offset, sizeof(uint8_t)); byte_offset += sizeof(uint8_t); mesh->face_vertices_.push_back(static_cast(v)); } break; + case tinyply::Type::INT16: { + ensure_space_at(byte_offset, 2); + int16_t v = 0; std::memcpy(&v, src + byte_offset, sizeof(int16_t)); byte_offset += sizeof(int16_t); mesh->face_vertices_.push_back(static_cast(v)); } break; + case tinyply::Type::UINT16: { + ensure_space_at(byte_offset, 2); + uint16_t v = 0; std::memcpy(&v, src + byte_offset, sizeof(uint16_t)); byte_offset += sizeof(uint16_t); mesh->face_vertices_.push_back(static_cast(v)); } break; + case tinyply::Type::INT32: { + ensure_space_at(byte_offset, 4); + int32_t v = 0; std::memcpy(&v, src + byte_offset, sizeof(int32_t)); byte_offset += sizeof(int32_t); mesh->face_vertices_.push_back(static_cast(v)); } break; + case tinyply::Type::UINT32: { + ensure_space_at(byte_offset, 4); + uint32_t v = 0; std::memcpy(&v, src + byte_offset, sizeof(uint32_t)); byte_offset += sizeof(uint32_t); mesh->face_vertices_.push_back(static_cast(v)); } break; + default: + // 不支持的类型,跳过一个元素宽度(尽量使用 faces->t 的 stride) + byte_offset += tinyply::PropertyTable[faces->t].stride; + break; + } + } + mesh->face_vertices_offset_.push_back(static_cast(mesh->face_vertices_.size())); + } + } else { + // 回退:如果 tinyply 未填充 listSizes,尝试直接从 buffer 中按 header 的 countType 读取每个面的 count(兼容老实现) + std::fprintf(stderr, "PlyModelHandler: face listSizes not available for %s, falling back to buffer counts\n", path.string().c_str()); + // 重置读取偏移 + size_t testOffset = 0; + bool ok = true; + try { + for (size_t f = 0; f < total_faces; ++f) { + uint32_t vertex_count = 0; + // read_count using prop_list_count_type but from buffer/testOffset + switch (prop_list_count_type) { + case tinyply::Type::UINT8: { + ensure_space_at(testOffset, 1); + uint8_t v; std::memcpy(&v, src + testOffset, 1); vertex_count = v; testOffset += 1; } break; + case tinyply::Type::INT8: { + ensure_space_at(testOffset, 1); + int8_t v; std::memcpy(&v, src + testOffset, 1); vertex_count = static_cast(static_cast(v)); testOffset += 1; } break; + case tinyply::Type::UINT16: { + ensure_space_at(testOffset, 2); + uint16_t v; std::memcpy(&v, src + testOffset, 2); vertex_count = v; testOffset += 2; } break; + case tinyply::Type::INT16: { + ensure_space_at(testOffset, 2); + int16_t v; std::memcpy(&v, src + testOffset, 2); vertex_count = static_cast(static_cast(v)); testOffset += 2; } break; + case tinyply::Type::UINT32: { + ensure_space_at(testOffset, 4); + uint32_t v; std::memcpy(&v, src + testOffset, 4); vertex_count = v; testOffset += 4; } break; + case tinyply::Type::INT32: { + ensure_space_at(testOffset, 4); + int32_t v; std::memcpy(&v, src + testOffset, 4); vertex_count = static_cast(v); testOffset += 4; } break; + default: { + ensure_space_at(testOffset, 1); + uint8_t v; std::memcpy(&v, src + testOffset, 1); vertex_count = v; testOffset += 1; } break; + } + + for (uint32_t k = 0; k < vertex_count; ++k) { + uint32_t idx = 0; + switch (prop_elem_type) { + case tinyply::Type::INT8: { + ensure_space_at(testOffset, 1); + int8_t vv; std::memcpy(&vv, src + testOffset, 1); idx = static_cast(static_cast(vv)); testOffset += 1; } break; + case tinyply::Type::UINT8: { + ensure_space_at(testOffset, 1); + uint8_t vv; std::memcpy(&vv, src + testOffset, 1); idx = vv; testOffset += 1; } break; + case tinyply::Type::INT16: { + ensure_space_at(testOffset, 2); + int16_t vv; std::memcpy(&vv, src + testOffset, 2); idx = static_cast(static_cast(vv)); testOffset += 2; } break; + case tinyply::Type::UINT16: { + ensure_space_at(testOffset, 2); + uint16_t vv; std::memcpy(&vv, src + testOffset, 2); idx = vv; testOffset += 2; } break; + case tinyply::Type::INT32: { + ensure_space_at(testOffset, 4); + int32_t vv; std::memcpy(&vv, src + testOffset, 4); idx = static_cast(vv); testOffset += 4; } break; + case tinyply::Type::UINT32: { + ensure_space_at(testOffset, 4); + uint32_t vv; std::memcpy(&vv, src + testOffset, 4); idx = vv; testOffset += 4; } break; + default: + throw std::runtime_error("PlyModelHandler: unsupported face index type in fallback"); + } + mesh->face_vertices_.push_back(static_cast(idx)); + } + mesh->face_vertices_offset_.push_back(static_cast(mesh->face_vertices_.size())); + } + } catch (const std::exception& e) { + ok = false; + std::fprintf(stderr, "PlyModelHandler: fallback parsing failed for %s: %s\n", path.string().c_str(), e.what()); + } + if (!ok) { + // If fallback failed, abort reading faces + std::fprintf(stderr, "PlyModelHandler: cannot parse faces for %s\n", path.string().c_str()); + } + } + } else if (prop_is_list && prop_list_count > 0) { + // 固定长度列表:每个 face 按 prop_list_count 个元素排列(没有 count 字段) + for (size_t f = 0; f < total_faces; ++f) { + for (size_t k = 0; k < prop_list_count; ++k) { + switch (prop_elem_type) { + case tinyply::Type::INT8: { + int8_t v = 0; std::memcpy(&v, src + byte_offset, sizeof(int8_t)); byte_offset += sizeof(int8_t); mesh->face_vertices_.push_back(static_cast(v)); } break; + case tinyply::Type::UINT8: { + uint8_t v = 0; std::memcpy(&v, src + byte_offset, sizeof(uint8_t)); byte_offset += sizeof(uint8_t); mesh->face_vertices_.push_back(static_cast(v)); } break; + case tinyply::Type::INT16: { + int16_t v = 0; std::memcpy(&v, src + byte_offset, sizeof(int16_t)); byte_offset += sizeof(int16_t); mesh->face_vertices_.push_back(static_cast(v)); } break; + case tinyply::Type::UINT16: { + uint16_t v = 0; std::memcpy(&v, src + byte_offset, sizeof(uint16_t)); byte_offset += sizeof(uint16_t); mesh->face_vertices_.push_back(static_cast(v)); } break; + case tinyply::Type::INT32: { + int32_t v = 0; std::memcpy(&v, src + byte_offset, sizeof(int32_t)); byte_offset += sizeof(int32_t); mesh->face_vertices_.push_back(static_cast(v)); } break; + case tinyply::Type::UINT32: { + uint32_t v = 0; std::memcpy(&v, src + byte_offset, sizeof(uint32_t)); byte_offset += sizeof(uint32_t); mesh->face_vertices_.push_back(static_cast(v)); } break; + default: + byte_offset += tinyply::PropertyTable[faces->t].stride; + break; + } + } + mesh->face_vertices_offset_.push_back(static_cast(mesh->face_vertices_.size())); + } + } else { + std::fprintf(stderr, "PlyModelHandler: cannot determine face vertex count/type for %s\n", path.string().c_str()); + } + } + + auto model_data = std::make_unique(std::move(mesh)); + model_data->model_name_ = path.filename().string(); + return model_data; + + } catch (const std::exception& e) { + std::fprintf(stderr, "PlyModelHandler: error reading %s: %s\n", path.string().c_str(), e.what()); + return {}; + } +} + +void PlyModelHandler::write_model(const ModelData& data, const fs::path& path, const std::vector& args) +{ + try { + auto mesh_data = data.asMeshData(); + if (!mesh_data) { + std::fprintf(stderr, "PlyModelHandler: only mesh data is supported for writing\n"); + return; + } + + const auto& verts = mesh_data->vertex_positions_; + const auto& offsets = mesh_data->face_vertices_offset_; + const auto& faces = mesh_data->face_vertices_; + + size_t vertex_count = verts.size(); + size_t face_count = offsets.size() > 1 ? offsets.size() - 1 : 0; + + // 创建tinyply文件 + tinyply::PlyFile file; + + // 准备顶点数据 + std::vector vertices_float; + vertices_float.reserve(vertex_count * 3); + for (const auto& v : verts) { + vertices_float.push_back(static_cast(v[0])); + vertices_float.push_back(static_cast(v[1])); + vertices_float.push_back(static_cast(v[2])); + } + + // 准备面数据 + std::vector face_indices; + std::vector face_vertexs; + for (size_t fi = 0; fi < face_count; ++fi) { + Index start = offsets[fi]; + Index end = offsets[fi + 1]; + Index nv = end - start; + + face_indices.push_back(static_cast(nv)); // 顶点数量 + //face_vertexs_count.push_back(static_cast(nv)); + for (Index k = start; k < end; ++k) { + face_indices.push_back(static_cast(faces[k])); // 顶点索引 + face_vertexs.push_back(static_cast(faces[k])); + } + } + + // 添加顶点属性 + file.add_properties_to_element("vertex", { "x", "y", "z" }, + tinyply::Type::FLOAT32, vertex_count, + reinterpret_cast(vertices_float.data()), + tinyply::Type::INVALID, 0); + + // 添加面属性 + + file.add_properties_to_element("face", { "vertex_indices" }, + tinyply::Type::INT32, face_count, + reinterpret_cast(face_indices.data()), + tinyply::Type::INT8,0); + + /* + file.add_properties_to_element("face", { "vertex_indices" }, + tinyply::Type::INT32, face_count, + reinterpret_cast(face_vertexs.data()), + tinyply::Type::INT8, 4); + */ + // 写入文件 + std::ofstream ofs(path, std::ios::binary); + if (!ofs) { + std::fprintf(stderr, "PlyModelHandler: cannot create file %s\n", path.string().c_str()); + return; + } + + // 默认使用ascii格式写入 + file.write(ofs, false); + + } catch (const std::exception& e) { + std::fprintf(stderr, "PlyModelHandler: error writing %s: %s\n", path.string().c_str(), e.what()); + } +} + +std::vector PlyModelHandler::read_args_type() const +{ + return {}; +} + +std::vector PlyModelHandler::write_args_type() const +{ + return {}; +} + +} // namespace systems::io \ No newline at end of file diff --git a/plugins/io/PlyModelPlugin/PlyModelHandler.h b/plugins/io/PlyModelPlugin/PlyModelHandler.h new file mode 100644 index 0000000..88b47cf --- /dev/null +++ b/plugins/io/PlyModelPlugin/PlyModelHandler.h @@ -0,0 +1,28 @@ +/** + * @file PlyModelHandler.h + * @author 龚正(1740124400@qq.com) + */ +#ifndef PLY_MODEL_HANDLER_H +#define PLY_MODEL_HANDLER_H +#include "ModelIOHandler.h" +#include +class ModelData; +class CTMeshModel; + +namespace systems::io { +/** + * @brief PLY模型文件处理器 + */ +class PlyModelHandler : public ModelIOHandler { +public: + PlyModelHandler() = default; + ~PlyModelHandler() override = default; + + std::unique_ptr read_model(const fs::path& path, const std::vector& args) override; + void write_model(const ModelData& data, const fs::path& path, const std::vector& args) override; + std::vector read_args_type() const override; + std::vector write_args_type() const override; +}; + +} +#endif // !PLY_MODEL_HANDLER_H \ No newline at end of file diff --git a/plugins/io/PlyModelPlugin/PlyModelPlugin.h b/plugins/io/PlyModelPlugin/PlyModelPlugin.h new file mode 100644 index 0000000..40b3a64 --- /dev/null +++ b/plugins/io/PlyModelPlugin/PlyModelPlugin.h @@ -0,0 +1,24 @@ +/** + * @file PlyModelPlugin.h + * @author 龚正(1740124400@qq.com) + */ +#ifndef PLY_MODEL_PLUGIN_H +#define PLY_MODEL_PLUGIN_H +#include "HandlerCreatorDestroyerFactory.h" +#include "PluginBase.h" +#include "PlyModelHandler.h" +#include + +namespace systems::io { +class PlyModelPlugin : public QObject, public PluginBase { + Q_OBJECT + Q_INTERFACES(systems::PluginBase) + Q_PLUGIN_METADATA(IID "com.PreCess.systems.io.PlyModelPlugin/1.0" FILE "PlyModelPlugin.json") +private: + const HandlerCreatorDestroyer& getHandlerCreatorDestroyer() noexcept override final + { + return HandlerCreatorDestroyerFactory::get(); + } +}; +} +#endif // !PLY_MODEL_PLUGIN_H \ No newline at end of file diff --git a/plugins/io/PlyModelPlugin/PlyModelPlugin.json b/plugins/io/PlyModelPlugin/PlyModelPlugin.json new file mode 100644 index 0000000..d854744 --- /dev/null +++ b/plugins/io/PlyModelPlugin/PlyModelPlugin.json @@ -0,0 +1,7 @@ +{ + "system": "ModelIOSystem", + "handler": { + "file_type": "Stanford PLY file", + "extensions": [ "ply" ] + } +} \ No newline at end of file diff --git a/plugins/io/PlyModelPlugin/Plytest/CMakeLists.txt b/plugins/io/PlyModelPlugin/Plytest/CMakeLists.txt new file mode 100644 index 0000000..096d89b --- /dev/null +++ b/plugins/io/PlyModelPlugin/Plytest/CMakeLists.txt @@ -0,0 +1,2 @@ +precess_add_test(TestPlyModelIO TestPlyModelIO.cpp) +precess_test_link_libraries(TestPlyModelIO PlyModelPluginlib DataTest) diff --git a/plugins/io/PlyModelPlugin/Plytest/TestPlyModelIO.cpp b/plugins/io/PlyModelPlugin/Plytest/TestPlyModelIO.cpp new file mode 100644 index 0000000..e45814a --- /dev/null +++ b/plugins/io/PlyModelPlugin/Plytest/TestPlyModelIO.cpp @@ -0,0 +1,108 @@ +// Test for PlyModelHandler: read PLY files, verify mesh content, write out and verify written file +#include "PlyModelHandler.h" +#include "ModelData.h" +#include "MeshData.h" + +#include +#include +#include +#include +#include + +using namespace systems::io; +namespace fs = std::filesystem; + +static bool models_equal(const ModelData& a, const ModelData& b, double eps = 1e-6) +{ + const auto* ma = a.asMeshData(); + const auto* mb = b.asMeshData(); + if (!ma || !mb) return false; + + if (ma->vertex_positions_.size() != mb->vertex_positions_.size()) return false; + if (ma->face_vertices_offset_.size() != mb->face_vertices_offset_.size()) return false; + if (ma->face_vertices_.size() != mb->face_vertices_.size()) return false; + + // compare vertices + for (size_t i = 0; i < ma->vertex_positions_.size(); ++i) { + for (int k = 0; k < 3; ++k) { + double va = ma->vertex_positions_[i][k]; + double vb = mb->vertex_positions_[i][k]; + if (std::fabs(va - vb) > eps) return false; + } + } + + // compare face offsets + for (size_t i = 0; i < ma->face_vertices_offset_.size(); ++i) { + if (ma->face_vertices_offset_[i] != mb->face_vertices_offset_[i]) return false; + } + + // compare face indices + for (size_t i = 0; i < ma->face_vertices_.size(); ++i) { + if (ma->face_vertices_[i] != mb->face_vertices_[i]) return false; + } + + return true; +} + +static fs::path test_dir_from_source() +{ + // Determine path to test files relative to this source file. + fs::path p = fs::path(__FILE__).parent_path(); + return p; +} + +TEST_CASE("PlyModelHandler ReadWrite simple.ply") +{ + PlyModelHandler handler; + fs::path dir = test_dir_from_source(); + fs::path infile = dir / "simple.ply"; + REQUIRE(fs::exists(infile)); + + auto model = handler.read_model(infile, {}); + REQUIRE(model != nullptr); + auto* mesh = model->asMeshData(); + REQUIRE(mesh != nullptr); + + // simple.ply: 5 vertices, 3 faces + REQUIRE(mesh->vertex_positions_.size() == 5u); + REQUIRE(mesh->face_vertices_offset_.size() == 4u); // offsets contain 0 + 3 faces + REQUIRE(mesh->face_vertices_.size() == 3u * 3u); // each face has 3 vertices in this file + + // write out + fs::path out = dir / "simple_write.ply"; + handler.write_model(*model, out, {}); + REQUIRE(fs::exists(out)); + + // read back written file and compare semantically (positions and topology) + auto model2 = handler.read_model(out, {}); + REQUIRE(model2 != nullptr); + REQUIRE(models_equal(*model, *model2)); +} + +TEST_CASE("PlyModelHandler ReadWrite test.ply") +{ + PlyModelHandler handler; + fs::path dir = test_dir_from_source(); + fs::path infile = dir / "test.ply"; + REQUIRE(fs::exists(infile)); + + auto model = handler.read_model(infile, {}); + REQUIRE(model != nullptr); + auto* mesh = model->asMeshData(); + REQUIRE(mesh != nullptr); + + // test.ply: 10 vertices, 7 faces + REQUIRE(mesh->vertex_positions_.size() == 10u); + REQUIRE(mesh->face_vertices_offset_.size() == 8u); // 0 + 7 faces + + // write out + fs::path out = dir / "test_write.ply"; + handler.write_model(*model, out, {}); + REQUIRE(fs::exists(out)); + + // read back written file and compare semantically + auto model2 = handler.read_model(out, {}); + REQUIRE(model2 != nullptr); + REQUIRE(models_equal(*model, *model2)); +} + diff --git a/plugins/io/PlyModelPlugin/Plytest/simple.ply b/plugins/io/PlyModelPlugin/Plytest/simple.ply new file mode 100644 index 0000000..f680e8c --- /dev/null +++ b/plugins/io/PlyModelPlugin/Plytest/simple.ply @@ -0,0 +1,18 @@ +ply +format ascii 1.0 +comment Simple test mesh +element vertex 5 +property float x +property float y +property float z +element face 3 +property list uchar int vertex_indices +end_header +0 0 0 +1 0 0 +0.5 1 0 +0 0 1 +0.5 0.5 0.5 +3 0 1 2 +3 0 1 3 +3 1 2 4 \ No newline at end of file diff --git a/plugins/io/PlyModelPlugin/Plytest/simple_write.ply b/plugins/io/PlyModelPlugin/Plytest/simple_write.ply new file mode 100644 index 0000000..2ada792 --- /dev/null +++ b/plugins/io/PlyModelPlugin/Plytest/simple_write.ply @@ -0,0 +1,17 @@ +ply +format ascii 1.0 +element vertex 5 +property float x +property float y +property float z +element face 3 +property list char int vertex_indices +end_header +0 0 0 +1 0 0 +0.5 1 0 +0 0 1 +0.5 0.5 0.5 +3 0 1 2 +3 0 1 3 +3 1 2 4 diff --git a/plugins/io/PlyModelPlugin/Plytest/test.ply b/plugins/io/PlyModelPlugin/Plytest/test.ply new file mode 100644 index 0000000..f45214b --- /dev/null +++ b/plugins/io/PlyModelPlugin/Plytest/test.ply @@ -0,0 +1,27 @@ +ply +format ascii 1.0 +comment Created for mesh software testing +element vertex 10 +property float x +property float y +property float z +element face 7 +property list uint32 int32 vertex_indices +end_header +0 0 0 +1 0 0 +1 1 0 +0 1 0 +0 0 1 +1 0 1 +1 1 1 +0 1 1 +1 0 2 +0 1 2 +3 4 8 9 +4 0 1 2 3 +4 4 5 6 7 +4 0 4 7 3 +4 1 5 6 2 +4 0 1 5 4 +4 3 2 6 7 \ No newline at end of file diff --git a/plugins/io/PlyModelPlugin/Plytest/test_write.ply b/plugins/io/PlyModelPlugin/Plytest/test_write.ply new file mode 100644 index 0000000..7b4ca5b --- /dev/null +++ b/plugins/io/PlyModelPlugin/Plytest/test_write.ply @@ -0,0 +1,26 @@ +ply +format ascii 1.0 +element vertex 10 +property float x +property float y +property float z +element face 7 +property list char int vertex_indices +end_header +0 0 0 +1 0 0 +1 1 0 +0 1 0 +0 0 1 +1 0 1 +1 1 1 +0 1 1 +1 0 2 +0 1 2 +3 4 8 9 +4 0 1 2 3 +4 4 5 6 7 +4 0 4 7 3 +4 1 5 6 2 +4 0 1 5 4 +4 3 2 6 7 diff --git a/plugins/io/PlyModelPlugin/tinyply.h b/plugins/io/PlyModelPlugin/tinyply.h new file mode 100644 index 0000000..c152096 --- /dev/null +++ b/plugins/io/PlyModelPlugin/tinyply.h @@ -0,0 +1,1108 @@ +/* + * tinyply 2.3.4 (https://github.com/ddiakopoulos/tinyply) + * + * A single-header, zero-dependency (except the C++ STL) public domain implementation + * of the PLY mesh file format. Requires C++11; errors are handled through exceptions. + * + * This software is in the public domain. Where that dedication is not + * recognized, you are granted a perpetual, irrevocable license to copy, + * distribute, and modify this file as you see fit. + * + * Authored by Dimitri Diakopoulos (http://www.dimitridiakopoulos.com) + * + * tinyply.h may be included in many files, however in a single compiled file, + * the implementation must be created with the following defined prior to header inclusion + * #define TINYPLY_IMPLEMENTATION + * + */ + +//////////////////////// +// tinyply header // +//////////////////////// + +#ifndef tinyply_h +#define tinyply_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tinyply { + +enum class Type : uint8_t { + INVALID, + INT8, + UINT8, + INT16, + UINT16, + INT32, + UINT32, + FLOAT32, + FLOAT64 +}; + +struct PropertyInfo { + PropertyInfo() { }; + PropertyInfo(int stride, std::string str) + : stride(stride) + , str(str) + { + } + int stride { 0 }; + std::string str; +}; + +static std::map PropertyTable { + { Type::INT8, PropertyInfo(1, std::string("char")) }, + { Type::UINT8, PropertyInfo(1, std::string("uchar")) }, + { Type::INT16, PropertyInfo(2, std::string("short")) }, + { Type::UINT16, PropertyInfo(2, std::string("ushort")) }, + { Type::INT32, PropertyInfo(4, std::string("int")) }, + { Type::UINT32, PropertyInfo(4, std::string("uint")) }, + { Type::FLOAT32, PropertyInfo(4, std::string("float")) }, + { Type::FLOAT64, PropertyInfo(8, std::string("double")) }, + { Type::INVALID, PropertyInfo(0, std::string("INVALID")) } +}; + +class Buffer { + uint8_t* alias { nullptr }; + struct delete_array { + void operator()(uint8_t* p) { delete[] p; } + }; + std::unique_ptr data; + size_t size { 0 }; + +public: + Buffer() { }; + Buffer(const size_t size) + : data(new uint8_t[size], delete_array()) + , size(size) + { + alias = data.get(); + } // allocating + Buffer(const uint8_t* ptr) + : alias(const_cast(ptr)) + { + } // non-allocating, todo: set size? + uint8_t* get() { return alias; } + const uint8_t* get_const() const { return alias; } + size_t size_bytes() const { return size; } +}; + +struct PlyData { + Type t; + Buffer buffer; + size_t count { 0 }; + bool isList { false }; + std::vector listSizes; // per-item list sizes when property is a list (populated during read) +}; + +struct PlyProperty { + PlyProperty(std::istream& is); + PlyProperty(Type type, std::string& _name) + : name(_name) + , propertyType(type) + { + } + PlyProperty(Type list_type, Type prop_type, std::string& _name, size_t list_count) + : name(_name) + , propertyType(prop_type) + , isList(true) + , listType(list_type) + , listCount(list_count) + { + } + std::string name; + Type propertyType { Type::INVALID }; + bool isList { false }; + Type listType { Type::INVALID }; + size_t listCount { 0 }; +}; + +struct PlyElement { + PlyElement(std::istream& istream); + PlyElement(const std::string& _name, size_t count) + : name(_name) + , size(count) + { + } + std::string name; + size_t size { 0 }; + std::vector properties; +}; + +struct PlyFile { + struct PlyFileImpl; + std::unique_ptr impl; + + PlyFile(); + ~PlyFile(); + + /* + * The ply format requires an ascii header. This can be used to determine at + * runtime which properties or elements exist in the file. Limited validation of the + * header is performed; it is assumed the header correctly reflects the contents of the + * payload. This function may throw. Returns true on success, false on failure. + */ + bool parse_header(std::istream& is); + + /* + * Execute a read operation. Data must be requested via `request_properties_from_element(...)` + * prior to calling this function. + */ + void read(std::istream& is); + + /* + * `write` performs no validation and assumes that the data passed into + * `add_properties_to_element` is well-formed. + */ + void write(std::ostream& os, bool isBinary); + + /* + * These functions are valid after a call to `parse_header(...)`. In the case of + * writing, get_comments() reference may also be used to add new comments to the ply header. + */ + std::vector get_elements() const; + std::vector get_info() const; + std::vector& get_comments(); + bool is_binary_file() const; + + /* + * In the general case where |list_size_hint| is zero, `read` performs a two-pass + * parse to support variable length lists. The most general use of the + * ply format is storing triangle meshes. When this fact is known a-priori, we can pass + * an expected list length that will apply to this element. Doing so results in an up-front + * memory allocation and a single-pass import, a 2x performance optimization. + */ + std::shared_ptr request_properties_from_element(const std::string& elementKey, + const std::vector propertyKeys, const uint32_t list_size_hint = 0); + + void add_properties_to_element(const std::string& elementKey, + const std::vector propertyKeys, + const Type type, + const size_t count, + const uint8_t* data, + const Type listType, + const size_t listCount); +}; + +} // end namespace tinyply + +#endif // end tinyply_h + +//////////////////////////////// +// tinyply implementation // +//////////////////////////////// + +#ifdef TINYPLY_IMPLEMENTATION + +#include +#include +#include +#include +#include + +namespace tinyply { + +using namespace std; + +template +inline T2 endian_swap(const T& v) noexcept { return v; } +template <> +inline uint16_t endian_swap(const uint16_t& v) noexcept { return (v << 8) | (v >> 8); } +template <> +inline uint32_t endian_swap(const uint32_t& v) noexcept { return (v << 24) | ((v << 8) & 0x00ff0000) | ((v >> 8) & 0x0000ff00) | (v >> 24); } +template <> +inline uint64_t endian_swap(const uint64_t& v) noexcept +{ + return (((v & 0x00000000000000ffLL) << 56) | ((v & 0x000000000000ff00LL) << 40) | ((v & 0x0000000000ff0000LL) << 24) | ((v & 0x00000000ff000000LL) << 8) | ((v & 0x000000ff00000000LL) >> 8) | ((v & 0x0000ff0000000000LL) >> 24) | ((v & 0x00ff000000000000LL) >> 40) | ((v & 0xff00000000000000LL) >> 56)); +} +template <> +inline int16_t endian_swap(const int16_t& v) noexcept +{ + uint16_t r = endian_swap(*(uint16_t*)&v); + return *(int16_t*)&r; +} +template <> +inline int32_t endian_swap(const int32_t& v) noexcept +{ + uint32_t r = endian_swap(*(uint32_t*)&v); + return *(int32_t*)&r; +} +template <> +inline int64_t endian_swap(const int64_t& v) noexcept +{ + uint64_t r = endian_swap(*(uint64_t*)&v); + return *(int64_t*)&r; +} +template <> +inline float endian_swap(const uint32_t& v) noexcept +{ + union { + float f; + uint32_t i; + }; + i = endian_swap(v); + return f; +} +template <> +inline double endian_swap(const uint64_t& v) noexcept +{ + union { + double d; + uint64_t i; + }; + i = endian_swap(v); + return d; +} + +inline uint32_t hash_fnv1a(const std::string& str) noexcept +{ + static const uint32_t fnv1aBase32 = 0x811C9DC5u; + static const uint32_t fnv1aPrime32 = 0x01000193u; + uint32_t result = fnv1aBase32; + for (auto& c : str) { + result ^= static_cast(c); + result *= fnv1aPrime32; + } + return result; +} + +inline Type property_type_from_string(const std::string& t) noexcept +{ + if (t == "int8" || t == "char") + return Type::INT8; + else if (t == "uint8" || t == "uchar") + return Type::UINT8; + else if (t == "int16" || t == "short") + return Type::INT16; + else if (t == "uint16" || t == "ushort") + return Type::UINT16; + else if (t == "int32" || t == "int") + return Type::INT32; + else if (t == "uint32" || t == "uint") + return Type::UINT32; + else if (t == "float32" || t == "float") + return Type::FLOAT32; + else if (t == "float64" || t == "double") + return Type::FLOAT64; + return Type::INVALID; +} + +struct PlyFile::PlyFileImpl { + struct PlyDataCursor { + size_t byteOffset { 0 }; + size_t totalSizeBytes { 0 }; + }; + + struct ParsingHelper { + std::shared_ptr data; + std::shared_ptr cursor; + uint32_t list_size_hint; + }; + + struct PropertyLookup { + ParsingHelper* helper { nullptr }; + bool skip { false }; + size_t prop_stride { 0 }; // precomputed + size_t list_stride { 0 }; // precomputed + }; + + std::unordered_map userData; + + bool isBinary = false; + bool isBigEndian = false; + std::vector elements; + std::vector comments; + std::vector objInfo; + uint8_t scratch[64]; // large enough for max list size + + void read(std::istream& is); + void write(std::ostream& os, bool isBinary); + + std::shared_ptr request_properties_from_element(const std::string& elementKey, + const std::vector propertyKeys, + const uint32_t list_size_hint); + + void add_properties_to_element(const std::string& elementKey, + const std::vector propertyKeys, + const Type type, const size_t count, const uint8_t* data, const Type listType, const size_t listCount); + + size_t read_property_binary(const size_t& stride, void* dest, size_t& destOffset, size_t destSize, std::istream& is); + size_t read_property_ascii(const Type& t, const size_t& stride, void* dest, size_t& destOffset, size_t destSize, std::istream& is); + + std::vector> make_property_lookup_table(); + + bool parse_header(std::istream& is); + void parse_data(std::istream& is, bool firstPass); + void read_header_format(std::istream& is); + void read_header_element(std::istream& is); + void read_header_property(std::istream& is); + void read_header_text(std::string line, std::vector& place, int erase = 0); + + void write_header(std::ostream& os) noexcept; + void write_ascii_internal(std::ostream& os) noexcept; + void write_binary_internal(std::ostream& os) noexcept; + void write_property_ascii(Type t, std::ostream& os, const uint8_t* src, size_t& srcOffset); + void write_property_binary(std::ostream& os, const uint8_t* src, size_t& srcOffset, const size_t& stride) noexcept; +}; + +PlyProperty::PlyProperty(std::istream& is) + : isList(false) +{ + std::string type; + is >> type; + if (type == "list") { + std::string countType; + is >> countType >> type; + listType = property_type_from_string(countType); + isList = true; + } + propertyType = property_type_from_string(type); + is >> name; +} + +PlyElement::PlyElement(std::istream& is) +{ + is >> name >> size; +} + +template +inline T ply_read_ascii(std::istream& is) +{ + T data; + is >> data; + return data; +} + +template +inline void endian_swap_buffer(uint8_t* data_ptr, const size_t num_bytes, const size_t stride) +{ + for (size_t count = 0; count < num_bytes; count += stride) { + *(reinterpret_cast(data_ptr)) = endian_swap(*(reinterpret_cast(data_ptr))); + data_ptr += stride; + } +} + +template +void ply_cast_ascii(void* dest, std::istream& is) +{ + *(static_cast(dest)) = ply_read_ascii(is); +} + +int64_t find_element(const std::string& key, const std::vector& list) +{ + for (size_t i = 0; i < list.size(); i++) + if (list[i].name == key) + return i; + return -1; +} + +int64_t find_property(const std::string& key, const std::vector& list) +{ + for (size_t i = 0; i < list.size(); ++i) + if (list[i].name == key) + return i; + return -1; +} + +// The `userData` table is an easy data structure for capturing what data the +// user would like out of the ply file, but an inner-loop hash lookup is non-ideal. +// The property lookup table flattens the table down into a 2D array optimized +// for parsing. The first index is the element, and the second index is the property. +std::vector> PlyFile::PlyFileImpl::make_property_lookup_table() +{ + std::vector> element_property_lookup; + + for (auto& element : elements) { + std::vector lookups; + + for (auto& property : element.properties) { + PropertyLookup f; + + auto cursorIt = userData.find(hash_fnv1a(element.name + property.name)); + if (cursorIt != userData.end()) + f.helper = &cursorIt->second; + else + f.skip = true; + + f.prop_stride = PropertyTable[property.propertyType].stride; + if (property.isList) + f.list_stride = PropertyTable[property.listType].stride; + + lookups.push_back(f); + } + + element_property_lookup.push_back(lookups); + } + + return element_property_lookup; +} + +bool PlyFile::PlyFileImpl::parse_header(std::istream& is) +{ + std::string line; + bool success = true; + while (std::getline(is, line)) { + std::istringstream ls(line); + std::string token; + ls >> token; + if (token == "ply" || token == "PLY" || token == "") + continue; + else if (token == "comment") + read_header_text(line, comments, 8); + else if (token == "format") + read_header_format(ls); + else if (token == "element") + read_header_element(ls); + else if (token == "property") + read_header_property(ls); + else if (token == "obj_info") + read_header_text(line, objInfo, 9); + else if (token == "end_header") + break; + else + success = false; // unexpected header field + } + return success; +} + +void PlyFile::PlyFileImpl::read_header_text(std::string line, std::vector& place, int erase) +{ + place.push_back((erase > 0) ? line.erase(0, erase) : line); +} + +void PlyFile::PlyFileImpl::read_header_format(std::istream& is) +{ + std::string s; + (is >> s); + if (s == "binary_little_endian") + isBinary = true; + else if (s == "binary_big_endian") + isBinary = isBigEndian = true; +} + +void PlyFile::PlyFileImpl::read_header_element(std::istream& is) +{ + elements.emplace_back(is); +} + +void PlyFile::PlyFileImpl::read_header_property(std::istream& is) +{ + if (!elements.size()) + throw std::runtime_error("no elements defined; file is malformed"); + elements.back().properties.emplace_back(is); +} + +size_t PlyFile::PlyFileImpl::read_property_binary(const size_t& stride, void* dest, size_t& destOffset, size_t destSize, std::istream& is) +{ + if (destOffset + stride > destSize) { + throw std::runtime_error("unexpected EOF. malformed file?"); + } + + destOffset += stride; + is.read((char*)dest, stride); + return stride; +} + +size_t PlyFile::PlyFileImpl::read_property_ascii(const Type& t, const size_t& stride, void* dest, size_t& destOffset, size_t destSize, std::istream& is) +{ + if (destOffset + stride > destSize) { + throw std::runtime_error("unexpected EOF. malformed file?"); + } + + destOffset += stride; + switch (t) { + case Type::INT8: + *((int8_t*)dest) = static_cast(ply_read_ascii(is)); + break; + case Type::UINT8: + *((uint8_t*)dest) = static_cast(ply_read_ascii(is)); + break; + case Type::INT16: + ply_cast_ascii(dest, is); + break; + case Type::UINT16: + ply_cast_ascii(dest, is); + break; + case Type::INT32: + ply_cast_ascii(dest, is); + break; + case Type::UINT32: + ply_cast_ascii(dest, is); + break; + case Type::FLOAT32: + ply_cast_ascii(dest, is); + break; + case Type::FLOAT64: + ply_cast_ascii(dest, is); + break; + case Type::INVALID: + throw std::invalid_argument("invalid ply property"); + } + return stride; +} + +void PlyFile::PlyFileImpl::write_property_ascii(Type t, std::ostream& os, const uint8_t* src, size_t& srcOffset) +{ + switch (t) { + case Type::INT8: + os << static_cast(*reinterpret_cast(src)); + break; + case Type::UINT8: + os << static_cast(*reinterpret_cast(src)); + break; + case Type::INT16: + os << *reinterpret_cast(src); + break; + case Type::UINT16: + os << *reinterpret_cast(src); + break; + case Type::INT32: + os << *reinterpret_cast(src); + break; + case Type::UINT32: + os << *reinterpret_cast(src); + break; + case Type::FLOAT32: + os << *reinterpret_cast(src); + break; + case Type::FLOAT64: + os << *reinterpret_cast(src); + break; + case Type::INVALID: + throw std::invalid_argument("invalid ply property"); + } + os << " "; + srcOffset += PropertyTable[t].stride; +} + +void PlyFile::PlyFileImpl::write_property_binary(std::ostream& os, const uint8_t* src, size_t& srcOffset, const size_t& stride) noexcept +{ + os.write((char*)src, stride); + srcOffset += stride; +} + +void PlyFile::PlyFileImpl::read(std::istream& is) +{ + std::vector> buffers; + for (auto& entry : userData) + buffers.push_back(entry.second.data); + + // Discover if we can allocate up front without parsing the file twice + uint32_t list_hints = 0; + for (auto& b : buffers) + for (auto& entry : userData) { + list_hints += entry.second.list_size_hint; + (void)b; + } + + // No list hints? Then we need to calculate how much memory to allocate + if (list_hints == 0) { + parse_data(is, true); + } + + // Count the number of properties (required for allocation) + // e.g. if we have properties x y and z requested, we ensure + // that their buffer points to the same PlyData + std::unordered_map unique_data_count; + for (auto& ptr : buffers) + unique_data_count[ptr.get()] += 1; + + // Since group-requested properties share the same cursor, + // we need to find unique cursors so we only allocate once + std::sort(buffers.begin(), buffers.end()); + buffers.erase(std::unique(buffers.begin(), buffers.end()), buffers.end()); + + // We sorted by ptrs on PlyData, need to remap back onto its cursor in the userData table + for (auto& b : buffers) { + for (auto& entry : userData) { + if (entry.second.data == b && b->buffer.get() == nullptr) { + // If we didn't receive any list hints, it means we did two passes over the + // file to compute the total length of all (potentially) variable-length lists + if (list_hints == 0) { + b->buffer = Buffer(entry.second.cursor->totalSizeBytes); + } else { + // otherwise, we can allocate up front, skipping the first pass. + const size_t list_size_multiplier = (entry.second.data->isList ? entry.second.list_size_hint : 1); + auto bytes_per_property = entry.second.data->count * PropertyTable[entry.second.data->t].stride * list_size_multiplier; + bytes_per_property *= unique_data_count[b.get()]; + b->buffer = Buffer(bytes_per_property); + } + } + } + } + + // Populate the data + parse_data(is, false); + + // In-place big-endian to little-endian swapping if required + if (isBigEndian) { + for (auto& b : buffers) { + uint8_t* data_ptr = b->buffer.get(); + const size_t stride = PropertyTable[b->t].stride; + const size_t buffer_size_bytes = b->buffer.size_bytes(); + + switch (b->t) { + case Type::INT16: + endian_swap_buffer(data_ptr, buffer_size_bytes, stride); + break; + case Type::UINT16: + endian_swap_buffer(data_ptr, buffer_size_bytes, stride); + break; + case Type::INT32: + endian_swap_buffer(data_ptr, buffer_size_bytes, stride); + break; + case Type::UINT32: + endian_swap_buffer(data_ptr, buffer_size_bytes, stride); + break; + case Type::FLOAT32: + endian_swap_buffer(data_ptr, buffer_size_bytes, stride); + break; + case Type::FLOAT64: + endian_swap_buffer(data_ptr, buffer_size_bytes, stride); + break; + default: + break; + } + } + } +} + +void PlyFile::PlyFileImpl::write(std::ostream& os, bool _isBinary) +{ + for (auto& d : userData) { + d.second.cursor->byteOffset = 0; + } + if (_isBinary) { + isBinary = true; + isBigEndian = false; + write_binary_internal(os); + } else { + isBinary = false; + isBigEndian = false; + write_ascii_internal(os); + } +} + +void PlyFile::PlyFileImpl::write_binary_internal(std::ostream& os) noexcept +{ + isBinary = true; + + write_header(os); + + uint8_t listSize[4] = { 0, 0, 0, 0 }; + size_t dummyCount = 0; + + auto element_property_lookup = make_property_lookup_table(); + + size_t element_idx = 0; + for (auto& e : elements) { + for (size_t i = 0; i < e.size; ++i) { + size_t property_index = 0; + for (auto& p : e.properties) { + auto& f = element_property_lookup[element_idx][property_index]; + auto* helper = f.helper; + if (f.skip || helper == nullptr) + continue; + + if (p.isList) { + std::memcpy(listSize, &p.listCount, sizeof(uint32_t)); + write_property_binary(os, listSize, dummyCount, f.list_stride); + write_property_binary(os, (helper->data->buffer.get_const() + helper->cursor->byteOffset), helper->cursor->byteOffset, f.prop_stride * p.listCount); + } else { + write_property_binary(os, (helper->data->buffer.get_const() + helper->cursor->byteOffset), helper->cursor->byteOffset, f.prop_stride); + } + property_index++; + } + } + element_idx++; + } +} +/*����ԭ���Ĵ���ʵ�֣������޷�����䳤����������� +void PlyFile::PlyFileImpl::write_ascii_internal(std::ostream& os) noexcept +{ + write_header(os); + + auto element_property_lookup = make_property_lookup_table(); + + size_t element_idx = 0; + for (auto& e : elements) { + for (size_t i = 0; i < e.size; ++i) { + size_t property_index = 0; + for (auto& p : e.properties) { + auto& f = element_property_lookup[element_idx][property_index]; + auto* helper = f.helper; + if (f.skip || helper == nullptr) + continue; + + if (p.isList) { + os << p.listCount << " "; + for (size_t j = 0; j < p.listCount; ++j) { + write_property_ascii(p.propertyType, os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset); + } + } else { + write_property_ascii(p.propertyType, os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset); + } + property_index++; + } + os << "\n"; + } + element_idx++; + } +} +*/ + +//��������չ�Ĵ���ʵ�֣�Ŀ���ǿ�������䳤���� +void PlyFile::PlyFileImpl::write_ascii_internal(std::ostream& os) noexcept +{ + write_header(os); + + auto element_property_lookup = make_property_lookup_table(); + + size_t element_idx = 0; + for (auto& e : elements) { + for (size_t i = 0; i < e.size; ++i) { + size_t property_index = 0; + for (auto& p : e.properties) { + auto& f = element_property_lookup[element_idx][property_index]; + auto* helper = f.helper; + if (f.skip || helper == nullptr) + continue; + + if (p.isList) { + if (0==p.listCount) { + // �䳤�б����������ж�ȡ�б����� + uint8_t listSize = *(helper->data->buffer.get() + helper->cursor->byteOffset); + helper->cursor->byteOffset += sizeof(uint32_t); + os << static_cast(listSize) << " "; + + for (size_t j = 0; j < listSize; ++j) { + write_property_ascii(p.propertyType, os, + (helper->data->buffer.get() + helper->cursor->byteOffset), + helper->cursor->byteOffset); + } + } else { + os << p.listCount << " "; + for (size_t j = 0; j < p.listCount; ++j) { + write_property_ascii(p.propertyType, os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset); + } + } + + } else { + write_property_ascii(p.propertyType, os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset); + } + property_index++; + } + os << "\n"; + } + element_idx++; + } +} + +void PlyFile::PlyFileImpl::write_header(std::ostream& os) noexcept +{ + const std::locale& fixLoc = std::locale("C"); + os.imbue(fixLoc); + + os << "ply\n"; + if (isBinary) + os << ((isBigEndian) ? "format binary_big_endian 1.0" : "format binary_little_endian 1.0") << "\n"; + else + os << "format ascii 1.0\n"; + + for (const auto& comment : comments) + os << "comment " << comment << "\n"; + + auto property_lookup = make_property_lookup_table(); + + size_t element_idx = 0; + for (auto& e : elements) { + os << "element " << e.name << " " << e.size << "\n"; + size_t property_idx = 0; + for (const auto& p : e.properties) { + PropertyLookup& lookup = property_lookup[element_idx][property_idx]; + + if (!lookup.skip) { + if (p.isList) { + os << "property list " << PropertyTable[p.listType].str << " " + << PropertyTable[p.propertyType].str << " " << p.name << "\n"; + } else { + os << "property " << PropertyTable[p.propertyType].str << " " << p.name << "\n"; + } + } + property_idx++; + } + element_idx++; + } + os << "end_header\n"; +} + +std::shared_ptr PlyFile::PlyFileImpl::request_properties_from_element(const std::string& elementKey, + const std::vector propertyKeys, + const uint32_t list_size_hint) +{ + if (elements.empty()) + throw std::runtime_error("header had no elements defined. malformed file?"); + if (elementKey.empty()) + throw std::invalid_argument("`elementKey` argument is empty"); + if (propertyKeys.empty()) + throw std::invalid_argument("`propertyKeys` argument is empty"); + + std::shared_ptr out_data = std::make_shared(); + + const int64_t elementIndex = find_element(elementKey, elements); + + std::vector keys_not_found; + + // Sanity check if the user requested element is in the pre-parsed header + if (elementIndex >= 0) { + // We found the element + const PlyElement& element = elements[elementIndex]; + + // Each key in `propertyKey` gets an entry into the userData map (keyed by a hash of + // element name and property name), but groups of properties (requested from the + // public api through this function) all share the same `ParsingHelper`. When it comes + // time to .read(), we check the number of unique PlyData shared pointers + // and allocate a single buffer that will be used by each property key group. + // That way, properties like, {"x", "y", "z"} will all be put into the same buffer. + + ParsingHelper helper; + helper.data = out_data; + helper.data->count = element.size; // how many items are in the element? + helper.data->isList = false; + helper.data->t = Type::INVALID; + helper.cursor = std::make_shared(); + helper.list_size_hint = list_size_hint; + + // Find each of the keys + for (const auto& key : propertyKeys) { + const int64_t propertyIndex = find_property(key, element.properties); + if (propertyIndex < 0) + keys_not_found.push_back(key); + } + + if (keys_not_found.size()) { + std::stringstream ss; + for (auto& str : keys_not_found) + ss << str << ", "; + throw std::invalid_argument("the following property keys were not found in the header: " + ss.str()); + } + + for (const auto& key : propertyKeys) { + const int64_t propertyIndex = find_property(key, element.properties); + const PlyProperty& property = element.properties[propertyIndex]; + helper.data->t = property.propertyType; + helper.data->isList = property.isList; + auto result = userData.insert(std::pair(hash_fnv1a(element.name + property.name), helper)); + if (result.second == false) { + throw std::invalid_argument("element-property key has already been requested: " + element.name + " " + property.name); + } + } + + // Sanity check that all properties share the same type + std::vector propertyTypes; + for (const auto& key : propertyKeys) { + const int64_t propertyIndex = find_property(key, element.properties); + const PlyProperty& property = element.properties[propertyIndex]; + propertyTypes.push_back(property.propertyType); + } + + if (std::adjacent_find(propertyTypes.begin(), propertyTypes.end(), std::not_equal_to()) != propertyTypes.end()) { + throw std::invalid_argument("all requested properties must share the same type."); + } + } else + throw std::invalid_argument("the element key was not found in the header: " + elementKey); + + return out_data; +} + +void PlyFile::PlyFileImpl::add_properties_to_element(const std::string& elementKey, + const std::vector propertyKeys, + const Type type, const size_t count, const uint8_t* data, const Type listType, const size_t listCount) +{ + ParsingHelper helper; + helper.data = std::make_shared(); + helper.data->count = count; + helper.data->t = type; + helper.data->buffer = Buffer(data); // we should also set size for safety reasons + helper.cursor = std::make_shared(); + + auto create_property_on_element = [&](PlyElement& e) { + for (auto key : propertyKeys) { + PlyProperty newProp = (listType == Type::INVALID) ? PlyProperty(type, key) : PlyProperty(listType, type, key, listCount); + userData.insert(std::pair(hash_fnv1a(elementKey + key), helper)); + e.properties.push_back(newProp); + } + }; + + const int64_t idx = find_element(elementKey, elements); + if (idx >= 0) { + PlyElement& e = elements[idx]; + create_property_on_element(e); + } else { + PlyElement newElement = (listType == Type::INVALID) ? PlyElement(elementKey, count) : PlyElement(elementKey, count); + create_property_on_element(newElement); + elements.push_back(newElement); + } +} + +void PlyFile::PlyFileImpl::parse_data(std::istream& is, bool firstPass) +{ + std::function read; + std::function skip; + + const auto start = is.tellg(); + + uint32_t listSize = 0; + size_t dummyCount = 0; + std::string skip_ascii_buffer; + + // Special case mirroring read_property_binary but for list types; this + // has an additional big endian check to flip the data in place immediately + // after reading. We do this as a performance optimization; endian flipping is + // done on regular properties as a post-process after reading (also for optimization) + // but we need the correct little-endian list count as we read the file. + auto read_list_binary = [this](const Type& t, void* dst, size_t& destOffset, const size_t& stride, std::istream& _is) noexcept { + destOffset += stride; + _is.read((char*)dst, stride); + + if (isBigEndian) { + switch (t) { + case Type::INT16: + *(int16_t*)dst = endian_swap(*(int16_t*)dst); + break; + case Type::UINT16: + *(uint16_t*)dst = endian_swap(*(uint16_t*)dst); + break; + case Type::INT32: + *(int32_t*)dst = endian_swap(*(int32_t*)dst); + break; + case Type::UINT32: + *(uint32_t*)dst = endian_swap(*(uint32_t*)dst); + break; + default: + break; + } + } + + return stride; + }; + + if (isBinary) { + read = [this, &listSize, &dummyCount, &read_list_binary](PropertyLookup& f, const PlyProperty& p, uint8_t* dest, size_t& destOffset, size_t destSize, std::istream& _is) { + if (!p.isList) { + return read_property_binary(f.prop_stride, dest + destOffset, destOffset, destSize, _is); + } + read_list_binary(p.listType, &listSize, dummyCount, f.list_stride, _is); // the list size + return read_property_binary(f.prop_stride * listSize, dest + destOffset, destOffset, destSize, _is); // properties in list + }; + skip = [this, &listSize, &dummyCount, &read_list_binary](PropertyLookup& f, const PlyProperty& p, std::istream& _is) noexcept { + if (!p.isList) { + _is.read((char*)scratch, f.prop_stride); + return f.prop_stride; + } + read_list_binary(p.listType, &listSize, dummyCount, f.list_stride, _is); // the list size (does not count for memory alloc) + auto bytes_to_skip = f.prop_stride * listSize; + _is.ignore(bytes_to_skip); + return bytes_to_skip; + }; + } else { + read = [this, &listSize, &dummyCount](PropertyLookup& f, const PlyProperty& p, uint8_t* dest, size_t& destOffset, size_t destSize, std::istream& _is) { + if (!p.isList) { + read_property_ascii(p.propertyType, f.prop_stride, dest + destOffset, destOffset, destSize, _is); + } else { + dummyCount = 0; + read_property_ascii(p.listType, f.list_stride, &listSize, dummyCount, sizeof(listSize), _is); // the list size + for (size_t i = 0; i < listSize; ++i) { + read_property_ascii(p.propertyType, f.prop_stride, dest + destOffset, destOffset, destSize, _is); + } + } + }; + skip = [this, &listSize, &dummyCount, &skip_ascii_buffer](PropertyLookup& f, const PlyProperty& p, std::istream& _is) noexcept { + skip_ascii_buffer.clear(); + if (p.isList) { + dummyCount = 0; + read_property_ascii(p.listType, f.list_stride, &listSize, dummyCount, sizeof(listSize), _is); // the list size (does not count for memory alloc) + for (size_t i = 0; i < listSize; ++i) + _is >> skip_ascii_buffer; // properties in list + return listSize * f.prop_stride; + } + _is >> skip_ascii_buffer; + return f.prop_stride; + }; + } + + std::vector> element_property_lookup = make_property_lookup_table(); + size_t element_idx = 0; + size_t property_idx = 0; + ParsingHelper* helper { nullptr }; + + // This is the inner import loop + for (auto& element : elements) { + for (size_t count = 0; count < element.size; ++count) { + property_idx = 0; + for (auto& property : element.properties) { + PropertyLookup& lookup = element_property_lookup[element_idx][property_idx]; + + if (!lookup.skip) { + helper = lookup.helper; + if (firstPass) { + helper->cursor->totalSizeBytes += skip(lookup, property, is); + // 如果这是一个 list 属性,在 firstPass 时记录每个条目的 listSize, + // 以便分配缓冲区后,调用方可以获得每个元素的实际长度(支持混合长度)。 + if (property.isList) { + helper->data->listSizes.push_back(listSize); + } + } else { + const size_t destSize = helper->data->buffer.size_bytes(); + read(lookup, property, helper->data->buffer.get(), helper->cursor->byteOffset, destSize, is); + } + } else { + skip(lookup, property, is); + } + property_idx++; + } + } + element_idx++; + } + + // Reset istream position to the start of the data + if (firstPass) + is.seekg(start, is.beg); +} + +// Wrap the public interface: + +PlyFile::PlyFile() { impl.reset(new PlyFileImpl()); } +PlyFile::~PlyFile() { } +bool PlyFile::parse_header(std::istream& is) { return impl->parse_header(is); } +void PlyFile::read(std::istream& is) { return impl->read(is); } +void PlyFile::write(std::ostream& os, bool isBinary) { return impl->write(os, isBinary); } +std::vector PlyFile::get_elements() const { return impl->elements; } +std::vector& PlyFile::get_comments() { return impl->comments; } +std::vector PlyFile::get_info() const { return impl->objInfo; } +bool PlyFile::is_binary_file() const { return impl->isBinary; } +std::shared_ptr PlyFile::request_properties_from_element(const std::string& elementKey, + const std::vector propertyKeys, + const uint32_t list_size_hint) +{ + return impl->request_properties_from_element(elementKey, propertyKeys, list_size_hint); +} +void PlyFile::add_properties_to_element(const std::string& elementKey, + const std::vector propertyKeys, + const Type type, const size_t count, const uint8_t* data, const Type listType, const size_t listCount) +{ + return impl->add_properties_to_element(elementKey, propertyKeys, type, count, data, listType, listCount); +} + +} // end namespace tinyply + +#endif // end TINYPLY_IMPLEMENTATION -- Gitee