vision
Loading...
Searching...
No Matches
tinyply.h
1/*
2 * tinyply 2.3.3 (https://github.com/ddiakopoulos/tinyply)
3 *
4 * A single-header, zero-dependency (except the C++ STL) public domain implementation
5 * of the PLY mesh file format. Requires C++11; errors are handled through exceptions.
6 *
7 * This software is in the public domain. Where that dedication is not
8 * recognized, you are granted a perpetual, irrevocable license to copy,
9 * distribute, and modify this file as you see fit.
10 *
11 * Authored by Dimitri Diakopoulos (http://www.dimitridiakopoulos.com)
12 *
13 * tinyply.h may be included in many files, however in a single compiled file,
14 * the implementation must be created with the following defined prior to header inclusion
15 * #define TINYPLY_IMPLEMENTATION
16 *
17 */
18
20// tinyply header //
22
23#ifndef tinyply_h
24#define tinyply_h
25
26#include <vector>
27#include <string>
28#include <stdint.h>
29#include <cstddef>
30#include <sstream>
31#include <memory>
32#include <unordered_map>
33#include <map>
34#include <algorithm>
35#include <functional>
36
37namespace tinyply
38{
39
40 enum class Type : uint8_t
41 {
42 INVALID,
43 INT8,
44 UINT8,
45 INT16,
46 UINT16,
47 INT32,
48 UINT32,
49 FLOAT32,
50 FLOAT64
51 };
52
54 {
55 PropertyInfo() {};
56 PropertyInfo(int stride, std::string str)
57 : stride(stride), str(str) {}
58 int stride {0};
59 std::string str;
60 };
61
62 static std::map<Type, PropertyInfo> PropertyTable
63 {
64 { Type::INT8, PropertyInfo(1, std::string("char")) },
65 { Type::UINT8, PropertyInfo(1, std::string("uchar")) },
66 { Type::INT16, PropertyInfo(2, std::string("short")) },
67 { Type::UINT16, PropertyInfo(2, std::string("ushort")) },
68 { Type::INT32, PropertyInfo(4, std::string("int")) },
69 { Type::UINT32, PropertyInfo(4, std::string("uint")) },
70 { Type::FLOAT32, PropertyInfo(4, std::string("float")) },
71 { Type::FLOAT64, PropertyInfo(8, std::string("double")) },
72 { Type::INVALID, PropertyInfo(0, std::string("INVALID"))}
73 };
74
75 class Buffer
76 {
77 uint8_t * alias{ nullptr };
78 struct delete_array { void operator()(uint8_t * p) { delete[] p; } };
79 std::unique_ptr<uint8_t, decltype(Buffer::delete_array())> data;
80 size_t size {0};
81 public:
82 Buffer() {};
83 Buffer(const size_t size) : data(new uint8_t[size], delete_array()), size(size) { alias = data.get(); } // allocating
84 Buffer(const uint8_t * ptr): alias(const_cast<uint8_t*>(ptr)) { } // non-allocating, todo: set size?
85 uint8_t * get() { return alias; }
86 const uint8_t * get_const() {return const_cast<const uint8_t*>(alias); }
87 size_t size_bytes() const { return size; }
88 };
89
90 struct PlyData
91 {
92 Type t;
93 Buffer buffer;
94 size_t count {0};
95 bool isList {false};
96 };
97
99 {
100 PlyProperty(std::istream & is);
101 PlyProperty(Type type, std::string & _name) : name(_name), propertyType(type) {}
102 PlyProperty(Type list_type, Type prop_type, std::string & _name, size_t list_count)
103 : name(_name), propertyType(prop_type), isList(true), listType(list_type), listCount(list_count) {}
104 std::string name;
105 Type propertyType{ Type::INVALID };
106 bool isList{ false };
107 Type listType{ Type::INVALID };
108 size_t listCount {0};
109 };
110
112 {
113 PlyElement(std::istream & istream);
114 PlyElement(const std::string & _name, size_t count) : name(_name), size(count) {}
115 std::string name;
116 size_t size {0};
117 std::vector<PlyProperty> properties;
118 };
119
120 struct PlyFile
121 {
122 struct PlyFileImpl;
123 std::unique_ptr<PlyFileImpl> impl;
124
125 PlyFile();
126 ~PlyFile();
127
128 /*
129 * The ply format requires an ascii header. This can be used to determine at
130 * runtime which properties or elements exist in the file. Limited validation of the
131 * header is performed; it is assumed the header correctly reflects the contents of the
132 * payload. This function may throw. Returns true on success, false on failure.
133 */
134 bool parse_header(std::istream & is);
135
136 /*
137 * Execute a read operation. Data must be requested via `request_properties_from_element(...)`
138 * prior to calling this function.
139 */
140 void read(std::istream & is);
141
142 /*
143 * `write` performs no validation and assumes that the data passed into
144 * `add_properties_to_element` is well-formed.
145 */
146 void write(std::ostream & os, bool isBinary);
147
148 /*
149 * These functions are valid after a call to `parse_header(...)`. In the case of
150 * writing, get_comments() reference may also be used to add new comments to the ply header.
151 */
152 std::vector<PlyElement> get_elements() const;
153 std::vector<std::string> get_info() const;
154 std::vector<std::string> & get_comments();
155 bool is_binary_file() const;
156
157 /*
158 * In the general case where |list_size_hint| is zero, `read` performs a two-pass
159 * parse to support variable length lists. The most general use of the
160 * ply format is storing triangle meshes. When this fact is known a-priori, we can pass
161 * an expected list length that will apply to this element. Doing so results in an up-front
162 * memory allocation and a single-pass import, a 2x performance optimization.
163 */
164 std::shared_ptr<PlyData> request_properties_from_element(const std::string & elementKey,
165 const std::vector<std::string> propertyKeys, const uint32_t list_size_hint = 0);
166
167 void add_properties_to_element(const std::string & elementKey,
168 const std::vector<std::string> propertyKeys,
169 const Type type,
170 const size_t count,
171 const uint8_t * data,
172 const Type listType,
173 const size_t listCount);
174 };
175
176} // end namespace tinyply
177
178#endif // end tinyply_h
179
181// tinyply implementation //
183
184#ifdef TINYPLY_IMPLEMENTATION
185
186#include <algorithm>
187#include <functional>
188#include <type_traits>
189#include <iostream>
190#include <cstring>
191
192namespace tinyply
193{
194
195using namespace std;
196
197template<typename T, typename T2> inline T2 endian_swap(const T & v) noexcept { return v; }
198template<> inline uint16_t endian_swap<uint16_t, uint16_t>(const uint16_t & v) noexcept { return (v << 8) | (v >> 8); }
199template<> inline uint32_t endian_swap<uint32_t, uint32_t>(const uint32_t & v) noexcept { return (v << 24) | ((v << 8) & 0x00ff0000) | ((v >> 8) & 0x0000ff00) | (v >> 24); }
200template<> inline uint64_t endian_swap<uint64_t, uint64_t>(const uint64_t & v) noexcept
201{
202 return (((v & 0x00000000000000ffLL) << 56) |
203 ((v & 0x000000000000ff00LL) << 40) |
204 ((v & 0x0000000000ff0000LL) << 24) |
205 ((v & 0x00000000ff000000LL) << 8) |
206 ((v & 0x000000ff00000000LL) >> 8) |
207 ((v & 0x0000ff0000000000LL) >> 24) |
208 ((v & 0x00ff000000000000LL) >> 40) |
209 ((v & 0xff00000000000000LL) >> 56));
210}
211template<> inline int16_t endian_swap<int16_t, int16_t>(const int16_t & v) noexcept { uint16_t r = endian_swap<uint16_t, uint16_t>(*(uint16_t*)&v); return *(int16_t*)&r; }
212template<> inline int32_t endian_swap<int32_t, int32_t>(const int32_t & v) noexcept { uint32_t r = endian_swap<uint32_t, uint32_t>(*(uint32_t*)&v); return *(int32_t*)&r; }
213template<> inline int64_t endian_swap<int64_t, int64_t>(const int64_t & v) noexcept { uint64_t r = endian_swap<uint64_t, uint64_t>(*(uint64_t*)&v); return *(int64_t*)&r; }
214template<> inline float endian_swap<uint32_t, float>(const uint32_t & v) noexcept { union { float f; uint32_t i; }; i = endian_swap<uint32_t, uint32_t>(v); return f; }
215template<> inline double endian_swap<uint64_t, double>(const uint64_t & v) noexcept { union { double d; uint64_t i; }; i = endian_swap<uint64_t, uint64_t>(v); return d; }
216
217inline uint32_t hash_fnv1a(const std::string & str) noexcept
218{
219 static const uint32_t fnv1aBase32 = 0x811C9DC5u;
220 static const uint32_t fnv1aPrime32 = 0x01000193u;
221 uint32_t result = fnv1aBase32;
222 for (auto & c : str) { result ^= static_cast<uint32_t>(c); result *= fnv1aPrime32; }
223 return result;
224}
225
226inline Type property_type_from_string(const std::string & t) noexcept
227{
228 if (t == "int8" || t == "char") return Type::INT8;
229 else if (t == "uint8" || t == "uchar") return Type::UINT8;
230 else if (t == "int16" || t == "short") return Type::INT16;
231 else if (t == "uint16" || t == "ushort") return Type::UINT16;
232 else if (t == "int32" || t == "int") return Type::INT32;
233 else if (t == "uint32" || t == "uint") return Type::UINT32;
234 else if (t == "float32" || t == "float") return Type::FLOAT32;
235 else if (t == "float64" || t == "double") return Type::FLOAT64;
236 return Type::INVALID;
237}
238
239struct PlyFile::PlyFileImpl
240{
241 struct PlyDataCursor
242 {
243 size_t byteOffset{ 0 };
244 size_t totalSizeBytes{ 0 };
245 };
246
247 struct ParsingHelper
248 {
249 std::shared_ptr<PlyData> data;
250 std::shared_ptr<PlyDataCursor> cursor;
251 uint32_t list_size_hint;
252 };
253
254 struct PropertyLookup
255 {
256 ParsingHelper * helper{ nullptr };
257 bool skip{ false };
258 size_t prop_stride{ 0 }; // precomputed
259 size_t list_stride{ 0 }; // precomputed
260 };
261
262 std::unordered_map<uint32_t, ParsingHelper> userData;
263
264 bool isBinary = false;
265 bool isBigEndian = false;
266 std::vector<PlyElement> elements;
267 std::vector<std::string> comments;
268 std::vector<std::string> objInfo;
269 uint8_t scratch[64]; // large enough for max list size
270
271 void read(std::istream & is);
272 void write(std::ostream & os, bool isBinary);
273
274 std::shared_ptr<PlyData> request_properties_from_element(const std::string & elementKey,
275 const std::vector<std::string> propertyKeys,
276 const uint32_t list_size_hint);
277
278 void add_properties_to_element(const std::string & elementKey,
279 const std::vector<std::string> propertyKeys,
280 const Type type, const size_t count, const uint8_t * data, const Type listType, const size_t listCount);
281
282 size_t read_property_binary(const size_t & stride, void * dest, size_t & destOffset, std::istream & is) noexcept;
283 size_t read_property_ascii(const Type & t, const size_t & stride, void * dest, size_t & destOffset, std::istream & is);
284
285 std::vector<std::vector<PropertyLookup>> make_property_lookup_table();
286
287 bool parse_header(std::istream & is);
288 void parse_data(std::istream & is, bool firstPass);
289 void read_header_format(std::istream & is);
290 void read_header_element(std::istream & is);
291 void read_header_property(std::istream & is);
292 void read_header_text(std::string line, std::vector<std::string> & place, int erase = 0);
293
294 void write_header(std::ostream & os) noexcept;
295 void write_ascii_internal(std::ostream & os) noexcept;
296 void write_binary_internal(std::ostream & os) noexcept;
297 void write_property_ascii(Type t, std::ostream & os, const uint8_t * src, size_t & srcOffset);
298 void write_property_binary(std::ostream & os, const uint8_t * src, size_t & srcOffset, const size_t & stride) noexcept;
299};
300
301PlyProperty::PlyProperty(std::istream & is) : isList(false)
302{
303 std::string type;
304 is >> type;
305 if (type == "list")
306 {
307 std::string countType;
308 is >> countType >> type;
309 listType = property_type_from_string(countType);
310 isList = true;
311 }
312 propertyType = property_type_from_string(type);
313 is >> name;
314}
315
316PlyElement::PlyElement(std::istream & is)
317{
318 is >> name >> size;
319}
320
321template<typename T> inline T ply_read_ascii(std::istream & is)
322{
323 T data;
324 is >> data;
325 return data;
326}
327
328template<typename T, typename T2>
329inline void endian_swap_buffer(uint8_t * data_ptr, const size_t num_bytes, const size_t stride)
330{
331 for (size_t count = 0; count < num_bytes; count += stride)
332 {
333 *(reinterpret_cast<T2 *>(data_ptr)) = endian_swap<T, T2>(*(reinterpret_cast<const T *>(data_ptr)));
334 data_ptr += stride;
335 }
336}
337
338template<typename T> void ply_cast_ascii(void * dest, std::istream & is)
339{
340 *(static_cast<T *>(dest)) = ply_read_ascii<T>(is);
341}
342
343int64_t find_element(const std::string & key, const std::vector<PlyElement> & list)
344{
345 for (size_t i = 0; i < list.size(); i++) if (list[i].name == key) return i;
346 return -1;
347}
348
349int64_t find_property(const std::string & key, const std::vector<PlyProperty> & list)
350{
351 for (size_t i = 0; i < list.size(); ++i) if (list[i].name == key) return i;
352 return -1;
353}
354
355// The `userData` table is an easy data structure for capturing what data the
356// user would like out of the ply file, but an inner-loop hash lookup is non-ideal.
357// The property lookup table flattens the table down into a 2D array optimized
358// for parsing. The first index is the element, and the second index is the property.
359std::vector<std::vector<PlyFile::PlyFileImpl::PropertyLookup>> PlyFile::PlyFileImpl::make_property_lookup_table()
360{
361 std::vector<std::vector<PropertyLookup>> element_property_lookup;
362
363 for (auto & element : elements)
364 {
365 std::vector<PropertyLookup> lookups;
366
367 for (auto & property : element.properties)
368 {
369 PropertyLookup f;
370
371 auto cursorIt = userData.find(hash_fnv1a(element.name + property.name));
372 if (cursorIt != userData.end()) f.helper = &cursorIt->second;
373 else f.skip = true;
374
375 f.prop_stride = PropertyTable[property.propertyType].stride;
376 if (property.isList) f.list_stride = PropertyTable[property.listType].stride;
377
378 lookups.push_back(f);
379 }
380
381 element_property_lookup.push_back(lookups);
382 }
383
384 return element_property_lookup;
385}
386
387bool PlyFile::PlyFileImpl::parse_header(std::istream & is)
388{
389 std::string line;
390 bool success = true;
391 while (std::getline(is, line))
392 {
393 std::istringstream ls(line);
394 std::string token;
395 ls >> token;
396 if (token == "ply" || token == "PLY" || token == "") continue;
397 else if (token == "comment") read_header_text(line, comments, 8);
398 else if (token == "format") read_header_format(ls);
399 else if (token == "element") read_header_element(ls);
400 else if (token == "property") read_header_property(ls);
401 else if (token == "obj_info") read_header_text(line, objInfo, 9);
402 else if (token == "end_header") break;
403 else success = false; // unexpected header field
404 }
405 return success;
406}
407
408void PlyFile::PlyFileImpl::read_header_text(std::string line, std::vector<std::string>& place, int erase)
409{
410 place.push_back((erase > 0) ? line.erase(0, erase) : line);
411}
412
413void PlyFile::PlyFileImpl::read_header_format(std::istream & is)
414{
415 std::string s;
416 (is >> s);
417 if (s == "binary_little_endian") isBinary = true;
418 else if (s == "binary_big_endian") isBinary = isBigEndian = true;
419}
420
421void PlyFile::PlyFileImpl::read_header_element(std::istream & is)
422{
423 elements.emplace_back(is);
424}
425
426void PlyFile::PlyFileImpl::read_header_property(std::istream & is)
427{
428 if (!elements.size()) throw std::runtime_error("no elements defined; file is malformed");
429 elements.back().properties.emplace_back(is);
430}
431
432size_t PlyFile::PlyFileImpl::read_property_binary(const size_t & stride, void * dest, size_t & destOffset, std::istream & is) noexcept
433{
434 destOffset += stride;
435 is.read((char*)dest, stride);
436 return stride;
437}
438
439size_t PlyFile::PlyFileImpl::read_property_ascii(const Type & t, const size_t & stride, void * dest, size_t & destOffset, std::istream & is)
440{
441 destOffset += stride;
442 switch (t)
443 {
444 case Type::INT8: *((int8_t *)dest) = static_cast<int8_t>(ply_read_ascii<int32_t>(is)); break;
445 case Type::UINT8: *((uint8_t *)dest) = static_cast<uint8_t>(ply_read_ascii<uint32_t>(is)); break;
446 case Type::INT16: ply_cast_ascii<int16_t>(dest, is); break;
447 case Type::UINT16: ply_cast_ascii<uint16_t>(dest, is); break;
448 case Type::INT32: ply_cast_ascii<int32_t>(dest, is); break;
449 case Type::UINT32: ply_cast_ascii<uint32_t>(dest, is); break;
450 case Type::FLOAT32: ply_cast_ascii<float>(dest, is); break;
451 case Type::FLOAT64: ply_cast_ascii<double>(dest, is); break;
452 case Type::INVALID: throw std::invalid_argument("invalid ply property");
453 }
454 return stride;
455}
456
457void PlyFile::PlyFileImpl::write_property_ascii(Type t, std::ostream & os, const uint8_t * src, size_t & srcOffset)
458{
459 switch (t)
460 {
461 case Type::INT8: os << static_cast<int32_t>(*reinterpret_cast<const int8_t*>(src)); break;
462 case Type::UINT8: os << static_cast<uint32_t>(*reinterpret_cast<const uint8_t*>(src)); break;
463 case Type::INT16: os << *reinterpret_cast<const int16_t*>(src); break;
464 case Type::UINT16: os << *reinterpret_cast<const uint16_t*>(src); break;
465 case Type::INT32: os << *reinterpret_cast<const int32_t*>(src); break;
466 case Type::UINT32: os << *reinterpret_cast<const uint32_t*>(src); break;
467 case Type::FLOAT32: os << *reinterpret_cast<const float*>(src); break;
468 case Type::FLOAT64: os << *reinterpret_cast<const double*>(src); break;
469 case Type::INVALID: throw std::invalid_argument("invalid ply property");
470 }
471 os << " ";
472 srcOffset += PropertyTable[t].stride;
473}
474
475void PlyFile::PlyFileImpl::write_property_binary(std::ostream & os, const uint8_t * src, size_t & srcOffset, const size_t & stride) noexcept
476{
477 os.write((char *)src, stride);
478 srcOffset += stride;
479}
480
481void PlyFile::PlyFileImpl::read(std::istream & is)
482{
483 std::vector<std::shared_ptr<PlyData>> buffers;
484 for (auto & entry : userData) buffers.push_back(entry.second.data);
485
486 // Discover if we can allocate up front without parsing the file twice
487 uint32_t list_hints = 0;
488 for (auto & b : buffers) for (auto & entry : userData) {list_hints += entry.second.list_size_hint;(void)b;}
489
490 // No list hints? Then we need to calculate how much memory to allocate
491 if (list_hints == 0)
492 {
493 parse_data(is, true);
494 }
495
496 // Count the number of properties (required for allocation)
497 // e.g. if we have properties x y and z requested, we ensure
498 // that their buffer points to the same PlyData
499 std::unordered_map<PlyData*, int32_t> unique_data_count;
500 for (auto & ptr : buffers) unique_data_count[ptr.get()] += 1;
501
502 // Since group-requested properties share the same cursor,
503 // we need to find unique cursors so we only allocate once
504 std::sort(buffers.begin(), buffers.end());
505 buffers.erase(std::unique(buffers.begin(), buffers.end()), buffers.end());
506
507 // We sorted by ptrs on PlyData, need to remap back onto its cursor in the userData table
508 for (auto & b : buffers)
509 {
510 for (auto & entry : userData)
511 {
512 if (entry.second.data == b && b->buffer.get() == nullptr)
513 {
514 // If we didn't receive any list hints, it means we did two passes over the
515 // file to compute the total length of all (potentially) variable-length lists
516 if (list_hints == 0)
517 {
518 b->buffer = Buffer(entry.second.cursor->totalSizeBytes);
519 }
520 else
521 {
522 // otherwise, we can allocate up front, skipping the first pass.
523 const size_t list_size_multiplier = (entry.second.data->isList ? entry.second.list_size_hint : 1);
524 auto bytes_per_property = entry.second.data->count * PropertyTable[entry.second.data->t].stride * list_size_multiplier;
525 bytes_per_property *= unique_data_count[b.get()];
526 b->buffer = Buffer(bytes_per_property);
527 }
528
529 }
530 }
531 }
532
533 // Populate the data
534 parse_data(is, false);
535
536 // In-place big-endian to little-endian swapping if required
537 if (isBigEndian)
538 {
539 for (auto & b : buffers)
540 {
541 uint8_t * data_ptr = b->buffer.get();
542 const size_t stride = PropertyTable[b->t].stride;
543 const size_t buffer_size_bytes = b->buffer.size_bytes();
544
545 switch (b->t)
546 {
547 case Type::INT16: endian_swap_buffer<int16_t, int16_t>(data_ptr, buffer_size_bytes, stride); break;
548 case Type::UINT16: endian_swap_buffer<uint16_t, uint16_t>(data_ptr, buffer_size_bytes, stride); break;
549 case Type::INT32: endian_swap_buffer<int32_t, int32_t>(data_ptr, buffer_size_bytes, stride); break;
550 case Type::UINT32: endian_swap_buffer<uint32_t, uint32_t>(data_ptr, buffer_size_bytes, stride); break;
551 case Type::FLOAT32: endian_swap_buffer<uint32_t, float>(data_ptr, buffer_size_bytes, stride); break;
552 case Type::FLOAT64: endian_swap_buffer<uint64_t, double>(data_ptr, buffer_size_bytes, stride); break;
553 default: break;
554 }
555 }
556 }
557}
558
559void PlyFile::PlyFileImpl::write(std::ostream & os, bool _isBinary)
560{
561 for (auto & d : userData) { d.second.cursor->byteOffset = 0; }
562 if (_isBinary)
563 {
564 isBinary = true;
565 isBigEndian = false;
566 write_binary_internal(os);
567 }
568 else
569 {
570 isBinary = false;
571 isBigEndian = false;
572 write_ascii_internal(os);
573 }
574}
575
576void PlyFile::PlyFileImpl::write_binary_internal(std::ostream & os) noexcept
577{
578 isBinary = true;
579
580 write_header(os);
581
582 uint8_t listSize[4] = { 0, 0, 0, 0 };
583 size_t dummyCount = 0;
584
585 auto element_property_lookup = make_property_lookup_table();
586
587 size_t element_idx = 0;
588 for (auto & e : elements)
589 {
590 for (size_t i = 0; i < e.size; ++i)
591 {
592 size_t property_index = 0;
593 for (auto & p : e.properties)
594 {
595 auto & f = element_property_lookup[element_idx][property_index];
596 auto * helper = f.helper;
597 if (f.skip || helper == nullptr) continue;
598
599 if (p.isList)
600 {
601 std::memcpy(listSize, &p.listCount, sizeof(uint32_t));
602 write_property_binary(os, listSize, dummyCount, f.list_stride);
603 write_property_binary(os, (helper->data->buffer.get_const() + helper->cursor->byteOffset), helper->cursor->byteOffset, f.prop_stride * p.listCount);
604 }
605 else
606 {
607 write_property_binary(os, (helper->data->buffer.get_const() + helper->cursor->byteOffset), helper->cursor->byteOffset, f.prop_stride);
608 }
609 property_index++;
610 }
611 }
612 element_idx++;
613 }
614}
615
616void PlyFile::PlyFileImpl::write_ascii_internal(std::ostream & os) noexcept
617{
618 write_header(os);
619
620 auto element_property_lookup = make_property_lookup_table();
621
622 size_t element_idx = 0;
623 for (auto & e : elements)
624 {
625 for (size_t i = 0; i < e.size; ++i)
626 {
627 size_t property_index = 0;
628 for (auto & p : e.properties)
629 {
630 auto & f = element_property_lookup[element_idx][property_index];
631 auto * helper = f.helper;
632 if (f.skip || helper == nullptr) continue;
633
634 if (p.isList)
635 {
636 os << p.listCount << " ";
637 for (size_t j = 0; j < p.listCount; ++j)
638 {
639 write_property_ascii(p.propertyType, os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset);
640 }
641 }
642 else
643 {
644 write_property_ascii(p.propertyType, os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset);
645 }
646 property_index++;
647 }
648 os << "\n";
649 }
650 element_idx++;
651 }
652}
653
654void PlyFile::PlyFileImpl::write_header(std::ostream & os) noexcept
655{
656 const std::locale & fixLoc = std::locale("C");
657 os.imbue(fixLoc);
658
659 os << "ply\n";
660 if (isBinary) os << ((isBigEndian) ? "format binary_big_endian 1.0" : "format binary_little_endian 1.0") << "\n";
661 else os << "format ascii 1.0\n";
662
663 for (const auto & comment : comments) os << "comment " << comment << "\n";
664
665 auto property_lookup = make_property_lookup_table();
666
667 size_t element_idx = 0;
668 for (auto & e : elements)
669 {
670 os << "element " << e.name << " " << e.size << "\n";
671 size_t property_idx = 0;
672 for (const auto & p : e.properties)
673 {
674 PropertyLookup & lookup = property_lookup[element_idx][property_idx];
675
676 if (!lookup.skip)
677 {
678 if (p.isList)
679 {
680 os << "property list " << PropertyTable[p.listType].str << " "
681 << PropertyTable[p.propertyType].str << " " << p.name << "\n";
682 }
683 else
684 {
685 os << "property " << PropertyTable[p.propertyType].str << " " << p.name << "\n";
686 }
687 }
688 property_idx++;
689 }
690 element_idx++;
691 }
692 os << "end_header\n";
693}
694
695std::shared_ptr<PlyData> PlyFile::PlyFileImpl::request_properties_from_element(const std::string & elementKey,
696 const std::vector<std::string> propertyKeys,
697 const uint32_t list_size_hint)
698{
699 if (elements.empty()) throw std::runtime_error("header had no elements defined. malformed file?");
700 if (elementKey.empty()) throw std::invalid_argument("`elementKey` argument is empty");
701 if (propertyKeys.empty()) throw std::invalid_argument("`propertyKeys` argument is empty");
702
703 std::shared_ptr<PlyData> out_data = std::make_shared<PlyData>();
704
705 const int64_t elementIndex = find_element(elementKey, elements);
706
707 std::vector<std::string> keys_not_found;
708
709 // Sanity check if the user requested element is in the pre-parsed header
710 if (elementIndex >= 0)
711 {
712 // We found the element
713 const PlyElement & element = elements[elementIndex];
714
715 // Each key in `propertyKey` gets an entry into the userData map (keyed by a hash of
716 // element name and property name), but groups of properties (requested from the
717 // public api through this function) all share the same `ParsingHelper`. When it comes
718 // time to .read(), we check the number of unique PlyData shared pointers
719 // and allocate a single buffer that will be used by each property key group.
720 // That way, properties like, {"x", "y", "z"} will all be put into the same buffer.
721
722 ParsingHelper helper;
723 helper.data = out_data;
724 helper.data->count = element.size; // how many items are in the element?
725 helper.data->isList = false;
726 helper.data->t = Type::INVALID;
727 helper.cursor = std::make_shared<PlyDataCursor>();
728 helper.list_size_hint = list_size_hint;
729
730 // Find each of the keys
731 for (const auto & key : propertyKeys)
732 {
733 const int64_t propertyIndex = find_property(key, element.properties);
734 if (propertyIndex < 0) keys_not_found.push_back(key);
735 }
736
737 if (keys_not_found.size())
738 {
739 std::stringstream ss;
740 for (auto & str : keys_not_found) ss << str << ", ";
741 throw std::invalid_argument("the following property keys were not found in the header: " + ss.str());
742 }
743
744 for (const auto & key : propertyKeys)
745 {
746 const int64_t propertyIndex = find_property(key, element.properties);
747 const PlyProperty & property = element.properties[propertyIndex];
748 helper.data->t = property.propertyType;
749 helper.data->isList = property.isList;
750 auto result = userData.insert(std::pair<uint32_t, ParsingHelper>(hash_fnv1a(element.name + property.name), helper));
751 if (result.second == false)
752 {
753 throw std::invalid_argument("element-property key has already been requested: " + element.name + " " + property.name);
754 }
755 }
756
757 // Sanity check that all properties share the same type
758 std::vector<Type> propertyTypes;
759 for (const auto & key : propertyKeys)
760 {
761 const int64_t propertyIndex = find_property(key, element.properties);
762 const PlyProperty & property = element.properties[propertyIndex];
763 propertyTypes.push_back(property.propertyType);
764 }
765
766 if (std::adjacent_find(propertyTypes.begin(), propertyTypes.end(), std::not_equal_to<Type>()) != propertyTypes.end())
767 {
768 throw std::invalid_argument("all requested properties must share the same type.");
769 }
770 }
771 else throw std::invalid_argument("the element key was not found in the header: " + elementKey);
772
773 return out_data;
774}
775
776void PlyFile::PlyFileImpl::add_properties_to_element(const std::string & elementKey,
777 const std::vector<std::string> propertyKeys,
778 const Type type, const size_t count, const uint8_t * data, const Type listType, const size_t listCount)
779{
780 ParsingHelper helper;
781 helper.data = std::make_shared<PlyData>();
782 helper.data->count = count;
783 helper.data->t = type;
784 helper.data->buffer = Buffer(data); // we should also set size for safety reasons
785 helper.cursor = std::make_shared<PlyDataCursor>();
786
787 auto create_property_on_element = [&](PlyElement & e)
788 {
789 for (auto key : propertyKeys)
790 {
791 PlyProperty newProp = (listType == Type::INVALID) ? PlyProperty(type, key) : PlyProperty(listType, type, key, listCount);
792 userData.insert(std::pair<uint32_t, ParsingHelper>(hash_fnv1a(elementKey + key), helper));
793 e.properties.push_back(newProp);
794 }
795 };
796
797 const int64_t idx = find_element(elementKey, elements);
798 if (idx >= 0)
799 {
800 PlyElement & e = elements[idx];
801 create_property_on_element(e);
802 }
803 else
804 {
805 PlyElement newElement = (listType == Type::INVALID) ? PlyElement(elementKey, count) : PlyElement(elementKey, count);
806 create_property_on_element(newElement);
807 elements.push_back(newElement);
808 }
809}
810
811void PlyFile::PlyFileImpl::parse_data(std::istream & is, bool firstPass)
812{
813 std::function<void(PropertyLookup & f, const PlyProperty & p, uint8_t * dest, size_t & destOffset, std::istream & is)> read;
814 std::function<size_t(PropertyLookup & f, const PlyProperty & p, std::istream & is)> skip;
815
816 const auto start = is.tellg();
817
818 uint32_t listSize = 0;
819 size_t dummyCount = 0;
820 std::string skip_ascii_buffer;
821
822 // Special case mirroring read_property_binary but for list types; this
823 // has an additional big endian check to flip the data in place immediately
824 // after reading. We do this as a performance optimization; endian flipping is
825 // done on regular properties as a post-process after reading (also for optimization)
826 // but we need the correct little-endian list count as we read the file.
827 auto read_list_binary = [this](const Type & t, void * dst, size_t & destOffset, const size_t & stride, std::istream & _is) noexcept
828 {
829 destOffset += stride;
830 _is.read((char*)dst, stride);
831
832 if (isBigEndian)
833 {
834 switch (t)
835 {
836 case Type::INT16: *(int16_t*)dst = endian_swap<int16_t, int16_t>(*(int16_t*)dst); break;
837 case Type::UINT16: *(uint16_t*)dst = endian_swap<uint16_t, uint16_t>(*(uint16_t*)dst); break;
838 case Type::INT32: *(int32_t*)dst = endian_swap<int32_t, int32_t>(*(int32_t*)dst); break;
839 case Type::UINT32: *(uint32_t*)dst = endian_swap<uint32_t, uint32_t>(*(uint32_t*)dst); break;
840 default: break;
841 }
842 }
843
844 return stride;
845 };
846
847 if (isBinary)
848 {
849 read = [this, &listSize, &dummyCount, &read_list_binary](PropertyLookup & f, const PlyProperty & p, uint8_t * dest, size_t & destOffset, std::istream & _is) noexcept
850 {
851 if (!p.isList)
852 {
853 return read_property_binary(f.prop_stride, dest + destOffset, destOffset, _is);
854 }
855 read_list_binary(p.listType, &listSize, dummyCount, f.list_stride, _is); // the list size
856 return read_property_binary(f.prop_stride * listSize, dest + destOffset, destOffset, _is); // properties in list
857 };
858 skip = [this, &listSize, &dummyCount, &read_list_binary](PropertyLookup & f, const PlyProperty & p, std::istream & _is) noexcept
859 {
860 if (!p.isList)
861 {
862 _is.read((char*)scratch, f.prop_stride);
863 return f.prop_stride;
864 }
865 read_list_binary(p.listType, &listSize, dummyCount, f.list_stride, _is); // the list size (does not count for memory alloc)
866 auto bytes_to_skip = f.prop_stride * listSize;
867 _is.ignore(bytes_to_skip);
868 return bytes_to_skip;
869 };
870 }
871 else
872 {
873 read = [this, &listSize, &dummyCount](PropertyLookup & f, const PlyProperty & p, uint8_t * dest, size_t & destOffset, std::istream & _is) noexcept
874 {
875 if (!p.isList)
876 {
877 read_property_ascii(p.propertyType, f.prop_stride, dest + destOffset, destOffset, _is);
878 }
879 else
880 {
881 read_property_ascii(p.listType, f.list_stride, &listSize, dummyCount, _is); // the list size
882 for (size_t i = 0; i < listSize; ++i)
883 {
884 read_property_ascii(p.propertyType, f.prop_stride, dest + destOffset, destOffset, _is);
885 }
886 }
887 };
888 skip = [this, &listSize, &dummyCount, &skip_ascii_buffer](PropertyLookup & f, const PlyProperty & p, std::istream & _is) noexcept
889 {
890 skip_ascii_buffer.clear();
891 if (p.isList)
892 {
893 read_property_ascii(p.listType, f.list_stride, &listSize, dummyCount, _is); // the list size (does not count for memory alloc)
894 for (size_t i = 0; i < listSize; ++i) _is >> skip_ascii_buffer; // properties in list
895 return listSize * f.prop_stride;
896 }
897 _is >> skip_ascii_buffer;
898 return f.prop_stride;
899 };
900 }
901
902 std::vector<std::vector<PropertyLookup>> element_property_lookup = make_property_lookup_table();
903 size_t element_idx = 0;
904 size_t property_idx = 0;
905 ParsingHelper * helper {nullptr};
906
907 // This is the inner import loop
908 for (auto & element : elements)
909 {
910 for (size_t count = 0; count < element.size; ++count)
911 {
912 property_idx = 0;
913 for (auto & property : element.properties)
914 {
915 PropertyLookup & lookup = element_property_lookup[element_idx][property_idx];
916
917 if (!lookup.skip)
918 {
919 helper = lookup.helper;
920 if (firstPass)
921 {
922 helper->cursor->totalSizeBytes += skip(lookup, property, is);
923
924 // These lines will be changed when tinyply supports
925 // variable length lists. We add it here so our header data structure
926 // contains enough info to write it back out again (e.g. transcoding).
927 if (property.listCount == 0) property.listCount = listSize;
928 if (property.listCount != listSize) throw std::runtime_error("variable length lists are not supported yet.");
929 }
930 else
931 {
932 read(lookup, property, helper->data->buffer.get(), helper->cursor->byteOffset, is);
933 }
934 }
935 else
936 {
937 skip(lookup, property, is);
938 }
939 property_idx++;
940 }
941 }
942 element_idx++;
943 }
944
945 // Reset istream position to the start of the data
946 if (firstPass) is.seekg(start, is.beg);
947}
948
949// Wrap the public interface:
950
951PlyFile::PlyFile() { impl.reset(new PlyFileImpl()); }
952PlyFile::~PlyFile() { }
953bool PlyFile::parse_header(std::istream & is) { return impl->parse_header(is); }
954void PlyFile::read(std::istream & is) { return impl->read(is); }
955void PlyFile::write(std::ostream & os, bool isBinary) { return impl->write(os, isBinary); }
956std::vector<PlyElement> PlyFile::get_elements() const { return impl->elements; }
957std::vector<std::string> & PlyFile::get_comments() { return impl->comments; }
958std::vector<std::string> PlyFile::get_info() const { return impl->objInfo; }
959bool PlyFile::is_binary_file() const { return impl->isBinary; }
960std::shared_ptr<PlyData> PlyFile::request_properties_from_element(const std::string & elementKey,
961 const std::vector<std::string> propertyKeys,
962 const uint32_t list_size_hint)
963{
964 return impl->request_properties_from_element(elementKey, propertyKeys, list_size_hint);
965}
966void PlyFile::add_properties_to_element(const std::string & elementKey,
967 const std::vector<std::string> propertyKeys,
968 const Type type, const size_t count, const uint8_t * data, const Type listType, const size_t listCount)
969{
970 return impl->add_properties_to_element(elementKey, propertyKeys, type, count, data, listType, listCount);
971}
972
973} // end namespace tinyply
974
975#endif // end TINYPLY_IMPLEMENTATION
Definition tinyply.h:76
Definition tinyply.h:78
Definition tinyply.h:91
Definition tinyply.h:112
Definition tinyply.h:121
Definition tinyply.h:99
Definition tinyply.h:54