diff --git a/README.md b/README.md index 8b282ca..b1cf5c2 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,9 @@ Generally speaking, hapPLY uses C++ exceptions to communicate errors-- most of t - `void Element::addProperty(std::string propertyName, std::vector& data)` Add a new property to an element type. `data` must be the same length as the number of elements of that type. -- `void addListProperty(std::string propertyName, std::vector>& data)` Add a new list property to an element type. `data` must be the same length as the number of elements of that type. +- `void addListProperty(std::string propertyName, std::vector>& data)` Add a new list property to an element type. `data` must be the same length as the number of elements of that type. The list size type will be `uint8`, so no list can contain more than 255 entries. + +- `void addSignedListProperty(std::string propertyName, std::vector>& data)` Add a new list property to an element type. `data` must be the same length as the number of elements of that type. The size type will be chosen to be the smallest unsigned integer type that can contain all of the entries in `data`. **Misc object options**: @@ -168,7 +170,6 @@ Generally speaking, hapPLY uses C++ exceptions to communicate errors-- most of t ## Known issues: - Writing floating-point values of `inf` or `nan` in ASCII mode is not supported, because the .ply format does not specify how they should be written (C++'s ofstream and ifstream don't even treat them consistently). These values work just fine in binary mode. -- Currently hapPLY does not allow the user to specify a type for the variable which indicates how many elements are in a list; it always uses `uchar` (and throws and error if the data does not fit in a uchar). Note that at least for mesh-like data, popular software only accepts `uchar`. - Almost all modern computers are little-endian. If you happen to have a big-endian platform, be aware that the codebase has not been tested in a big-endian environment, and might have bugs related to binary reading/writing there. Note that the _platform_ endianness is distinct from the _file_ endianness---reading/writing either big- or little-endian files certainly works just fine as long as you're running the code on a little-endian computer (as you problably are). diff --git a/happly.h b/happly.h index 41b445a..9e774a4 100644 --- a/happly.h +++ b/happly.h @@ -412,7 +412,7 @@ class TypedProperty : public Property { /** * @brief A property which is a list of value (eg, 3 doubles). Note that lists are always variable length per-element. */ -template +template class TypedListProperty : public Property { public: @@ -421,10 +421,17 @@ class TypedListProperty : public Property { * * @param name_ */ - TypedListProperty(const std::string& name_, int listCountBytes_) : Property(name_), listCountBytes(listCountBytes_) { + TypedListProperty(const std::string& name_) : Property(name_) { if (typeName() == "unknown") { throw std::runtime_error("Attempted property type does not match any type defined by the .ply format."); } + if (typeName() == "unknown") { + throw std::runtime_error("Attempted size type does not match any type defined by the .ply format."); + } else if(typeName() != typeName() && + typeName() != typeName() && + typeName() != typeName()) { + throw std::runtime_error("List property size must be an unsigned integer type."); + } flattenedIndexStart.push_back(0); }; @@ -439,15 +446,30 @@ class TypedListProperty : public Property { if (typeName() == "unknown") { throw std::runtime_error("Attempted property type does not match any type defined by the .ply format."); } + if (typeName() == "unknown") { + throw std::runtime_error("Attempted size type does not match any type defined by the .ply format."); + } else if(typeName() != typeName() && + typeName() != typeName() && + typeName() != typeName()) { + throw std::runtime_error("List property size must be an unsigned integer type."); + } + size_t maxCount = 0; // Populate list with data flattenedIndexStart.push_back(0); for (const std::vector& vec : data_) { for (const T& val : vec) { flattenedData.emplace_back(val); } + if(vec.size() > maxCount) + maxCount = vec.size(); flattenedIndexStart.push_back(flattenedData.size()); } + + if(maxCount > std::numeric_limits::max()) { + throw std::runtime_error( + "List property has an element with more entries than fit in the size type."); + } }; virtual ~TypedListProperty() override{}; @@ -497,7 +519,7 @@ class TypedListProperty : public Property { // Read the size of the list size_t count = 0; - stream.read(((char*)&count), listCountBytes); + stream.read(((char*)&count), sizeof(SizeT)); // Read list elements size_t currSize = flattenedData.size(); @@ -518,14 +540,8 @@ class TypedListProperty : public Property { // Read the size of the list size_t count = 0; - stream.read(((char*)&count), listCountBytes); - if (listCountBytes == 8) { - count = (size_t)swapEndian((uint64_t)count); - } else if (listCountBytes == 4) { - count = (size_t)swapEndian((uint32_t)count); - } else if (listCountBytes == 2) { - count = (size_t)swapEndian((uint16_t)count); - } + stream.read(((char*)&count), sizeof(SizeT)); + count = (size_t)swapEndian(static_cast(count)); // Read list elements size_t currSize = flattenedData.size(); @@ -543,13 +559,12 @@ class TypedListProperty : public Property { } /** - * @brief (reading) Write a header entry for this property. Note that we already use "uchar" for the list count type. + * @brief (reading) Write a header entry for this property. * * @param outStream Stream to write to. */ virtual void writeHeader(std::ostream& outStream) override { - // NOTE: We ALWAYS use uchar as the list count output type - outStream << "property list uchar " << typeName() << " " << name << "\n"; + outStream << "property list " << typeName() << " " << typeName() << " " << name << "\n"; } /** @@ -562,13 +577,7 @@ class TypedListProperty : public Property { size_t dataStart = flattenedIndexStart[iElement]; size_t dataEnd = flattenedIndexStart[iElement + 1]; - // Get the number of list elements as a uchar, and ensure the value fits size_t dataCount = dataEnd - dataStart; - if (dataCount > std::numeric_limits::max()) { - throw std::runtime_error( - "List property has an element with more entries than fit in a uchar. See note in README."); - } - outStream << dataCount; outStream.precision(std::numeric_limits::max_digits10); for (size_t iFlat = dataStart; iFlat < dataEnd; iFlat++) { @@ -586,16 +595,10 @@ class TypedListProperty : public Property { size_t dataStart = flattenedIndexStart[iElement]; size_t dataEnd = flattenedIndexStart[iElement + 1]; - // Get the number of list elements as a uchar, and ensure the value fits size_t dataCount = dataEnd - dataStart; - if (dataCount > std::numeric_limits::max()) { - throw std::runtime_error( - "List property has an element with more entries than fit in a uchar. See note in README."); - } - uint8_t count = static_cast(dataCount); - - outStream.write((char*)&count, sizeof(uint8_t)); - outStream.write((char*)&flattenedData[dataStart], count * sizeof(T)); + SizeT count = static_cast(dataCount); + outStream.write((char*)&count, sizeof(SizeT)); + outStream.write((char*)&flattenedData[dataStart], dataCount * sizeof(T)); } /** @@ -608,15 +611,10 @@ class TypedListProperty : public Property { size_t dataStart = flattenedIndexStart[iElement]; size_t dataEnd = flattenedIndexStart[iElement + 1]; - // Get the number of list elements as a uchar, and ensure the value fits size_t dataCount = dataEnd - dataStart; - if (dataCount > std::numeric_limits::max()) { - throw std::runtime_error( - "List property has an element with more entries than fit in a uchar. See note in README."); - } - uint8_t count = static_cast(dataCount); + SizeT count = swapEndian(static_cast(dataCount)); + outStream.write((char*)&count, sizeof(SizeT)); - outStream.write((char*)&count, sizeof(uint8_t)); for (size_t iFlat = dataStart; iFlat < dataEnd; iFlat++) { T value = swapEndian(flattenedData[iFlat]); outStream.write((char*)&value, sizeof(T)); @@ -649,11 +647,6 @@ class TypedListProperty : public Property { * begins. A final entry is included which is the length of flattenedData. Size is N_elem + 1. */ std::vector flattenedIndexStart; - - /** - * @brief The number of bytes used to store the count for lists of data. - */ - int listCountBytes = -1; }; @@ -693,7 +686,13 @@ inline std::unique_ptr createPropertyWithType(const std::string& name, // 8 bit unsigned if (typeStr == "uchar" || typeStr == "uint8") { if (isList) { - return std::unique_ptr(new TypedListProperty(name, listCountBytes)); + if(listCountBytes == 1) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 2) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 4) { + return std::unique_ptr(new TypedListProperty(name)); + } } else { return std::unique_ptr(new TypedProperty(name)); } @@ -702,7 +701,13 @@ inline std::unique_ptr createPropertyWithType(const std::string& name, // 16 bit unsigned else if (typeStr == "ushort" || typeStr == "uint16") { if (isList) { - return std::unique_ptr(new TypedListProperty(name, listCountBytes)); + if(listCountBytes == 1) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 2) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 4) { + return std::unique_ptr(new TypedListProperty(name)); + } } else { return std::unique_ptr(new TypedProperty(name)); } @@ -711,7 +716,13 @@ inline std::unique_ptr createPropertyWithType(const std::string& name, // 32 bit unsigned else if (typeStr == "uint" || typeStr == "uint32") { if (isList) { - return std::unique_ptr(new TypedListProperty(name, listCountBytes)); + if(listCountBytes == 1) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 2) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 4) { + return std::unique_ptr(new TypedListProperty(name)); + } } else { return std::unique_ptr(new TypedProperty(name)); } @@ -722,7 +733,13 @@ inline std::unique_ptr createPropertyWithType(const std::string& name, // 8 bit signed if (typeStr == "char" || typeStr == "int8") { if (isList) { - return std::unique_ptr(new TypedListProperty(name, listCountBytes)); + if(listCountBytes == 1) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 2) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 4) { + return std::unique_ptr(new TypedListProperty(name)); + } } else { return std::unique_ptr(new TypedProperty(name)); } @@ -731,7 +748,13 @@ inline std::unique_ptr createPropertyWithType(const std::string& name, // 16 bit signed else if (typeStr == "short" || typeStr == "int16") { if (isList) { - return std::unique_ptr(new TypedListProperty(name, listCountBytes)); + if(listCountBytes == 1) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 2) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 4) { + return std::unique_ptr(new TypedListProperty(name)); + } } else { return std::unique_ptr(new TypedProperty(name)); } @@ -740,7 +763,13 @@ inline std::unique_ptr createPropertyWithType(const std::string& name, // 32 bit signed else if (typeStr == "int" || typeStr == "int32") { if (isList) { - return std::unique_ptr(new TypedListProperty(name, listCountBytes)); + if(listCountBytes == 1) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 2) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 4) { + return std::unique_ptr(new TypedListProperty(name)); + } } else { return std::unique_ptr(new TypedProperty(name)); } @@ -751,7 +780,13 @@ inline std::unique_ptr createPropertyWithType(const std::string& name, // 32 bit float else if (typeStr == "float" || typeStr == "float32") { if (isList) { - return std::unique_ptr(new TypedListProperty(name, listCountBytes)); + if(listCountBytes == 1) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 2) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 4) { + return std::unique_ptr(new TypedListProperty(name)); + } } else { return std::unique_ptr(new TypedProperty(name)); } @@ -760,15 +795,19 @@ inline std::unique_ptr createPropertyWithType(const std::string& name, // 64 bit float else if (typeStr == "double" || typeStr == "float64") { if (isList) { - return std::unique_ptr(new TypedListProperty(name, listCountBytes)); + if(listCountBytes == 1) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 2) { + return std::unique_ptr(new TypedListProperty(name)); + } else if(listCountBytes == 4) { + return std::unique_ptr(new TypedListProperty(name)); + } } else { return std::unique_ptr(new TypedProperty(name)); } } - else { - throw std::runtime_error("Data type: " + typeStr + " cannot be mapped to .ply format"); - } + throw std::runtime_error("Data type: " + typeStr + " cannot be mapped to .ply format"); } /** @@ -888,7 +927,7 @@ class Element { } /** - * @brief Add a new list property for this element type. + * @brief Add a new list property for this element type. Uses an 8-bit list size. * * @tparam T The type of the property (eg, "double" for a list of doubles) * @param propertyName The name of the property @@ -916,7 +955,54 @@ class Element { } properties.push_back(std::unique_ptr( - new TypedListProperty::type>(propertyName, canonicalListVec))); + new TypedListProperty::type, uint8_t>(propertyName, canonicalListVec))); + } + + /** + * @brief Add a new list property for this element type. + * Uses the smallest list size type that will contain all of the elements. + * + * @tparam T The type of the property (eg, "double" for a list of doubles) + * @param propertyName The name of the property + * @param data The data for the property. Outer vector must have the same length as the number of elements. + */ + template + void addSizedListProperty(const std::string& propertyName, const std::vector>& data) { + + if (data.size() != count) { + throw std::runtime_error("PLY write: new property " + propertyName + " has size which does not match element"); + } + + // If there is already some property with this name, remove it + for (size_t i = 0; i < properties.size(); i++) { + if (properties[i]->name == propertyName) { + properties.erase(properties.begin() + i); + i--; + } + } + + // Copy to canonical type. Often a no-op, but takes care of standardizing widths across platforms. + size_t maxCount = 0; + std::vector::type>> canonicalListVec; + for (const std::vector& subList : data) { + canonicalListVec.emplace_back(subList.begin(), subList.end()); + if(subList.size() > maxCount) + maxCount = subList.size(); + } + + if(maxCount > std::numeric_limits::max()) { + throw std::runtime_error( + "List property has an element with more entries than fit in a uint32."); + } else if(maxCount > std::numeric_limits::max()) { + properties.push_back(std::unique_ptr( + new TypedListProperty::type, uint32_t>(propertyName, canonicalListVec))); + } else if(maxCount > std::numeric_limits::max()) { + properties.push_back(std::unique_ptr( + new TypedListProperty::type, uint16_t>(propertyName, canonicalListVec))); + } else { + properties.push_back(std::unique_ptr( + new TypedListProperty::type, uint8_t>(propertyName, canonicalListVec))); + } } /** @@ -995,9 +1081,17 @@ class Element { // Find the property std::unique_ptr& prop = getPropertyPtr(propertyName); - TypedListProperty* castedProp = dynamic_cast*>(prop.get()); - if (castedProp) { - return unflattenList(castedProp->flattenedData, castedProp->flattenedIndexStart); + TypedListProperty* castedProp8 = dynamic_cast*>(prop.get()); + if (castedProp8) { + return unflattenList(castedProp8->flattenedData, castedProp8->flattenedIndexStart); + } + TypedListProperty* castedProp16 = dynamic_cast*>(prop.get()); + if (castedProp16) { + return unflattenList(castedProp16->flattenedData, castedProp16->flattenedIndexStart); + } + TypedListProperty* castedProp32 = dynamic_cast*>(prop.get()); + if (castedProp32) { + return unflattenList(castedProp32->flattenedData, castedProp32->flattenedIndexStart); } // No match, failure @@ -1180,6 +1274,37 @@ class Element { } } + /** @brief Helper function which returns a buffer of the data from a sized list property. + * + * @tparam D The desired output type + * @tparam T The actual type of the property + * @tparam SizeT The size type of the list property + * @param castedProp The property to get + * + * @return The data, with the requested type + */ + template + std::vector> getBufferFromSizedListProperty(TypedListProperty &castedProp) + { + // Convert to flat buffer of new type + std::vector* castedFlatVec = nullptr; + std::vector castedFlatVecCopy; // we _might_ make a copy here, depending on is_same below + + if (std::is_same, std::vector>::value) { + // just use the array we already have + castedFlatVec = addressIfSame>(castedProp.flattenedData, 0 /* dummy arg to disambiguate */); + } else { + // make a copy + castedFlatVecCopy.reserve(castedProp.flattenedData.size()); + for (T& v : castedProp.flattenedData) { + castedFlatVecCopy.push_back(static_cast(v)); + } + castedFlatVec = &castedFlatVecCopy; + } + + // Unflatten and return + return unflattenList(*castedFlatVec, castedProp.flattenedIndexStart); + } /** * @brief Helper function which does the hard work to implement type promotion for list data getters. Throws if type @@ -1195,30 +1320,20 @@ class Element { std::vector> getDataFromListPropertyRecursive(Property* prop) { typedef typename CanonicalName::type Tcan; - TypedListProperty* castedProp = dynamic_cast*>(prop); - if (castedProp) { - // Succeeded, return a buffer of the data (copy while converting type) - - // Convert to flat buffer of new type - std::vector* castedFlatVec = nullptr; - std::vector castedFlatVecCopy; // we _might_ make a copy here, depending on is_same below - - if (std::is_same, std::vector>::value) { - // just use the array we already have - castedFlatVec = addressIfSame>(castedProp->flattenedData, 0 /* dummy arg to disambiguate */); - } else { - // make a copy - castedFlatVecCopy.reserve(castedProp->flattenedData.size()); - for (Tcan& v : castedProp->flattenedData) { - castedFlatVecCopy.push_back(static_cast(v)); - } - castedFlatVec = &castedFlatVecCopy; - } - - // Unflatten and return - return unflattenList(*castedFlatVec, castedProp->flattenedIndexStart); + TypedListProperty* castedProp8 = dynamic_cast*>(prop); + if (castedProp8) { + return getBufferFromSizedListProperty(*castedProp8); + } + TypedListProperty* castedProp16 = dynamic_cast*>(prop); + if (castedProp16) { + return getBufferFromSizedListProperty(*castedProp16); + } + TypedListProperty* castedProp32 = dynamic_cast*>(prop); + if (castedProp32) { + return getBufferFromSizedListProperty(*castedProp32); } + // not present in any size, try the next smaller type TypeChain chainType; if (chainType.hasChildType) { return getDataFromListPropertyRecursive::type>(prop); diff --git a/test/main_test.cpp b/test/main_test.cpp index 287adc2..53cb202 100644 --- a/test/main_test.cpp +++ b/test/main_test.cpp @@ -961,6 +961,338 @@ TEST(TypedListReadWriteTest, ReadWriteDoubleBinarySwap) { EXPECT_EQ(testData, testDataBinary); } +// Tests for list size +TEST(LongTypedListReadWriteTest, ReadWriteUShortIntASCII) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3}, {-3, 0, 2147483647, -2147483647 - 1, 122}, {}, {}, {3, 11}, {} + }; + testData.back().resize(300); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = i * 314159; + } + plyOut.getElement("test_elem").addSizedListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::ASCII); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataASCII = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataASCII); +} +TEST(LongTypedListReadWriteTest, ReadWriteUShortIntBinary) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3}, {-3, 0, 2147483647, -2147483647 - 1, 122}, {}, {}, {3, 11}, {} + }; + testData.back().resize(300); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = i * 314159; + } + plyOut.getElement("test_elem").addSizedListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::Binary); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} +TEST(LongTypedListReadWriteTest, ReadWriteUShortIntBinarySwap) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3}, {-3, 0, 2147483647, -2147483647 - 1, 122}, {}, {}, {3, 11}, {} + }; + testData.back().resize(300); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = i * 314159; + } + plyOut.getElement("test_elem").addSizedListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::BinaryBigEndian); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} +TEST(LongTypedListReadWriteTest, ReadWriteUIntIntASCII) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3}, {-3, 0, 2147483647, -2147483647 - 1, 122}, {}, {}, {3, 11}, {} + }; + testData.back().resize(70000); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = i * 314159; + } + plyOut.getElement("test_elem").addSizedListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::ASCII); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataASCII = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataASCII); +} +TEST(LongTypedListReadWriteTest, ReadWriteUIntIntBinary) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3}, {-3, 0, 2147483647, -2147483647 - 1, 122}, {}, {}, {3, 11}, {} + }; + testData.back().resize(70000); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = i * 314159; + } + plyOut.getElement("test_elem").addSizedListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::Binary); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} +TEST(LongTypedListReadWriteTest, ReadWriteUIntIntBinarySwap) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3}, {-3, 0, 2147483647, -2147483647 - 1, 122}, {}, {}, {3, 11}, {} + }; + testData.back().resize(70000); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = i * 314159; + } + plyOut.getElement("test_elem").addSizedListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::BinaryBigEndian); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} +TEST(LongTypedListReadWriteTest, ReadWriteUShortFloatASCII) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3.f, 14.44f, 42.4242f}, + { + 3.141592653589793238f, + -3.141592653589793238f, + std::numeric_limits::min(), + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::epsilon(), + 0.0f, + -0.0f, + 1e24f, + }, + {}, + {1.1f}, + {-121.5f, 1.111f}, + {}, + }; + testData.back().resize(300); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = sqrt(static_cast(i)); + } + plyOut.getElement("test_elem").addSizedListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::ASCII); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataASCII = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataASCII); +} +TEST(LongTypedListReadWriteTest, ReadWriteUShortFloatBinary) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3.f, 14.44f, 42.4242f}, + { + 3.141592653589793238f, + -3.141592653589793238f, + std::numeric_limits::min(), + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::epsilon(), + 0.0f, + -0.0f, + 1e24f, + }, + {}, + {1.1f}, + {-121.5f, 1.111f}, + {}, + }; + testData.back().resize(300); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = sqrt(static_cast(i)); + } + plyOut.getElement("test_elem").addSizedListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::Binary); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} +TEST(LongTypedListReadWriteTest, ReadWriteUShortFloatBinarySwap) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3.f, 14.44f, 42.4242f}, + { + 3.141592653589793238f, + -3.141592653589793238f, + std::numeric_limits::min(), + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::epsilon(), + 0.0f, + -0.0f, + 1e24f, + }, + {}, + {1.1f}, + {-121.5f, 1.111f}, + {}, + }; + testData.back().resize(300); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = sqrt(static_cast(i)); + } + plyOut.getElement("test_elem").addSizedListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::BinaryBigEndian); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} +TEST(LongTypedListReadWriteTest, ReadWriteUIntFloatASCII) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3.f, 14.44f, 42.4242f}, + { + 3.141592653589793238f, + -3.141592653589793238f, + std::numeric_limits::min(), + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::epsilon(), + 0.0f, + -0.0f, + 1e24f, + }, + {}, + {1.1f}, + {-121.5f, 1.111f}, + {}, + }; + testData.back().resize(70000); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = sqrt(static_cast(i)); + } + plyOut.getElement("test_elem").addSizedListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::ASCII); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataASCII = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataASCII); +} +TEST(LongTypedListReadWriteTest, ReadWriteUIntFloatBinary) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3.f, 14.44f, 42.4242f}, + { + 3.141592653589793238f, + -3.141592653589793238f, + std::numeric_limits::min(), + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::epsilon(), + 0.0f, + -0.0f, + 1e24f, + }, + {}, + {1.1f}, + {-121.5f, 1.111f}, + {}, + }; + testData.back().resize(70000); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = sqrt(static_cast(i)); + } + plyOut.getElement("test_elem").addSizedListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::Binary); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} +TEST(LongTypedListReadWriteTest, ReadWriteUIntFloatBinarySwap) { + + // Create a simple file + happly::PLYData plyOut; + plyOut.addElement("test_elem", 6); + std::vector> testData{ + {3.f, 14.44f, 42.4242f}, + { + 3.141592653589793238f, + -3.141592653589793238f, + std::numeric_limits::min(), + std::numeric_limits::max(), + std::numeric_limits::lowest(), + std::numeric_limits::epsilon(), + 0.0f, + -0.0f, + 1e24f, + }, + {}, + {1.1f}, + {-121.5f, 1.111f}, + {}, + }; + testData.back().resize(70000); + for(size_t i = 0 ; i < testData.back().size() ; i++) { + testData.back()[i] = sqrt(static_cast(i)); + } + plyOut.getElement("test_elem").addSizedListProperty("test_data", testData); + + // ASCII read/write + plyOut.write("temp.ply", happly::DataFormat::BinaryBigEndian); + happly::PLYData plyIn("temp.ply"); + std::vector> testDataBinary = plyIn.getElement("test_elem").getListProperty("test_data"); + EXPECT_EQ(testData, testDataBinary); +} + // === Test error and utility behavior // Errors get thrown