PLY: Improve robustness and performance of the new PLY importer #105842

Merged
Aras Pranckevicius merged 10 commits from aras_p/blender:ply-importer into main 2023-03-17 12:20:56 +01:00
20 changed files with 1255 additions and 1007 deletions

View File

@ -14,6 +14,7 @@ set(INC
../../makesdna
../../makesrna
../../windowmanager
../../../../extern/fast_float
../../../../extern/fmtlib/include
../../../../intern/guardedalloc
)
@ -31,13 +32,11 @@ set(SRC
exporter/ply_file_buffer_ascii.cc
exporter/ply_file_buffer_binary.cc
importer/ply_import.cc
importer/ply_import_ascii.cc
importer/ply_import_binary.cc
importer/ply_import_buffer.cc
importer/ply_import_data.cc
importer/ply_import_mesh.cc
IO_ply.cc
exporter/ply_export.hh
exporter/ply_export_data.hh
exporter/ply_export_header.hh
@ -46,14 +45,12 @@ set(SRC
exporter/ply_file_buffer_ascii.hh
exporter/ply_file_buffer_binary.hh
importer/ply_import.hh
importer/ply_import_ascii.hh
importer/ply_import_binary.hh
importer/ply_import_buffer.hh
importer/ply_import_data.hh
importer/ply_import_mesh.hh
IO_ply.h
intern/ply_data.hh
intern/ply_functions.cc
intern/ply_functions.hh
)
set(LIB
@ -65,10 +62,8 @@ blender_add_lib(bf_ply "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if (WITH_GTESTS)
set(TEST_SRC
tests/io_ply_importer_test.cc
tests/io_ply_exporter_test.cc
tests/io_ply_importer_test.cc
tests/io_ply_exporter_test.cc
)
set(TEST_INC
../../blenloader

View File

@ -39,8 +39,10 @@ void write_vertices(FileBuffer &buffer, const PlyData &ply_data)
void write_faces(FileBuffer &buffer, const PlyData &ply_data)
{
for (const Array<uint32_t> &face : ply_data.faces) {
buffer.write_face(char(face.size()), face);
const uint32_t *indices = ply_data.face_vertices.data();
for (uint32_t face_size : ply_data.face_sizes) {
buffer.write_face(char(face_size), Span<uint32_t>(indices, face_size));
indices += face_size;
}
buffer.write_to_file();
}

View File

@ -49,13 +49,13 @@ void write_header(FileBuffer &buffer,
buffer.write_header_scalar_property("float", "t");
}
if (!ply_data.faces.is_empty()) {
buffer.write_header_element("face", int32_t(ply_data.faces.size()));
if (!ply_data.face_sizes.is_empty()) {
buffer.write_header_element("face", int(ply_data.face_sizes.size()));
buffer.write_header_list_property("uchar", "uint", "vertex_indices");
}
if (!ply_data.edges.is_empty()) {
buffer.write_header_element("edge", int32_t(ply_data.edges.size()));
buffer.write_header_element("edge", int(ply_data.edges.size()));
buffer.write_header_scalar_property("int", "vertex1");
buffer.write_header_scalar_property("int", "vertex2");
}

View File

@ -120,11 +120,12 @@ void load_plydata(PlyData &plyData, Depsgraph *depsgraph, const PLYExportParams
&export_object_eval_, export_params.forward_axis, export_params.up_axis);
/* Load faces into plyData. */
plyData.face_vertices.reserve(mesh->totloop);
plyData.face_sizes.reserve(mesh->totpoly);
int loop_offset = 0;
const Span<MLoop> loops = mesh->loops();
for (const MPoly &poly : mesh->polys()) {
const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
Array<uint32_t> poly_verts(poly_loops.size());
for (int i = 0; i < poly_loops.size(); ++i) {
float2 uv;
@ -136,11 +137,10 @@ void load_plydata(PlyData &plyData, Depsgraph *depsgraph, const PLYExportParams
}
UV_vertex_key key = UV_vertex_key(uv, poly_loops[i].v);
int ply_vertex_index = vertex_map.lookup(key);
poly_verts[i] = (uint32_t(ply_vertex_index + vertex_offset));
plyData.face_vertices.append(ply_vertex_index + vertex_offset);
}
loop_offset += poly_loops.size();
plyData.faces.append(std::move(poly_verts));
loop_offset += poly.totloop;
plyData.face_sizes.append(poly.totloop);
}
Array<int> mesh_vertex_index_LUT(vertex_map.size());

View File

@ -14,7 +14,6 @@
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BLI_fileops.hh"
#include "BLI_math_vector.h"
#include "BLI_memory_utils.hh"
@ -22,53 +21,137 @@
#include "DEG_depsgraph_build.h"
#include "ply_data.hh"
#include "ply_functions.hh"
#include "ply_import.hh"
#include "ply_import_ascii.hh"
#include "ply_import_binary.hh"
#include "ply_import_buffer.hh"
#include "ply_import_data.hh"
#include "ply_import_mesh.hh"
namespace blender::io::ply {
void splitstr(std::string str, Vector<std::string> &words, const StringRef &deli)
/* If line starts with keyword, returns true and drops it from the line. */
static bool parse_keyword(Span<char> &str, StringRef keyword)
{
int pos;
while ((pos = int(str.find(deli))) != std::string::npos) {
words.append(str.substr(0, pos));
str.erase(0, pos + deli.size());
const size_t keyword_len = keyword.size();
if (str.size() < keyword_len) {
return false;
}
/* We add the final word to the vector. */
words.append(str.substr());
if (memcmp(str.data(), keyword.data(), keyword_len) != 0) {
return false;
}
str = str.drop_front(keyword_len);
return true;
}
enum PlyDataTypes from_string(const StringRef &input)
static Span<char> parse_word(Span<char> &str)
{
if (input == "uchar") {
size_t len = 0;
while (len < str.size() && str[len] > ' ') {
++len;
}
Span<char> word(str.begin(), len);
str = str.drop_front(len);
return word;
}
static void skip_space(Span<char> &str)
{
while (!str.is_empty() && str[0] <= ' ')
str = str.drop_front(1);
}
static PlyDataTypes type_from_string(Span<char> word)
{
StringRef input(word.data(), word.size());
if (input == "uchar" || input == "uint8") {
return PlyDataTypes::UCHAR;
}
if (input == "char") {
if (input == "char" || input == "int8") {
return PlyDataTypes::CHAR;
}
if (input == "ushort") {
if (input == "ushort" || input == "uint16") {
return PlyDataTypes::USHORT;
}
if (input == "short") {
if (input == "short" || input == "int16") {
return PlyDataTypes::SHORT;
}
if (input == "uint") {
if (input == "uint" || input == "uint32") {
return PlyDataTypes::UINT;
}
if (input == "int") {
if (input == "int" || input == "int32") {
return PlyDataTypes::INT;
}
if (input == "float") {
if (input == "float" || input == "float32") {
return PlyDataTypes::FLOAT;
}
if (input == "double") {
if (input == "double" || input == "float64") {
return PlyDataTypes::DOUBLE;
}
return PlyDataTypes::FLOAT;
return PlyDataTypes::NONE;
}
const char *read_header(PlyReadBuffer &file, PlyHeader &r_header)
{
Span<char> word, line;
line = file.read_line();
if (StringRef(line.data(), line.size()) != "ply") {
return "Invalid PLY header.";
}
while (true) { /* We break when end_header is encountered. */
line = file.read_line();
if (parse_keyword(line, "format")) {
skip_space(line);
if (parse_keyword(line, "ascii")) {
r_header.type = PlyFormatType::ASCII;
}
else if (parse_keyword(line, "binary_big_endian")) {
r_header.type = PlyFormatType::BINARY_BE;
}
else if (parse_keyword(line, "binary_little_endian")) {
r_header.type = PlyFormatType::BINARY_LE;
}
}
else if (parse_keyword(line, "element")) {
PlyElement element;
skip_space(line);
word = parse_word(line);
element.name = std::string(word.data(), word.size());
skip_space(line);
word = parse_word(line);
element.count = std::stoi(std::string(word.data(), word.size()));
r_header.elements.append(element);
}
else if (parse_keyword(line, "property")) {
PlyProperty property;
skip_space(line);
if (parse_keyword(line, "list")) {
skip_space(line);
property.count_type = type_from_string(parse_word(line));
}
skip_space(line);
property.type = type_from_string(parse_word(line));
skip_space(line);
word = parse_word(line);
property.name = std::string(word.data(), word.size());
r_header.elements.last().properties.append(property);
}
else if (parse_keyword(line, "end_header")) {
break;
}
else if (line.is_empty() || (line.first() >= '0' && line.first() <= '9') ||
line.first() == '-') {
/* A value was found before we broke out of the loop. No end_header. */
return "No end_header.";
}
}
file.after_header(r_header.type != PlyFormatType::ASCII);
for (PlyElement &el : r_header.elements) {
el.calc_stride();
}
return nullptr;
}
void importer_main(bContext *C, const PLYImportParams &import_params, wmOperator *op)
@ -85,75 +168,42 @@ void importer_main(Main *bmain,
const PLYImportParams &import_params,
wmOperator *op)
{
std::string line;
fstream infile(import_params.filepath, std::ios::in | std::ios::binary);
PlyHeader header;
while (true) { /* We break when end_header is encountered. */
safe_getline(infile, line);
if (header.header_size == 0 && line != "ply") {
fprintf(stderr, "PLY Importer: failed to read file. Invalid PLY header.\n");
BKE_report(op->reports, RPT_ERROR, "PLY Importer: Invalid PLY header.");
return;
}
header.header_size++;
Vector<std::string> words{};
splitstr(line, words, " ");
if (STREQ(words[0].c_str(), "format")) {
if (STREQ(words[1].c_str(), "ascii")) {
header.type = PlyFormatType::ASCII;
}
else if (STREQ(words[1].c_str(), "binary_big_endian")) {
header.type = PlyFormatType::BINARY_BE;
}
else if (STREQ(words[1].c_str(), "binary_little_endian")) {
header.type = PlyFormatType::BINARY_LE;
}
}
else if (STREQ(words[0].c_str(), "element")) {
header.elements.append(std::make_pair(words[1], std::stoi(words[2])));
if (STREQ(words[1].c_str(), "vertex")) {
header.vertex_count = std::stoi(words[2]);
}
else if (STREQ(words[1].c_str(), "face")) {
header.face_count = std::stoi(words[2]);
}
else if (STREQ(words[1].c_str(), "edge")) {
header.edge_count = std::stoi(words[2]);
}
}
else if (STREQ(words[0].c_str(), "property")) {
std::pair<std::string, PlyDataTypes> property;
property.first = words[2];
property.second = from_string(words[1]);
while (header.properties.size() < header.elements.size()) {
Vector<std::pair<std::string, PlyDataTypes>> temp;
header.properties.append(temp);
}
header.properties[header.elements.size() - 1].append(property);
}
else if (words[0] == "end_header") {
break;
}
else if ((words[0][0] >= '0' && words[0][0] <= '9') || words[0][0] == '-' || line.empty() ||
infile.eof()) {
/* A value was found before we broke out of the loop. No end_header. */
BKE_report(op->reports, RPT_ERROR, "PLY Importer: No end_header");
return;
}
}
/* Name used for both mesh and object. */
/* File base name used for both mesh and object. */
char ob_name[FILE_MAX];
BLI_strncpy(ob_name, BLI_path_basename(import_params.filepath), FILE_MAX);
BLI_path_extension_replace(ob_name, FILE_MAX, "");
Mesh *mesh = BKE_mesh_add(bmain, ob_name);
/* Parse header. */
PlyReadBuffer file(import_params.filepath, 64 * 1024);
PlyHeader header;
const char *err = read_header(file, header);
if (err != nullptr) {
fprintf(stderr, "PLY Importer: %s: %s\n", ob_name, err);
BKE_reportf(op->reports, RPT_ERROR, "PLY Importer: %s: %s", ob_name, err);
return;
}
/* Parse actual file data. */
std::unique_ptr<PlyData> data = import_ply_data(file, header);
if (data == nullptr) {
fprintf(stderr, "PLY Importer: failed importing %s, unknown error\n", ob_name);
BKE_report(op->reports, RPT_ERROR, "PLY Importer: failed importing, unknown error.");
return;
}
if (!data->error.empty()) {
fprintf(stderr, "PLY Importer: failed importing %s: %s\n", ob_name, data->error.c_str());
BKE_report(op->reports, RPT_ERROR, "PLY Importer: failed importing, unknown error.");
return;
}
if (data->vertices.is_empty()) {
fprintf(stderr, "PLY Importer: file %s contains no vertices\n", ob_name);
BKE_report(op->reports, RPT_ERROR, "PLY Importer: failed importing, no vertices.");
return;
}
/* Create mesh and do all prep work. */
Mesh *mesh = BKE_mesh_add(bmain, ob_name);
BKE_view_layer_base_deselect_all(scene, view_layer);
LayerCollection *lc = BKE_layer_collection_get_active(view_layer);
Object *obj = BKE_object_add_only_object(bmain, OB_MESH, ob_name);
@ -163,26 +213,13 @@ void importer_main(Main *bmain,
Base *base = BKE_view_layer_base_find(view_layer, obj);
BKE_view_layer_base_select_and_set_active(view_layer, base);
try {
std::unique_ptr<PlyData> data;
if (header.type == PlyFormatType::ASCII) {
data = import_ply_ascii(infile, &header);
}
else {
data = import_ply_binary(infile, &header);
}
Mesh *temp_val = convert_ply_to_mesh(*data, mesh, import_params);
if (import_params.merge_verts && temp_val != mesh) {
BKE_mesh_nomain_to_mesh(temp_val, mesh, obj);
}
}
catch (std::exception &e) {
fprintf(stderr, "PLY Importer: failed to read file. %s.\n", e.what());
BKE_report(op->reports, RPT_ERROR, "PLY Importer: failed to parse file.");
return;
/* Stuff ply data into the mesh. */
Mesh *temp_val = convert_ply_to_mesh(*data, mesh, import_params);
if (import_params.merge_verts && temp_val != mesh) {
BKE_mesh_nomain_to_mesh(temp_val, mesh, obj);
}
/* Object matrix and finishing up. */
float global_scale = import_params.global_scale;
if ((scene->unit.system != USER_UNIT_NONE) && import_params.use_scene_unit) {
global_scale *= scene->unit.scale_length;
@ -205,7 +242,5 @@ void importer_main(Main *bmain,
DEG_id_tag_update_ex(bmain, &obj->id, flags);
DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS);
DEG_relations_tag_update(bmain);
infile.close();
}
} // namespace blender::io::ply

View File

@ -11,7 +11,7 @@
namespace blender::io::ply {
enum PlyDataTypes from_string(const StringRef &input);
class PlyReadBuffer;
void splitstr(std::string str, Vector<std::string> &words, const StringRef &deli);
@ -25,4 +25,6 @@ void importer_main(Main *bmain,
const PLYImportParams &import_params,
wmOperator *op);
const char *read_header(PlyReadBuffer &file, PlyHeader &r_header);
} // namespace blender::io::ply

View File

@ -1,222 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup ply
*/
#include "ply_import_ascii.hh"
#include "ply_functions.hh"
#include <algorithm>
#include <fstream>
namespace blender::io::ply {
std::unique_ptr<PlyData> import_ply_ascii(fstream &file, PlyHeader *header)
{
std::unique_ptr<PlyData> data = std::make_unique<PlyData>(load_ply_ascii(file, header));
return data;
}
PlyData load_ply_ascii(fstream &file, const PlyHeader *header)
{
PlyData data;
/* Check if header contains alpha. */
std::pair<std::string, PlyDataTypes> alpha = {"alpha", PlyDataTypes::UCHAR};
bool has_alpha = std::find(header->properties[0].begin(), header->properties[0].end(), alpha) !=
header->properties[0].end();
/* Check if header contains colors. */
std::pair<std::string, PlyDataTypes> red = {"red", PlyDataTypes::UCHAR};
bool has_color = std::find(header->properties[0].begin(), header->properties[0].end(), red) !=
header->properties[0].end();
/* Check if header contains normals. */
std::pair<std::string, PlyDataTypes> normalx = {"nx", PlyDataTypes::FLOAT};
bool has_normals = std::find(header->properties[0].begin(),
header->properties[0].end(),
normalx) != header->properties[0].end();
/* Check if header contains uv data. */
std::pair<std::string, PlyDataTypes> uv = {"s", PlyDataTypes::FLOAT};
const bool has_uv = std::find(header->properties[0].begin(), header->properties[0].end(), uv) !=
header->properties[0].end();
int3 vertex_index = get_vertex_index(header);
int alpha_index;
int3 color_index;
int3 normal_index;
int2 uv_index;
if (has_alpha) {
alpha_index = get_index(header, "alpha", PlyDataTypes::UCHAR);
}
if (has_color) {
/* x=red, y=green, z=blue */
color_index = get_color_index(header);
}
if (has_normals) {
normal_index = get_normal_index(header);
}
if (has_uv) {
uv_index = get_uv_index(header);
}
for (int i = 0; i < header->vertex_count; i++) {
std::string line;
safe_getline(file, line);
Vector<std::string> value_vec = explode(line, ' ');
/* Vertex coords */
float3 vertex3;
vertex3.x = std::stof(value_vec[vertex_index.x]);
vertex3.y = std::stof(value_vec[vertex_index.y]);
vertex3.z = std::stof(value_vec[vertex_index.z]);
data.vertices.append(vertex3);
/* Vertex colors */
if (has_color) {
float4 colors4;
colors4.x = std::stof(value_vec[color_index.x]) / 255.0f;
colors4.y = std::stof(value_vec[color_index.y]) / 255.0f;
colors4.z = std::stof(value_vec[color_index.z]) / 255.0f;
if (has_alpha) {
colors4.w = std::stof(value_vec[alpha_index]) / 255.0f;
}
else {
colors4.w = 1.0f;
}
data.vertex_colors.append(colors4);
}
/* If normals */
if (has_normals) {
float3 normals3;
normals3.x = std::stof(value_vec[normal_index.x]);
normals3.y = std::stof(value_vec[normal_index.y]);
normals3.z = std::stof(value_vec[normal_index.z]);
data.vertex_normals.append(normals3);
}
/* If uv */
if (has_uv) {
float2 uvmap;
uvmap.x = std::stof(value_vec[uv_index.x]);
uvmap.y = std::stof(value_vec[uv_index.y]);
data.uv_coordinates.append(uvmap);
}
}
for (int i = 0; i < header->face_count; i++) {
std::string line;
getline(file, line);
Vector<std::string> value_vec = explode(line, ' ');
int count = std::stoi(value_vec[0]);
Array<uint> vertex_indices(count);
for (int j = 1; j <= count; j++) {
int index = std::stoi(value_vec[j]);
/* If the face has a vertex index that is outside the range. */
if (index >= data.vertices.size()) {
throw std::runtime_error("Vertex index out of bounds");
}
vertex_indices[j - 1] = index;
}
data.faces.append(vertex_indices);
}
for (int i = 0; i < header->edge_count; i++) {
std::string line;
getline(file, line);
Vector<std::string> value_vec = explode(line, ' ');
std::pair<int, int> edge = std::make_pair(stoi(value_vec[0]), stoi(value_vec[1]));
data.edges.append(edge);
}
return data;
}
int3 get_vertex_index(const PlyHeader *header)
{
int3 vertex_index;
vertex_index.x = get_index(header, "x", PlyDataTypes::FLOAT);
vertex_index.y = get_index(header, "y", PlyDataTypes::FLOAT);
vertex_index.z = get_index(header, "z", PlyDataTypes::FLOAT);
return vertex_index;
}
int3 get_color_index(const PlyHeader *header)
{
int3 color_index;
color_index.x = get_index(header, "red", PlyDataTypes::UCHAR);
color_index.y = get_index(header, "green", PlyDataTypes::UCHAR);
color_index.z = get_index(header, "blue", PlyDataTypes::UCHAR);
return color_index;
}
int3 get_normal_index(const PlyHeader *header)
{
int3 normal_index;
normal_index.x = get_index(header, "nx", PlyDataTypes::FLOAT);
normal_index.y = get_index(header, "ny", PlyDataTypes::FLOAT);
normal_index.z = get_index(header, "nz", PlyDataTypes::FLOAT);
return normal_index;
}
int2 get_uv_index(const PlyHeader *header)
{
int2 uv_index;
uv_index.x = get_index(header, "s", PlyDataTypes::FLOAT);
uv_index.y = get_index(header, "t", PlyDataTypes::FLOAT);
return uv_index;
}
int get_index(const PlyHeader *header, std::string property, PlyDataTypes datatype)
{
std::pair<std::string, PlyDataTypes> pair = {property, datatype};
const std::pair<std::string, blender::io::ply::PlyDataTypes> *it = std::find(
header->properties[0].begin(), header->properties[0].end(), pair);
return int(it - header->properties[0].begin());
}
Vector<std::string> explode(const StringRef str, const char &ch)
{
std::string next;
Vector<std::string> result;
/* For each character in the string. */
for (char c : str) {
/* If we've hit the terminal character. */
if (c == ch) {
/* If we have some characters accumulated. */
if (!next.empty()) {
/* Add them to the result vector. */
result.append(next);
next.clear();
}
}
else {
/* Accumulate the next character into the sequence. */
next += c;
}
}
if (!next.empty()) {
result.append(next);
}
return result;
}
} // namespace blender::io::ply

View File

@ -1,39 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup ply
*/
#pragma once
#include "BLI_fileops.hh"
#include "DNA_mesh_types.h"
#include "IO_ply.h"
#include "ply_data.hh"
namespace blender::io::ply {
/**
* The function that gets called from the importer.
* \param file: The PLY file that was opened.
* \param header: The information in the PLY header.
*/
std::unique_ptr<PlyData> import_ply_ascii(fstream &file, PlyHeader *header);
/**
* Loads the information from the PLY file in ASCII format to the #PlyData data-structure.
* \param file: The PLY file that was opened.
* \param header: The information in the PLY header.
* \return The #PlyData data-structure that can be used for conversion to a Mesh.
*/
PlyData load_ply_ascii(fstream &file, const PlyHeader *header);
int3 get_vertex_index(const PlyHeader *header);
int3 get_color_index(const PlyHeader *header);
int3 get_normal_index(const PlyHeader *header);
int2 get_uv_index(const PlyHeader *header);
int get_index(const PlyHeader *header, std::string property, PlyDataTypes datatype);
Vector<std::string> explode(const StringRef str, const char &ch);
} // namespace blender::io::ply

View File

@ -1,215 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup ply
*/
#include "BLI_array.hh"
#include "ply_import_binary.hh"
#include <fstream>
namespace blender::io::ply {
std::unique_ptr<PlyData> import_ply_binary(fstream &file, const PlyHeader *header)
{
std::unique_ptr<PlyData> data = std::make_unique<PlyData>(load_ply_binary(file, header));
return data;
}
template<typename T> T read(fstream &file, bool is_big_endian)
{
T returnVal;
file.read((char *)&returnVal, sizeof(returnVal));
check_file_errors(file);
if (is_big_endian) {
returnVal = swap_bytes<T>(returnVal);
}
return returnVal;
}
template uint8_t read<uint8_t>(fstream &file, bool is_big_endian);
template int8_t read<int8_t>(fstream &file, bool is_big_endian);
template uint16_t read<uint16_t>(fstream &file, bool is_big_endian);
template int16_t read<int16_t>(fstream &file, bool is_big_endian);
template uint32_t read<uint32_t>(fstream &file, bool is_big_endian);
template int32_t read<int32_t>(fstream &file, bool is_big_endian);
template float read<float>(fstream &file, bool is_big_endian);
template double read<double>(fstream &file, bool is_big_endian);
void check_file_errors(const fstream &file)
{
if (file.bad()) {
throw std::ios_base::failure("Read/Write error on io operation");
}
if (file.fail()) {
throw std::ios_base::failure("Logical error on io operation");
}
if (file.eof()) {
throw std::ios_base::failure("Reached end of the file");
}
}
void discard_value(fstream &file, const PlyDataTypes type)
{
switch (type) {
case CHAR:
read<int8_t>(file, false);
break;
case UCHAR:
read<uint8_t>(file, false);
break;
case SHORT:
read<int16_t>(file, false);
break;
case USHORT:
read<uint16_t>(file, false);
break;
case INT:
read<int32_t>(file, false);
break;
case UINT:
read<uint32_t>(file, false);
break;
case FLOAT:
read<float>(file, false);
break;
case DOUBLE:
read<double>(file, false);
break;
}
}
PlyData load_ply_binary(fstream &file, const PlyHeader *header)
{
PlyData data;
bool is_big_endian = header->type == PlyFormatType::BINARY_BE;
for (int i = 0; i < header->elements.size(); i++) {
if (header->elements[i].first == "vertex") {
/* Import vertices. */
load_vertex_data(file, header, &data, i);
}
else if (header->elements[i].first == "edge") {
/* Import edges. */
for (int j = 0; j < header->elements[i].second; j++) {
std::pair<int, int> vertex_indices;
for (auto [name, type] : header->properties[i]) {
if (name == "vertex1") {
vertex_indices.first = int(read<int32_t>(file, is_big_endian));
}
else if (name == "vertex2") {
vertex_indices.second = int(read<int32_t>(file, is_big_endian));
}
else {
discard_value(file, type);
}
}
data.edges.append(vertex_indices);
}
}
else if (header->elements[i].first == "face") {
/* Import faces. */
for (int j = 0; j < header->elements[i].second; j++) {
/* Assume vertex_index_count_type is uchar. */
uint8_t count = read<uint8_t>(file, is_big_endian);
Array<uint> vertex_indices(count);
/* Loop over the amount of vertex indices in this face. */
for (uint8_t k = 0; k < count; k++) {
uint32_t index = read<uint32_t>(file, is_big_endian);
/* If the face has a vertex index that is outside the range. */
if (index >= data.vertices.size()) {
throw std::runtime_error("Vertex index out of bounds");
}
vertex_indices[k] = index;
}
data.faces.append(vertex_indices);
}
}
else {
/* Nothing else is supported. */
for (int j = 0; j < header->elements[i].second; j++) {
for (auto [name, type] : header->properties[i]) {
discard_value(file, type);
}
}
}
}
return data;
}
void load_vertex_data(fstream &file, const PlyHeader *header, PlyData *r_data, int index)
{
bool hasNormal = false;
bool hasColor = false;
bool hasUv = false;
bool is_big_endian = header->type == PlyFormatType::BINARY_BE;
for (int i = 0; i < header->vertex_count; i++) {
float3 coord{0};
float3 normal{0};
float4 color{1};
float2 uv{0};
for (auto [name, type] : header->properties[index]) {
if (name == "x") {
coord.x = read<float>(file, is_big_endian);
}
else if (name == "y") {
coord.y = read<float>(file, is_big_endian);
}
else if (name == "z") {
coord.z = read<float>(file, is_big_endian);
}
else if (name == "nx") {
normal.x = read<float>(file, is_big_endian);
hasNormal = true;
}
else if (name == "ny") {
normal.y = read<float>(file, is_big_endian);
}
else if (name == "nz") {
normal.z = read<float>(file, is_big_endian);
}
else if (name == "red") {
color.x = read<uint8_t>(file, is_big_endian) / 255.0f;
hasColor = true;
}
else if (name == "green") {
color.y = read<uint8_t>(file, is_big_endian) / 255.0f;
}
else if (name == "blue") {
color.z = read<uint8_t>(file, is_big_endian) / 255.0f;
}
else if (name == "alpha") {
color.w = read<uint8_t>(file, is_big_endian) / 255.0f;
}
else if (name == "s") {
uv.x = read<float>(file, is_big_endian);
hasUv = true;
}
else if (name == "t") {
uv.y = read<float>(file, is_big_endian);
}
else {
/* No other properties are supported yet. */
discard_value(file, type);
}
}
r_data->vertices.append(coord);
if (hasNormal) {
r_data->vertex_normals.append(normal);
}
if (hasColor) {
r_data->vertex_colors.append(color);
}
if (hasUv) {
r_data->uv_coordinates.append(uv);
}
}
}
} // namespace blender::io::ply

View File

@ -1,71 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup ply
*/
#pragma once
#include "BLI_endian_switch.h"
#include "BLI_fileops.hh"
#include "ply_data.hh"
namespace blender::io::ply {
/**
* The function that gets called from the importer.
* \param file: The PLY file that was opened.
* \param header: The information in the PLY header.
* \return The #PlyData data-structure that can be used for conversion to a #Mesh.
*/
std::unique_ptr<PlyData> import_ply_binary(fstream &file, const PlyHeader *header);
/**
* Loads the information from the PLY file in binary format to the #PlyData data-structure.
* \param file: The PLY file that was opened.
* \param header: The information in the PLY header.
* \return The #PlyData data-structure that can be used for conversion to a Mesh.
*/
PlyData load_ply_binary(fstream &file, const PlyHeader *header);
void load_vertex_data(fstream &file, const PlyHeader *header, PlyData *r_data, int index);
void check_file_errors(const fstream &file);
void discard_value(fstream &file, const PlyDataTypes type);
template<typename T> T swap_bytes(T input)
{
/* In big endian, the most-significant byte is first.
* So, we need to swap the byte order. */
/* 0xAC in LE should become 0xCA in BE. */
if (sizeof(T) == 1) {
return input;
}
if constexpr (sizeof(T) == 2) {
uint16_t value = reinterpret_cast<uint16_t &>(input);
BLI_endian_switch_uint16(&value);
return reinterpret_cast<T &>(value);
}
if constexpr (sizeof(T) == 4) {
/* Reinterpret this data as uint32 for easy rearranging of bytes. */
uint32_t value = reinterpret_cast<uint32_t &>(input);
BLI_endian_switch_uint32(&value);
return reinterpret_cast<T &>(value);
}
if constexpr (sizeof(T) == 8) {
/* Reinterpret this data as uint64 for easy rearranging of bytes. */
uint64_t value = reinterpret_cast<uint64_t &>(input);
BLI_endian_switch_uint64(&value);
return reinterpret_cast<T &>(value);
}
}
template<typename T> T read(fstream &file, bool isBigEndian);
} // namespace blender::io::ply

View File

@ -0,0 +1,116 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "ply_import_buffer.hh"
#include "BLI_fileops.h"
#include <string.h>
static inline bool is_newline(char ch)
{
return ch == '\n' || ch == '\r';
}
namespace blender::io::ply {
PlyReadBuffer::PlyReadBuffer(const char *file_path, size_t read_buffer_size)
: buffer_(read_buffer_size), read_buffer_size_(read_buffer_size)
{
file_ = BLI_fopen(file_path, "rb");
}
PlyReadBuffer::~PlyReadBuffer()
{
if (file_ != nullptr) {
fclose(file_);
}
}
void PlyReadBuffer::after_header(bool is_binary)
{
is_binary_ = is_binary;
}
Span<char> PlyReadBuffer::read_line()
{
if (is_binary_) {
throw std::runtime_error("PLY read_line should not be used in binary mode");
}
if (pos_ >= last_newline_) {
refill_buffer();
}
BLI_assert(last_newline_ <= buffer_.size());
int res_begin = pos_;
while (pos_ < last_newline_ && !is_newline(buffer_[pos_])) {
pos_++;
}
int res_end = pos_;
/* Move past newlines (possibly multiple for different line endings). */
while (pos_ < buf_used_ && is_newline(buffer_[pos_])) {
pos_++;
}
return Span<char>(buffer_.data() + res_begin, res_end - res_begin);
}
bool PlyReadBuffer::read_bytes(void *dst, size_t size)
{
while (size > 0) {
if (pos_ + size > buf_used_) {
if (!refill_buffer()) {
return false;
}
}
int to_copy = int(size);
if (to_copy > buf_used_)
to_copy = buf_used_;
memcpy(dst, buffer_.data() + pos_, to_copy);
pos_ += to_copy;
dst = (char *)dst + to_copy;
size -= to_copy;
}
return true;
}
bool PlyReadBuffer::refill_buffer()
{
BLI_assert(pos_ <= buf_used_);
BLI_assert(pos_ <= buffer_.size());
BLI_assert(buf_used_ <= buffer_.size());
if (file_ == nullptr || at_eof_) {
return false; /* File is fully read. */
}
/* Move any leftover to start of buffer. */
int keep = buf_used_ - pos_;
if (keep > 0) {
memmove(buffer_.data(), buffer_.data() + pos_, keep);
}
/* Read in data from the file. */
size_t read = fread(buffer_.data() + keep, 1, read_buffer_size_ - keep, file_) + keep;
at_eof_ = read < read_buffer_size_;
pos_ = 0;
buf_used_ = int(read);
/* Find last newline. */
if (!is_binary_) {
int last_nl = buf_used_;
if (!at_eof_) {
while (last_nl > 0) {
--last_nl;
if (is_newline(buffer_[last_nl])) {
break;
}
}
if (!is_newline(buffer_[last_nl])) {
/* Whole line did not fit into our read buffer. */
throw std::runtime_error("PLY text line did not fit into the read buffer");
}
}
last_newline_ = last_nl;
}
return true;
}
} // namespace blender::io::ply

View File

@ -0,0 +1,51 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup ply
*/
#pragma once
#include <stddef.h>
#include <stdio.h>
#include "BLI_array.hh"
#include "BLI_span.hh"
namespace blender::io::ply {
/**
* Reads underlying PLY file in large chunks, and provides interface for ascii/header
* parsing to read individual lines, and for binary parsing to read chunks of bytes.
*/
class PlyReadBuffer {
public:
PlyReadBuffer(const char *file_path, size_t read_buffer_size = 64 * 1024);
~PlyReadBuffer();
/** After header is parsed, indicate whether the rest of reading will be ascii or binary. */
void after_header(bool is_binary);
/** Gets the next line from the file as a Span. The line does not include any newline characters.
*/
Span<char> read_line();
/** Reads a number of bytes into provided destination pointer. Returns false if this amount of
* bytes can not be read. */
bool read_bytes(void *dst, size_t size);
private:
bool refill_buffer();
private:
FILE *file_ = nullptr;
Array<char> buffer_;
int pos_ = 0;
int buf_used_ = 0;
int last_newline_ = 0;
size_t read_buffer_size_ = 0;
bool at_eof_ = false;
bool is_binary_ = false;
};
} // namespace blender::io::ply

View File

@ -0,0 +1,606 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup ply
*/
#include "ply_import_data.hh"
#include "ply_data.hh"
#include "ply_import_buffer.hh"
#include "BLI_endian_switch.h"
#include "fast_float.h"
#include <algorithm>
#include <charconv>
static bool is_whitespace(char c)
{
return c <= ' ';
}
static const char *drop_whitespace(const char *p, const char *end)
{
while (p < end && is_whitespace(*p)) {
++p;
}
return p;
}
static const char *drop_non_whitespace(const char *p, const char *end)
{
while (p < end && !is_whitespace(*p)) {
++p;
}
return p;
}
static const char *drop_plus(const char *p, const char *end)
{
if (p < end && *p == '+') {
++p;
}
return p;
}
static const char *parse_float(const char *p, const char *end, float fallback, float &dst)
{
p = drop_whitespace(p, end);
p = drop_plus(p, end);
fast_float::from_chars_result res = fast_float::from_chars(p, end, dst);
if (ELEM(res.ec, std::errc::invalid_argument, std::errc::result_out_of_range)) {
dst = fallback;
}
return res.ptr;
}
static const char *parse_int(const char *p, const char *end, int fallback, int &dst)
{
p = drop_whitespace(p, end);
p = drop_plus(p, end);
std::from_chars_result res = std::from_chars(p, end, dst);
if (ELEM(res.ec, std::errc::invalid_argument, std::errc::result_out_of_range)) {
dst = fallback;
}
return res.ptr;
}
static void endian_switch(uint8_t *ptr, int type_size)
{
if (type_size == 2) {
BLI_endian_switch_uint16((uint16_t *)ptr);
}
else if (type_size == 4) {
BLI_endian_switch_uint32((uint32_t *)ptr);
}
else if (type_size == 8) {
BLI_endian_switch_uint64((uint64_t *)ptr);
}
}
static void endian_switch_array(uint8_t *ptr, int type_size, int size)
{
if (type_size == 2) {
BLI_endian_switch_uint16_array((uint16_t *)ptr, size);
}
else if (type_size == 4) {
BLI_endian_switch_uint32_array((uint32_t *)ptr, size);
}
else if (type_size == 8) {
BLI_endian_switch_uint64_array((uint64_t *)ptr, size);
}
}
namespace blender::io::ply {
static const int data_type_size[] = {0, 1, 1, 2, 2, 4, 4, 4, 8};
static_assert(std::size(data_type_size) == PLY_TYPE_COUNT, "PLY data type size table mismatch");
static const float data_type_normalizer[] = {
1.0f, 127.0f, 255.0f, 32767.0f, 65535.0f, float(INT_MAX), float(UINT_MAX), 1.0f, 1.0f};
static_assert(std::size(data_type_normalizer) == PLY_TYPE_COUNT,
"PLY data type normalization factor table mismatch");
void PlyElement::calc_stride()
{
stride = 0;
for (PlyProperty &p : properties) {
if (p.count_type != PlyDataTypes::NONE) {
stride = 0;
return;
}
stride += data_type_size[p.type];
}
}
static int get_index(const PlyElement &element, StringRef property)
{
for (int i = 0, n = int(element.properties.size()); i != n; i++) {
const PlyProperty &prop = element.properties[i];
if (prop.name == property) {
return i;
}
}
return -1;
}
static const char *parse_row_ascii(PlyReadBuffer &file, Vector<float> &r_values)
{
Span<char> line = file.read_line();
/* Parse whole line as floats. */
const char *p = line.data();
const char *end = p + line.size();
int value_idx = 0;
while (p < end && value_idx < r_values.size()) {
float val;
p = parse_float(p, end, 0.0f, val);
r_values[value_idx++] = val;
}
return nullptr;
}
template<typename T> static T get_binary_value(PlyDataTypes type, const uint8_t *&r_ptr)
{
T val = 0;
switch (type) {
case NONE:
break;
case CHAR:
val = *(int8_t *)r_ptr;
r_ptr += 1;
break;
case UCHAR:
val = *(uint8_t *)r_ptr;
r_ptr += 1;
break;
case SHORT:
val = *(int16_t *)r_ptr;
r_ptr += 2;
break;
case USHORT:
val = *(uint16_t *)r_ptr;
r_ptr += 2;
break;
case INT:
val = *(int32_t *)r_ptr;
r_ptr += 4;
break;
case UINT:
val = *(int32_t *)r_ptr;
r_ptr += 4;
break;
case FLOAT:
val = *(float *)r_ptr;
r_ptr += 4;
break;
case DOUBLE:
val = *(double *)r_ptr;
r_ptr += 8;
break;
default:
BLI_assert_msg(false, "Unknown property type");
}
return val;
}
static const char *parse_row_binary(PlyReadBuffer &file,
const PlyHeader &header,
const PlyElement &element,
Vector<uint8_t> &r_scratch,
Vector<float> &r_values)
{
if (element.stride == 0) {
return "Vertex/Edge element contains list properties, this is not supported";
}
BLI_assert(r_scratch.size() == element.stride);
BLI_assert(r_values.size() == element.properties.size());
if (!file.read_bytes(r_scratch.data(), r_scratch.size())) {
return "Could not read row of binary property";
}
const uint8_t *ptr = r_scratch.data();
if (header.type == PlyFormatType::BINARY_LE) {
/* Little endian: just read/convert the values. */
for (int i = 0, n = int(element.properties.size()); i != n; i++) {
const PlyProperty &prop = element.properties[i];
float val = get_binary_value<float>(prop.type, ptr);
r_values[i] = val;
}
}
else if (header.type == PlyFormatType::BINARY_BE) {
/* Big endian: read, switch endian, convert the values. */
for (int i = 0, n = int(element.properties.size()); i != n; i++) {
const PlyProperty &prop = element.properties[i];
endian_switch((uint8_t *)ptr, data_type_size[prop.type]);
float val = get_binary_value<float>(prop.type, ptr);
r_values[i] = val;
}
}
else {
return "Unknown binary ply format for vertex element";
}
return nullptr;
}
static const char *load_vertex_element(PlyReadBuffer &file,
const PlyHeader &header,
const PlyElement &element,
PlyData *data)
{
/* Figure out vertex component indices. */
int3 vertex_index = {get_index(element, "x"), get_index(element, "y"), get_index(element, "z")};
int3 color_index = {
get_index(element, "red"), get_index(element, "green"), get_index(element, "blue")};
int3 normal_index = {
get_index(element, "nx"), get_index(element, "ny"), get_index(element, "nz")};
int2 uv_index = {get_index(element, "s"), get_index(element, "t")};
int alpha_index = get_index(element, "alpha");
bool has_vertex = vertex_index.x >= 0 && vertex_index.y >= 0 && vertex_index.z >= 0;
bool has_color = color_index.x >= 0 && color_index.y >= 0 && color_index.z >= 0;
bool has_normal = normal_index.x >= 0 && normal_index.y >= 0 && normal_index.z >= 0;
bool has_uv = uv_index.x >= 0 && uv_index.y >= 0;
bool has_alpha = alpha_index >= 0;
if (!has_vertex) {
return "Vertex positions are not present in the file";
}
data->vertices.reserve(element.count);
if (has_color) {
data->vertex_colors.reserve(element.count);
}
if (has_normal) {
data->vertex_normals.reserve(element.count);
}
if (has_uv) {
data->uv_coordinates.reserve(element.count);
}
float4 color_norm = {1, 1, 1, 1};
if (has_color) {
color_norm.x = data_type_normalizer[element.properties[color_index.x].type];
color_norm.y = data_type_normalizer[element.properties[color_index.y].type];
color_norm.z = data_type_normalizer[element.properties[color_index.z].type];
}
if (has_alpha) {
color_norm.w = data_type_normalizer[element.properties[alpha_index].type];
}
Vector<float> value_vec(element.properties.size());
Vector<uint8_t> scratch;
if (header.type != PlyFormatType::ASCII) {
scratch.resize(element.stride);
}
for (int i = 0; i < element.count; i++) {
const char *error = nullptr;
if (header.type == PlyFormatType::ASCII) {
error = parse_row_ascii(file, value_vec);
}
else {
error = parse_row_binary(file, header, element, scratch, value_vec);
}
if (error != nullptr) {
return error;
}
/* Vertex coord */
float3 vertex3;
vertex3.x = value_vec[vertex_index.x];
vertex3.y = value_vec[vertex_index.y];
vertex3.z = value_vec[vertex_index.z];
data->vertices.append(vertex3);
/* Vertex color */
if (has_color) {
float4 colors4;
colors4.x = value_vec[color_index.x] / color_norm.x;
colors4.y = value_vec[color_index.y] / color_norm.y;
colors4.z = value_vec[color_index.z] / color_norm.z;
if (has_alpha) {
colors4.w = value_vec[alpha_index] / color_norm.w;
}
else {
colors4.w = 1.0f;
}
data->vertex_colors.append(colors4);
}
/* If normals */
if (has_normal) {
float3 normals3;
normals3.x = value_vec[normal_index.x];
normals3.y = value_vec[normal_index.y];
normals3.z = value_vec[normal_index.z];
data->vertex_normals.append(normals3);
}
/* If uv */
if (has_uv) {
float2 uvmap;
uvmap.x = value_vec[uv_index.x];
uvmap.y = value_vec[uv_index.y];
data->uv_coordinates.append(uvmap);
}
}
return nullptr;
}
static uint32_t read_list_count(PlyReadBuffer &file,
const PlyProperty &prop,
Vector<uint8_t> &scratch,
bool big_endian)
{
scratch.resize(8);
file.read_bytes(scratch.data(), data_type_size[prop.count_type]);
const uint8_t *ptr = scratch.data();
if (big_endian)
endian_switch((uint8_t *)ptr, data_type_size[prop.count_type]);
uint32_t count = get_binary_value<uint32_t>(prop.count_type, ptr);
return count;
}
static void skip_property(PlyReadBuffer &file,
const PlyProperty &prop,
Vector<uint8_t> &scratch,
bool big_endian)
{
if (prop.count_type == PlyDataTypes::NONE) {
scratch.resize(8);
file.read_bytes(scratch.data(), data_type_size[prop.type]);
}
else {
uint32_t count = read_list_count(file, prop, scratch, big_endian);
scratch.resize(count * data_type_size[prop.type]);
file.read_bytes(scratch.data(), scratch.size());
}
}
static const char *load_face_element(PlyReadBuffer &file,
const PlyHeader &header,
const PlyElement &element,
PlyData *data)
{
int prop_index = get_index(element, "vertex_indices");
if (prop_index < 0) {
prop_index = get_index(element, "vertex_index");
}
if (prop_index < 0 && element.properties.size() == 1) {
prop_index = 0;
}
if (prop_index < 0) {
return "Face element does not contain vertex indices property";
}
const PlyProperty &prop = element.properties[prop_index];
if (prop.count_type == PlyDataTypes::NONE) {
return "Face element vertex indices property must be a list";
}
data->face_vertices.reserve(element.count * 3);
data->face_sizes.reserve(element.count);
if (header.type == PlyFormatType::ASCII) {
for (int i = 0; i < element.count; i++) {
/* Read line */
Span<char> line = file.read_line();
const char *p = line.data();
const char *end = p + line.size();
int count = 0;
/* Skip any properties before vertex indices. */
for (int j = 0; j < prop_index; j++) {
p = drop_whitespace(p, end);
if (element.properties[j].count_type == PlyDataTypes::NONE) {
p = drop_non_whitespace(p, end);
}
else {
p = parse_int(p, end, 0, count);
for (int k = 0; k < count; ++k) {
p = drop_whitespace(p, end);
p = drop_non_whitespace(p, end);
}
}
}
/* Parse vertex indices list. */
p = parse_int(p, end, 0, count);
if (count < 1 || count > 255) {
return "Invalid face size, must be between 1 and 255";
}
for (int j = 0; j < count; j++) {
int index;
p = parse_int(p, end, 0, index);
data->face_vertices.append(index);
}
data->face_sizes.append(count);
}
}
else {
Vector<uint8_t> scratch(64);
for (int i = 0; i < element.count; i++) {
const uint8_t *ptr;
/* Skip any properties before vertex indices. */
for (int j = 0; j < prop_index; j++) {
skip_property(
file, element.properties[j], scratch, header.type == PlyFormatType::BINARY_BE);
}
/* Read vertex indices list. */
uint32_t count = read_list_count(
file, prop, scratch, header.type == PlyFormatType::BINARY_BE);
if (count < 1 || count > 255) {
return "Invalid face size, must be between 1 and 255";
}
scratch.resize(count * data_type_size[prop.type]);
file.read_bytes(scratch.data(), scratch.size());
ptr = scratch.data();
if (header.type == PlyFormatType::BINARY_BE)
endian_switch_array((uint8_t *)ptr, data_type_size[prop.type], count);
for (int j = 0; j < count; ++j) {
uint32_t index = get_binary_value<uint32_t>(prop.type, ptr);
data->face_vertices.append(index);
}
data->face_sizes.append(count);
/* Skip any properties after vertex indices. */
for (int j = prop_index + 1; j < element.properties.size(); j++) {
skip_property(
file, element.properties[j], scratch, header.type == PlyFormatType::BINARY_BE);
}
}
}
return nullptr;
}
static const char *load_tristrips_element(PlyReadBuffer &file,
const PlyHeader &header,
const PlyElement &element,
PlyData *data)
{
if (element.count != 1) {
return "Tristrips element should contain one row";
}
if (element.properties.size() != 1) {
return "Tristrips element should contain one property";
}
const PlyProperty &prop = element.properties[0];
if (prop.count_type == PlyDataTypes::NONE) {
return "Tristrips element property must be a list";
}
Vector<int> strip;
if (header.type == PlyFormatType::ASCII) {
Span<char> line = file.read_line();
const char *p = line.data();
const char *end = p + line.size();
int count = 0;
p = parse_int(p, end, 0, count);
strip.resize(count);
for (int j = 0; j < count; j++) {
int index;
p = parse_int(p, end, 0, index);
strip[j] = index;
}
}
else {
Vector<uint8_t> scratch(64);
const uint8_t *ptr;
uint32_t count = read_list_count(file, prop, scratch, header.type == PlyFormatType::BINARY_BE);
strip.resize(count);
scratch.resize(count * data_type_size[prop.type]);
file.read_bytes(scratch.data(), scratch.size());
ptr = scratch.data();
if (header.type == PlyFormatType::BINARY_BE)
endian_switch_array((uint8_t *)ptr, data_type_size[prop.type], count);
for (int j = 0; j < count; ++j) {
int index = get_binary_value<int>(prop.type, ptr);
strip[j] = index;
}
}
/* Decode triangle strip (with possible -1 restart indices) into faces. */
size_t start = 0;
for (size_t i = 0; i < strip.size(); i++) {
if (strip[i] == -1) {
/* Restart strip. */
start = i + 1;
}
else if (i - start >= 2) {
int a = strip[i - 2], b = strip[i - 1], c = strip[i];
/* Flip odd triangles. */
if ((i - start) & 1) {
SWAP(int, a, b);
}
/* Add triangle if it's not degenerate. */
if (a != b && a != c && b != c) {
data->face_vertices.append(a);
data->face_vertices.append(b);
data->face_vertices.append(c);
data->face_sizes.append(3);
}
}
}
return nullptr;
}
static const char *load_edge_element(PlyReadBuffer &file,
const PlyHeader &header,
const PlyElement &element,
PlyData *data)
{
int prop_vertex1 = get_index(element, "vertex1");
int prop_vertex2 = get_index(element, "vertex2");
if (prop_vertex1 < 0 || prop_vertex2 < 0) {
return "Edge element does not contain vertex1 and vertex2 properties";
}
data->edges.reserve(element.count);
Vector<float> value_vec(element.properties.size());
Vector<uint8_t> scratch;
if (header.type != PlyFormatType::ASCII) {
scratch.resize(element.stride);
}
for (int i = 0; i < element.count; i++) {
const char *error = nullptr;
if (header.type == PlyFormatType::ASCII) {
error = parse_row_ascii(file, value_vec);
}
else {
error = parse_row_binary(file, header, element, scratch, value_vec);
}
if (error != nullptr) {
return error;
}
int index1 = value_vec[prop_vertex1];
int index2 = value_vec[prop_vertex2];
data->edges.append(std::make_pair(index1, index2));
}
return nullptr;
}
std::unique_ptr<PlyData> import_ply_data(PlyReadBuffer &file, PlyHeader &header)
{
std::unique_ptr<PlyData> data = std::make_unique<PlyData>();
for (const PlyElement &element : header.elements) {
const char *error = nullptr;
if (element.name == "vertex") {
error = load_vertex_element(file, header, element, data.get());
}
else if (element.name == "face") {
error = load_face_element(file, header, element, data.get());
}
else if (element.name == "tristrips") {
error = load_tristrips_element(file, header, element, data.get());
}
else if (element.name == "edge") {
error = load_edge_element(file, header, element, data.get());
}
if (error != nullptr) {
data->error = error;
return data;
}
}
return data;
}
} // namespace blender::io::ply

View File

@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup ply
*/
#pragma once
#include "ply_data.hh"
namespace blender::io::ply {
class PlyReadBuffer;
/**
* Loads the information from a PLY file to a #PlyData data-structure.
* \param file: The PLY file that was opened.
* \param header: The information in the PLY header.
* \return The #PlyData data-structure that can be used for conversion to a Mesh.
*/
std::unique_ptr<PlyData> import_ply_data(PlyReadBuffer &file, PlyHeader &header);
} // namespace blender::io::ply

View File

@ -33,36 +33,44 @@ Mesh *convert_ply_to_mesh(PlyData &data, Mesh *mesh, const PLYImportParams &para
CustomData_add_layer(&mesh->edata, CD_MEDGE, CD_SET_DEFAULT, mesh->totedge);
MutableSpan<MEdge> edges = mesh->edges_for_write();
for (int i = 0; i < mesh->totedge; i++) {
edges[i].v1 = data.edges[i].first;
edges[i].v2 = data.edges[i].second;
uint32_t v1 = data.edges[i].first;
uint32_t v2 = data.edges[i].second;
if (v1 >= mesh->totvert) {
fprintf(stderr, "Invalid PLY vertex index in edge %i/1: %u\n", i, v1);
v1 = 0;
}
if (v2 >= mesh->totvert) {
fprintf(stderr, "Invalid PLY vertex index in edge %i/2: %u\n", i, v2);
v2 = 0;
}
edges[i].v1 = v1;
edges[i].v2 = v2;
}
}
/* Add faces to the mesh. */
if (!data.faces.is_empty()) {
/* Specify amount of total faces. */
mesh->totpoly = int(data.faces.size());
mesh->totloop = 0;
for (int i = 0; i < data.faces.size(); i++) {
/* Add number of loops from the vertex indices in the face. */
mesh->totloop += data.faces[i].size();
}
if (!data.face_sizes.is_empty()) {
/* Create poly and loop layers. */
mesh->totpoly = int(data.face_sizes.size());
mesh->totloop = int(data.face_vertices.size());
CustomData_add_layer(&mesh->pdata, CD_MPOLY, CD_SET_DEFAULT, mesh->totpoly);
CustomData_add_layer(&mesh->ldata, CD_MLOOP, CD_SET_DEFAULT, mesh->totloop);
MutableSpan<MPoly> polys = mesh->polys_for_write();
MutableSpan<MLoop> loops = mesh->loops_for_write();
int offset = 0;
/* Iterate over amount of faces. */
/* Fill in face data. */
uint32_t offset = 0;
for (int i = 0; i < mesh->totpoly; i++) {
int size = int(data.faces[i].size());
/* Set the index from where this face starts and specify the amount of edges it has. */
uint32_t size = data.face_sizes[i];
polys[i].loopstart = offset;
polys[i].totloop = size;
for (int j = 0; j < size; j++) {
/* Set the vertex index of the loop to the one in PlyData. */
loops[offset + j].v = data.faces[i][j];
uint32_t v = data.face_vertices[offset + j];
if (v >= mesh->totvert) {
fprintf(stderr, "Invalid PLY vertex index in face %i loop %i: %u\n", i, j, v);
v = 0;
}
loops[offset + j].v = v;
}
offset += size;
}
@ -93,12 +101,8 @@ Mesh *convert_ply_to_mesh(PlyData &data, Mesh *mesh, const PLYImportParams &para
if (!data.uv_coordinates.is_empty()) {
bke::SpanAttributeWriter<float2> uv_map = attributes.lookup_or_add_for_write_only_span<float2>(
"UVMap", ATTR_DOMAIN_CORNER);
int counter = 0;
for (int i = 0; i < data.faces.size(); i++) {
for (int j = 0; j < data.faces[i].size(); j++) {
uv_map.span[counter] = data.uv_coordinates[data.faces[i][j]];
counter++;
}
for (size_t i = 0; i < data.face_vertices.size(); i++) {
uv_map.span[i] = data.uv_coordinates[data.face_vertices[i]];
}
uv_map.finish();
}

View File

@ -12,30 +12,38 @@
namespace blender::io::ply {
enum PlyDataTypes { CHAR, UCHAR, SHORT, USHORT, INT, UINT, FLOAT, DOUBLE };
enum PlyDataTypes { NONE, CHAR, UCHAR, SHORT, USHORT, INT, UINT, FLOAT, DOUBLE, PLY_TYPE_COUNT };
struct PlyData {
Vector<float3> vertices;
Vector<float3> vertex_normals;
/* Value between 0 and 1. */
Vector<float4> vertex_colors;
Vector<float4> vertex_colors; /* Linear space, 0..1 range colors. */
Vector<std::pair<int, int>> edges;
Vector<float3> edge_colors;
Vector<Array<uint32_t>> faces;
Vector<uint32_t> face_vertices;
Vector<uint32_t> face_sizes;
Vector<float2> uv_coordinates;
std::string error;
};
enum PlyFormatType { ASCII, BINARY_LE, BINARY_BE };
struct PlyProperty {
std::string name;
PlyDataTypes type = PlyDataTypes::NONE;
PlyDataTypes count_type = PlyDataTypes::NONE; /* NONE means it's not a list property */
};
struct PlyElement {
std::string name;
int count = 0;
Vector<PlyProperty> properties;
int stride = 0;
void calc_stride();
};
struct PlyHeader {
int vertex_count = 0;
int edge_count = 0;
int face_count = 0;
int header_size = 0;
/* List of elements in ply file with their count. */
Vector<std::pair<std::string, int>> elements;
/* List of properties (Name, type) per element. */
Vector<Vector<std::pair<std::string, PlyDataTypes>>> properties;
Vector<PlyElement> elements;
PlyFormatType type;
};

View File

@ -1,51 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup ply
*/
#include "ply_functions.hh"
namespace blender::io::ply {
line_ending safe_getline(fstream &file, std::string &line)
{
line.clear();
std::streambuf *sb = file.rdbuf();
std::istream::sentry se(file, true);
line_ending possible = UNSET;
char c;
while (sb->sgetc() != std::streambuf::traits_type::eof()) {
c = char(sb->sgetc());
switch (c) {
case '\n':
if (possible == UNSET) {
possible = LF;
}
else if (possible == CR) {
possible = CR_LF;
}
break;
case '\r':
if (possible == UNSET) {
possible = CR;
}
else if (possible == LF) {
possible = LF_CR;
}
break;
default:
/* If a different character is encountered after the line ending is set, we know to return.
*/
if (possible != UNSET) {
return possible;
}
line += c;
break;
}
sb->sbumpc();
}
return possible;
}
} // namespace blender::io::ply

View File

@ -1,26 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup ply
*/
#pragma once
#include "BLI_fileops.hh"
#include <string>
namespace blender::io::ply {
enum line_ending { CR_LF, LF, CR, LF_CR, UNSET };
/**
* Reads a line in the ply file in a line-ending safe manner. All different line endings are
* supported. This also supports a mix of different line endings in the same file. CR (\\r), LF
* (\\n), CR/LF (\\r\\n), LF/CR (\\n\\r).
* \param file: The file stream.
* \param line: The string you want to read to.
* \return The line ending enum if you're interested.
*/
line_ending safe_getline(fstream &file, std::string &line);
} // namespace blender::io::ply

View File

@ -21,7 +21,7 @@
namespace blender::io::ply {
class PlyExportTest : public BlendfileLoadingBaseTest {
class ply_export_test : public BlendfileLoadingBaseTest {
public:
bool load_file_and_depsgraph(const std::string &filepath,
const eEvaluationMode eval_mode = DAG_EVAL_VIEWPORT)
@ -68,8 +68,9 @@ static std::unique_ptr<PlyData> load_cube(PLYExportParams &params)
{-1.122082, -1.122082, -1.122082},
};
plyData->faces = {
{0, 2, 6, 4}, {3, 7, 6, 2}, {7, 5, 4, 6}, {5, 7, 3, 1}, {1, 3, 2, 0}, {5, 1, 0, 4}};
plyData->face_sizes = {4, 4, 4, 4, 4, 4};
plyData->face_vertices = {0, 2, 6, 4, 3, 7, 6, 2, 7, 5, 4, 6,
5, 7, 3, 1, 1, 3, 2, 0, 5, 1, 0, 4};
if (params.export_normals)
plyData->vertex_normals = {
@ -126,7 +127,7 @@ static std::vector<char> read_temp_file_in_vectorchar(const std::string &file_pa
return res;
}
TEST_F(PlyExportTest, WriteHeaderAscii)
TEST_F(ply_export_test, WriteHeaderAscii)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -164,7 +165,7 @@ TEST_F(PlyExportTest, WriteHeaderAscii)
ASSERT_STREQ(result.c_str(), expected.c_str());
}
TEST_F(PlyExportTest, WriteHeaderBinary)
TEST_F(ply_export_test, WriteHeaderBinary)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -202,7 +203,7 @@ TEST_F(PlyExportTest, WriteHeaderBinary)
ASSERT_STREQ(result.c_str(), expected.c_str());
}
TEST_F(PlyExportTest, WriteVerticesAscii)
TEST_F(ply_export_test, WriteVerticesAscii)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -234,7 +235,7 @@ TEST_F(PlyExportTest, WriteVerticesAscii)
ASSERT_STREQ(result.c_str(), expected.c_str());
}
TEST_F(PlyExportTest, WriteVerticesBinary)
TEST_F(ply_export_test, WriteVerticesBinary)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -270,7 +271,7 @@ TEST_F(PlyExportTest, WriteVerticesBinary)
}
}
TEST_F(PlyExportTest, WriteFacesAscii)
TEST_F(ply_export_test, WriteFacesAscii)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -300,7 +301,7 @@ TEST_F(PlyExportTest, WriteFacesAscii)
ASSERT_STREQ(result.c_str(), expected.c_str());
}
TEST_F(PlyExportTest, WriteFacesBinary)
TEST_F(ply_export_test, WriteFacesBinary)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -336,7 +337,7 @@ TEST_F(PlyExportTest, WriteFacesBinary)
}
}
TEST_F(PlyExportTest, WriteVertexNormalsAscii)
TEST_F(ply_export_test, WriteVertexNormalsAscii)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -368,7 +369,7 @@ TEST_F(PlyExportTest, WriteVertexNormalsAscii)
ASSERT_STREQ(result.c_str(), expected.c_str());
}
TEST_F(PlyExportTest, WriteVertexNormalsBinary)
TEST_F(ply_export_test, WriteVertexNormalsBinary)
{
std::string filePath = get_temp_ply_filename(temp_file_path);
PLYExportParams _params = {};
@ -410,7 +411,7 @@ TEST_F(PlyExportTest, WriteVertexNormalsBinary)
}
}
class ply_exporter_ply_data_test : public PlyExportTest {
class ply_exporter_ply_data_test : public ply_export_test {
public:
PlyData load_ply_data_from_blendfile(const std::string &blendfile, PLYExportParams &params)
{

View File

@ -1,249 +1,278 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "tests/blendfile_loading_base_test.h"
#include "testing/testing.h"
#include "BKE_attribute.hh"
#include "BKE_mesh.hh"
#include "BKE_object.h"
#include "BLI_fileops.hh"
#include "BLI_hash_mm2a.h"
#include "BLO_readfile.h"
#include "DEG_depsgraph_query.h"
#include "IO_ply.h"
#include "ply_data.hh"
#include "ply_import.hh"
#include "ply_import_binary.hh"
#include "ply_import_buffer.hh"
#include "ply_import_data.hh"
namespace blender::io::ply {
struct Expectation {
std::string name;
PlyFormatType type;
int totvert, totpoly, totedge;
int totvert, totpoly, totindex, totedge;
uint16_t polyhash = 0, edgehash = 0;
float3 vert_first, vert_last;
float3 normal_first = {0, 0, 0};
float2 uv_first;
float2 uv_first = {0, 0};
float4 color_first = {-1, -1, -1, -1};
};
class PlyImportTest : public BlendfileLoadingBaseTest {
class ply_import_test : public testing::Test {
public:
void import_and_check(const char *path, const Expectation *expect, size_t expect_count)
void import_and_check(const char *path, const Expectation &exp)
{
if (!blendfile_load("io_tests/blend_geometry/all_quads.blend")) {
std::string ply_path = blender::tests::flags_test_asset_dir() + "/io_tests/ply/" + path;
/* Use a small read buffer size for better coverage of buffer refilling behavior. */
PlyReadBuffer infile(ply_path.c_str(), 128);
PlyHeader header;
const char *header_err = read_header(infile, header);
if (header_err != nullptr) {
ADD_FAILURE();
return;
}
PLYImportParams params;
params.global_scale = 1.0f;
params.forward_axis = IO_AXIS_NEGATIVE_Z;
params.up_axis = IO_AXIS_Y;
params.merge_verts = false;
params.vertex_colors = PLY_VERTEX_COLOR_NONE;
/* Import the test file. */
std::string ply_path = blender::tests::flags_test_asset_dir() + "/io_tests/ply/" + path;
strncpy(params.filepath, ply_path.c_str(), FILE_MAX - 1);
importer_main(bfile->main, bfile->curscene, bfile->cur_view_layer, params, nullptr);
depsgraph_create(DAG_EVAL_VIEWPORT);
DEGObjectIterSettings deg_iter_settings{};
deg_iter_settings.depsgraph = depsgraph;
deg_iter_settings.flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY |
DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET | DEG_ITER_OBJECT_FLAG_VISIBLE |
DEG_ITER_OBJECT_FLAG_DUPLI;
size_t object_index = 0;
/* Iterate over the objects in the viewport */
DEG_OBJECT_ITER_BEGIN (&deg_iter_settings, object) {
if (object_index >= expect_count) {
ADD_FAILURE();
break;
}
const Expectation &exp = expect[object_index];
ASSERT_STREQ(object->id.name, exp.name.c_str());
EXPECT_V3_NEAR(object->loc, float3(0, 0, 0), 0.0001f);
EXPECT_V3_NEAR(object->scale, float3(1, 1, 1), 0.0001f);
if (object->type == OB_MESH) {
Mesh *mesh = BKE_object_get_evaluated_mesh(object);
/* Test if mesh has expected amount of vertices, edges, and faces. */
ASSERT_EQ(mesh->totvert, exp.totvert);
ASSERT_EQ(mesh->totedge, exp.totedge);
ASSERT_EQ(mesh->totpoly, exp.totpoly);
/* Test if first and last vertices match. */
const Span<float3> verts = mesh->vert_positions();
EXPECT_V3_NEAR(verts.first(), exp.vert_first, 0.0001f);
EXPECT_V3_NEAR(verts.last(), exp.vert_last, 0.0001f);
/* Fetch normal data from mesh and test if it matches expectation. */
if (BKE_mesh_has_custom_loop_normals(mesh)) {
const Span<float3> vertex_normals = mesh->vert_normals();
ASSERT_FALSE(vertex_normals.is_empty());
EXPECT_V3_NEAR(vertex_normals[0], exp.normal_first, 0.0001f);
}
/* Fetch UV data from mesh and test if it matches expectation. */
blender::bke::AttributeAccessor attributes = mesh->attributes();
VArray<float2> uvs = attributes.lookup<float2>("UVMap");
float2 uv_first = !uvs.is_empty() ? uvs[0] : float2(0, 0);
EXPECT_V2_NEAR(uv_first, exp.uv_first, 0.0001f);
/* Check if expected mesh has vertex colors, and tests if it matches. */
if (CustomData_has_layer(&mesh->vdata, CD_PROP_COLOR)) {
const float4 *colors = (const float4 *)CustomData_get_layer(&mesh->vdata, CD_PROP_COLOR);
ASSERT_TRUE(colors != nullptr);
EXPECT_V4_NEAR(colors[0], exp.color_first, 0.0001f);
}
}
++object_index;
std::unique_ptr<PlyData> data = import_ply_data(infile, header);
if (!data->error.empty()) {
fprintf(stderr, "%s\n", data->error.c_str());
ASSERT_EQ(0, exp.totvert);
ASSERT_EQ(0, exp.totpoly);
return;
}
DEG_OBJECT_ITER_END;
EXPECT_EQ(object_index, expect_count);
/* Test expected amount of vertices, edges, and faces. */
ASSERT_EQ(data->vertices.size(), exp.totvert);
ASSERT_EQ(data->edges.size(), exp.totedge);
ASSERT_EQ(data->face_sizes.size(), exp.totpoly);
ASSERT_EQ(data->face_vertices.size(), exp.totindex);
/* Test hash of face and edge index data. */
BLI_HashMurmur2A hash;
BLI_hash_mm2a_init(&hash, 0);
uint32_t offset = 0;
for (uint32_t face_size : data->face_sizes) {
BLI_hash_mm2a_add(&hash, (const unsigned char *)&data->face_vertices[offset], face_size * 4);
offset += face_size;
}
uint16_t face_hash = BLI_hash_mm2a_end(&hash);
if (!data->face_vertices.is_empty()) {
ASSERT_EQ(face_hash, exp.polyhash);
}
if (!data->edges.is_empty()) {
uint16_t edge_hash = BLI_hash_mm2((const unsigned char *)data->edges.data(),
data->edges.size() * sizeof(data->edges[0]),
0);
ASSERT_EQ(edge_hash, exp.edgehash);
}
/* Test if first and last vertices match. */
EXPECT_V3_NEAR(data->vertices.first(), exp.vert_first, 0.0001f);
EXPECT_V3_NEAR(data->vertices.last(), exp.vert_last, 0.0001f);
/* Check if first normal matches. */
float3 got_normal = data->vertex_normals.is_empty() ? float3(0, 0, 0) :
data->vertex_normals.first();
EXPECT_V3_NEAR(got_normal, exp.normal_first, 0.0001f);
/* Check if first UV matches. */
float2 got_uv = data->uv_coordinates.is_empty() ? float2(0, 0) : data->uv_coordinates.first();
EXPECT_V2_NEAR(got_uv, exp.uv_first, 0.0001f);
/* Check if first color matches. */
float4 got_color = data->vertex_colors.is_empty() ? float4(-1, -1, -1, -1) :
data->vertex_colors.first();
EXPECT_V4_NEAR(got_color, exp.color_first, 0.0001f);
}
};
TEST_F(PlyImportTest, PLYImportCube)
TEST_F(ply_import_test, PLYImportCube)
{
Expectation expect[] = {{"OBCube",
ASCII,
8,
6,
12,
float3(1, 1, -1),
float3(-1, 1, 1),
float3(0.5773, 0.5773, -0.5773),
float2(0, 0)},
{"OBcube_ascii",
ASCII,
24,
6,
24,
float3(1, 1, -1),
float3(-1, 1, 1),
float3(0, 0, -1),
float2(0.979336, 0.844958),
float4(1, 0.8470, 0, 1)}};
import_and_check("cube_ascii.ply", expect, 2);
Expectation expect = {24,
6,
24,
0,
26429,
0,
float3(1, 1, -1),
float3(-1, 1, 1),
float3(0, 0, -1),
float2(0.979336, 0.844958),
float4(1, 0.8470, 0, 1)};
import_and_check("cube_ascii.ply", expect);
}
TEST_F(PlyImportTest, PLYImportASCIIEdgeTest)
TEST_F(ply_import_test, PLYImportWireframeCube)
{
Expectation expect[] = {{"OBCube",
ASCII,
8,
6,
12,
float3(1, 1, -1),
float3(-1, 1, 1),
float3(0.5773, 0.5773, -0.5773)},
{"OBASCII_wireframe_cube",
ASCII,
8,
0,
12,
float3(-1, -1, -1),
float3(1, 1, 1),
float3(-2, 0, -1)}};
import_and_check("ASCII_wireframe_cube.ply", expect, 2);
Expectation expect = {8, 0, 0, 12, 0, 31435, float3(-1, -1, -1), float3(1, 1, 1)};
import_and_check("ASCII_wireframe_cube.ply", expect);
import_and_check("wireframe_cube.ply", expect);
}
TEST_F(PlyImportTest, PLYImportBunny)
TEST_F(ply_import_test, PLYImportBunny)
{
Expectation expect[] = {{"OBCube",
ASCII,
8,
6,
12,
float3(1, 1, -1),
float3(-1, 1, 1),
float3(0.5773, 0.5773, -0.5773)},
{"OBbunny2",
BINARY_LE,
1623,
1000,
1513,
float3(0.0380425, 0.109755, 0.0161689),
float3(-0.0722821, 0.143895, -0.0129091),
float3(-2, -2, -2)}};
import_and_check("bunny2.ply", expect, 2);
Expectation expect = {1623,
1000,
3000,
0,
62556,
0,
float3(0.0380425, 0.109755, 0.0161689),
float3(-0.0722821, 0.143895, -0.0129091)};
import_and_check("bunny2.ply", expect);
}
TEST_F(PlyImportTest, PlyImportManySmallHoles)
TEST_F(ply_import_test, PlyImportManySmallHoles)
{
Expectation expect[] = {{"OBCube",
ASCII,
8,
6,
12,
float3(1, 1, -1),
float3(-1, 1, 1),
float3(0.5773, 0.5773, -0.5773)},
{"OBmany_small_holes",
BINARY_LE,
2004,
3524,
5564,
float3(-0.0131592, -0.0598382, 1.58958),
float3(-0.0177622, 0.0105153, 1.61977),
float3(-2, -2, -2),
float2(0, 0),
float4(0.7215, 0.6784, 0.6627, 1)}};
import_and_check("many_small_holes.ply", expect, 2);
Expectation expect = {2004,
3524,
10572,
0,
15143,
0,
float3(-0.0131592, -0.0598382, 1.58958),
float3(-0.0177622, 0.0105153, 1.61977),
float3(0, 0, 0),
float2(0, 0),
float4(0.7215, 0.6784, 0.6627, 1)};
import_and_check("many_small_holes.ply", expect);
}
TEST_F(PlyImportTest, PlyImportWireframeCube)
TEST_F(ply_import_test, PlyImportColorNotFull)
{
Expectation expect[] = {{"OBCube",
ASCII,
8,
6,
12,
float3(1, 1, -1),
float3(-1, 1, 1),
float3(0.5773, 0.5773, -0.5773)},
{"OBwireframe_cube",
BINARY_LE,
8,
0,
12,
float3(-1, -1, -1),
float3(1, 1, 1),
float3(-2, -2, -2)}};
import_and_check("wireframe_cube.ply", expect, 2);
Expectation expect = {4, 1, 4, 0, 37235, 0, float3(1, 0, 1), float3(-1, 0, 1)};
import_and_check("color_not_full_a.ply", expect);
import_and_check("color_not_full_b.ply", expect);
}
TEST(PlyImportFunctionsTest, PlySwapBytes)
TEST_F(ply_import_test, PlyImportDoubleXYZ)
{
/* Individual bits shouldn't swap with each other. */
uint8_t val8 = 0xA8;
uint8_t exp8 = 0xA8;
uint8_t actual8 = swap_bytes<uint8_t>(val8);
ASSERT_EQ(exp8, actual8);
uint16_t val16 = 0xFEB0;
uint16_t exp16 = 0xB0FE;
uint16_t actual16 = swap_bytes<uint16_t>(val16);
ASSERT_EQ(exp16, actual16);
uint32_t val32 = 0x80A37B0A;
uint32_t exp32 = 0x0A7BA380;
uint32_t actual32 = swap_bytes<uint32_t>(val32);
ASSERT_EQ(exp32, actual32);
uint64_t val64 = 0x0102030405060708;
uint64_t exp64 = 0x0807060504030201;
uint64_t actual64 = swap_bytes<uint64_t>(val64);
ASSERT_EQ(exp64, actual64);
Expectation expect = {4,
1,
4,
0,
37235,
0,
float3(1, 0, 1),
float3(-1, 0, 1),
float3(0, 0, 0),
float2(0, 0),
float4(1, 0, 0, 1)};
import_and_check("double_xyz_a.ply", expect);
import_and_check("double_xyz_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportFaceIndicesNotFirstProp)
{
Expectation expect = {4, 2, 6, 0, 4136, 0, float3(1, 0, 1), float3(-1, 0, 1)};
import_and_check("face_indices_not_first_prop_a.ply", expect);
import_and_check("face_indices_not_first_prop_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportFaceIndicesPrecededByList)
{
Expectation expect = {4, 2, 6, 0, 4136, 0, float3(1, 0, 1), float3(-1, 0, 1)};
import_and_check("face_indices_preceded_by_list_a.ply", expect);
import_and_check("face_indices_preceded_by_list_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportFaceUVsColors)
{
Expectation expect = {4, 1, 4, 0, 37235, 0, float3(1, 0, 1), float3(-1, 0, 1)};
import_and_check("face_uvs_colors_a.ply", expect);
import_and_check("face_uvs_colors_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportFacesFirst)
{
Expectation expect = {4,
1,
4,
0,
37235,
0,
float3(1, 0, 1),
float3(-1, 0, 1),
float3(0, 0, 0),
float2(0, 0),
float4(1, 0, 0, 1)};
import_and_check("faces_first_a.ply", expect);
import_and_check("faces_first_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportFloatFormats)
{
Expectation expect = {4,
1,
4,
0,
37235,
0,
float3(1, 0, 1),
float3(-1, 0, 1),
float3(0, 0, 0),
float2(0, 0),
float4(0.5f, 0, 0.25f, 1)};
import_and_check("float_formats_a.ply", expect);
import_and_check("float_formats_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportPositionNotFull)
{
Expectation expect = {0, 0, 0, 0};
import_and_check("position_not_full_a.ply", expect);
import_and_check("position_not_full_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportTristrips)
{
Expectation expect = {6, 4, 12, 0, 3404, 0, float3(1, 0, 1), float3(-3, 0, 1)};
import_and_check("tristrips_a.ply", expect);
import_and_check("tristrips_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportTypeAliases)
{
Expectation expect = {4,
1,
4,
0,
37235,
0,
float3(1, 0, 1),
float3(-1, 0, 1),
float3(0, 0, 0),
float2(0, 0),
float4(220 / 255.0f, 20 / 255.0f, 20 / 255.0f, 1)};
import_and_check("type_aliases_a.ply", expect);
import_and_check("type_aliases_b.ply", expect);
import_and_check("type_aliases_be_b.ply", expect);
}
TEST_F(ply_import_test, PlyImportVertexCompOrder)
{
Expectation expect = {4,
1,
4,
0,
37235,
0,
float3(1, 0, 1),
float3(-1, 0, 1),
float3(0, 0, 0),
float2(0, 0),
float4(0.8f, 0.2f, 0, 1)};
import_and_check("vertex_comp_order_a.ply", expect);
import_and_check("vertex_comp_order_b.ply", expect);
}
//@TODO: test with vertex element having list properties
//@TODO: test with edges starting with non-vertex index properties
//@TODO: test various malformed headers
//@TODO: UVs with: s,t; u,v; texture_u,texture_v; texture_s,texture_t (from miniply)
//@TODO: colors with: r,g,b in addition to red,green,blue (from miniply)
//@TODO: importing bunny2 with old importer results in smooth shading; flat shading with new one
} // namespace blender::io::ply