tinyply.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. #include "tinyply.h"
  2. // Moved from origin tinyply.h
  3. ////////////////////////////////
  4. // tinyply implementation //
  5. ////////////////////////////////
  6. #include <algorithm>
  7. #include <functional>
  8. #include <type_traits>
  9. #include <iostream>
  10. #include <cstring>
  11. #include <cassert>
  12. #include <stdint.h>
  13. namespace igl
  14. {
  15. namespace tinyply
  16. {
  17. template<typename T, typename T2> T2 endian_swap(const T & /*v*/) noexcept {assert(false);} //{ return v; }
  18. template<> uint16_t IGL_INLINE endian_swap<uint16_t, uint16_t>(const uint16_t & v) noexcept { return (v << 8) | (v >> 8); }
  19. template<> uint32_t IGL_INLINE endian_swap<uint32_t, uint32_t>(const uint32_t & v) noexcept { return (v << 24) | ((v << 8) & 0x00ff0000) | ((v >> 8) & 0x0000ff00) | (v >> 24); }
  20. template<> uint64_t IGL_INLINE endian_swap<uint64_t, uint64_t>(const uint64_t & v) noexcept
  21. {
  22. return (((v & 0x00000000000000ffLL) << 56) |
  23. ((v & 0x000000000000ff00LL) << 40) |
  24. ((v & 0x0000000000ff0000LL) << 24) |
  25. ((v & 0x00000000ff000000LL) << 8) |
  26. ((v & 0x000000ff00000000LL) >> 8) |
  27. ((v & 0x0000ff0000000000LL) >> 24) |
  28. ((v & 0x00ff000000000000LL) >> 40) |
  29. ((v & 0xff00000000000000LL) >> 56));
  30. }
  31. template<> int16_t IGL_INLINE 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; }
  32. template<> int32_t IGL_INLINE 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; }
  33. template<> int64_t IGL_INLINE 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; }
  34. template<> float IGL_INLINE 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; }
  35. template<> double IGL_INLINE 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; }
  36. IGL_INLINE uint32_t hash_fnv1a(const std::string & str) noexcept
  37. {
  38. static const uint32_t fnv1aBase32 = 0x811C9DC5u;
  39. static const uint32_t fnv1aPrime32 = 0x01000193u;
  40. uint32_t result = fnv1aBase32;
  41. for (auto & c : str) { result ^= static_cast<uint32_t>(c); result *= fnv1aPrime32; }
  42. return result;
  43. }
  44. IGL_INLINE Type property_type_from_string(const std::string & t) noexcept
  45. {
  46. if (t == "int8" || t == "char") return Type::INT8;
  47. else if (t == "uint8" || t == "uchar") return Type::UINT8;
  48. else if (t == "int16" || t == "short") return Type::INT16;
  49. else if (t == "uint16" || t == "ushort") return Type::UINT16;
  50. else if (t == "int32" || t == "int") return Type::INT32;
  51. else if (t == "uint32" || t == "uint") return Type::UINT32;
  52. else if (t == "float32" || t == "float") return Type::FLOAT32;
  53. else if (t == "float64" || t == "double") return Type::FLOAT64;
  54. return Type::INVALID;
  55. }
  56. struct PlyFile::PlyFileImpl
  57. {
  58. struct PlyDataCursor
  59. {
  60. size_t byteOffset{ 0 };
  61. size_t totalSizeBytes{ 0 };
  62. };
  63. struct ParsingHelper
  64. {
  65. std::shared_ptr<PlyData> data;
  66. std::shared_ptr<PlyDataCursor> cursor;
  67. uint32_t list_size_hint;
  68. };
  69. struct PropertyLookup
  70. {
  71. ParsingHelper * helper{ nullptr };
  72. bool skip{ false };
  73. size_t prop_stride{ 0 }; // precomputed
  74. size_t list_stride{ 0 }; // precomputed
  75. };
  76. std::unordered_map<uint32_t, ParsingHelper> userData;
  77. bool isBinary = false;
  78. bool isBigEndian = false;
  79. std::vector<PlyElement> elements;
  80. std::vector<std::string> comments;
  81. std::vector<std::string> objInfo;
  82. uint8_t scratch[64]; // large enough for max list size
  83. void read(std::istream & is);
  84. void write(std::ostream & os, bool isBinary);
  85. std::shared_ptr<PlyData> request_properties_from_element(const std::string & elementKey,
  86. const std::vector<std::string> propertyKeys,
  87. const uint32_t list_size_hint);
  88. void add_properties_to_element(const std::string & elementKey,
  89. const std::vector<std::string> propertyKeys,
  90. const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount);
  91. size_t read_property_binary(const size_t & stride, void * dest, size_t & destOffset, std::istream & is) noexcept;
  92. size_t read_property_ascii(const Type & t, const size_t & stride, void * dest, size_t & destOffset, std::istream & is);
  93. std::vector<std::vector<PropertyLookup>> make_property_lookup_table();
  94. bool parse_header(std::istream & is);
  95. void parse_data(std::istream & is, bool firstPass);
  96. void read_header_format(std::istream & is);
  97. void read_header_element(std::istream & is);
  98. void read_header_property(std::istream & is);
  99. void read_header_text(std::string line, std::vector<std::string> & place, int erase = 0);
  100. void write_header(std::ostream & os) noexcept;
  101. void write_ascii_internal(std::ostream & os) noexcept;
  102. void write_binary_internal(std::ostream & os) noexcept;
  103. void write_property_ascii(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset);
  104. void write_property_binary(std::ostream & os, uint8_t * src, size_t & srcOffset, const size_t & stride) noexcept;
  105. };
  106. IGL_INLINE PlyProperty::PlyProperty(std::istream & is) : isList(false)
  107. {
  108. std::string type;
  109. is >> type;
  110. if (type == "list")
  111. {
  112. std::string countType;
  113. is >> countType >> type;
  114. listType = property_type_from_string(countType);
  115. isList = true;
  116. }
  117. propertyType = property_type_from_string(type);
  118. is >> name;
  119. }
  120. IGL_INLINE PlyElement::PlyElement(std::istream & is)
  121. {
  122. is >> name >> size;
  123. }
  124. template<typename T> IGL_INLINE T ply_read_ascii(std::istream & is)
  125. {
  126. T data;
  127. is >> data;
  128. return data;
  129. }
  130. template<typename T, typename T2>
  131. IGL_INLINE void endian_swap_buffer(uint8_t * data_ptr, const size_t num_bytes, const size_t stride)
  132. {
  133. for (size_t count = 0; count < num_bytes; count += stride)
  134. {
  135. *(reinterpret_cast<T2 *>(data_ptr)) = endian_swap<T, T2>(*(reinterpret_cast<const T *>(data_ptr)));
  136. data_ptr += stride;
  137. }
  138. }
  139. template<typename T> void ply_cast_ascii(void * dest, std::istream & is)
  140. {
  141. *(static_cast<T *>(dest)) = ply_read_ascii<T>(is);
  142. }
  143. IGL_INLINE int64_t find_element(const std::string & key, const std::vector<PlyElement> & list)
  144. {
  145. for (size_t i = 0; i < list.size(); i++) if (list[i].name == key) return i;
  146. return -1;
  147. }
  148. IGL_INLINE int64_t find_property(const std::string & key, const std::vector<PlyProperty> & list)
  149. {
  150. for (size_t i = 0; i < list.size(); ++i) if (list[i].name == key) return i;
  151. return -1;
  152. }
  153. // The `userData` table is an easy data structure for capturing what data the
  154. // user would like out of the ply file, but an inner-loop hash lookup is non-ideal.
  155. // The property lookup table flattens the table down into a 2D array optimized
  156. // for parsing. The first index is the element, and the second index is the property.
  157. IGL_INLINE std::vector<std::vector<PlyFile::PlyFileImpl::PropertyLookup>> PlyFile::PlyFileImpl::make_property_lookup_table()
  158. {
  159. std::vector<std::vector<PropertyLookup>> element_property_lookup;
  160. for (auto & element : elements)
  161. {
  162. std::vector<PropertyLookup> lookups;
  163. for (auto & property : element.properties)
  164. {
  165. PropertyLookup f;
  166. auto cursorIt = userData.find(hash_fnv1a(element.name + property.name));
  167. if (cursorIt != userData.end()) f.helper = &cursorIt->second;
  168. else f.skip = true;
  169. f.prop_stride = PropertyTable[property.propertyType].stride;
  170. if (property.isList) f.list_stride = PropertyTable[property.listType].stride;
  171. lookups.push_back(f);
  172. }
  173. element_property_lookup.push_back(lookups);
  174. }
  175. return element_property_lookup;
  176. }
  177. IGL_INLINE bool PlyFile::PlyFileImpl::parse_header(std::istream & is)
  178. {
  179. std::string line;
  180. bool success = true;
  181. while (std::getline(is, line))
  182. {
  183. std::istringstream ls(line);
  184. std::string token;
  185. ls >> token;
  186. if (token == "ply" || token == "PLY" || token == "") continue;
  187. else if (token == "comment") read_header_text(line, comments, 8);
  188. else if (token == "format") read_header_format(ls);
  189. else if (token == "element") read_header_element(ls);
  190. else if (token == "property") read_header_property(ls);
  191. else if (token == "obj_info") read_header_text(line, objInfo, 9);
  192. else if (token == "end_header") break;
  193. else success = false; // unexpected header field
  194. }
  195. return success;
  196. }
  197. IGL_INLINE void PlyFile::PlyFileImpl::read_header_text(std::string line, std::vector<std::string>& place, int erase)
  198. {
  199. place.push_back((erase > 0) ? line.erase(0, erase) : line);
  200. }
  201. IGL_INLINE void PlyFile::PlyFileImpl::read_header_format(std::istream & is)
  202. {
  203. std::string s;
  204. (is >> s);
  205. if (s == "binary_little_endian") isBinary = true;
  206. else if (s == "binary_big_endian") isBinary = isBigEndian = true;
  207. }
  208. IGL_INLINE void PlyFile::PlyFileImpl::read_header_element(std::istream & is)
  209. {
  210. elements.emplace_back(is);
  211. }
  212. IGL_INLINE void PlyFile::PlyFileImpl::read_header_property(std::istream & is)
  213. {
  214. if (!elements.size()) throw std::runtime_error("no elements defined; file is malformed");
  215. elements.back().properties.emplace_back(is);
  216. }
  217. IGL_INLINE size_t PlyFile::PlyFileImpl::read_property_binary(const size_t & stride, void * dest, size_t & destOffset, std::istream & is) noexcept
  218. {
  219. destOffset += stride;
  220. is.read((char*)dest, stride);
  221. return stride;
  222. }
  223. IGL_INLINE size_t PlyFile::PlyFileImpl::read_property_ascii(const Type & t, const size_t & stride, void * dest, size_t & destOffset, std::istream & is)
  224. {
  225. destOffset += stride;
  226. switch (t)
  227. {
  228. case Type::INT8: *((int8_t *)dest) = static_cast<int8_t>(ply_read_ascii<int32_t>(is)); break;
  229. case Type::UINT8: *((uint8_t *)dest) = static_cast<uint8_t>(ply_read_ascii<uint32_t>(is)); break;
  230. case Type::INT16: ply_cast_ascii<int16_t>(dest, is); break;
  231. case Type::UINT16: ply_cast_ascii<uint16_t>(dest, is); break;
  232. case Type::INT32: ply_cast_ascii<int32_t>(dest, is); break;
  233. case Type::UINT32: ply_cast_ascii<uint32_t>(dest, is); break;
  234. case Type::FLOAT32: ply_cast_ascii<float>(dest, is); break;
  235. case Type::FLOAT64: ply_cast_ascii<double>(dest, is); break;
  236. case Type::INVALID: throw std::invalid_argument("invalid ply property");
  237. }
  238. return stride;
  239. }
  240. IGL_INLINE void PlyFile::PlyFileImpl::write_property_ascii(Type t, std::ostream & os, uint8_t * src, size_t & srcOffset)
  241. {
  242. switch (t)
  243. {
  244. case Type::INT8: os << static_cast<int32_t>(*reinterpret_cast<int8_t*>(src)); break;
  245. case Type::UINT8: os << static_cast<uint32_t>(*reinterpret_cast<uint8_t*>(src)); break;
  246. case Type::INT16: os << *reinterpret_cast<int16_t*>(src); break;
  247. case Type::UINT16: os << *reinterpret_cast<uint16_t*>(src); break;
  248. case Type::INT32: os << *reinterpret_cast<int32_t*>(src); break;
  249. case Type::UINT32: os << *reinterpret_cast<uint32_t*>(src); break;
  250. case Type::FLOAT32: os << *reinterpret_cast<float*>(src); break;
  251. case Type::FLOAT64: os << *reinterpret_cast<double*>(src); break;
  252. case Type::INVALID: throw std::invalid_argument("invalid ply property");
  253. }
  254. os << " ";
  255. srcOffset += PropertyTable[t].stride;
  256. }
  257. IGL_INLINE void PlyFile::PlyFileImpl::write_property_binary(std::ostream & os, uint8_t * src, size_t & srcOffset, const size_t & stride) noexcept
  258. {
  259. os.write((char *)src, stride);
  260. srcOffset += stride;
  261. }
  262. IGL_INLINE void PlyFile::PlyFileImpl::read(std::istream & is)
  263. {
  264. std::vector<std::shared_ptr<PlyData>> buffers;
  265. for (auto & entry : userData) buffers.push_back(entry.second.data);
  266. // Discover if we can allocate up front without parsing the file twice
  267. uint32_t list_hints = 0;
  268. for (auto & b : buffers) for (auto & entry : userData) {list_hints += entry.second.list_size_hint;(void)b;}
  269. // No list hints? Then we need to calculate how much memory to allocate
  270. if (list_hints == 0)
  271. {
  272. parse_data(is, true);
  273. }
  274. // Count the number of properties (required for allocation)
  275. // e.g. if we have properties x y and z requested, we ensure
  276. // that their buffer points to the same PlyData
  277. std::unordered_map<PlyData*, int32_t> unique_data_count;
  278. for (auto & ptr : buffers) unique_data_count[ptr.get()] += 1;
  279. // Since group-requested properties share the same cursor,
  280. // we need to find unique cursors so we only allocate once
  281. std::sort(buffers.begin(), buffers.end());
  282. buffers.erase(std::unique(buffers.begin(), buffers.end()), buffers.end());
  283. // We sorted by ptrs on PlyData, need to remap back onto its cursor in the userData table
  284. for (auto & b : buffers)
  285. {
  286. for (auto & entry : userData)
  287. {
  288. if (entry.second.data == b && b->buffer.get() == nullptr)
  289. {
  290. // If we didn't receive any list hints, it means we did two passes over the
  291. // file to compute the total length of all (potentially) variable-length lists
  292. if (list_hints == 0)
  293. {
  294. b->buffer = Buffer(entry.second.cursor->totalSizeBytes);
  295. }
  296. else
  297. {
  298. // otherwise, we can allocate up front, skipping the first pass.
  299. const size_t list_size_multiplier = (entry.second.data->isList ? entry.second.list_size_hint : 1);
  300. auto bytes_per_property = entry.second.data->count * PropertyTable[entry.second.data->t].stride * list_size_multiplier;
  301. bytes_per_property *= unique_data_count[b.get()];
  302. b->buffer = Buffer(bytes_per_property);
  303. }
  304. }
  305. }
  306. }
  307. // Populate the data
  308. parse_data(is, false);
  309. // In-place big-endian to little-endian swapping if required
  310. if (isBigEndian)
  311. {
  312. for (auto & b : buffers)
  313. {
  314. uint8_t * data_ptr = b->buffer.get();
  315. const size_t stride = PropertyTable[b->t].stride;
  316. const size_t buffer_size_bytes = b->buffer.size_bytes();
  317. switch (b->t)
  318. {
  319. case Type::INT16: endian_swap_buffer<int16_t, int16_t>(data_ptr, buffer_size_bytes, stride); break;
  320. case Type::UINT16: endian_swap_buffer<uint16_t, uint16_t>(data_ptr, buffer_size_bytes, stride); break;
  321. case Type::INT32: endian_swap_buffer<int32_t, int32_t>(data_ptr, buffer_size_bytes, stride); break;
  322. case Type::UINT32: endian_swap_buffer<uint32_t, uint32_t>(data_ptr, buffer_size_bytes, stride); break;
  323. case Type::FLOAT32: endian_swap_buffer<uint32_t, float>(data_ptr, buffer_size_bytes, stride); break;
  324. case Type::FLOAT64: endian_swap_buffer<uint64_t, double>(data_ptr, buffer_size_bytes, stride); break;
  325. default: break;
  326. }
  327. }
  328. }
  329. }
  330. IGL_INLINE void PlyFile::PlyFileImpl::write(std::ostream & os, bool _isBinary)
  331. {
  332. for (auto & d : userData) { d.second.cursor->byteOffset = 0; }
  333. if (_isBinary)
  334. {
  335. isBinary = true;
  336. isBigEndian = false;
  337. write_binary_internal(os);
  338. }
  339. else
  340. {
  341. isBinary = false;
  342. isBigEndian = false;
  343. write_ascii_internal(os);
  344. }
  345. }
  346. IGL_INLINE void PlyFile::PlyFileImpl::write_binary_internal(std::ostream & os) noexcept
  347. {
  348. isBinary = true;
  349. write_header(os);
  350. uint8_t listSize[4] = { 0, 0, 0, 0 };
  351. size_t dummyCount = 0;
  352. auto element_property_lookup = make_property_lookup_table();
  353. size_t element_idx = 0;
  354. for (auto & e : elements)
  355. {
  356. for (size_t i = 0; i < e.size; ++i)
  357. {
  358. size_t property_index = 0;
  359. for (auto & p : e.properties)
  360. {
  361. auto & f = element_property_lookup[element_idx][property_index];
  362. auto * helper = f.helper;
  363. if (f.skip || helper == nullptr) continue;
  364. if (p.isList)
  365. {
  366. std::memcpy(listSize, &p.listCount, sizeof(uint32_t));
  367. write_property_binary(os, listSize, dummyCount, f.list_stride);
  368. write_property_binary(os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset, f.prop_stride * p.listCount);
  369. }
  370. else
  371. {
  372. write_property_binary(os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset, f.prop_stride);
  373. }
  374. property_index++;
  375. }
  376. }
  377. element_idx++;
  378. }
  379. }
  380. IGL_INLINE void PlyFile::PlyFileImpl::write_ascii_internal(std::ostream & os) noexcept
  381. {
  382. write_header(os);
  383. auto element_property_lookup = make_property_lookup_table();
  384. size_t element_idx = 0;
  385. for (auto & e : elements)
  386. {
  387. for (size_t i = 0; i < e.size; ++i)
  388. {
  389. size_t property_index = 0;
  390. for (auto & p : e.properties)
  391. {
  392. auto & f = element_property_lookup[element_idx][property_index];
  393. auto * helper = f.helper;
  394. if (f.skip || helper == nullptr) continue;
  395. if (p.isList)
  396. {
  397. os << p.listCount << " ";
  398. for (size_t j = 0; j < p.listCount; ++j)
  399. {
  400. write_property_ascii(p.propertyType, os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset);
  401. }
  402. }
  403. else
  404. {
  405. write_property_ascii(p.propertyType, os, (helper->data->buffer.get() + helper->cursor->byteOffset), helper->cursor->byteOffset);
  406. }
  407. property_index++;
  408. }
  409. os << "\n";
  410. }
  411. element_idx++;
  412. }
  413. }
  414. IGL_INLINE void PlyFile::PlyFileImpl::write_header(std::ostream & os) noexcept
  415. {
  416. const std::locale & fixLoc = std::locale("C");
  417. os.imbue(fixLoc);
  418. os << "ply\n";
  419. if (isBinary) os << ((isBigEndian) ? "format binary_big_endian 1.0" : "format binary_little_endian 1.0") << "\n";
  420. else os << "format ascii 1.0\n";
  421. for (const auto & comment : comments) os << "comment " << comment << "\n";
  422. auto property_lookup = make_property_lookup_table();
  423. size_t element_idx = 0;
  424. for (auto & e : elements)
  425. {
  426. os << "element " << e.name << " " << e.size << "\n";
  427. size_t property_idx = 0;
  428. for (const auto & p : e.properties)
  429. {
  430. PropertyLookup & lookup = property_lookup[element_idx][property_idx];
  431. if (!lookup.skip)
  432. {
  433. if (p.isList)
  434. {
  435. os << "property list " << PropertyTable[p.listType].str << " "
  436. << PropertyTable[p.propertyType].str << " " << p.name << "\n";
  437. }
  438. else
  439. {
  440. os << "property " << PropertyTable[p.propertyType].str << " " << p.name << "\n";
  441. }
  442. }
  443. property_idx++;
  444. }
  445. element_idx++;
  446. }
  447. os << "end_header\n";
  448. }
  449. IGL_INLINE std::shared_ptr<PlyData> PlyFile::PlyFileImpl::request_properties_from_element(const std::string & elementKey,
  450. const std::vector<std::string> propertyKeys,
  451. const uint32_t list_size_hint)
  452. {
  453. if (elements.empty()) throw std::runtime_error("header had no elements defined. malformed file?");
  454. if (elementKey.empty()) throw std::invalid_argument("`elementKey` argument is empty");
  455. if (propertyKeys.empty()) throw std::invalid_argument("`propertyKeys` argument is empty");
  456. std::shared_ptr<PlyData> out_data = std::make_shared<PlyData>();
  457. const int64_t elementIndex = find_element(elementKey, elements);
  458. std::vector<std::string> keys_not_found;
  459. // Sanity check if the user requested element is in the pre-parsed header
  460. if (elementIndex >= 0)
  461. {
  462. // We found the element
  463. const PlyElement & element = elements[elementIndex];
  464. // Each key in `propertyKey` gets an entry into the userData map (keyed by a hash of
  465. // element name and property name), but groups of properties (requested from the
  466. // public api through this function) all share the same `ParsingHelper`. When it comes
  467. // time to .read(), we check the number of unique PlyData shared pointers
  468. // and allocate a single buffer that will be used by each property key group.
  469. // That way, properties like, {"x", "y", "z"} will all be put into the same buffer.
  470. ParsingHelper helper;
  471. helper.data = out_data;
  472. helper.data->count = element.size; // how many items are in the element?
  473. helper.data->isList = false;
  474. helper.data->t = Type::INVALID;
  475. helper.cursor = std::make_shared<PlyDataCursor>();
  476. helper.list_size_hint = list_size_hint;
  477. // Find each of the keys
  478. for (const auto & key : propertyKeys)
  479. {
  480. const int64_t propertyIndex = find_property(key, element.properties);
  481. if (propertyIndex < 0) keys_not_found.push_back(key);
  482. }
  483. if (keys_not_found.size())
  484. {
  485. std::stringstream ss;
  486. for (auto & str : keys_not_found) ss << str << ", ";
  487. throw std::invalid_argument("the following property keys were not found in the header: " + ss.str());
  488. }
  489. for (const auto & key : propertyKeys)
  490. {
  491. const int64_t propertyIndex = find_property(key, element.properties);
  492. const PlyProperty & property = element.properties[propertyIndex];
  493. helper.data->t = property.propertyType;
  494. helper.data->isList = property.isList;
  495. auto result = userData.insert(std::pair<uint32_t, ParsingHelper>(hash_fnv1a(element.name + property.name), helper));
  496. if (result.second == false)
  497. {
  498. throw std::invalid_argument("element-property key has already been requested: " + element.name + " " + property.name);
  499. }
  500. }
  501. // Sanity check that all properties share the same type
  502. std::vector<Type> propertyTypes;
  503. for (const auto & key : propertyKeys)
  504. {
  505. const int64_t propertyIndex = find_property(key, element.properties);
  506. const PlyProperty & property = element.properties[propertyIndex];
  507. propertyTypes.push_back(property.propertyType);
  508. }
  509. if (std::adjacent_find(propertyTypes.begin(), propertyTypes.end(), std::not_equal_to<Type>()) != propertyTypes.end())
  510. {
  511. throw std::invalid_argument("all requested properties must share the same type.");
  512. }
  513. }
  514. else throw std::invalid_argument("the element key was not found in the header: " + elementKey);
  515. return out_data;
  516. }
  517. IGL_INLINE void PlyFile::PlyFileImpl::add_properties_to_element(const std::string & elementKey,
  518. const std::vector<std::string> propertyKeys,
  519. const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount)
  520. {
  521. ParsingHelper helper;
  522. helper.data = std::make_shared<PlyData>();
  523. helper.data->count = count;
  524. helper.data->t = type;
  525. helper.data->buffer = Buffer(data); // we should also set size for safety reasons
  526. helper.cursor = std::make_shared<PlyDataCursor>();
  527. auto create_property_on_element = [&](PlyElement & e)
  528. {
  529. for (auto key : propertyKeys)
  530. {
  531. PlyProperty newProp = (listType == Type::INVALID) ? PlyProperty(type, key) : PlyProperty(listType, type, key, listCount);
  532. userData.insert(std::pair<uint32_t, ParsingHelper>(hash_fnv1a(elementKey + key), helper));
  533. e.properties.push_back(newProp);
  534. }
  535. };
  536. const int64_t idx = find_element(elementKey, elements);
  537. if (idx >= 0)
  538. {
  539. PlyElement & e = elements[idx];
  540. create_property_on_element(e);
  541. }
  542. else
  543. {
  544. PlyElement newElement = (listType == Type::INVALID) ? PlyElement(elementKey, count) : PlyElement(elementKey, count);
  545. create_property_on_element(newElement);
  546. elements.push_back(newElement);
  547. }
  548. }
  549. IGL_INLINE void PlyFile::PlyFileImpl::parse_data(std::istream & is, bool firstPass)
  550. {
  551. std::function<void(PropertyLookup & f, const PlyProperty & p, uint8_t * dest, size_t & destOffset, std::istream & is)> read;
  552. std::function<size_t(PropertyLookup & f, const PlyProperty & p, std::istream & is)> skip;
  553. const auto start = is.tellg();
  554. uint32_t listSize = 0;
  555. size_t dummyCount = 0;
  556. std::string skip_ascii_buffer;
  557. // Special case mirroring read_property_binary but for list types; this
  558. // has an additional big endian check to flip the data in place immediately
  559. // after reading. We do this as a performance optimization; endian flipping is
  560. // done on regular properties as a post-process after reading (also for optimization)
  561. // but we need the correct little-endian list count as we read the file.
  562. auto read_list_binary = [this](const Type & t, void * dst, size_t & destOffset, const size_t & stride, std::istream & _is) noexcept
  563. {
  564. destOffset += stride;
  565. _is.read((char*)dst, stride);
  566. if (isBigEndian)
  567. {
  568. switch (t)
  569. {
  570. case Type::INT16: *(int16_t*)dst = endian_swap<int16_t, int16_t>(*(int16_t*)dst); break;
  571. case Type::UINT16: *(uint16_t*)dst = endian_swap<uint16_t, uint16_t>(*(uint16_t*)dst); break;
  572. case Type::INT32: *(int32_t*)dst = endian_swap<int32_t, int32_t>(*(int32_t*)dst); break;
  573. case Type::UINT32: *(uint32_t*)dst = endian_swap<uint32_t, uint32_t>(*(uint32_t*)dst); break;
  574. default: break;
  575. }
  576. }
  577. return stride;
  578. };
  579. if (isBinary)
  580. {
  581. read = [this, &listSize, &dummyCount, &read_list_binary](PropertyLookup & f, const PlyProperty & p, uint8_t * dest, size_t & destOffset, std::istream & _is) noexcept
  582. {
  583. if (!p.isList)
  584. {
  585. return read_property_binary(f.prop_stride, dest + destOffset, destOffset, _is);
  586. }
  587. read_list_binary(p.listType, &listSize, dummyCount, f.list_stride, _is); // the list size
  588. return read_property_binary(f.prop_stride * listSize, dest + destOffset, destOffset, _is); // properties in list
  589. };
  590. skip = [this, &listSize, &dummyCount, &read_list_binary](PropertyLookup & f, const PlyProperty & p, std::istream & _is) noexcept
  591. {
  592. if (!p.isList)
  593. {
  594. _is.read((char*)scratch, f.prop_stride);
  595. return f.prop_stride;
  596. }
  597. read_list_binary(p.listType, &listSize, dummyCount, f.list_stride, _is); // the list size (does not count for memory alloc)
  598. auto bytes_to_skip = f.prop_stride * listSize;
  599. _is.ignore(bytes_to_skip);
  600. return bytes_to_skip;
  601. };
  602. }
  603. else
  604. {
  605. read = [this, &listSize, &dummyCount](PropertyLookup & f, const PlyProperty & p, uint8_t * dest, size_t & destOffset, std::istream & _is) noexcept
  606. {
  607. if (!p.isList)
  608. {
  609. read_property_ascii(p.propertyType, f.prop_stride, dest + destOffset, destOffset, _is);
  610. }
  611. else
  612. {
  613. read_property_ascii(p.listType, f.list_stride, &listSize, dummyCount, _is); // the list size
  614. for (size_t i = 0; i < listSize; ++i)
  615. {
  616. read_property_ascii(p.propertyType, f.prop_stride, dest + destOffset, destOffset, _is);
  617. }
  618. }
  619. };
  620. skip = [this, &listSize, &dummyCount, &skip_ascii_buffer](PropertyLookup & f, const PlyProperty & p, std::istream & _is) noexcept
  621. {
  622. skip_ascii_buffer.clear();
  623. if (p.isList)
  624. {
  625. read_property_ascii(p.listType, f.list_stride, &listSize, dummyCount, _is); // the list size (does not count for memory alloc)
  626. for (size_t i = 0; i < listSize; ++i) _is >> skip_ascii_buffer; // properties in list
  627. return listSize * f.prop_stride;
  628. }
  629. _is >> skip_ascii_buffer;
  630. return f.prop_stride;
  631. };
  632. }
  633. std::vector<std::vector<PropertyLookup>> element_property_lookup = make_property_lookup_table();
  634. size_t element_idx = 0;
  635. size_t property_idx = 0;
  636. ParsingHelper * helper {nullptr};
  637. // This is the inner import loop
  638. for (auto & element : elements)
  639. {
  640. for (size_t count = 0; count < element.size; ++count)
  641. {
  642. property_idx = 0;
  643. for (auto & property : element.properties)
  644. {
  645. PropertyLookup & lookup = element_property_lookup[element_idx][property_idx];
  646. if (!lookup.skip)
  647. {
  648. helper = lookup.helper;
  649. if (firstPass)
  650. {
  651. helper->cursor->totalSizeBytes += skip(lookup, property, is);
  652. // These lines will be changed when tinyply supports
  653. // variable length lists. We add it here so our header data structure
  654. // contains enough info to write it back out again (e.g. transcoding).
  655. if (property.listCount == 0) property.listCount = listSize;
  656. if (property.listCount != listSize) throw std::runtime_error("variable length lists are not supported yet.");
  657. }
  658. else
  659. {
  660. read(lookup, property, helper->data->buffer.get(), helper->cursor->byteOffset, is);
  661. }
  662. }
  663. else
  664. {
  665. skip(lookup, property, is);
  666. }
  667. property_idx++;
  668. }
  669. }
  670. element_idx++;
  671. }
  672. // Reset istream position to the start of the data
  673. if (firstPass) is.seekg(start, is.beg);
  674. }
  675. // Wrap the public interface:
  676. IGL_INLINE PlyFile::PlyFile() { impl.reset(new PlyFileImpl()); }
  677. IGL_INLINE PlyFile::~PlyFile() { }
  678. IGL_INLINE bool PlyFile::parse_header(std::istream & is) { return impl->parse_header(is); }
  679. IGL_INLINE void PlyFile::read(std::istream & is) { return impl->read(is); }
  680. IGL_INLINE void PlyFile::write(std::ostream & os, bool isBinary) { return impl->write(os, isBinary); }
  681. IGL_INLINE std::vector<PlyElement> PlyFile::get_elements() const { return impl->elements; }
  682. IGL_INLINE std::vector<std::string> & PlyFile::get_comments() { return impl->comments; }
  683. IGL_INLINE std::vector<std::string> PlyFile::get_info() const { return impl->objInfo; }
  684. IGL_INLINE bool PlyFile::is_binary_file() const { return impl->isBinary; }
  685. IGL_INLINE std::shared_ptr<PlyData> PlyFile::request_properties_from_element(const std::string & elementKey,
  686. const std::vector<std::string> propertyKeys,
  687. const uint32_t list_size_hint)
  688. {
  689. return impl->request_properties_from_element(elementKey, propertyKeys, list_size_hint);
  690. }
  691. IGL_INLINE void PlyFile::add_properties_to_element(const std::string & elementKey,
  692. const std::vector<std::string> propertyKeys,
  693. const Type type, const size_t count, uint8_t * data, const Type listType, const size_t listCount)
  694. {
  695. return impl->add_properties_to_element(elementKey, propertyKeys, type, count, data, listType, listCount);
  696. }
  697. } // tinyply
  698. } // igl