ISAAC  0.2.11
Flight software for the ISAAC project, adding functionality to the Astrobee robot, operating inside the International Space Station.
All Classes Functions Variables Pages
happly.h
1 #pragma once
2 
3 /* A header-only implementation of the .ply file format.
4  * https://github.com/nmwsharp/happly
5  * By Nicholas Sharp - nsharp@cs.cmu.edu
6  *
7  * Version 2, July 20, 2019
8  */
9 
10 /*
11 MIT License
12 
13 Copyright (c) 2018 Nick Sharp
14 
15 Permission is hereby granted, free of charge, to any person obtaining a copy
16 of this software and associated documentation files (the "Software"), to deal
17 in the Software without restriction, including without limitation the rights
18 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19 copies of the Software, and to permit persons to whom the Software is
20 furnished to do so, subject to the following conditions:
21 
22 The above copyright notice and this permission notice shall be included in all
23 copies or substantial portions of the Software.
24 
25 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31 SOFTWARE.
32 */
33 
34 
35 // clang-format off
36 /*
37 
38  === Changelog ===
39 
40  Significant changes to the file recorded here.
41 
42  - Version 5 (Aug 22, 2020) Minor: skip blank lines before properties in ASCII files
43  - Version 4 (Sep 11, 2019) Change internal list format to be flat. Other small perf fixes and cleanup.
44  - Version 3 (Aug 1, 2019) Add support for big endian and obj_info
45  - Version 2 (July 20, 2019) Catch exceptions by const reference.
46  - Version 1 (undated) Initial version. Unnamed changes before version numbering.
47 
48 */
49 // clang-format on
50 
51 #include <array>
52 #include <cctype>
53 #include <fstream>
54 #include <iostream>
55 #include <limits>
56 #include <memory>
57 #include <sstream>
58 #include <string>
59 #include <type_traits>
60 #include <vector>
61 #include <climits>
62 
63 // General namespace wrapping all Happly things.
64 namespace happly {
65 
66 // Enum specifying binary or ASCII filetypes. Binary can be little-endian
67 // (default) or big endian.
68 enum class DataFormat { ASCII, Binary, BinaryBigEndian };
69 
70 // Type name strings
71 // clang-format off
72 template <typename T> std::string typeName() { return "unknown"; }
73 template<> inline std::string typeName<int8_t>() { return "char"; }
74 template<> inline std::string typeName<uint8_t>() { return "uchar"; }
75 template<> inline std::string typeName<int16_t>() { return "short"; }
76 template<> inline std::string typeName<uint16_t>() { return "ushort"; }
77 template<> inline std::string typeName<int32_t>() { return "int"; }
78 template<> inline std::string typeName<uint32_t>() { return "uint"; }
79 template<> inline std::string typeName<float>() { return "float"; }
80 template<> inline std::string typeName<double>() { return "double"; }
81 
82 // Template hackery that makes getProperty<T>() and friends pretty while automatically picking up smaller types
83 namespace {
84 
85 // A pointer for the equivalent/smaller equivalent of a type (eg. when a double is requested a float works too, etc)
86 // long int is intentionally absent to avoid platform confusion
87 template <class T> struct TypeChain { bool hasChildType = false; typedef T type; };
88 template <> struct TypeChain<int64_t> { bool hasChildType = true; typedef int32_t type; };
89 template <> struct TypeChain<int32_t> { bool hasChildType = true; typedef int16_t type; };
90 template <> struct TypeChain<int16_t> { bool hasChildType = true; typedef int8_t type; };
91 template <> struct TypeChain<uint64_t> { bool hasChildType = true; typedef uint32_t type; };
92 template <> struct TypeChain<uint32_t> { bool hasChildType = true; typedef uint16_t type; };
93 template <> struct TypeChain<uint16_t> { bool hasChildType = true; typedef uint8_t type; };
94 template <> struct TypeChain<double> { bool hasChildType = true; typedef float type; };
95 
96 template <class T> struct CanonicalName { typedef T type; };
97 template <> struct CanonicalName<char> { typedef int8_t type; };
98 template <> struct CanonicalName<unsigned char> { typedef uint8_t type; };
99 template <> struct CanonicalName<size_t> { typedef std::conditional<std::is_same<std::make_signed<size_t>::type, int>::value, uint32_t, uint64_t>::type type; };
100 
101 // Used to change behavior of >> for 8bit ints, which does not do what we want.
102 template <class T> struct SerializeType { typedef T type; };
103 template <> struct SerializeType<uint8_t> { typedef int32_t type; };
104 template <> struct SerializeType< int8_t> { typedef int32_t type; };
105 
106 // Give address only if types are same (used below when conditionally copying data)
107 // last int/char arg is to resolve ambiguous overloads, just always pass 0 and the int version will be preferred
108 template <typename S, typename T>
109 S* addressIfSame(T&, char) {
110  throw std::runtime_error("tried to take address for types that are not same");
111  return nullptr;}
112 template <typename S>
113 S* addressIfSame(S& t, int) {return &t;}
114 
115 // clang-format on
116 } // namespace
117 
123 class Property {
124 
125 public:
131  Property(const std::string& name_) : name(name_){};
132  virtual ~Property(){};
133 
134  std::string name;
135 
141  virtual void reserve(size_t capacity) = 0;
142 
149  virtual void parseNext(const std::vector<std::string>& tokens, size_t& currEntry) = 0;
150 
156  virtual void readNext(std::istream& stream) = 0;
157 
163  virtual void readNextBigEndian(std::istream& stream) = 0;
164 
170  virtual void writeHeader(std::ostream& outStream) = 0;
171 
178  virtual void writeDataASCII(std::ostream& outStream, size_t iElement) = 0;
179 
186  virtual void writeDataBinary(std::ostream& outStream, size_t iElement) = 0;
187 
194  virtual void writeDataBinaryBigEndian(std::ostream& outStream, size_t iElement) = 0;
195 
201  virtual size_t size() = 0;
202 
208  virtual std::string propertyTypeName() = 0;
209 };
210 
211 namespace {
212 
219 bool isLittleEndian() {
220  int32_t oneVal = 0x1;
221  char* numPtr = (char*)&oneVal;
222  return (numPtr[0] == 1);
223 }
224 
232 template <typename T>
233 T swapEndian(T val) {
234  char* bytes = reinterpret_cast<char*>(&val);
235  for (unsigned int i = 0; i < sizeof(val) / 2; i++) {
236  std::swap(bytes[sizeof(val) - 1 - i], bytes[i]);
237  }
238  return val;
239 }
240 
241 
242 // Unpack flattened list from the convention used in TypedListProperty
243 template <typename T>
244 std::vector<std::vector<T>> unflattenList(const std::vector<T>& flatList, const std::vector<size_t> flatListStarts) {
245  size_t outerCount = flatListStarts.size() - 1;
246 
247  // Put the output here
248  std::vector<std::vector<T>> outLists(outerCount);
249 
250  if (outerCount == 0) {
251  return outLists; // quick out for empty
252  }
253 
254  // Copy each sublist
255  for (size_t iOuter = 0; iOuter < outerCount; iOuter++) {
256  size_t iFlatStart = flatListStarts[iOuter];
257  size_t iFlatEnd = flatListStarts[iOuter + 1];
258  outLists[iOuter].insert(outLists[iOuter].begin(), flatList.begin() + iFlatStart, flatList.begin() + iFlatEnd);
259  }
260 
261  return outLists;
262 }
263 
264 
265 }; // namespace
266 
267 
271 template <class T>
272 class TypedProperty : public Property {
273 
274 public:
280  TypedProperty(const std::string& name_) : Property(name_) {
281  if (typeName<T>() == "unknown") {
282  // TODO should really be a compile-time error
283  throw std::runtime_error("Attempted property type does not match any type defined by the .ply format.");
284  }
285  };
286 
293  TypedProperty(const std::string& name_, const std::vector<T>& data_) : Property(name_), data(data_) {
294  if (typeName<T>() == "unknown") {
295  throw std::runtime_error("Attempted property type does not match any type defined by the .ply format.");
296  }
297  };
298 
299  virtual ~TypedProperty() override{};
300 
306  virtual void reserve(size_t capacity) override { data.reserve(capacity); }
307 
314  virtual void parseNext(const std::vector<std::string>& tokens, size_t& currEntry) override {
315  data.emplace_back();
316  std::istringstream iss(tokens[currEntry]);
317  typename SerializeType<T>::type tmp; // usually the same type as T
318  iss >> tmp;
319  data.back() = tmp;
320  currEntry++;
321  };
322 
328  virtual void readNext(std::istream& stream) override {
329  data.emplace_back();
330  stream.read((char*)&data.back(), sizeof(T));
331  }
332 
338  virtual void readNextBigEndian(std::istream& stream) override {
339  data.emplace_back();
340  stream.read((char*)&data.back(), sizeof(T));
341  data.back() = swapEndian(data.back());
342  }
343 
349  virtual void writeHeader(std::ostream& outStream) override {
350  outStream << "property " << typeName<T>() << " " << name << "\n";
351  }
352 
359  virtual void writeDataASCII(std::ostream& outStream, size_t iElement) override {
360  outStream.precision(std::numeric_limits<T>::max_digits10);
361  outStream << static_cast<typename SerializeType<T>::type>(data[iElement]); // case is usually a no-op
362  }
363 
370  virtual void writeDataBinary(std::ostream& outStream, size_t iElement) override {
371  outStream.write((char*)&data[iElement], sizeof(T));
372  }
373 
380  virtual void writeDataBinaryBigEndian(std::ostream& outStream, size_t iElement) override {
381  auto value = swapEndian(data[iElement]);
382  outStream.write((char*)&value, sizeof(T));
383  }
384 
390  virtual size_t size() override { return data.size(); }
391 
392 
398  virtual std::string propertyTypeName() override { return typeName<T>(); }
399 
403  std::vector<T> data;
404 };
405 
406 
410 template <class T>
411 class TypedListProperty : public Property {
412 
413 public:
419  TypedListProperty(const std::string& name_, int listCountBytes_) : Property(name_), listCountBytes(listCountBytes_) {
420  if (typeName<T>() == "unknown") {
421  throw std::runtime_error("Attempted property type does not match any type defined by the .ply format.");
422  }
423 
424  flattenedIndexStart.push_back(0);
425  };
426 
433  TypedListProperty(const std::string& name_, const std::vector<std::vector<T>>& data_) : Property(name_) {
434  if (typeName<T>() == "unknown") {
435  throw std::runtime_error("Attempted property type does not match any type defined by the .ply format.");
436  }
437 
438  // Populate list with data
439  flattenedIndexStart.push_back(0);
440  for (const std::vector<T>& vec : data_) {
441  for (const T& val : vec) {
442  flattenedData.emplace_back(val);
443  }
444  flattenedIndexStart.push_back(flattenedData.size());
445  }
446  };
447 
448  virtual ~TypedListProperty() override{};
449 
455  virtual void reserve(size_t capacity) override {
456  flattenedData.reserve(3 * capacity); // optimize for triangle meshes
457  flattenedIndexStart.reserve(capacity + 1);
458  }
459 
466  virtual void parseNext(const std::vector<std::string>& tokens, size_t& currEntry) override {
467 
468  std::istringstream iss(tokens[currEntry]);
469  size_t count;
470  iss >> count;
471  currEntry++;
472 
473  size_t currSize = flattenedData.size();
474  size_t afterSize = currSize + count;
475  flattenedData.resize(afterSize);
476  for (size_t iFlat = currSize; iFlat < afterSize; iFlat++) {
477  std::istringstream iss(tokens[currEntry]);
478  typename SerializeType<T>::type tmp; // usually the same type as T
479  iss >> tmp;
480  flattenedData[iFlat] = tmp;
481  currEntry++;
482  }
483  flattenedIndexStart.emplace_back(afterSize);
484  }
485 
491  virtual void readNext(std::istream& stream) override {
492 
493  // Read the size of the list
494  size_t count = 0;
495  stream.read(((char*)&count), listCountBytes);
496 
497  // Read list elements
498  size_t currSize = flattenedData.size();
499  size_t afterSize = currSize + count;
500  flattenedData.resize(afterSize);
501  if (count > 0) {
502  stream.read((char*)&flattenedData[currSize], count * sizeof(T));
503  }
504  flattenedIndexStart.emplace_back(afterSize);
505  }
506 
512  virtual void readNextBigEndian(std::istream& stream) override {
513 
514  // Read the size of the list
515  size_t count = 0;
516  stream.read(((char*)&count), listCountBytes);
517  if (listCountBytes == 8) {
518  count = (size_t)swapEndian((uint64_t)count);
519  } else if (listCountBytes == 4) {
520  count = (size_t)swapEndian((uint32_t)count);
521  } else if (listCountBytes == 2) {
522  count = (size_t)swapEndian((uint16_t)count);
523  }
524 
525  // Read list elements
526  size_t currSize = flattenedData.size();
527  size_t afterSize = currSize + count;
528  flattenedData.resize(afterSize);
529  if (count > 0) {
530  stream.read((char*)&flattenedData[currSize], count * sizeof(T));
531  }
532  flattenedIndexStart.emplace_back(afterSize);
533 
534  // Swap endian order of list elements
535  for (size_t iFlat = currSize; iFlat < afterSize; iFlat++) {
536  flattenedData[iFlat] = swapEndian(flattenedData[iFlat]);
537  }
538  }
539 
545  virtual void writeHeader(std::ostream& outStream) override {
546  // NOTE: We ALWAYS use uchar as the list count output type
547  outStream << "property list uchar " << typeName<T>() << " " << name << "\n";
548  }
549 
556  virtual void writeDataASCII(std::ostream& outStream, size_t iElement) override {
557  size_t dataStart = flattenedIndexStart[iElement];
558  size_t dataEnd = flattenedIndexStart[iElement + 1];
559 
560  // Get the number of list elements as a uchar, and ensure the value fits
561  size_t dataCount = dataEnd - dataStart;
562  if (dataCount > std::numeric_limits<uint8_t>::max()) {
563  throw std::runtime_error(
564  "List property has an element with more entries than fit in a uchar. See note in README.");
565  }
566 
567  outStream << dataCount;
568  outStream.precision(std::numeric_limits<T>::max_digits10);
569  for (size_t iFlat = dataStart; iFlat < dataEnd; iFlat++) {
570  outStream << " " << static_cast<typename SerializeType<T>::type>(flattenedData[iFlat]); // cast is usually a no-op
571  }
572  }
573 
580  virtual void writeDataBinary(std::ostream& outStream, size_t iElement) override {
581  size_t dataStart = flattenedIndexStart[iElement];
582  size_t dataEnd = flattenedIndexStart[iElement + 1];
583 
584  // Get the number of list elements as a uchar, and ensure the value fits
585  size_t dataCount = dataEnd - dataStart;
586  if (dataCount > std::numeric_limits<uint8_t>::max()) {
587  throw std::runtime_error(
588  "List property has an element with more entries than fit in a uchar. See note in README.");
589  }
590  uint8_t count = static_cast<uint8_t>(dataCount);
591 
592  outStream.write((char*)&count, sizeof(uint8_t));
593  outStream.write((char*)&flattenedData[dataStart], count * sizeof(T));
594  }
595 
602  virtual void writeDataBinaryBigEndian(std::ostream& outStream, size_t iElement) override {
603  size_t dataStart = flattenedIndexStart[iElement];
604  size_t dataEnd = flattenedIndexStart[iElement + 1];
605 
606  // Get the number of list elements as a uchar, and ensure the value fits
607  size_t dataCount = dataEnd - dataStart;
608  if (dataCount > std::numeric_limits<uint8_t>::max()) {
609  throw std::runtime_error(
610  "List property has an element with more entries than fit in a uchar. See note in README.");
611  }
612  uint8_t count = static_cast<uint8_t>(dataCount);
613 
614  outStream.write((char*)&count, sizeof(uint8_t));
615  for (size_t iFlat = dataStart; iFlat < dataEnd; iFlat++) {
616  T value = swapEndian(flattenedData[iFlat]);
617  outStream.write((char*)&value, sizeof(T));
618  }
619  }
620 
626  virtual size_t size() override { return flattenedIndexStart.size() - 1; }
627 
628 
634  virtual std::string propertyTypeName() override { return typeName<T>(); }
635 
640  std::vector<T> flattenedData;
641 
646  std::vector<size_t> flattenedIndexStart;
647 
651  int listCountBytes = -1;
652 };
653 
654 
665 inline std::unique_ptr<Property> createPropertyWithType(const std::string& name, const std::string& typeStr,
666  bool isList, const std::string& listCountTypeStr) {
667 
668  // == Figure out how many bytes the list count field has, if this is a list type
669  // Note: some files seem to use signed types here, we read the width but always parse as if unsigned
670  int listCountBytes = -1;
671  if (isList) {
672  if (listCountTypeStr == "uchar" || listCountTypeStr == "uint8" || listCountTypeStr == "char" ||
673  listCountTypeStr == "int8") {
674  listCountBytes = 1;
675  } else if (listCountTypeStr == "ushort" || listCountTypeStr == "uint16" || listCountTypeStr == "short" ||
676  listCountTypeStr == "int16") {
677  listCountBytes = 2;
678  } else if (listCountTypeStr == "uint" || listCountTypeStr == "uint32" || listCountTypeStr == "int" ||
679  listCountTypeStr == "int32") {
680  listCountBytes = 4;
681  } else {
682  throw std::runtime_error("Unrecognized list count type: " + listCountTypeStr);
683  }
684  }
685 
686  // = Unsigned int
687 
688  // 8 bit unsigned
689  if (typeStr == "uchar" || typeStr == "uint8") {
690  if (isList) {
691  return std::unique_ptr<Property>(new TypedListProperty<uint8_t>(name, listCountBytes));
692  } else {
693  return std::unique_ptr<Property>(new TypedProperty<uint8_t>(name));
694  }
695  }
696 
697  // 16 bit unsigned
698  else if (typeStr == "ushort" || typeStr == "uint16") {
699  if (isList) {
700  return std::unique_ptr<Property>(new TypedListProperty<uint16_t>(name, listCountBytes));
701  } else {
702  return std::unique_ptr<Property>(new TypedProperty<uint16_t>(name));
703  }
704  }
705 
706  // 32 bit unsigned
707  else if (typeStr == "uint" || typeStr == "uint32") {
708  if (isList) {
709  return std::unique_ptr<Property>(new TypedListProperty<uint32_t>(name, listCountBytes));
710  } else {
711  return std::unique_ptr<Property>(new TypedProperty<uint32_t>(name));
712  }
713  }
714 
715  // = Signed int
716 
717  // 8 bit signed
718  if (typeStr == "char" || typeStr == "int8") {
719  if (isList) {
720  return std::unique_ptr<Property>(new TypedListProperty<int8_t>(name, listCountBytes));
721  } else {
722  return std::unique_ptr<Property>(new TypedProperty<int8_t>(name));
723  }
724  }
725 
726  // 16 bit signed
727  else if (typeStr == "short" || typeStr == "int16") {
728  if (isList) {
729  return std::unique_ptr<Property>(new TypedListProperty<int16_t>(name, listCountBytes));
730  } else {
731  return std::unique_ptr<Property>(new TypedProperty<int16_t>(name));
732  }
733  }
734 
735  // 32 bit signed
736  else if (typeStr == "int" || typeStr == "int32") {
737  if (isList) {
738  return std::unique_ptr<Property>(new TypedListProperty<int32_t>(name, listCountBytes));
739  } else {
740  return std::unique_ptr<Property>(new TypedProperty<int32_t>(name));
741  }
742  }
743 
744  // = Float
745 
746  // 32 bit float
747  else if (typeStr == "float" || typeStr == "float32") {
748  if (isList) {
749  return std::unique_ptr<Property>(new TypedListProperty<float>(name, listCountBytes));
750  } else {
751  return std::unique_ptr<Property>(new TypedProperty<float>(name));
752  }
753  }
754 
755  // 64 bit float
756  else if (typeStr == "double" || typeStr == "float64") {
757  if (isList) {
758  return std::unique_ptr<Property>(new TypedListProperty<double>(name, listCountBytes));
759  } else {
760  return std::unique_ptr<Property>(new TypedProperty<double>(name));
761  }
762  }
763 
764  else {
765  throw std::runtime_error("Data type: " + typeStr + " cannot be mapped to .ply format");
766  }
767 }
768 
774 class Element {
775 
776 public:
783  Element(const std::string& name_, size_t count_) : name(name_), count(count_) {}
784 
785  std::string name;
786  size_t count;
787  std::vector<std::unique_ptr<Property>> properties;
788 
796  bool hasProperty(const std::string& target) {
797  for (std::unique_ptr<Property>& prop : properties) {
798  if (prop->name == target) {
799  return true;
800  }
801  }
802  return false;
803  }
804 
813  template <class T>
814  bool hasPropertyType(const std::string& target) {
815  for (std::unique_ptr<Property>& prop : properties) {
816  if (prop->name == target) {
817  TypedProperty<T>* castedProp = dynamic_cast<TypedProperty<T>*>(prop.get());
818  if (castedProp) {
819  return true;
820  }
821  return false;
822  }
823  }
824  return false;
825  }
826 
832  std::vector<std::string> getPropertyNames() {
833  std::vector<std::string> names;
834  for (std::unique_ptr<Property>& p : properties) {
835  names.push_back(p->name);
836  }
837  return names;
838  }
839 
847  std::unique_ptr<Property>& getPropertyPtr(const std::string& target) {
848  for (std::unique_ptr<Property>& prop : properties) {
849  if (prop->name == target) {
850  return prop;
851  }
852  }
853  throw std::runtime_error("PLY parser: element " + name + " does not have property " + target);
854  }
855 
863  template <class T>
864  void addProperty(const std::string& propertyName, const std::vector<T>& data) {
865 
866  if (data.size() != count) {
867  throw std::runtime_error("PLY write: new property " + propertyName + " has size which does not match element");
868  }
869 
870  // If there is already some property with this name, remove it
871  for (size_t i = 0; i < properties.size(); i++) {
872  if (properties[i]->name == propertyName) {
873  properties.erase(properties.begin() + i);
874  i--;
875  }
876  }
877 
878  // Copy to canonical type. Often a no-op, but takes care of standardizing widths across platforms.
879  std::vector<typename CanonicalName<T>::type> canonicalVec(data.begin(), data.end());
880 
881  properties.push_back(
882  std::unique_ptr<Property>(new TypedProperty<typename CanonicalName<T>::type>(propertyName, canonicalVec)));
883  }
884 
892  template <class T>
893  void addListProperty(const std::string& propertyName, const std::vector<std::vector<T>>& data) {
894 
895  if (data.size() != count) {
896  throw std::runtime_error("PLY write: new property " + propertyName + " has size which does not match element");
897  }
898 
899  // If there is already some property with this name, remove it
900  for (size_t i = 0; i < properties.size(); i++) {
901  if (properties[i]->name == propertyName) {
902  properties.erase(properties.begin() + i);
903  i--;
904  }
905  }
906 
907  // Copy to canonical type. Often a no-op, but takes care of standardizing widths across platforms.
908  std::vector<std::vector<typename CanonicalName<T>::type>> canonicalListVec;
909  for (const std::vector<T>& subList : data) {
910  canonicalListVec.emplace_back(subList.begin(), subList.end());
911  }
912 
913  properties.push_back(std::unique_ptr<Property>(
914  new TypedListProperty<typename CanonicalName<T>::type>(propertyName, canonicalListVec)));
915  }
916 
926  template <class T>
927  std::vector<T> getProperty(const std::string& propertyName) {
928 
929  // Find the property
930  std::unique_ptr<Property>& prop = getPropertyPtr(propertyName);
931 
932  // Get a copy of the data with auto-promoting type magic
933  return getDataFromPropertyRecursive<T, T>(prop.get());
934  }
935 
945  template <class T>
946  std::vector<T> getPropertyType(const std::string& propertyName) {
947 
948  // Find the property
949  std::unique_ptr<Property>& prop = getPropertyPtr(propertyName);
950  TypedProperty<T>* castedProp = dynamic_cast<TypedProperty<T>*>(prop);
951  if (castedProp) {
952  return castedProp->data;
953  }
954 
955  // No match, failure
956  throw std::runtime_error("PLY parser: property " + prop->name + " is not of type type " + typeName<T>() +
957  ". Has type " + prop->propertyTypeName());
958  }
959 
969  template <class T>
970  std::vector<std::vector<T>> getListProperty(const std::string& propertyName) {
971 
972  // Find the property
973  std::unique_ptr<Property>& prop = getPropertyPtr(propertyName);
974 
975  // Get a copy of the data with auto-promoting type magic
976  return getDataFromListPropertyRecursive<T, T>(prop.get());
977  }
978 
988  template <class T>
989  std::vector<std::vector<T>> getListPropertyType(const std::string& propertyName) {
990 
991  // Find the property
992  std::unique_ptr<Property>& prop = getPropertyPtr(propertyName);
993  TypedListProperty<T>* castedProp = dynamic_cast<TypedListProperty<T>*>(prop);
994  if (castedProp) {
995  return unflattenList(castedProp->flattenedData, castedProp->flattenedIndexStart);
996  }
997 
998  // No match, failure
999  throw std::runtime_error("PLY parser: list property " + prop->name + " is not of type " + typeName<T>() +
1000  ". Has type " + prop->propertyTypeName());
1001  }
1002 
1003 
1015  template <class T>
1016  std::vector<std::vector<T>> getListPropertyAnySign(const std::string& propertyName) {
1017 
1018  // Find the property
1019  std::unique_ptr<Property>& prop = getPropertyPtr(propertyName);
1020 
1021  // Get a copy of the data with auto-promoting type magic
1022  try {
1023  // First, try the usual approach, looking for a version of the property with the same signed-ness and possibly
1024  // smaller size
1025  return getDataFromListPropertyRecursive<T, T>(prop.get());
1026  } catch (const std::runtime_error& orig_e) {
1027 
1028  // If the usual approach fails, look for a version with opposite signed-ness
1029  try {
1030 
1031  // This type has the oppopsite signeness as the input type
1032  typedef typename CanonicalName<T>::type Tcan;
1033  typedef typename std::conditional<std::is_signed<Tcan>::value, typename std::make_unsigned<Tcan>::type,
1034  typename std::make_signed<Tcan>::type>::type OppsignType;
1035 
1036  return getDataFromListPropertyRecursive<T, OppsignType>(prop.get());
1037 
1038  } catch (const std::runtime_error&) {
1039  throw orig_e;
1040  }
1041 
1042  throw orig_e;
1043  }
1044  }
1045 
1046 
1050  void validate() {
1051 
1052  // Make sure no properties have duplicate names, and no names have whitespace
1053  for (size_t iP = 0; iP < properties.size(); iP++) {
1054  for (char c : properties[iP]->name) {
1055  if (std::isspace(c)) {
1056  throw std::runtime_error("Ply validate: illegal whitespace in name " + properties[iP]->name);
1057  }
1058  }
1059  for (size_t jP = iP + 1; jP < properties.size(); jP++) {
1060  if (properties[iP]->name == properties[jP]->name) {
1061  throw std::runtime_error("Ply validate: multiple properties with name " + properties[iP]->name);
1062  }
1063  }
1064  }
1065 
1066  // Make sure all properties have right length
1067  for (size_t iP = 0; iP < properties.size(); iP++) {
1068  if (properties[iP]->size() != count) {
1069  throw std::runtime_error("Ply validate: property has wrong size. " + properties[iP]->name +
1070  " does not match element size.");
1071  }
1072  }
1073  }
1074 
1080  void writeHeader(std::ostream& outStream) {
1081 
1082  outStream << "element " << name << " " << count << "\n";
1083 
1084  for (std::unique_ptr<Property>& p : properties) {
1085  p->writeHeader(outStream);
1086  }
1087  }
1088 
1095  void writeDataASCII(std::ostream& outStream) {
1096  // Question: what is the proper output for an element with no properties? Here, we write a blank line, so there is
1097  // one line per element no matter what.
1098  for (size_t iE = 0; iE < count; iE++) {
1099  for (size_t iP = 0; iP < properties.size(); iP++) {
1100  properties[iP]->writeDataASCII(outStream, iE);
1101  if (iP < properties.size() - 1) {
1102  outStream << " ";
1103  }
1104  }
1105  outStream << "\n";
1106  }
1107  }
1108 
1109 
1116  void writeDataBinary(std::ostream& outStream) {
1117  for (size_t iE = 0; iE < count; iE++) {
1118  for (size_t iP = 0; iP < properties.size(); iP++) {
1119  properties[iP]->writeDataBinary(outStream, iE);
1120  }
1121  }
1122  }
1123 
1124 
1131  void writeDataBinaryBigEndian(std::ostream& outStream) {
1132  for (size_t iE = 0; iE < count; iE++) {
1133  for (size_t iP = 0; iP < properties.size(); iP++) {
1134  properties[iP]->writeDataBinaryBigEndian(outStream, iE);
1135  }
1136  }
1137  }
1138 
1139 
1150  template <class D, class T>
1151  std::vector<D> getDataFromPropertyRecursive(Property* prop) {
1152 
1153  typedef typename CanonicalName<T>::type Tcan;
1154 
1155  { // Try to return data of type D from a property of type T
1156  TypedProperty<Tcan>* castedProp = dynamic_cast<TypedProperty<Tcan>*>(prop);
1157  if (castedProp) {
1158  // Succeeded, return a buffer of the data (copy while converting type)
1159  std::vector<D> castedVec;
1160  castedVec.reserve(castedProp->data.size());
1161  for (Tcan& v : castedProp->data) {
1162  castedVec.push_back(static_cast<D>(v));
1163  }
1164  return castedVec;
1165  }
1166  }
1167 
1168  TypeChain<Tcan> chainType;
1169  if (chainType.hasChildType) {
1170  return getDataFromPropertyRecursive<D, typename TypeChain<Tcan>::type>(prop);
1171  } else {
1172  // No smaller type to try, failure
1173  throw std::runtime_error("PLY parser: property " + prop->name + " cannot be coerced to requested type " +
1174  typeName<D>() + ". Has type " + prop->propertyTypeName());
1175  }
1176  }
1177 
1178 
1189  template <class D, class T>
1190  std::vector<std::vector<D>> getDataFromListPropertyRecursive(Property* prop) {
1191  typedef typename CanonicalName<T>::type Tcan;
1192 
1193  TypedListProperty<Tcan>* castedProp = dynamic_cast<TypedListProperty<Tcan>*>(prop);
1194  if (castedProp) {
1195  // Succeeded, return a buffer of the data (copy while converting type)
1196 
1197  // Convert to flat buffer of new type
1198  std::vector<D>* castedFlatVec = nullptr;
1199  std::vector<D> castedFlatVecCopy; // we _might_ make a copy here, depending on is_same below
1200 
1201  if (std::is_same<std::vector<D>, std::vector<Tcan>>::value) {
1202  // just use the array we already have
1203  castedFlatVec = addressIfSame<std::vector<D>>(castedProp->flattenedData, 0 /* dummy arg to disambiguate */);
1204  } else {
1205  // make a copy
1206  castedFlatVecCopy.reserve(castedProp->flattenedData.size());
1207  for (Tcan& v : castedProp->flattenedData) {
1208  castedFlatVecCopy.push_back(static_cast<D>(v));
1209  }
1210  castedFlatVec = &castedFlatVecCopy;
1211  }
1212 
1213  // Unflatten and return
1214  return unflattenList(*castedFlatVec, castedProp->flattenedIndexStart);
1215  }
1216 
1217  TypeChain<Tcan> chainType;
1218  if (chainType.hasChildType) {
1219  return getDataFromListPropertyRecursive<D, typename TypeChain<Tcan>::type>(prop);
1220  } else {
1221  // No smaller type to try, failure
1222  throw std::runtime_error("PLY parser: list property " + prop->name +
1223  " cannot be coerced to requested type list " + typeName<D>() + ". Has type list " +
1224  prop->propertyTypeName());
1225  }
1226  }
1227 };
1228 
1229 
1230 // Some string helpers
1231 namespace {
1232 
1233 inline std::string trimSpaces(const std::string& input) {
1234  size_t start = 0;
1235  while (start < input.size() && input[start] == ' ') start++;
1236  size_t end = input.size();
1237  while (end > start && (input[end - 1] == ' ' || input[end - 1] == '\n' || input[end - 1] == '\r')) end--;
1238  return input.substr(start, end - start);
1239 }
1240 
1241 inline std::vector<std::string> tokenSplit(const std::string& input) {
1242  std::vector<std::string> result;
1243  size_t curr = 0;
1244  size_t found = 0;
1245  while ((found = input.find_first_of(' ', curr)) != std::string::npos) {
1246  std::string token = input.substr(curr, found - curr);
1247  token = trimSpaces(token);
1248  if (token.size() > 0) {
1249  result.push_back(token);
1250  }
1251  curr = found + 1;
1252  }
1253  std::string token = input.substr(curr);
1254  token = trimSpaces(token);
1255  if (token.size() > 0) {
1256  result.push_back(token);
1257  }
1258 
1259  return result;
1260 }
1261 
1262 inline bool startsWith(const std::string& input, const std::string& query) {
1263  return input.compare(0, query.length(), query) == 0;
1264 }
1265 }; // namespace
1266 
1267 
1271 class PLYData {
1272 
1273 public:
1277  PLYData(){};
1278 
1285  PLYData(const std::string& filename, bool verbose = false) {
1286 
1287  using std::cout;
1288  using std::endl;
1289  using std::string;
1290  using std::vector;
1291 
1292  if (verbose) cout << "PLY parser: Reading ply file: " << filename << endl;
1293 
1294  // Open a file in binary always, in case it turns out to have binary data.
1295  std::ifstream inStream(filename, std::ios::binary);
1296  if (inStream.fail()) {
1297  throw std::runtime_error("PLY parser: Could not open file " + filename);
1298  }
1299 
1300  parsePLY(inStream, verbose);
1301 
1302  if (verbose) {
1303  cout << " - Finished parsing file." << endl;
1304  }
1305  }
1306 
1313  PLYData(std::istream& inStream, bool verbose = false) {
1314 
1315  using std::cout;
1316  using std::endl;
1317 
1318  if (verbose) cout << "PLY parser: Reading ply file from stream" << endl;
1319 
1320  parsePLY(inStream, verbose);
1321 
1322  if (verbose) {
1323  cout << " - Finished parsing stream." << endl;
1324  }
1325  }
1326 
1330  void validate() {
1331 
1332  for (size_t iE = 0; iE < elements.size(); iE++) {
1333  for (char c : elements[iE].name) {
1334  if (std::isspace(c)) {
1335  throw std::runtime_error("Ply validate: illegal whitespace in element name " + elements[iE].name);
1336  }
1337  }
1338  for (size_t jE = iE + 1; jE < elements.size(); jE++) {
1339  if (elements[iE].name == elements[jE].name) {
1340  throw std::runtime_error("Ply validate: duplcate element name " + elements[iE].name);
1341  }
1342  }
1343  }
1344 
1345  // Do a quick validation sanity check
1346  for (Element& e : elements) {
1347  e.validate();
1348  }
1349  }
1350 
1357  void write(const std::string& filename, DataFormat format = DataFormat::ASCII) {
1358  outputDataFormat = format;
1359 
1360  validate();
1361 
1362  // Open stream for writing
1363  std::ofstream outStream(filename, std::ios::out | std::ios::binary);
1364  if (!outStream.good()) {
1365  throw std::runtime_error("Ply writer: Could not open output file " + filename + " for writing");
1366  }
1367 
1368  writePLY(outStream);
1369  }
1370 
1377  void write(std::ostream& outStream, DataFormat format = DataFormat::ASCII) {
1378  outputDataFormat = format;
1379 
1380  validate();
1381 
1382  writePLY(outStream);
1383  }
1384 
1392  Element& getElement(const std::string& target) {
1393  for (Element& e : elements) {
1394  if (e.name == target) return e;
1395  }
1396  throw std::runtime_error("PLY parser: no element with name: " + target);
1397  }
1398 
1399 
1407  bool hasElement(const std::string& target) {
1408  for (Element& e : elements) {
1409  if (e.name == target) return true;
1410  }
1411  return false;
1412  }
1413 
1414 
1420  std::vector<std::string> getElementNames() {
1421  std::vector<std::string> names;
1422  for (Element& e : elements) {
1423  names.push_back(e.name);
1424  }
1425  return names;
1426  }
1427 
1428 
1435  void addElement(const std::string& name, size_t count) { elements.emplace_back(name, count); }
1436 
1437  // === Common-case helpers
1438 
1439 
1447  std::vector<std::array<double, 3>> getVertexPositions(const std::string& vertexElementName = "vertex") {
1448 
1449  std::vector<double> xPos = getElement(vertexElementName).getProperty<double>("x");
1450  std::vector<double> yPos = getElement(vertexElementName).getProperty<double>("y");
1451  std::vector<double> zPos = getElement(vertexElementName).getProperty<double>("z");
1452 
1453  std::vector<std::array<double, 3>> result(xPos.size());
1454  for (size_t i = 0; i < result.size(); i++) {
1455  result[i][0] = xPos[i];
1456  result[i][1] = yPos[i];
1457  result[i][2] = zPos[i];
1458  }
1459 
1460  return result;
1461  }
1462 
1470  std::vector<std::array<unsigned char, 3>> getVertexColors(const std::string& vertexElementName = "vertex") {
1471 
1472  std::vector<unsigned char> r = getElement(vertexElementName).getProperty<unsigned char>("red");
1473  std::vector<unsigned char> g = getElement(vertexElementName).getProperty<unsigned char>("green");
1474  std::vector<unsigned char> b = getElement(vertexElementName).getProperty<unsigned char>("blue");
1475 
1476  std::vector<std::array<unsigned char, 3>> result(r.size());
1477  for (size_t i = 0; i < result.size(); i++) {
1478  result[i][0] = r[i];
1479  result[i][1] = g[i];
1480  result[i][2] = b[i];
1481  }
1482 
1483  return result;
1484  }
1485 
1493  template <typename T = size_t>
1494  std::vector<std::vector<T>> getFaceIndices() {
1495 
1496  for (const std::string& f : std::vector<std::string>{"face"}) {
1497  for (const std::string& p : std::vector<std::string>{"vertex_indices", "vertex_index"}) {
1498  try {
1499  return getElement(f).getListPropertyAnySign<T>(p);
1500  } catch (const std::runtime_error&) {
1501  // that's fine
1502  }
1503  }
1504  }
1505  throw std::runtime_error("PLY parser: could not find face vertex indices attribute under any common name.");
1506  }
1507 
1508 
1514  void addVertexPositions(std::vector<std::array<double, 3>>& vertexPositions) {
1515 
1516  std::string vertexName = "vertex";
1517  size_t N = vertexPositions.size();
1518 
1519  // Create the element
1520  if (!hasElement(vertexName)) {
1521  addElement(vertexName, N);
1522  }
1523 
1524  // De-interleave
1525  std::vector<double> xPos(N);
1526  std::vector<double> yPos(N);
1527  std::vector<double> zPos(N);
1528  for (size_t i = 0; i < vertexPositions.size(); i++) {
1529  xPos[i] = vertexPositions[i][0];
1530  yPos[i] = vertexPositions[i][1];
1531  zPos[i] = vertexPositions[i][2];
1532  }
1533 
1534  // Store
1535  getElement(vertexName).addProperty<double>("x", xPos);
1536  getElement(vertexName).addProperty<double>("y", yPos);
1537  getElement(vertexName).addProperty<double>("z", zPos);
1538  }
1539 
1545  void addVertexColors(std::vector<std::array<unsigned char, 3>>& colors) {
1546 
1547  std::string vertexName = "vertex";
1548  size_t N = colors.size();
1549 
1550  // Create the element
1551  if (!hasElement(vertexName)) {
1552  addElement(vertexName, N);
1553  }
1554 
1555  // De-interleave
1556  std::vector<unsigned char> r(N);
1557  std::vector<unsigned char> g(N);
1558  std::vector<unsigned char> b(N);
1559  for (size_t i = 0; i < colors.size(); i++) {
1560  r[i] = colors[i][0];
1561  g[i] = colors[i][1];
1562  b[i] = colors[i][2];
1563  }
1564 
1565  // Store
1566  getElement(vertexName).addProperty<unsigned char>("red", r);
1567  getElement(vertexName).addProperty<unsigned char>("green", g);
1568  getElement(vertexName).addProperty<unsigned char>("blue", b);
1569  }
1570 
1576  void addVertexColors(std::vector<std::array<double, 3>>& colors) {
1577 
1578  std::string vertexName = "vertex";
1579  size_t N = colors.size();
1580 
1581  // Create the element
1582  if (!hasElement(vertexName)) {
1583  addElement(vertexName, N);
1584  }
1585 
1586  auto toChar = [](double v) {
1587  if (v < 0.0) v = 0.0;
1588  if (v > 1.0) v = 1.0;
1589  return static_cast<unsigned char>(v * 255.);
1590  };
1591 
1592  // De-interleave
1593  std::vector<unsigned char> r(N);
1594  std::vector<unsigned char> g(N);
1595  std::vector<unsigned char> b(N);
1596  for (size_t i = 0; i < colors.size(); i++) {
1597  r[i] = toChar(colors[i][0]);
1598  g[i] = toChar(colors[i][1]);
1599  b[i] = toChar(colors[i][2]);
1600  }
1601 
1602  // Store
1603  getElement(vertexName).addProperty<unsigned char>("red", r);
1604  getElement(vertexName).addProperty<unsigned char>("green", g);
1605  getElement(vertexName).addProperty<unsigned char>("blue", b);
1606  }
1607 
1608 
1615  template <typename T>
1616  void addFaceIndices(std::vector<std::vector<T>>& indices) {
1617 
1618  std::string faceName = "face";
1619  size_t N = indices.size();
1620 
1621  // Create the element
1622  if (!hasElement(faceName)) {
1623  addElement(faceName, N);
1624  }
1625 
1626  // Cast to 32 bit
1627  typedef typename std::conditional<std::is_signed<T>::value, int32_t, uint32_t>::type IndType;
1628  std::vector<std::vector<IndType>> intInds;
1629  for (std::vector<T>& l : indices) {
1630  std::vector<IndType> thisInds;
1631  for (T& val : l) {
1632  IndType valConverted = static_cast<IndType>(val);
1633  if (valConverted != val) {
1634  throw std::runtime_error("Index value " + std::to_string(val) +
1635  " could not be converted to a .ply integer without loss of data. Note that .ply "
1636  "only supports 32-bit ints.");
1637  }
1638  thisInds.push_back(valConverted);
1639  }
1640  intInds.push_back(thisInds);
1641  }
1642 
1643  // Store
1644  getElement(faceName).addListProperty<IndType>("vertex_indices", intInds);
1645  }
1646 
1647 
1651  std::vector<std::string> comments;
1652 
1653 
1657  std::vector<std::string> objInfoComments;
1658 
1659 private:
1660  std::vector<Element> elements;
1661  const int majorVersion = 1; // I'll buy you a drink if these ever get bumped
1662  const int minorVersion = 0;
1663 
1664  DataFormat inputDataFormat = DataFormat::ASCII; // set when reading from a file
1665  DataFormat outputDataFormat = DataFormat::ASCII; // option for writing files
1666 
1667 
1668  // === Reading ===
1669 
1676  void parsePLY(std::istream& inStream, bool verbose) {
1677 
1678  // == Process the header
1679  parseHeader(inStream, verbose);
1680 
1681 
1682  // === Parse data from a binary file
1683  if (inputDataFormat == DataFormat::Binary) {
1684  parseBinary(inStream, verbose);
1685  }
1686  // === Parse data from an binary file
1687  else if (inputDataFormat == DataFormat::BinaryBigEndian) {
1688  parseBinaryBigEndian(inStream, verbose);
1689  }
1690  // === Parse data from an ASCII file
1691  else if (inputDataFormat == DataFormat::ASCII) {
1692  parseASCII(inStream, verbose);
1693  }
1694  }
1695 
1702  void parseHeader(std::istream& inStream, bool verbose) {
1703 
1704  using std::cout;
1705  using std::endl;
1706  using std::string;
1707  using std::vector;
1708 
1709  // First two lines are predetermined
1710  { // First line is magic constant
1711  string plyLine;
1712  std::getline(inStream, plyLine);
1713  if (trimSpaces(plyLine) != "ply") {
1714  throw std::runtime_error("PLY parser: File does not appear to be ply file. First line should be 'ply'");
1715  }
1716  }
1717 
1718  { // second line is version
1719  string styleLine;
1720  std::getline(inStream, styleLine);
1721  vector<string> tokens = tokenSplit(styleLine);
1722  if (tokens.size() != 3) throw std::runtime_error("PLY parser: bad format line");
1723  std::string formatStr = tokens[0];
1724  std::string typeStr = tokens[1];
1725  std::string versionStr = tokens[2];
1726 
1727  // "format"
1728  if (formatStr != "format") throw std::runtime_error("PLY parser: bad format line");
1729 
1730  // ascii/binary
1731  if (typeStr == "ascii") {
1732  inputDataFormat = DataFormat::ASCII;
1733  if (verbose) cout << " - Type: ascii" << endl;
1734  } else if (typeStr == "binary_little_endian") {
1735  inputDataFormat = DataFormat::Binary;
1736  if (verbose) cout << " - Type: binary" << endl;
1737  } else if (typeStr == "binary_big_endian") {
1738  inputDataFormat = DataFormat::BinaryBigEndian;
1739  if (verbose) cout << " - Type: binary big endian" << endl;
1740  } else {
1741  throw std::runtime_error("PLY parser: bad format line");
1742  }
1743 
1744  // version
1745  if (versionStr != "1.0") {
1746  throw std::runtime_error("PLY parser: encountered file with version != 1.0. Don't know how to parse that");
1747  }
1748  if (verbose) cout << " - Version: " << versionStr << endl;
1749  }
1750 
1751  // Consume header line by line
1752  while (inStream.good()) {
1753  string line;
1754  std::getline(inStream, line);
1755 
1756  // Parse a comment
1757  if (startsWith(line, "comment")) {
1758  string comment = line.substr(8);
1759  if (verbose) cout << " - Comment: " << comment << endl;
1760  comments.push_back(comment);
1761  continue;
1762  }
1763 
1764  // Parse an obj_info comment
1765  if (startsWith(line, "obj_info")) {
1766  string infoComment = line.substr(9);
1767  if (verbose) cout << " - obj_info: " << infoComment << endl;
1768  objInfoComments.push_back(infoComment);
1769  continue;
1770  }
1771 
1772  // Parse an element
1773  else if (startsWith(line, "element")) {
1774  vector<string> tokens = tokenSplit(line);
1775  if (tokens.size() != 3) throw std::runtime_error("PLY parser: Invalid element line");
1776  string name = tokens[1];
1777  size_t count;
1778  std::istringstream iss(tokens[2]);
1779  iss >> count;
1780  elements.emplace_back(name, count);
1781  if (verbose) cout << " - Found element: " << name << " (count = " << count << ")" << endl;
1782  continue;
1783  }
1784 
1785  // Parse a property list
1786  else if (startsWith(line, "property list")) {
1787  vector<string> tokens = tokenSplit(line);
1788  if (tokens.size() != 5) throw std::runtime_error("PLY parser: Invalid property list line");
1789  if (elements.size() == 0) throw std::runtime_error("PLY parser: Found property list without previous element");
1790  string countType = tokens[2];
1791  string type = tokens[3];
1792  string name = tokens[4];
1793  elements.back().properties.push_back(createPropertyWithType(name, type, true, countType));
1794  if (verbose)
1795  cout << " - Found list property: " << name << " (count type = " << countType << ", data type = " << type
1796  << ")" << endl;
1797  continue;
1798  }
1799 
1800  // Parse a property
1801  else if (startsWith(line, "property")) {
1802  vector<string> tokens = tokenSplit(line);
1803  if (tokens.size() != 3) throw std::runtime_error("PLY parser: Invalid property line");
1804  if (elements.size() == 0) throw std::runtime_error("PLY parser: Found property without previous element");
1805  string type = tokens[1];
1806  string name = tokens[2];
1807  elements.back().properties.push_back(createPropertyWithType(name, type, false, ""));
1808  if (verbose) cout << " - Found property: " << name << " (type = " << type << ")" << endl;
1809  continue;
1810  }
1811 
1812  // Parse end of header
1813  else if (startsWith(line, "end_header")) {
1814  break;
1815  }
1816 
1817  // Error!
1818  else {
1819  throw std::runtime_error("Unrecognized header line: " + line);
1820  }
1821  }
1822  }
1823 
1830  void parseASCII(std::istream& inStream, bool verbose) {
1831 
1832  using std::string;
1833  using std::vector;
1834 
1835  // Read all elements
1836  for (Element& elem : elements) {
1837 
1838  if (verbose) {
1839  std::cout << " - Processing element: " << elem.name << std::endl;
1840  }
1841 
1842  for (size_t iP = 0; iP < elem.properties.size(); iP++) {
1843  elem.properties[iP]->reserve(elem.count);
1844  }
1845  for (size_t iEntry = 0; iEntry < elem.count; iEntry++) {
1846 
1847  string line;
1848  std::getline(inStream, line);
1849 
1850  // Some .ply files seem to include empty lines before the start of property data (though this is not specified
1851  // in the format description). We attempt to recover and parse such files by skipping any empty lines.
1852  if (!elem.properties.empty()) { // if the element has no properties, the line _should_ be blank, presumably
1853  while (line.empty()) { // skip lines until we hit something nonempty
1854  std::getline(inStream, line);
1855  }
1856  }
1857 
1858  vector<string> tokens = tokenSplit(line);
1859  size_t iTok = 0;
1860  for (size_t iP = 0; iP < elem.properties.size(); iP++) {
1861  elem.properties[iP]->parseNext(tokens, iTok);
1862  }
1863  }
1864  }
1865  }
1866 
1873  void parseBinary(std::istream& inStream, bool verbose) {
1874 
1875  if (!isLittleEndian()) {
1876  throw std::runtime_error("binary reading assumes little endian system");
1877  }
1878 
1879  using std::string;
1880  using std::vector;
1881 
1882  // Read all elements
1883  for (Element& elem : elements) {
1884 
1885  if (verbose) {
1886  std::cout << " - Processing element: " << elem.name << std::endl;
1887  }
1888 
1889  for (size_t iP = 0; iP < elem.properties.size(); iP++) {
1890  elem.properties[iP]->reserve(elem.count);
1891  }
1892  for (size_t iEntry = 0; iEntry < elem.count; iEntry++) {
1893  for (size_t iP = 0; iP < elem.properties.size(); iP++) {
1894  elem.properties[iP]->readNext(inStream);
1895  }
1896  }
1897  }
1898  }
1899 
1906  void parseBinaryBigEndian(std::istream& inStream, bool verbose) {
1907 
1908  if (!isLittleEndian()) {
1909  throw std::runtime_error("binary reading assumes little endian system");
1910  }
1911 
1912  using std::string;
1913  using std::vector;
1914 
1915  // Read all elements
1916  for (Element& elem : elements) {
1917 
1918  if (verbose) {
1919  std::cout << " - Processing element: " << elem.name << std::endl;
1920  }
1921 
1922  for (size_t iP = 0; iP < elem.properties.size(); iP++) {
1923  elem.properties[iP]->reserve(elem.count);
1924  }
1925  for (size_t iEntry = 0; iEntry < elem.count; iEntry++) {
1926  for (size_t iP = 0; iP < elem.properties.size(); iP++) {
1927  elem.properties[iP]->readNextBigEndian(inStream);
1928  }
1929  }
1930  }
1931  }
1932 
1933  // === Writing ===
1934 
1935 
1941  void writePLY(std::ostream& outStream) {
1942 
1943  writeHeader(outStream);
1944 
1945  // Write all elements
1946  for (Element& e : elements) {
1947  if (outputDataFormat == DataFormat::Binary) {
1948  if (!isLittleEndian()) {
1949  throw std::runtime_error("binary writing assumes little endian system");
1950  }
1951  e.writeDataBinary(outStream);
1952  } else if (outputDataFormat == DataFormat::BinaryBigEndian) {
1953  if (!isLittleEndian()) {
1954  throw std::runtime_error("binary writing assumes little endian system");
1955  }
1956  e.writeDataBinaryBigEndian(outStream);
1957  } else if (outputDataFormat == DataFormat::ASCII) {
1958  e.writeDataASCII(outStream);
1959  }
1960  }
1961  }
1962 
1963 
1969  void writeHeader(std::ostream& outStream) {
1970 
1971  // Magic line
1972  outStream << "ply\n";
1973 
1974  // Type line
1975  outStream << "format ";
1976  if (outputDataFormat == DataFormat::Binary) {
1977  outStream << "binary_little_endian ";
1978  } else if (outputDataFormat == DataFormat::BinaryBigEndian) {
1979  outStream << "binary_big_endian ";
1980  } else if (outputDataFormat == DataFormat::ASCII) {
1981  outStream << "ascii ";
1982  }
1983 
1984  // Version number
1985  outStream << majorVersion << "." << minorVersion << "\n";
1986 
1987  // Write comments
1988  bool hasHapplyComment = false;
1989  std::string happlyComment = "Written with hapPLY (https://github.com/nmwsharp/happly)";
1990  for (const std::string& comment : comments) {
1991  if (comment == happlyComment) hasHapplyComment = true;
1992  outStream << "comment " << comment << "\n";
1993  }
1994  if (!hasHapplyComment) {
1995  outStream << "comment " << happlyComment << "\n";
1996  }
1997 
1998  // Write obj_info comments
1999  for (const std::string& comment : objInfoComments) {
2000  outStream << "obj_info " << comment << "\n";
2001  }
2002 
2003  // Write elements (and their properties)
2004  for (Element& e : elements) {
2005  e.writeHeader(outStream);
2006  }
2007 
2008  // End header
2009  outStream << "end_header\n";
2010  }
2011 };
2012 
2013 } // namespace happly
happly::PLYData::getVertexColors
std::vector< std::array< unsigned char, 3 > > getVertexColors(const std::string &vertexElementName="vertex")
Common-case helper get mesh vertex colors.
Definition: happly.h:1470
happly::PLYData::PLYData
PLYData(std::istream &inStream, bool verbose=false)
Initialize a PLYData by reading from a stringstream. Throws if any failures occur.
Definition: happly.h:1313
happly::Property::propertyTypeName
virtual std::string propertyTypeName()=0
A string naming the type of the property.
happly::Element::Element
Element(const std::string &name_, size_t count_)
Create a new element type.
Definition: happly.h:783
happly::Element::getPropertyPtr
std::unique_ptr< Property > & getPropertyPtr(const std::string &target)
Low-level method to get a pointer to a property. Users probably don't need to call this.
Definition: happly.h:847
happly::Element::writeDataBinary
void writeDataBinary(std::ostream &outStream)
(binary writing) Writes out all of the data for every element of this element type to the stream,...
Definition: happly.h:1116
happly::PLYData::getElementNames
std::vector< std::string > getElementNames()
A list of the names of all elements.
Definition: happly.h:1420
happly::PLYData::getElement
Element & getElement(const std::string &target)
Get an element type by name ("vertices")
Definition: happly.h:1392
happly::PLYData::addVertexColors
void addVertexColors(std::vector< std::array< unsigned char, 3 >> &colors)
Common-case helper set mesh vertex colors. Creates a vertex element, if necessary.
Definition: happly.h:1545
happly::Property::size
virtual size_t size()=0
Number of element entries for this property.
happly::PLYData::addElement
void addElement(const std::string &name, size_t count)
Add a new element type to the object.
Definition: happly.h:1435
happly::TypedListProperty::flattenedData
std::vector< T > flattenedData
The (flattened) data for the property, as formed by concatenating all of the individual element lists...
Definition: happly.h:640
happly::Element::getPropertyType
std::vector< T > getPropertyType(const std::string &propertyName)
Get a vector of a data from a property for this element. Unlike getProperty(), only returns if the pl...
Definition: happly.h:946
happly::TypedListProperty::writeHeader
virtual void writeHeader(std::ostream &outStream) override
(reading) Write a header entry for this property. Note that we already use "uchar" for the list count...
Definition: happly.h:545
happly::TypedListProperty::TypedListProperty
TypedListProperty(const std::string &name_, const std::vector< std::vector< T >> &data_)
Create a new property and initialize with data.
Definition: happly.h:433
happly::PLYData::comments
std::vector< std::string > comments
Comments for the file. When writing, each entry will be written as a sequential comment line.
Definition: happly.h:1651
happly::TypedProperty::readNext
virtual void readNext(std::istream &stream) override
(binary reading) Copy the next value of this property from a stream of bits.
Definition: happly.h:328
happly::Element::hasProperty
bool hasProperty(const std::string &target)
Check if a property exists.
Definition: happly.h:796
happly::Element::getProperty
std::vector< T > getProperty(const std::string &propertyName)
Get a vector of a data from a property for this element. Automatically promotes to larger types....
Definition: happly.h:927
happly::Property::readNext
virtual void readNext(std::istream &stream)=0
(binary reading) Copy the next value of this property from a stream of bits.
happly::TypedProperty::reserve
virtual void reserve(size_t capacity) override
Reserve memory.
Definition: happly.h:306
happly::Element::getListProperty
std::vector< std::vector< T > > getListProperty(const std::string &propertyName)
Get a vector of lists of data from a property for this element. Automatically promotes to larger type...
Definition: happly.h:970
happly::TypedProperty::size
virtual size_t size() override
Number of element entries for this property.
Definition: happly.h:390
happly::TypedProperty::readNextBigEndian
virtual void readNextBigEndian(std::istream &stream) override
(binary reading) Copy the next value of this property from a stream of bits.
Definition: happly.h:338
happly::Property::Property
Property(const std::string &name_)
Create a new Property with the given name.
Definition: happly.h:131
happly::Property::parseNext
virtual void parseNext(const std::vector< std::string > &tokens, size_t &currEntry)=0
(ASCII reading) Parse out the next value of this property from a list of tokens.
happly::TypedListProperty::propertyTypeName
virtual std::string propertyTypeName() override
A string naming the type of the property.
Definition: happly.h:634
happly::PLYData::validate
void validate()
Perform sanity checks on the file, throwing if any fail.
Definition: happly.h:1330
happly::PLYData::PLYData
PLYData(const std::string &filename, bool verbose=false)
Initialize a PLYData by reading from a file. Throws if any failures occur.
Definition: happly.h:1285
happly::TypedProperty::data
std::vector< T > data
The actual data contained in the property.
Definition: happly.h:403
happly::Property::writeDataASCII
virtual void writeDataASCII(std::ostream &outStream, size_t iElement)=0
(ASCII writing) write this property for some element to a stream in plaintext
happly::TypedListProperty::reserve
virtual void reserve(size_t capacity) override
Reserve memory.
Definition: happly.h:455
happly::TypedProperty::TypedProperty
TypedProperty(const std::string &name_)
Create a new Property with the given name.
Definition: happly.h:280
happly::TypedProperty::writeDataBinary
virtual void writeDataBinary(std::ostream &outStream, size_t iElement) override
(binary writing) copy the bits of this property for some element to a stream
Definition: happly.h:370
happly::PLYData::hasElement
bool hasElement(const std::string &target)
Check if an element type exists.
Definition: happly.h:1407
happly::TypedProperty::writeDataBinaryBigEndian
virtual void writeDataBinaryBigEndian(std::ostream &outStream, size_t iElement) override
(binary writing) copy the bits of this property for some element to a stream
Definition: happly.h:380
happly::TypedProperty
A property which takes a single value (not a list).
Definition: happly.h:272
happly::Element::writeDataASCII
void writeDataASCII(std::ostream &outStream)
(ASCII writing) Writes out all of the data for every element of this element type to the stream,...
Definition: happly.h:1095
happly::TypedListProperty::size
virtual size_t size() override
Number of element entries for this property.
Definition: happly.h:626
happly::Element::getPropertyNames
std::vector< std::string > getPropertyNames()
A list of the names of all properties.
Definition: happly.h:832
happly::TypedListProperty::writeDataASCII
virtual void writeDataASCII(std::ostream &outStream, size_t iElement) override
(ASCII writing) write this property for some element to a stream in plaintext
Definition: happly.h:556
happly::Element::addProperty
void addProperty(const std::string &propertyName, const std::vector< T > &data)
Add a new (plain, not list) property for this element type.
Definition: happly.h:864
happly::TypedProperty::writeDataASCII
virtual void writeDataASCII(std::ostream &outStream, size_t iElement) override
(ASCII writing) write this property for some element to a stream in plaintext
Definition: happly.h:359
happly::Element::addListProperty
void addListProperty(const std::string &propertyName, const std::vector< std::vector< T >> &data)
Add a new list property for this element type.
Definition: happly.h:893
happly::Element::hasPropertyType
bool hasPropertyType(const std::string &target)
Check if a property exists with the requested type.
Definition: happly.h:814
happly::TypedProperty::propertyTypeName
virtual std::string propertyTypeName() override
A string naming the type of the property.
Definition: happly.h:398
happly::PLYData::getFaceIndices
std::vector< std::vector< T > > getFaceIndices()
Common-case helper to get face indices for a mesh. If not template type is given, size_t is used....
Definition: happly.h:1494
happly::TypedListProperty
A property which is a list of value (eg, 3 doubles). Note that lists are always variable length per-e...
Definition: happly.h:411
happly::TypedListProperty::readNextBigEndian
virtual void readNextBigEndian(std::istream &stream) override
(binary reading) Copy the next value of this property from a stream of bits.
Definition: happly.h:512
happly::Element
An element (more properly an element type) in the .ply object. Tracks the name of the elemnt type (eg...
Definition: happly.h:774
happly::TypedListProperty::readNext
virtual void readNext(std::istream &stream) override
(binary reading) Copy the next value of this property from a stream of bits.
Definition: happly.h:491
happly::TypedListProperty::writeDataBinaryBigEndian
virtual void writeDataBinaryBigEndian(std::ostream &outStream, size_t iElement) override
(binary writing) copy the bits of this property for some element to a stream
Definition: happly.h:602
happly::Property::reserve
virtual void reserve(size_t capacity)=0
Reserve memory.
happly::PLYData::write
void write(std::ostream &outStream, DataFormat format=DataFormat::ASCII)
Write this data to an output stream.
Definition: happly.h:1377
happly::TypedListProperty::parseNext
virtual void parseNext(const std::vector< std::string > &tokens, size_t &currEntry) override
(ASCII reading) Parse out the next value of this property from a list of tokens.
Definition: happly.h:466
happly::PLYData::addVertexPositions
void addVertexPositions(std::vector< std::array< double, 3 >> &vertexPositions)
Common-case helper set mesh vertex positons. Creates vertex element, if necessary.
Definition: happly.h:1514
happly::PLYData::addFaceIndices
void addFaceIndices(std::vector< std::vector< T >> &indices)
Common-case helper to set face indices. Creates a face element if needed. The input type will be cast...
Definition: happly.h:1616
happly::Element::writeHeader
void writeHeader(std::ostream &outStream)
Writes out this element's information to the file header.
Definition: happly.h:1080
happly::TypedProperty::writeHeader
virtual void writeHeader(std::ostream &outStream) override
(reading) Write a header entry for this property.
Definition: happly.h:349
happly::PLYData::objInfoComments
std::vector< std::string > objInfoComments
obj_info comments for the file. When writing, each entry will be written as a sequential comment line...
Definition: happly.h:1657
happly::PLYData::write
void write(const std::string &filename, DataFormat format=DataFormat::ASCII)
Write this data to a .ply file.
Definition: happly.h:1357
happly::Property
A generic property, which is associated with some element. Can be plain Property or a ListProperty,...
Definition: happly.h:123
happly::Property::writeHeader
virtual void writeHeader(std::ostream &outStream)=0
(reading) Write a header entry for this property.
happly::TypedProperty::TypedProperty
TypedProperty(const std::string &name_, const std::vector< T > &data_)
Create a new property and initialize with data.
Definition: happly.h:293
happly::PLYData
Primary class; represents a set of data in the .ply format.
Definition: happly.h:1271
happly::TypedListProperty::writeDataBinary
virtual void writeDataBinary(std::ostream &outStream, size_t iElement) override
(binary writing) copy the bits of this property for some element to a stream
Definition: happly.h:580
happly::Property::readNextBigEndian
virtual void readNextBigEndian(std::istream &stream)=0
(binary reading) Copy the next value of this property from a stream of bits.
happly::PLYData::PLYData
PLYData()
Create an empty PLYData object.
Definition: happly.h:1277
happly::Property::writeDataBinary
virtual void writeDataBinary(std::ostream &outStream, size_t iElement)=0
(binary writing) copy the bits of this property for some element to a stream
happly::TypedListProperty::listCountBytes
int listCountBytes
The number of bytes used to store the count for lists of data.
Definition: happly.h:651
happly::TypedListProperty::TypedListProperty
TypedListProperty(const std::string &name_, int listCountBytes_)
Create a new Property with the given name.
Definition: happly.h:419
happly::Property::writeDataBinaryBigEndian
virtual void writeDataBinaryBigEndian(std::ostream &outStream, size_t iElement)=0
(binary writing) copy the bits of this property for some element to a stream
happly::TypedProperty::parseNext
virtual void parseNext(const std::vector< std::string > &tokens, size_t &currEntry) override
(ASCII reading) Parse out the next value of this property from a list of tokens.
Definition: happly.h:314
happly::PLYData::addVertexColors
void addVertexColors(std::vector< std::array< double, 3 >> &colors)
Common-case helper set mesh vertex colors. Creates a vertex element, if necessary.
Definition: happly.h:1576
happly::Element::writeDataBinaryBigEndian
void writeDataBinaryBigEndian(std::ostream &outStream)
(binary writing) Writes out all of the data for every element of this element type to the stream,...
Definition: happly.h:1131
happly::Element::getListPropertyAnySign
std::vector< std::vector< T > > getListPropertyAnySign(const std::string &propertyName)
Get a vector of lists of data from a property for this element. Automatically promotes to larger type...
Definition: happly.h:1016
happly::Element::getDataFromPropertyRecursive
std::vector< D > getDataFromPropertyRecursive(Property *prop)
Helper function which does the hard work to implement type promotion for data getters....
Definition: happly.h:1151
happly::Element::getListPropertyType
std::vector< std::vector< T > > getListPropertyType(const std::string &propertyName)
Get a vector of a data from a property for this element. Unlike getProperty(), only returns if the pl...
Definition: happly.h:989
happly::Element::getDataFromListPropertyRecursive
std::vector< std::vector< D > > getDataFromListPropertyRecursive(Property *prop)
Helper function which does the hard work to implement type promotion for list data getters....
Definition: happly.h:1190
happly::TypedListProperty::flattenedIndexStart
std::vector< size_t > flattenedIndexStart
Indices in to flattenedData. The i'th element gives the index in to flattenedData where the element's...
Definition: happly.h:646
happly::Element::validate
void validate()
Performs sanity checks on the element, throwing if any fail.
Definition: happly.h:1050
happly::PLYData::getVertexPositions
std::vector< std::array< double, 3 > > getVertexPositions(const std::string &vertexElementName="vertex")
Common-case helper get mesh vertex positions.
Definition: happly.h:1447