forked from blender/blender
main sync #3
@ -14,6 +14,7 @@ set(INC
|
|||||||
../../makesdna
|
../../makesdna
|
||||||
../../makesrna
|
../../makesrna
|
||||||
../../windowmanager
|
../../windowmanager
|
||||||
|
../../../../extern/fast_float
|
||||||
../../../../extern/fmtlib/include
|
../../../../extern/fmtlib/include
|
||||||
../../../../intern/guardedalloc
|
../../../../intern/guardedalloc
|
||||||
)
|
)
|
||||||
@ -31,13 +32,11 @@ set(SRC
|
|||||||
exporter/ply_file_buffer_ascii.cc
|
exporter/ply_file_buffer_ascii.cc
|
||||||
exporter/ply_file_buffer_binary.cc
|
exporter/ply_file_buffer_binary.cc
|
||||||
importer/ply_import.cc
|
importer/ply_import.cc
|
||||||
importer/ply_import_ascii.cc
|
importer/ply_import_buffer.cc
|
||||||
importer/ply_import_binary.cc
|
importer/ply_import_data.cc
|
||||||
importer/ply_import_mesh.cc
|
importer/ply_import_mesh.cc
|
||||||
IO_ply.cc
|
IO_ply.cc
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
exporter/ply_export.hh
|
exporter/ply_export.hh
|
||||||
exporter/ply_export_data.hh
|
exporter/ply_export_data.hh
|
||||||
exporter/ply_export_header.hh
|
exporter/ply_export_header.hh
|
||||||
@ -46,14 +45,12 @@ set(SRC
|
|||||||
exporter/ply_file_buffer_ascii.hh
|
exporter/ply_file_buffer_ascii.hh
|
||||||
exporter/ply_file_buffer_binary.hh
|
exporter/ply_file_buffer_binary.hh
|
||||||
importer/ply_import.hh
|
importer/ply_import.hh
|
||||||
importer/ply_import_ascii.hh
|
importer/ply_import_buffer.hh
|
||||||
importer/ply_import_binary.hh
|
importer/ply_import_data.hh
|
||||||
importer/ply_import_mesh.hh
|
importer/ply_import_mesh.hh
|
||||||
IO_ply.h
|
IO_ply.h
|
||||||
|
|
||||||
intern/ply_data.hh
|
intern/ply_data.hh
|
||||||
intern/ply_functions.cc
|
|
||||||
intern/ply_functions.hh
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIB
|
set(LIB
|
||||||
@ -66,9 +63,7 @@ blender_add_lib(bf_ply "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
|
|||||||
if (WITH_GTESTS)
|
if (WITH_GTESTS)
|
||||||
set(TEST_SRC
|
set(TEST_SRC
|
||||||
tests/io_ply_importer_test.cc
|
tests/io_ply_importer_test.cc
|
||||||
|
|
||||||
tests/io_ply_exporter_test.cc
|
tests/io_ply_exporter_test.cc
|
||||||
|
|
||||||
)
|
)
|
||||||
set(TEST_INC
|
set(TEST_INC
|
||||||
../../blenloader
|
../../blenloader
|
||||||
|
@ -39,8 +39,10 @@ void write_vertices(FileBuffer &buffer, const PlyData &ply_data)
|
|||||||
|
|
||||||
void write_faces(FileBuffer &buffer, const PlyData &ply_data)
|
void write_faces(FileBuffer &buffer, const PlyData &ply_data)
|
||||||
{
|
{
|
||||||
for (const Array<uint32_t> &face : ply_data.faces) {
|
const uint32_t *indices = ply_data.face_vertices.data();
|
||||||
buffer.write_face(char(face.size()), face);
|
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();
|
buffer.write_to_file();
|
||||||
}
|
}
|
||||||
|
@ -49,13 +49,13 @@ void write_header(FileBuffer &buffer,
|
|||||||
buffer.write_header_scalar_property("float", "t");
|
buffer.write_header_scalar_property("float", "t");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ply_data.faces.is_empty()) {
|
if (!ply_data.face_sizes.is_empty()) {
|
||||||
buffer.write_header_element("face", int32_t(ply_data.faces.size()));
|
buffer.write_header_element("face", int(ply_data.face_sizes.size()));
|
||||||
buffer.write_header_list_property("uchar", "uint", "vertex_indices");
|
buffer.write_header_list_property("uchar", "uint", "vertex_indices");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ply_data.edges.is_empty()) {
|
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", "vertex1");
|
||||||
buffer.write_header_scalar_property("int", "vertex2");
|
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);
|
&export_object_eval_, export_params.forward_axis, export_params.up_axis);
|
||||||
|
|
||||||
/* Load faces into plyData. */
|
/* Load faces into plyData. */
|
||||||
|
plyData.face_vertices.reserve(mesh->totloop);
|
||||||
|
plyData.face_sizes.reserve(mesh->totpoly);
|
||||||
int loop_offset = 0;
|
int loop_offset = 0;
|
||||||
const Span<MLoop> loops = mesh->loops();
|
const Span<MLoop> loops = mesh->loops();
|
||||||
for (const MPoly &poly : mesh->polys()) {
|
for (const MPoly &poly : mesh->polys()) {
|
||||||
const Span<MLoop> poly_loops = loops.slice(poly.loopstart, poly.totloop);
|
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) {
|
for (int i = 0; i < poly_loops.size(); ++i) {
|
||||||
float2 uv;
|
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);
|
UV_vertex_key key = UV_vertex_key(uv, poly_loops[i].v);
|
||||||
int ply_vertex_index = vertex_map.lookup(key);
|
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();
|
loop_offset += poly.totloop;
|
||||||
|
plyData.face_sizes.append(poly.totloop);
|
||||||
plyData.faces.append(std::move(poly_verts));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Array<int> mesh_vertex_index_LUT(vertex_map.size());
|
Array<int> mesh_vertex_index_LUT(vertex_map.size());
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
#include "DNA_object_types.h"
|
#include "DNA_object_types.h"
|
||||||
#include "DNA_scene_types.h"
|
#include "DNA_scene_types.h"
|
||||||
|
|
||||||
#include "BLI_fileops.hh"
|
|
||||||
#include "BLI_math_vector.h"
|
#include "BLI_math_vector.h"
|
||||||
#include "BLI_memory_utils.hh"
|
#include "BLI_memory_utils.hh"
|
||||||
|
|
||||||
@ -22,53 +21,137 @@
|
|||||||
#include "DEG_depsgraph_build.h"
|
#include "DEG_depsgraph_build.h"
|
||||||
|
|
||||||
#include "ply_data.hh"
|
#include "ply_data.hh"
|
||||||
#include "ply_functions.hh"
|
|
||||||
#include "ply_import.hh"
|
#include "ply_import.hh"
|
||||||
#include "ply_import_ascii.hh"
|
#include "ply_import_buffer.hh"
|
||||||
#include "ply_import_binary.hh"
|
#include "ply_import_data.hh"
|
||||||
#include "ply_import_mesh.hh"
|
#include "ply_import_mesh.hh"
|
||||||
|
|
||||||
namespace blender::io::ply {
|
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;
|
const size_t keyword_len = keyword.size();
|
||||||
|
if (str.size() < keyword_len) {
|
||||||
while ((pos = int(str.find(deli))) != std::string::npos) {
|
return false;
|
||||||
words.append(str.substr(0, pos));
|
|
||||||
str.erase(0, pos + deli.size());
|
|
||||||
}
|
}
|
||||||
/* We add the final word to the vector. */
|
if (memcmp(str.data(), keyword.data(), keyword_len) != 0) {
|
||||||
words.append(str.substr());
|
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;
|
return PlyDataTypes::UCHAR;
|
||||||
}
|
}
|
||||||
if (input == "char") {
|
if (input == "char" || input == "int8") {
|
||||||
return PlyDataTypes::CHAR;
|
return PlyDataTypes::CHAR;
|
||||||
}
|
}
|
||||||
if (input == "ushort") {
|
if (input == "ushort" || input == "uint16") {
|
||||||
return PlyDataTypes::USHORT;
|
return PlyDataTypes::USHORT;
|
||||||
}
|
}
|
||||||
if (input == "short") {
|
if (input == "short" || input == "int16") {
|
||||||
return PlyDataTypes::SHORT;
|
return PlyDataTypes::SHORT;
|
||||||
}
|
}
|
||||||
if (input == "uint") {
|
if (input == "uint" || input == "uint32") {
|
||||||
return PlyDataTypes::UINT;
|
return PlyDataTypes::UINT;
|
||||||
}
|
}
|
||||||
if (input == "int") {
|
if (input == "int" || input == "int32") {
|
||||||
return PlyDataTypes::INT;
|
return PlyDataTypes::INT;
|
||||||
}
|
}
|
||||||
if (input == "float") {
|
if (input == "float" || input == "float32") {
|
||||||
return PlyDataTypes::FLOAT;
|
return PlyDataTypes::FLOAT;
|
||||||
}
|
}
|
||||||
if (input == "double") {
|
if (input == "double" || input == "float64") {
|
||||||
return PlyDataTypes::DOUBLE;
|
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)
|
void importer_main(bContext *C, const PLYImportParams &import_params, wmOperator *op)
|
||||||
@ -85,75 +168,42 @@ void importer_main(Main *bmain,
|
|||||||
const PLYImportParams &import_params,
|
const PLYImportParams &import_params,
|
||||||
wmOperator *op)
|
wmOperator *op)
|
||||||
{
|
{
|
||||||
|
/* File base name used for both mesh and object. */
|
||||||
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. */
|
|
||||||
char ob_name[FILE_MAX];
|
char ob_name[FILE_MAX];
|
||||||
BLI_strncpy(ob_name, BLI_path_basename(import_params.filepath), FILE_MAX);
|
BLI_strncpy(ob_name, BLI_path_basename(import_params.filepath), FILE_MAX);
|
||||||
BLI_path_extension_replace(ob_name, 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);
|
BKE_view_layer_base_deselect_all(scene, view_layer);
|
||||||
LayerCollection *lc = BKE_layer_collection_get_active(view_layer);
|
LayerCollection *lc = BKE_layer_collection_get_active(view_layer);
|
||||||
Object *obj = BKE_object_add_only_object(bmain, OB_MESH, ob_name);
|
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);
|
Base *base = BKE_view_layer_base_find(view_layer, obj);
|
||||||
BKE_view_layer_base_select_and_set_active(view_layer, base);
|
BKE_view_layer_base_select_and_set_active(view_layer, base);
|
||||||
|
|
||||||
try {
|
/* Stuff ply data into the mesh. */
|
||||||
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);
|
Mesh *temp_val = convert_ply_to_mesh(*data, mesh, import_params);
|
||||||
if (import_params.merge_verts && temp_val != mesh) {
|
if (import_params.merge_verts && temp_val != mesh) {
|
||||||
BKE_mesh_nomain_to_mesh(temp_val, mesh, obj);
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/* Object matrix and finishing up. */
|
||||||
float global_scale = import_params.global_scale;
|
float global_scale = import_params.global_scale;
|
||||||
if ((scene->unit.system != USER_UNIT_NONE) && import_params.use_scene_unit) {
|
if ((scene->unit.system != USER_UNIT_NONE) && import_params.use_scene_unit) {
|
||||||
global_scale *= scene->unit.scale_length;
|
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_ex(bmain, &obj->id, flags);
|
||||||
DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS);
|
DEG_id_tag_update(&scene->id, ID_RECALC_BASE_FLAGS);
|
||||||
DEG_relations_tag_update(bmain);
|
DEG_relations_tag_update(bmain);
|
||||||
|
|
||||||
infile.close();
|
|
||||||
}
|
}
|
||||||
} // namespace blender::io::ply
|
} // namespace blender::io::ply
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace blender::io::ply {
|
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);
|
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,
|
const PLYImportParams &import_params,
|
||||||
wmOperator *op);
|
wmOperator *op);
|
||||||
|
|
||||||
|
const char *read_header(PlyReadBuffer &file, PlyHeader &r_header);
|
||||||
|
|
||||||
} // namespace blender::io::ply
|
} // 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);
|
CustomData_add_layer(&mesh->edata, CD_MEDGE, CD_SET_DEFAULT, mesh->totedge);
|
||||||
MutableSpan<MEdge> edges = mesh->edges_for_write();
|
MutableSpan<MEdge> edges = mesh->edges_for_write();
|
||||||
for (int i = 0; i < mesh->totedge; i++) {
|
for (int i = 0; i < mesh->totedge; i++) {
|
||||||
edges[i].v1 = data.edges[i].first;
|
uint32_t v1 = data.edges[i].first;
|
||||||
edges[i].v2 = data.edges[i].second;
|
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. */
|
/* Add faces to the mesh. */
|
||||||
if (!data.faces.is_empty()) {
|
if (!data.face_sizes.is_empty()) {
|
||||||
/* Specify amount of total faces. */
|
/* Create poly and loop layers. */
|
||||||
mesh->totpoly = int(data.faces.size());
|
mesh->totpoly = int(data.face_sizes.size());
|
||||||
mesh->totloop = 0;
|
mesh->totloop = int(data.face_vertices.size());
|
||||||
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();
|
|
||||||
}
|
|
||||||
CustomData_add_layer(&mesh->pdata, CD_MPOLY, CD_SET_DEFAULT, mesh->totpoly);
|
CustomData_add_layer(&mesh->pdata, CD_MPOLY, CD_SET_DEFAULT, mesh->totpoly);
|
||||||
CustomData_add_layer(&mesh->ldata, CD_MLOOP, CD_SET_DEFAULT, mesh->totloop);
|
CustomData_add_layer(&mesh->ldata, CD_MLOOP, CD_SET_DEFAULT, mesh->totloop);
|
||||||
MutableSpan<MPoly> polys = mesh->polys_for_write();
|
MutableSpan<MPoly> polys = mesh->polys_for_write();
|
||||||
MutableSpan<MLoop> loops = mesh->loops_for_write();
|
MutableSpan<MLoop> loops = mesh->loops_for_write();
|
||||||
|
|
||||||
int offset = 0;
|
/* Fill in face data. */
|
||||||
/* Iterate over amount of faces. */
|
uint32_t offset = 0;
|
||||||
for (int i = 0; i < mesh->totpoly; i++) {
|
for (int i = 0; i < mesh->totpoly; i++) {
|
||||||
int size = int(data.faces[i].size());
|
uint32_t size = data.face_sizes[i];
|
||||||
/* Set the index from where this face starts and specify the amount of edges it has. */
|
|
||||||
polys[i].loopstart = offset;
|
polys[i].loopstart = offset;
|
||||||
polys[i].totloop = size;
|
polys[i].totloop = size;
|
||||||
|
|
||||||
for (int j = 0; j < size; j++) {
|
for (int j = 0; j < size; j++) {
|
||||||
/* Set the vertex index of the loop to the one in PlyData. */
|
uint32_t v = data.face_vertices[offset + j];
|
||||||
loops[offset + j].v = data.faces[i][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;
|
offset += size;
|
||||||
}
|
}
|
||||||
@ -93,12 +101,8 @@ Mesh *convert_ply_to_mesh(PlyData &data, Mesh *mesh, const PLYImportParams ¶
|
|||||||
if (!data.uv_coordinates.is_empty()) {
|
if (!data.uv_coordinates.is_empty()) {
|
||||||
bke::SpanAttributeWriter<float2> uv_map = attributes.lookup_or_add_for_write_only_span<float2>(
|
bke::SpanAttributeWriter<float2> uv_map = attributes.lookup_or_add_for_write_only_span<float2>(
|
||||||
"UVMap", ATTR_DOMAIN_CORNER);
|
"UVMap", ATTR_DOMAIN_CORNER);
|
||||||
int counter = 0;
|
for (size_t i = 0; i < data.face_vertices.size(); i++) {
|
||||||
for (int i = 0; i < data.faces.size(); i++) {
|
uv_map.span[i] = data.uv_coordinates[data.face_vertices[i]];
|
||||||
for (int j = 0; j < data.faces[i].size(); j++) {
|
|
||||||
uv_map.span[counter] = data.uv_coordinates[data.faces[i][j]];
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
uv_map.finish();
|
uv_map.finish();
|
||||||
}
|
}
|
||||||
|
@ -12,30 +12,38 @@
|
|||||||
|
|
||||||
namespace blender::io::ply {
|
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 {
|
struct PlyData {
|
||||||
Vector<float3> vertices;
|
Vector<float3> vertices;
|
||||||
Vector<float3> vertex_normals;
|
Vector<float3> vertex_normals;
|
||||||
/* Value between 0 and 1. */
|
Vector<float4> vertex_colors; /* Linear space, 0..1 range colors. */
|
||||||
Vector<float4> vertex_colors;
|
|
||||||
Vector<std::pair<int, int>> edges;
|
Vector<std::pair<int, int>> edges;
|
||||||
Vector<float3> edge_colors;
|
Vector<uint32_t> face_vertices;
|
||||||
Vector<Array<uint32_t>> faces;
|
Vector<uint32_t> face_sizes;
|
||||||
Vector<float2> uv_coordinates;
|
Vector<float2> uv_coordinates;
|
||||||
|
std::string error;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum PlyFormatType { ASCII, BINARY_LE, BINARY_BE };
|
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 {
|
struct PlyHeader {
|
||||||
int vertex_count = 0;
|
Vector<PlyElement> elements;
|
||||||
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;
|
|
||||||
PlyFormatType type;
|
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 {
|
namespace blender::io::ply {
|
||||||
|
|
||||||
class PlyExportTest : public BlendfileLoadingBaseTest {
|
class ply_export_test : public BlendfileLoadingBaseTest {
|
||||||
public:
|
public:
|
||||||
bool load_file_and_depsgraph(const std::string &filepath,
|
bool load_file_and_depsgraph(const std::string &filepath,
|
||||||
const eEvaluationMode eval_mode = DAG_EVAL_VIEWPORT)
|
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},
|
{-1.122082, -1.122082, -1.122082},
|
||||||
};
|
};
|
||||||
|
|
||||||
plyData->faces = {
|
plyData->face_sizes = {4, 4, 4, 4, 4, 4};
|
||||||
{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_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)
|
if (params.export_normals)
|
||||||
plyData->vertex_normals = {
|
plyData->vertex_normals = {
|
||||||
@ -126,7 +127,7 @@ static std::vector<char> read_temp_file_in_vectorchar(const std::string &file_pa
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(PlyExportTest, WriteHeaderAscii)
|
TEST_F(ply_export_test, WriteHeaderAscii)
|
||||||
{
|
{
|
||||||
std::string filePath = get_temp_ply_filename(temp_file_path);
|
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||||
PLYExportParams _params = {};
|
PLYExportParams _params = {};
|
||||||
@ -164,7 +165,7 @@ TEST_F(PlyExportTest, WriteHeaderAscii)
|
|||||||
ASSERT_STREQ(result.c_str(), expected.c_str());
|
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);
|
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||||
PLYExportParams _params = {};
|
PLYExportParams _params = {};
|
||||||
@ -202,7 +203,7 @@ TEST_F(PlyExportTest, WriteHeaderBinary)
|
|||||||
ASSERT_STREQ(result.c_str(), expected.c_str());
|
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);
|
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||||
PLYExportParams _params = {};
|
PLYExportParams _params = {};
|
||||||
@ -234,7 +235,7 @@ TEST_F(PlyExportTest, WriteVerticesAscii)
|
|||||||
ASSERT_STREQ(result.c_str(), expected.c_str());
|
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);
|
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||||
PLYExportParams _params = {};
|
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);
|
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||||
PLYExportParams _params = {};
|
PLYExportParams _params = {};
|
||||||
@ -300,7 +301,7 @@ TEST_F(PlyExportTest, WriteFacesAscii)
|
|||||||
ASSERT_STREQ(result.c_str(), expected.c_str());
|
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);
|
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||||
PLYExportParams _params = {};
|
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);
|
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||||
PLYExportParams _params = {};
|
PLYExportParams _params = {};
|
||||||
@ -368,7 +369,7 @@ TEST_F(PlyExportTest, WriteVertexNormalsAscii)
|
|||||||
ASSERT_STREQ(result.c_str(), expected.c_str());
|
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);
|
std::string filePath = get_temp_ply_filename(temp_file_path);
|
||||||
PLYExportParams _params = {};
|
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:
|
public:
|
||||||
PlyData load_ply_data_from_blendfile(const std::string &blendfile, PLYExportParams ¶ms)
|
PlyData load_ply_data_from_blendfile(const std::string &blendfile, PLYExportParams ¶ms)
|
||||||
{
|
{
|
||||||
|
@ -1,249 +1,278 @@
|
|||||||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
|
||||||
#include "tests/blendfile_loading_base_test.h"
|
#include "testing/testing.h"
|
||||||
|
|
||||||
#include "BKE_attribute.hh"
|
#include "BLI_fileops.hh"
|
||||||
#include "BKE_mesh.hh"
|
#include "BLI_hash_mm2a.h"
|
||||||
#include "BKE_object.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.hh"
|
||||||
#include "ply_import_binary.hh"
|
#include "ply_import_buffer.hh"
|
||||||
|
#include "ply_import_data.hh"
|
||||||
|
|
||||||
namespace blender::io::ply {
|
namespace blender::io::ply {
|
||||||
|
|
||||||
struct Expectation {
|
struct Expectation {
|
||||||
std::string name;
|
int totvert, totpoly, totindex, totedge;
|
||||||
PlyFormatType type;
|
uint16_t polyhash = 0, edgehash = 0;
|
||||||
int totvert, totpoly, totedge;
|
|
||||||
float3 vert_first, vert_last;
|
float3 vert_first, vert_last;
|
||||||
float3 normal_first = {0, 0, 0};
|
float3 normal_first = {0, 0, 0};
|
||||||
float2 uv_first;
|
float2 uv_first = {0, 0};
|
||||||
float4 color_first = {-1, -1, -1, -1};
|
float4 color_first = {-1, -1, -1, -1};
|
||||||
};
|
};
|
||||||
|
|
||||||
class PlyImportTest : public BlendfileLoadingBaseTest {
|
class ply_import_test : public testing::Test {
|
||||||
public:
|
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();
|
ADD_FAILURE();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
std::unique_ptr<PlyData> data = import_ply_data(infile, header);
|
||||||
PLYImportParams params;
|
if (!data->error.empty()) {
|
||||||
params.global_scale = 1.0f;
|
fprintf(stderr, "%s\n", data->error.c_str());
|
||||||
params.forward_axis = IO_AXIS_NEGATIVE_Z;
|
ASSERT_EQ(0, exp.totvert);
|
||||||
params.up_axis = IO_AXIS_Y;
|
ASSERT_EQ(0, exp.totpoly);
|
||||||
params.merge_verts = false;
|
return;
|
||||||
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];
|
/* 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);
|
||||||
|
|
||||||
ASSERT_STREQ(object->id.name, exp.name.c_str());
|
/* Test hash of face and edge index data. */
|
||||||
EXPECT_V3_NEAR(object->loc, float3(0, 0, 0), 0.0001f);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
EXPECT_V3_NEAR(object->scale, float3(1, 1, 1), 0.0001f);
|
if (!data->edges.is_empty()) {
|
||||||
if (object->type == OB_MESH) {
|
uint16_t edge_hash = BLI_hash_mm2((const unsigned char *)data->edges.data(),
|
||||||
Mesh *mesh = BKE_object_get_evaluated_mesh(object);
|
data->edges.size() * sizeof(data->edges[0]),
|
||||||
|
0);
|
||||||
/* Test if mesh has expected amount of vertices, edges, and faces. */
|
ASSERT_EQ(edge_hash, exp.edgehash);
|
||||||
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. */
|
/* Test if first and last vertices match. */
|
||||||
const Span<float3> verts = mesh->vert_positions();
|
EXPECT_V3_NEAR(data->vertices.first(), exp.vert_first, 0.0001f);
|
||||||
EXPECT_V3_NEAR(verts.first(), exp.vert_first, 0.0001f);
|
EXPECT_V3_NEAR(data->vertices.last(), exp.vert_last, 0.0001f);
|
||||||
EXPECT_V3_NEAR(verts.last(), exp.vert_last, 0.0001f);
|
|
||||||
|
|
||||||
/* Fetch normal data from mesh and test if it matches expectation. */
|
/* Check if first normal matches. */
|
||||||
if (BKE_mesh_has_custom_loop_normals(mesh)) {
|
float3 got_normal = data->vertex_normals.is_empty() ? float3(0, 0, 0) :
|
||||||
const Span<float3> vertex_normals = mesh->vert_normals();
|
data->vertex_normals.first();
|
||||||
ASSERT_FALSE(vertex_normals.is_empty());
|
EXPECT_V3_NEAR(got_normal, exp.normal_first, 0.0001f);
|
||||||
EXPECT_V3_NEAR(vertex_normals[0], exp.normal_first, 0.0001f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fetch UV data from mesh and test if it matches expectation. */
|
/* Check if first UV matches. */
|
||||||
blender::bke::AttributeAccessor attributes = mesh->attributes();
|
float2 got_uv = data->uv_coordinates.is_empty() ? float2(0, 0) : data->uv_coordinates.first();
|
||||||
VArray<float2> uvs = attributes.lookup<float2>("UVMap");
|
EXPECT_V2_NEAR(got_uv, exp.uv_first, 0.0001f);
|
||||||
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. */
|
/* Check if first color matches. */
|
||||||
if (CustomData_has_layer(&mesh->vdata, CD_PROP_COLOR)) {
|
float4 got_color = data->vertex_colors.is_empty() ? float4(-1, -1, -1, -1) :
|
||||||
const float4 *colors = (const float4 *)CustomData_get_layer(&mesh->vdata, CD_PROP_COLOR);
|
data->vertex_colors.first();
|
||||||
ASSERT_TRUE(colors != nullptr);
|
EXPECT_V4_NEAR(got_color, exp.color_first, 0.0001f);
|
||||||
EXPECT_V4_NEAR(colors[0], exp.color_first, 0.0001f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++object_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
DEG_OBJECT_ITER_END;
|
|
||||||
EXPECT_EQ(object_index, expect_count);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(PlyImportTest, PLYImportCube)
|
TEST_F(ply_import_test, PLYImportCube)
|
||||||
{
|
{
|
||||||
Expectation expect[] = {{"OBCube",
|
Expectation expect = {24,
|
||||||
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,
|
6,
|
||||||
24,
|
24,
|
||||||
|
0,
|
||||||
|
26429,
|
||||||
|
0,
|
||||||
float3(1, 1, -1),
|
float3(1, 1, -1),
|
||||||
float3(-1, 1, 1),
|
float3(-1, 1, 1),
|
||||||
float3(0, 0, -1),
|
float3(0, 0, -1),
|
||||||
float2(0.979336, 0.844958),
|
float2(0.979336, 0.844958),
|
||||||
float4(1, 0.8470, 0, 1)}};
|
float4(1, 0.8470, 0, 1)};
|
||||||
import_and_check("cube_ascii.ply", expect, 2);
|
import_and_check("cube_ascii.ply", expect);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(PlyImportTest, PLYImportASCIIEdgeTest)
|
TEST_F(ply_import_test, PLYImportWireframeCube)
|
||||||
{
|
{
|
||||||
Expectation expect[] = {{"OBCube",
|
Expectation expect = {8, 0, 0, 12, 0, 31435, float3(-1, -1, -1), float3(1, 1, 1)};
|
||||||
ASCII,
|
import_and_check("ASCII_wireframe_cube.ply", expect);
|
||||||
8,
|
import_and_check("wireframe_cube.ply", expect);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(PlyImportTest, PLYImportBunny)
|
TEST_F(ply_import_test, PLYImportBunny)
|
||||||
{
|
{
|
||||||
Expectation expect[] = {{"OBCube",
|
Expectation expect = {1623,
|
||||||
ASCII,
|
|
||||||
8,
|
|
||||||
6,
|
|
||||||
12,
|
|
||||||
float3(1, 1, -1),
|
|
||||||
float3(-1, 1, 1),
|
|
||||||
float3(0.5773, 0.5773, -0.5773)},
|
|
||||||
{"OBbunny2",
|
|
||||||
BINARY_LE,
|
|
||||||
1623,
|
|
||||||
1000,
|
1000,
|
||||||
1513,
|
3000,
|
||||||
|
0,
|
||||||
|
62556,
|
||||||
|
0,
|
||||||
float3(0.0380425, 0.109755, 0.0161689),
|
float3(0.0380425, 0.109755, 0.0161689),
|
||||||
float3(-0.0722821, 0.143895, -0.0129091),
|
float3(-0.0722821, 0.143895, -0.0129091)};
|
||||||
float3(-2, -2, -2)}};
|
import_and_check("bunny2.ply", expect);
|
||||||
import_and_check("bunny2.ply", expect, 2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(PlyImportTest, PlyImportManySmallHoles)
|
TEST_F(ply_import_test, PlyImportManySmallHoles)
|
||||||
{
|
{
|
||||||
Expectation expect[] = {{"OBCube",
|
Expectation expect = {2004,
|
||||||
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,
|
3524,
|
||||||
5564,
|
10572,
|
||||||
|
0,
|
||||||
|
15143,
|
||||||
|
0,
|
||||||
float3(-0.0131592, -0.0598382, 1.58958),
|
float3(-0.0131592, -0.0598382, 1.58958),
|
||||||
float3(-0.0177622, 0.0105153, 1.61977),
|
float3(-0.0177622, 0.0105153, 1.61977),
|
||||||
float3(-2, -2, -2),
|
float3(0, 0, 0),
|
||||||
float2(0, 0),
|
float2(0, 0),
|
||||||
float4(0.7215, 0.6784, 0.6627, 1)}};
|
float4(0.7215, 0.6784, 0.6627, 1)};
|
||||||
import_and_check("many_small_holes.ply", expect, 2);
|
import_and_check("many_small_holes.ply", expect);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(PlyImportTest, PlyImportWireframeCube)
|
TEST_F(ply_import_test, PlyImportColorNotFull)
|
||||||
{
|
{
|
||||||
Expectation expect[] = {{"OBCube",
|
Expectation expect = {4, 1, 4, 0, 37235, 0, float3(1, 0, 1), float3(-1, 0, 1)};
|
||||||
ASCII,
|
import_and_check("color_not_full_a.ply", expect);
|
||||||
8,
|
import_and_check("color_not_full_b.ply", expect);
|
||||||
6,
|
}
|
||||||
12,
|
|
||||||
float3(1, 1, -1),
|
TEST_F(ply_import_test, PlyImportDoubleXYZ)
|
||||||
float3(-1, 1, 1),
|
{
|
||||||
float3(0.5773, 0.5773, -0.5773)},
|
Expectation expect = {4,
|
||||||
{"OBwireframe_cube",
|
1,
|
||||||
BINARY_LE,
|
4,
|
||||||
8,
|
|
||||||
0,
|
0,
|
||||||
12,
|
37235,
|
||||||
float3(-1, -1, -1),
|
0,
|
||||||
float3(1, 1, 1),
|
float3(1, 0, 1),
|
||||||
float3(-2, -2, -2)}};
|
float3(-1, 0, 1),
|
||||||
import_and_check("wireframe_cube.ply", expect, 2);
|
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(PlyImportFunctionsTest, PlySwapBytes)
|
TEST_F(ply_import_test, PlyImportFaceIndicesNotFirstProp)
|
||||||
{
|
{
|
||||||
/* Individual bits shouldn't swap with each other. */
|
Expectation expect = {4, 2, 6, 0, 4136, 0, float3(1, 0, 1), float3(-1, 0, 1)};
|
||||||
uint8_t val8 = 0xA8;
|
import_and_check("face_indices_not_first_prop_a.ply", expect);
|
||||||
uint8_t exp8 = 0xA8;
|
import_and_check("face_indices_not_first_prop_b.ply", expect);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
} // namespace blender::io::ply
|
||||||
|
Loading…
Reference in New Issue
Block a user