Blender V4.3
ply_import_data.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include "ply_import_data.hh"
10#include "ply_data.hh"
11#include "ply_import_buffer.hh"
12
13#include "BLI_endian_switch.h"
14#include "BLI_string_ref.hh"
15
16#include "fast_float.h"
17
18#include <charconv>
19
20static bool is_whitespace(char c)
21{
22 return c <= ' ';
23}
24
25static const char *drop_whitespace(const char *p, const char *end)
26{
27 while (p < end && is_whitespace(*p)) {
28 ++p;
29 }
30 return p;
31}
32
33static const char *drop_non_whitespace(const char *p, const char *end)
34{
35 while (p < end && !is_whitespace(*p)) {
36 ++p;
37 }
38 return p;
39}
40
41static const char *drop_plus(const char *p, const char *end)
42{
43 if (p < end && *p == '+') {
44 ++p;
45 }
46 return p;
47}
48
49static const char *parse_float(const char *p, const char *end, float fallback, float &dst)
50{
51 p = drop_whitespace(p, end);
52 p = drop_plus(p, end);
53 fast_float::from_chars_result res = fast_float::from_chars(p, end, dst);
54 if (ELEM(res.ec, std::errc::invalid_argument, std::errc::result_out_of_range)) {
55 dst = fallback;
56 }
57 return res.ptr;
58}
59
60static const char *parse_int(const char *p, const char *end, int fallback, int &dst)
61{
62 p = drop_whitespace(p, end);
63 p = drop_plus(p, end);
64 std::from_chars_result res = std::from_chars(p, end, dst);
65 if (ELEM(res.ec, std::errc::invalid_argument, std::errc::result_out_of_range)) {
66 dst = fallback;
67 }
68 return res.ptr;
69}
70
71static void endian_switch(uint8_t *ptr, int type_size)
72{
73 if (type_size == 2) {
75 }
76 else if (type_size == 4) {
78 }
79 else if (type_size == 8) {
81 }
82}
83
84static void endian_switch_array(uint8_t *ptr, int type_size, int size)
85{
86 if (type_size == 2) {
88 }
89 else if (type_size == 4) {
91 }
92 else if (type_size == 8) {
94 }
95}
96
97namespace blender::io::ply {
98
99static const int data_type_size[] = {0, 1, 1, 2, 2, 4, 4, 4, 8};
100static_assert(std::size(data_type_size) == PLY_TYPE_COUNT, "PLY data type size table mismatch");
101
102static const float data_type_normalizer[] = {
103 1.0f, 127.0f, 255.0f, 32767.0f, 65535.0f, float(INT_MAX), float(UINT_MAX), 1.0f, 1.0f};
104static_assert(std::size(data_type_normalizer) == PLY_TYPE_COUNT,
105 "PLY data type normalization factor table mismatch");
106
108{
109 stride = 0;
110 for (PlyProperty &p : properties) {
111 if (p.count_type != PlyDataTypes::NONE) {
112 stride = 0;
113 return;
114 }
115 stride += data_type_size[p.type];
116 }
117}
118
119static int get_index(const PlyElement &element, StringRef property)
120{
121 for (int i = 0, n = int(element.properties.size()); i != n; i++) {
122 const PlyProperty &prop = element.properties[i];
123 if (prop.name == property) {
124 return i;
125 }
126 }
127 return -1;
128}
129
130static const char *parse_row_ascii(PlyReadBuffer &file, Vector<float> &r_values)
131{
132 Span<char> line = file.read_line();
133 if (line.is_empty()) {
134 return "Could not read row of ascii property";
135 }
136
137 /* Parse whole line as floats. */
138 const char *p = line.data();
139 const char *end = p + line.size();
140 int value_idx = 0;
141 while (p < end && value_idx < r_values.size()) {
142 float val;
143 p = parse_float(p, end, 0.0f, val);
144 r_values[value_idx++] = val;
145 }
146 return nullptr;
147}
148
149template<typename T> static T get_binary_value(PlyDataTypes type, const uint8_t *&r_ptr)
150{
151 T val = 0;
152 switch (type) {
153 case NONE:
154 break;
155 case CHAR:
156 val = *(int8_t *)r_ptr;
157 r_ptr += 1;
158 break;
159 case UCHAR:
160 val = *(uint8_t *)r_ptr;
161 r_ptr += 1;
162 break;
163 case SHORT:
164 val = *(int16_t *)r_ptr;
165 r_ptr += 2;
166 break;
167 case USHORT:
168 val = *(uint16_t *)r_ptr;
169 r_ptr += 2;
170 break;
171 case INT:
172 val = *(int32_t *)r_ptr;
173 r_ptr += 4;
174 break;
175 case UINT:
176 val = *(int32_t *)r_ptr;
177 r_ptr += 4;
178 break;
179 case FLOAT:
180 val = *(float *)r_ptr;
181 r_ptr += 4;
182 break;
183 case DOUBLE:
184 val = *(double *)r_ptr;
185 r_ptr += 8;
186 break;
187 default:
188 BLI_assert_msg(false, "Unknown property type");
189 }
190 return val;
191}
192
193static const char *parse_row_binary(PlyReadBuffer &file,
194 const PlyHeader &header,
195 const PlyElement &element,
196 Vector<uint8_t> &r_scratch,
197 Vector<float> &r_values)
198{
199 if (element.stride == 0) {
200 return "Vertex/Edge element contains list properties, this is not supported";
201 }
202 BLI_assert(r_scratch.size() == element.stride);
203 BLI_assert(r_values.size() == element.properties.size());
204 if (!file.read_bytes(r_scratch.data(), r_scratch.size())) {
205 return "Could not read row of binary property";
206 }
207
208 const uint8_t *ptr = r_scratch.data();
209 if (header.type == PlyFormatType::BINARY_LE) {
210 /* Little endian: just read/convert the values. */
211 for (int i = 0, n = int(element.properties.size()); i != n; i++) {
212 const PlyProperty &prop = element.properties[i];
213 float val = get_binary_value<float>(prop.type, ptr);
214 r_values[i] = val;
215 }
216 }
217 else if (header.type == PlyFormatType::BINARY_BE) {
218 /* Big endian: read, switch endian, convert the values. */
219 for (int i = 0, n = int(element.properties.size()); i != n; i++) {
220 const PlyProperty &prop = element.properties[i];
222 float val = get_binary_value<float>(prop.type, ptr);
223 r_values[i] = val;
224 }
225 }
226 else {
227 return "Unknown binary ply format for vertex element";
228 }
229 return nullptr;
230}
231
232static const char *load_vertex_element(PlyReadBuffer &file,
233 const PlyHeader &header,
234 const PlyElement &element,
235 PlyData *data)
236{
237 /* Figure out vertex component indices. */
238 int3 vertex_index = {get_index(element, "x"), get_index(element, "y"), get_index(element, "z")};
239 int3 color_index = {
240 get_index(element, "red"), get_index(element, "green"), get_index(element, "blue")};
241 int3 normal_index = {
242 get_index(element, "nx"), get_index(element, "ny"), get_index(element, "nz")};
243 int2 uv_index = {get_index(element, "s"), get_index(element, "t")};
244 int alpha_index = get_index(element, "alpha");
245
246 bool has_vertex = vertex_index.x >= 0 && vertex_index.y >= 0 && vertex_index.z >= 0;
247 bool has_color = color_index.x >= 0 && color_index.y >= 0 && color_index.z >= 0;
248 bool has_normal = normal_index.x >= 0 && normal_index.y >= 0 && normal_index.z >= 0;
249 bool has_uv = uv_index.x >= 0 && uv_index.y >= 0;
250 bool has_alpha = alpha_index >= 0;
251
252 if (!has_vertex) {
253 return "Vertex positions are not present in the file";
254 }
255
256 Vector<int64_t> custom_attr_indices;
257 for (const int64_t prop_idx : element.properties.index_range()) {
258 const PlyProperty &prop = element.properties[prop_idx];
259 bool is_standard = ELEM(
260 prop.name, "x", "y", "z", "nx", "ny", "nz", "red", "green", "blue", "alpha", "s", "t");
261 if (is_standard)
262 continue;
263
264 custom_attr_indices.append(prop_idx);
265 PlyCustomAttribute attr(prop.name, element.count);
266 data->vertex_custom_attr.append(attr);
267 }
268
269 data->vertices.reserve(element.count);
270 if (has_color) {
271 data->vertex_colors.reserve(element.count);
272 }
273 if (has_normal) {
274 data->vertex_normals.reserve(element.count);
275 }
276 if (has_uv) {
277 data->uv_coordinates.reserve(element.count);
278 }
279
280 float4 color_norm = {1, 1, 1, 1};
281 if (has_color) {
282 color_norm.x = data_type_normalizer[element.properties[color_index.x].type];
283 color_norm.y = data_type_normalizer[element.properties[color_index.y].type];
284 color_norm.z = data_type_normalizer[element.properties[color_index.z].type];
285 }
286 if (has_alpha) {
287 color_norm.w = data_type_normalizer[element.properties[alpha_index].type];
288 }
289
290 Vector<float> value_vec(element.properties.size());
291 Vector<uint8_t> scratch;
292 if (header.type != PlyFormatType::ASCII) {
293 scratch.resize(element.stride);
294 }
295
296 for (int i = 0; i < element.count; i++) {
297
298 const char *error = nullptr;
299 if (header.type == PlyFormatType::ASCII) {
300 error = parse_row_ascii(file, value_vec);
301 }
302 else {
303 error = parse_row_binary(file, header, element, scratch, value_vec);
304 }
305 if (error != nullptr) {
306 return error;
307 }
308
309 /* Vertex coord */
310 float3 vertex3;
311 vertex3.x = value_vec[vertex_index.x];
312 vertex3.y = value_vec[vertex_index.y];
313 vertex3.z = value_vec[vertex_index.z];
314 data->vertices.append(vertex3);
315
316 /* Vertex color */
317 if (has_color) {
318 float4 colors4;
319 colors4.x = value_vec[color_index.x] / color_norm.x;
320 colors4.y = value_vec[color_index.y] / color_norm.y;
321 colors4.z = value_vec[color_index.z] / color_norm.z;
322 if (has_alpha) {
323 colors4.w = value_vec[alpha_index] / color_norm.w;
324 }
325 else {
326 colors4.w = 1.0f;
327 }
328 data->vertex_colors.append(colors4);
329 }
330
331 /* If normals */
332 if (has_normal) {
333 float3 normals3;
334 normals3.x = value_vec[normal_index.x];
335 normals3.y = value_vec[normal_index.y];
336 normals3.z = value_vec[normal_index.z];
337 data->vertex_normals.append(normals3);
338 }
339
340 /* If uv */
341 if (has_uv) {
342 float2 uvmap;
343 uvmap.x = value_vec[uv_index.x];
344 uvmap.y = value_vec[uv_index.y];
345 data->uv_coordinates.append(uvmap);
346 }
347
348 /* Custom attributes */
349 for (const int64_t ci : custom_attr_indices.index_range()) {
350 float value = value_vec[custom_attr_indices[ci]];
351 data->vertex_custom_attr[ci].data[i] = value;
352 }
353 }
354 return nullptr;
355}
356
358 const PlyProperty &prop,
359 Vector<uint8_t> &scratch,
360 bool big_endian)
361{
362 scratch.resize(8);
363 file.read_bytes(scratch.data(), data_type_size[prop.count_type]);
364 const uint8_t *ptr = scratch.data();
365 if (big_endian) {
367 }
369 return count;
370}
371
372static void skip_property(PlyReadBuffer &file,
373 const PlyProperty &prop,
374 Vector<uint8_t> &scratch,
375 bool big_endian)
376{
377 if (prop.count_type == PlyDataTypes::NONE) {
378 scratch.resize(8);
379 file.read_bytes(scratch.data(), data_type_size[prop.type]);
380 }
381 else {
382 uint32_t count = read_list_count(file, prop, scratch, big_endian);
383 scratch.resize(count * data_type_size[prop.type]);
384 file.read_bytes(scratch.data(), scratch.size());
385 }
386}
387
388static const char *load_face_element(PlyReadBuffer &file,
389 const PlyHeader &header,
390 const PlyElement &element,
391 PlyData *data)
392{
393 int prop_index = get_index(element, "vertex_indices");
394 if (prop_index < 0) {
395 prop_index = get_index(element, "vertex_index");
396 }
397 if (prop_index < 0 && element.properties.size() == 1) {
398 prop_index = 0;
399 }
400 if (prop_index < 0) {
401 return "Face element does not contain vertex indices property";
402 }
403 const PlyProperty &prop = element.properties[prop_index];
404 if (prop.count_type == PlyDataTypes::NONE) {
405 return "Face element vertex indices property must be a list";
406 }
407
408 data->face_vertices.reserve(element.count * 3);
409 data->face_sizes.reserve(element.count);
410
411 if (header.type == PlyFormatType::ASCII) {
412 for (int i = 0; i < element.count; i++) {
413 /* Read line */
414 Span<char> line = file.read_line();
415
416 const char *p = line.data();
417 const char *end = p + line.size();
418 int count = 0;
419
420 /* Skip any properties before vertex indices. */
421 for (int j = 0; j < prop_index; j++) {
422 p = drop_whitespace(p, end);
423 if (element.properties[j].count_type == PlyDataTypes::NONE) {
424 p = drop_non_whitespace(p, end);
425 }
426 else {
427 p = parse_int(p, end, 0, count);
428 for (int k = 0; k < count; ++k) {
429 p = drop_whitespace(p, end);
430 p = drop_non_whitespace(p, end);
431 }
432 }
433 }
434
435 /* Parse vertex indices list. */
436 p = parse_int(p, end, 0, count);
437 if (count < 1 || count > 255) {
438 return "Invalid face size, must be between 1 and 255";
439 }
440 /* Previous python based importer was accepting faces with fewer
441 * than 3 vertices, and silently dropping them. */
442 if (count < 3) {
443 fprintf(stderr, "PLY Importer: ignoring face %i (%i vertices)\n", i, count);
444 continue;
445 }
446
447 for (int j = 0; j < count; j++) {
448 int index;
449 p = parse_int(p, end, 0, index);
450 data->face_vertices.append(index);
451 }
452 data->face_sizes.append(count);
453 }
454 }
455 else {
456 Vector<uint8_t> scratch(64);
457
458 for (int i = 0; i < element.count; i++) {
459 const uint8_t *ptr;
460
461 /* Skip any properties before vertex indices. */
462 for (int j = 0; j < prop_index; j++) {
464 file, element.properties[j], scratch, header.type == PlyFormatType::BINARY_BE);
465 }
466
467 /* Read vertex indices list. */
469 file, prop, scratch, header.type == PlyFormatType::BINARY_BE);
470 if (count < 1 || count > 255) {
471 return "Invalid face size, must be between 1 and 255";
472 }
473
474 scratch.resize(count * data_type_size[prop.type]);
475 file.read_bytes(scratch.data(), scratch.size());
476 /* Previous python based importer was accepting faces with fewer
477 * than 3 vertices, and silently dropping them. */
478 if (count < 3) {
479 fprintf(stderr, "PLY Importer: ignoring face %i (%i vertices)\n", i, int(count));
480 }
481 else {
482 ptr = scratch.data();
483 if (header.type == PlyFormatType::BINARY_BE) {
485 }
486 for (int j = 0; j < count; ++j) {
488 data->face_vertices.append(index);
489 }
490 data->face_sizes.append(count);
491 }
492
493 /* Skip any properties after vertex indices. */
494 for (int j = prop_index + 1; j < element.properties.size(); j++) {
496 file, element.properties[j], scratch, header.type == PlyFormatType::BINARY_BE);
497 }
498 }
499 }
500 return nullptr;
501}
502
503static const char *load_tristrips_element(PlyReadBuffer &file,
504 const PlyHeader &header,
505 const PlyElement &element,
506 PlyData *data)
507{
508 if (element.count != 1) {
509 return "Tristrips element should contain one row";
510 }
511 if (element.properties.size() != 1) {
512 return "Tristrips element should contain one property";
513 }
514 const PlyProperty &prop = element.properties[0];
515 if (prop.count_type == PlyDataTypes::NONE) {
516 return "Tristrips element property must be a list";
517 }
518
519 Vector<int> strip;
520
521 if (header.type == PlyFormatType::ASCII) {
522 Span<char> line = file.read_line();
523
524 const char *p = line.data();
525 const char *end = p + line.size();
526 int count = 0;
527 p = parse_int(p, end, 0, count);
528
529 strip.resize(count);
530 for (int j = 0; j < count; j++) {
531 int index;
532 p = parse_int(p, end, 0, index);
533 strip[j] = index;
534 }
535 }
536 else {
537 Vector<uint8_t> scratch(64);
538
539 const uint8_t *ptr;
540
541 uint32_t count = read_list_count(file, prop, scratch, header.type == PlyFormatType::BINARY_BE);
542
543 strip.resize(count);
544 scratch.resize(count * data_type_size[prop.type]);
545 file.read_bytes(scratch.data(), scratch.size());
546 ptr = scratch.data();
547 if (header.type == PlyFormatType::BINARY_BE) {
549 }
550 for (int j = 0; j < count; ++j) {
551 int index = get_binary_value<int>(prop.type, ptr);
552 strip[j] = index;
553 }
554 }
555
556 /* Decode triangle strip (with possible -1 restart indices) into faces. */
557 size_t start = 0;
558
559 for (size_t i = 0; i < strip.size(); i++) {
560 if (strip[i] == -1) {
561 /* Restart strip. */
562 start = i + 1;
563 }
564 else if (i - start >= 2) {
565 int a = strip[i - 2], b = strip[i - 1], c = strip[i];
566 /* Flip odd triangles. */
567 if ((i - start) & 1) {
568 std::swap(a, b);
569 }
570 /* Add triangle if it's not degenerate. */
571 if (a != b && a != c && b != c) {
572 data->face_vertices.append(a);
573 data->face_vertices.append(b);
574 data->face_vertices.append(c);
575 data->face_sizes.append(3);
576 }
577 }
578 }
579 return nullptr;
580}
581
582static const char *load_edge_element(PlyReadBuffer &file,
583 const PlyHeader &header,
584 const PlyElement &element,
585 PlyData *data)
586{
587 int prop_vertex1 = get_index(element, "vertex1");
588 int prop_vertex2 = get_index(element, "vertex2");
589 if (prop_vertex1 < 0 || prop_vertex2 < 0) {
590 return "Edge element does not contain vertex1 and vertex2 properties";
591 }
592
593 data->edges.reserve(element.count);
594
595 Vector<float> value_vec(element.properties.size());
596 Vector<uint8_t> scratch;
597 if (header.type != PlyFormatType::ASCII) {
598 scratch.resize(element.stride);
599 }
600
601 for (int i = 0; i < element.count; i++) {
602 const char *error = nullptr;
603 if (header.type == PlyFormatType::ASCII) {
604 error = parse_row_ascii(file, value_vec);
605 }
606 else {
607 error = parse_row_binary(file, header, element, scratch, value_vec);
608 }
609 if (error != nullptr) {
610 return error;
611 }
612 int index1 = value_vec[prop_vertex1];
613 int index2 = value_vec[prop_vertex2];
614 data->edges.append(std::make_pair(index1, index2));
615 }
616 return nullptr;
617}
618
619static const char *skip_element(PlyReadBuffer &file,
620 const PlyHeader &header,
621 const PlyElement &element)
622{
623 if (header.type == PlyFormatType::ASCII) {
624 for (int i = 0; i < element.count; i++) {
625 Span<char> line = file.read_line();
626 (void)line;
627 }
628 }
629 else {
630 Vector<uint8_t> scratch(64);
631 for (int i = 0; i < element.count; i++) {
632 for (const PlyProperty &prop : element.properties) {
633 skip_property(file, prop, scratch, header.type == PlyFormatType::BINARY_BE);
634 }
635 }
636 }
637 return nullptr;
638}
639
640std::unique_ptr<PlyData> import_ply_data(PlyReadBuffer &file, PlyHeader &header)
641{
642 std::unique_ptr<PlyData> data = std::make_unique<PlyData>();
643
644 bool got_vertex = false, got_face = false, got_tristrips = false, got_edge = false;
645 for (const PlyElement &element : header.elements) {
646 const char *error = nullptr;
647 if (element.name == "vertex") {
648 error = load_vertex_element(file, header, element, data.get());
649 got_vertex = true;
650 }
651 else if (element.name == "face") {
652 error = load_face_element(file, header, element, data.get());
653 got_face = true;
654 }
655 else if (element.name == "tristrips") {
656 error = load_tristrips_element(file, header, element, data.get());
657 got_tristrips = true;
658 }
659 else if (element.name == "edge") {
660 error = load_edge_element(file, header, element, data.get());
661 got_edge = true;
662 }
663 else {
664 error = skip_element(file, header, element);
665 }
666 if (error != nullptr) {
667 data->error = error;
668 return data;
669 }
670 if (got_vertex && got_face && got_tristrips && got_edge) {
671 /* We have parsed all the elements we'd need, skip the rest. */
672 break;
673 }
674 }
675
676 return data;
677}
678
679} // namespace blender::io::ply
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
BLI_INLINE void BLI_endian_switch_uint64(uint64_t *val) ATTR_NONNULL(1)
BLI_INLINE void BLI_endian_switch_uint16(unsigned short *val) ATTR_NONNULL(1)
void BLI_endian_switch_uint16_array(unsigned short *val, int size) ATTR_NONNULL(1)
BLI_INLINE void BLI_endian_switch_uint32(unsigned int *val) ATTR_NONNULL(1)
void BLI_endian_switch_uint64_array(uint64_t *val, int size) ATTR_NONNULL(1)
void BLI_endian_switch_uint32_array(unsigned int *val, int size) ATTR_NONNULL(1)
#define ELEM(...)
constexpr const T * data() const
Definition BLI_span.hh:216
int64_t size() const
void append(const T &value)
IndexRange index_range() const
void resize(const int64_t new_size)
local_group_size(16, 16) .push_constant(Type b
draw_view in_light_buf[] float
#define UINT_MAX
Definition hash_md5.cc:44
int count
static void error(const char *str)
static const char * load_face_element(PlyReadBuffer &file, const PlyHeader &header, const PlyElement &element, PlyData *data)
static const char * load_edge_element(PlyReadBuffer &file, const PlyHeader &header, const PlyElement &element, PlyData *data)
static const char * parse_row_ascii(PlyReadBuffer &file, Vector< float > &r_values)
static const char * parse_row_binary(PlyReadBuffer &file, const PlyHeader &header, const PlyElement &element, Vector< uint8_t > &r_scratch, Vector< float > &r_values)
static const char * load_vertex_element(PlyReadBuffer &file, const PlyHeader &header, const PlyElement &element, PlyData *data)
static uint32_t read_list_count(PlyReadBuffer &file, const PlyProperty &prop, Vector< uint8_t > &scratch, bool big_endian)
static const char * load_tristrips_element(PlyReadBuffer &file, const PlyHeader &header, const PlyElement &element, PlyData *data)
static int get_index(const PlyElement &element, StringRef property)
std::unique_ptr< PlyData > import_ply_data(PlyReadBuffer &file, PlyHeader &header)
static const int data_type_size[]
static const float data_type_normalizer[]
static void skip_property(PlyReadBuffer &file, const PlyProperty &prop, Vector< uint8_t > &scratch, bool big_endian)
static T get_binary_value(PlyDataTypes type, const uint8_t *&r_ptr)
static const char * skip_element(PlyReadBuffer &file, const PlyHeader &header, const PlyElement &element)
static void endian_switch_array(uint8_t *ptr, int type_size, int size)
static void endian_switch(uint8_t *ptr, int type_size)
static const char * drop_plus(const char *p, const char *end)
static const char * parse_int(const char *p, const char *end, int fallback, int &dst)
static const char * drop_whitespace(const char *p, const char *end)
static bool is_whitespace(char c)
static const char * parse_float(const char *p, const char *end, float fallback, float &dst)
static const char * drop_non_whitespace(const char *p, const char *end)
signed short int16_t
Definition stdint.h:76
unsigned short uint16_t
Definition stdint.h:79
unsigned int uint32_t
Definition stdint.h:80
__int64 int64_t
Definition stdint.h:89
signed int int32_t
Definition stdint.h:77
unsigned char uint8_t
Definition stdint.h:78
unsigned __int64 uint64_t
Definition stdint.h:90
signed char int8_t
Definition stdint.h:75
Vector< PlyProperty > properties
Definition ply_data.hh:51
Vector< PlyElement > elements
Definition ply_data.hh:58
PointerRNA * ptr
Definition wm_files.cc:4126