forked from blender/blender
main sync #3
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
116
source/blender/io/ply/importer/ply_import_buffer.cc
Normal file
116
source/blender/io/ply/importer/ply_import_buffer.cc
Normal 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
|
51
source/blender/io/ply/importer/ply_import_buffer.hh
Normal file
51
source/blender/io/ply/importer/ply_import_buffer.hh
Normal 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
|
606
source/blender/io/ply/importer/ply_import_data.cc
Normal file
606
source/blender/io/ply/importer/ply_import_data.cc
Normal 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
|
23
source/blender/io/ply/importer/ply_import_data.hh
Normal file
23
source/blender/io/ply/importer/ply_import_data.hh
Normal 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
|
@ -33,36 +33,44 @@ Mesh *convert_ply_to_mesh(PlyData &data, Mesh *mesh, const PLYImportParams ¶
|
||||
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 ¶
|
||||
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();
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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
|
@ -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
|
@ -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 ¶ms)
|
||||
{-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 ¶ms)
|
||||
{
|
||||
|
@ -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 (°_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
|
||||
|
Loading…
Reference in New Issue
Block a user