From 8374e140425580808b8ef5d074590b4236a30c4b Mon Sep 17 00:00:00 2001 From: Ondrej Stava Date: Wed, 8 Feb 2017 10:34:40 -0800 Subject: [PATCH] Merging changes from our private repo: 1. More optimized selection of prediction schemes for different compression levels on the encoder side. 2. Improved robustness to tampered input data (.drc) 3. Added support for strognly typed vectors of bools 4. Support for logging and squared lenght to our VectorND class 5. Added support for partially defined indices in .OBJ files 6. Added support for loading of normal vectors in .PLY files --- .../prediction_scheme_encoder_factory.cc | 30 ++++++++ .../prediction_scheme_encoder_factory.h | 25 +++---- .../sequential_attribute_decoder.cc | 3 +- .../sequential_integer_attribute_encoder.cc | 5 +- core/adaptive_rans_coding.cc | 2 +- core/data_buffer.cc | 13 +++- core/data_buffer.h | 4 +- core/draco_index_type_vector.h | 11 +-- core/vector_d.h | 46 +++++++++++-- core/vector_d_test.cc | 68 ++++++++++++++++++- io/obj_decoder.cc | 24 +++++-- io/obj_decoder_test.cc | 5 ++ io/obj_encoder.cc | 8 +-- io/ply_decoder.cc | 32 +++++++++ io/ply_decoder_test.cc | 20 +++++- javascript/example/DRACOLoader.js | 11 +-- point_cloud/point_attribute.cc | 6 +- point_cloud/point_attribute.h | 2 +- testdata/cube_att.ply | 50 ++++++++++++++ testdata/cube_att_partial.obj | 35 ++++++++++ 20 files changed, 344 insertions(+), 56 deletions(-) create mode 100644 testdata/cube_att.ply create mode 100644 testdata/cube_att_partial.obj diff --git a/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.cc b/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.cc index 6c3b17d..c6d2304 100644 --- a/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.cc +++ b/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.cc @@ -16,6 +16,36 @@ namespace draco { +PredictionSchemeMethod SelectPredictionMethod( + int att_id, const PointCloudEncoder *encoder) { + if (encoder->options()->GetSpeed() >= 10) { + return PREDICTION_NONE; // No prediction when the fastest speed is + // requested. + } + if (encoder->GetGeometryType() == TRIANGULAR_MESH) { + // Use speed setting to select the best encoding method. + const PointAttribute *const att = encoder->point_cloud()->attribute(att_id); + if (att->attribute_type() == GeometryAttribute::TEX_COORD) { + if (encoder->options()->GetSpeed() < 4) { + // Use texture coordinate prediction for speeds 0, 1, 2, 3. + return MESH_PREDICTION_TEX_COORDS; + } + } + // Handle other attribute types. + if (encoder->options()->GetSpeed() >= 8) { + return PREDICTION_DIFFERENCE; + } + if (encoder->options()->GetSpeed() >= 2) { + // Parallelogram prediction is used for speeds 2 - 7. + return MESH_PREDICTION_PARALLELOGRAM; + } + // Multi-parallelogram is used for speeds 0, 1. + return MESH_PREDICTION_MULTI_PARALLELOGRAM; + } + // Default option is delta coding. + return PREDICTION_DIFFERENCE; +} + // Returns the preferred prediction scheme based on the encoder options. PredictionSchemeMethod GetPredictionMethodFromOptions( int att_id, const EncoderOptions &options) { diff --git a/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.h b/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.h index ebbf9ca..975cbd7 100644 --- a/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.h +++ b/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.h @@ -23,6 +23,11 @@ namespace draco { +// Selects a prediction method based on the input geometry type and based on the +// encoder options. +PredictionSchemeMethod SelectPredictionMethod(int att_id, + const PointCloudEncoder *encoder); + // Creates a prediction scheme for a given encoder and given prediction method. // The prediction schemes are automatically initialized with encoder specific // data if needed. @@ -34,23 +39,9 @@ CreatePredictionSchemeForEncoder(PredictionSchemeMethod method, int att_id, const TransformT &transform) { const PointAttribute *const att = encoder->point_cloud()->attribute(att_id); if (method == PREDICTION_UNDEFINED) { - if (encoder->options()->GetSpeed() >= 10) { - return nullptr; // No prediction when the fastest speed is requested. - } - if (encoder->GetGeometryType() == TRIANGULAR_MESH) { - // Use speed setting to select the best encoding method. - if (encoder->options()->GetSpeed() >= 8) { - method = PREDICTION_DIFFERENCE; - } else if (encoder->options()->GetSpeed() >= 5) { - method = MESH_PREDICTION_PARALLELOGRAM; - } else { - if (att->attribute_type() == GeometryAttribute::TEX_COORD) { - method = MESH_PREDICTION_TEX_COORDS; - } else { - method = MESH_PREDICTION_MULTI_PARALLELOGRAM; - } - } - } + method = SelectPredictionMethod(att_id, encoder); + if (method == PREDICTION_NONE) + return nullptr; // No prediction is used. } if (encoder->GetGeometryType() == TRIANGULAR_MESH) { // Cast the encoder to mesh encoder. This is not necessarily safe if there diff --git a/compression/attributes/sequential_attribute_decoder.cc b/compression/attributes/sequential_attribute_decoder.cc index ecd416c..59a79d0 100644 --- a/compression/attributes/sequential_attribute_decoder.cc +++ b/compression/attributes/sequential_attribute_decoder.cc @@ -36,7 +36,8 @@ bool SequentialAttributeDecoder::InitializeStandalone( bool SequentialAttributeDecoder::Decode( const std::vector &point_ids, DecoderBuffer *in_buffer) { - attribute_->Reset(point_ids.size()); + if (!attribute_->Reset(point_ids.size())) + return false; if (!DecodeValues(point_ids, in_buffer)) return false; return true; diff --git a/compression/attributes/sequential_integer_attribute_encoder.cc b/compression/attributes/sequential_integer_attribute_encoder.cc index 1f03620..9eecd56 100644 --- a/compression/attributes/sequential_integer_attribute_encoder.cc +++ b/compression/attributes/sequential_integer_attribute_encoder.cc @@ -97,9 +97,8 @@ bool SequentialIntegerAttributeEncoder::EncodeValues( reinterpret_cast(values_.data())); } - if (encoder() == nullptr || - encoder()->options()->GetGlobalBool("use_built_in_attribute_compression", - true)) { + if (encoder() == nullptr || encoder()->options()->GetGlobalBool( + "use_built_in_attribute_compression", true)) { out_buffer->Encode(static_cast(1)); EncodeSymbols(reinterpret_cast(values_.data()), point_ids.size() * num_components, num_components, diff --git a/core/adaptive_rans_coding.cc b/core/adaptive_rans_coding.cc index 694bfae..e1f24a5 100644 --- a/core/adaptive_rans_coding.cc +++ b/core/adaptive_rans_coding.cc @@ -83,7 +83,7 @@ bool AdaptiveRAnsBitDecoder::StartDecoding(DecoderBuffer *source_buffer) { if (!source_buffer->Decode(&size_in_bytes)) return false; if (size_in_bytes > source_buffer->remaining_size()) - return false; + return false; if (ans_read_init(&ans_decoder_, reinterpret_cast( const_cast(source_buffer->data_head())), diff --git a/core/data_buffer.cc b/core/data_buffer.cc index a37a597..1812bf8 100644 --- a/core/data_buffer.cc +++ b/core/data_buffer.cc @@ -18,7 +18,10 @@ namespace draco { DataBuffer::DataBuffer() {} -void DataBuffer::Update(const void *data, int64_t size) { +bool DataBuffer::Update(const void *data, int64_t size) { + if (size < 0) + return false; + if (data == nullptr) { // If no data is provided, just resize the buffer. data_.resize(size); @@ -27,19 +30,25 @@ void DataBuffer::Update(const void *data, int64_t size) { data_.assign(byte_data, byte_data + size); } descriptor_.buffer_update_count++; + return true; } -void DataBuffer::Update(const void *data, int64_t size, int64_t offset) { +bool DataBuffer::Update(const void *data, int64_t size, int64_t offset) { if (data == nullptr) { + if (size + offset < 0) + return false; // If no data is provided, just resize the buffer. data_.resize(size + offset); } else { + if (size < 0) + return false; if (size + offset > static_cast(data_.size())) data_.resize(size + offset); const uint8_t *const byte_data = static_cast(data); std::copy(byte_data, byte_data + size, data_.data() + offset); } descriptor_.buffer_update_count++; + return true; } void DataBuffer::WriteDataToStream(std::ostream &stream) { diff --git a/core/data_buffer.h b/core/data_buffer.h index 1a5c1ea..329060d 100644 --- a/core/data_buffer.h +++ b/core/data_buffer.h @@ -36,10 +36,10 @@ struct DataBufferDescriptor { class DataBuffer { public: DataBuffer(); - void Update(const void *data, int64_t size); + bool Update(const void *data, int64_t size); // TODO(zhafang): The two update functions should be combined. I will // leave for now in case it breaks any geometry compression tools. - void Update(const void *data, int64_t size, int64_t offset); + bool Update(const void *data, int64_t size, int64_t offset); void WriteDataToStream(std::ostream &stream); // Reads data from the buffer. Potentially unsafe, called needs to ensure // the accessed memory is valid. diff --git a/core/draco_index_type_vector.h b/core/draco_index_type_vector.h index d5804eb..bb9321c 100644 --- a/core/draco_index_type_vector.h +++ b/core/draco_index_type_vector.h @@ -30,6 +30,9 @@ namespace draco { template class IndexTypeVector { public: + typedef typename std::vector::const_reference const_reference; + typedef typename std::vector::reference reference; + IndexTypeVector() {} explicit IndexTypeVector(size_t size) : vector_(size) {} IndexTypeVector(size_t size, const ValueTypeT &val) : vector_(size, val) {} @@ -45,16 +48,16 @@ class IndexTypeVector { void push_back(const ValueTypeT &val) { vector_.push_back(val); } void push_back(ValueTypeT &&val) { vector_.push_back(std::move(val)); } - inline ValueTypeT &operator[](const IndexTypeT &index) { + inline reference operator[](const IndexTypeT &index) { return vector_[index.value()]; } - inline const ValueTypeT &operator[](const IndexTypeT &index) const { + inline const_reference operator[](const IndexTypeT &index) const { return vector_[index.value()]; } - inline ValueTypeT &at(const IndexTypeT &index) { + inline reference at(const IndexTypeT &index) { return vector_[index.value()]; } - inline const ValueTypeT &at(const IndexTypeT &index) const { + inline const_reference at(const IndexTypeT &index) const { return vector_[index.value()]; } const ValueTypeT *data() const { return vector_.data(); } diff --git a/core/vector_d.h b/core/vector_d.h index 8676ff1..e0fcf53 100644 --- a/core/vector_d.h +++ b/core/vector_d.h @@ -24,41 +24,43 @@ namespace draco { // D-dimensional vector class with basic operations. template class VectorD { - typedef VectorD Self; - public: + typedef VectorD Self; + typedef CoeffT CoefficientType; + static constexpr int dimension = dimension_t; + VectorD() { for (int i = 0; i < dimension_t; ++i) (*this)[i] = CoeffT(0); } // The following constructor does not compile in opt mode, which for now led - // to the further down, which is not ideal. + // to the constructors further down, which is not ideal. // TODO(hemmer): fix constructor below and remove others. // template // explicit VectorD(Args... args) : v_({args...}) {} VectorD(const CoeffT &c0, const CoeffT &c1) : v_({{c0, c1}}) { - CHECK(dimension_t == 2); + CHECK_EQ(dimension_t, 2); v_[0] = c0; v_[1] = c1; } VectorD(const CoeffT &c0, const CoeffT &c1, const CoeffT &c2) : v_({{c0, c1, c2}}) { - CHECK(dimension_t == 3); + CHECK_EQ(dimension_t, 3); } VectorD(const CoeffT &c0, const CoeffT &c1, const CoeffT &c2, const CoeffT &c3) : v_({{c0, c1, c2, c3}}) { - CHECK(dimension_t == 4); + CHECK_EQ(dimension_t, 4); } VectorD(const CoeffT &c0, const CoeffT &c1, const CoeffT &c2, const CoeffT &c3, const CoeffT &c4) : v_({{c0, c1, c2, c3, c4}}) { - CHECK(dimension_t == 5); + CHECK_EQ(dimension_t, 5); } VectorD(const Self &o) { @@ -160,6 +162,25 @@ VectorD operator*(const CoeffT &o, return v * o; } +// Calculates the squared distance between two points. +template +CoeffT SquaredDistance(const VectorD v1, + const VectorD v2) { + CoeffT difference; + CoeffT squared_distance = 0; + // Check each index seperately so difference is never negative and underflow + // is avoided for unsigned types. + for (int i = 0; i < dimension_t; ++i) { + if (v1[i] >= v2[i]) { + difference = v1[i] - v2[i]; + } else { + difference = v2[i] - v1[i]; + } + squared_distance += (difference * difference); + } + return squared_distance; +} + typedef VectorD Vector2f; typedef VectorD Vector3f; typedef VectorD Vector4f; @@ -172,4 +193,15 @@ typedef VectorD Vector5ui; } // namespace draco +template +inline ::std::basic_ostream &operator<<( + ::std::basic_ostream &out, + const draco::VectorD &vec) { + for (int i = 0; i < dimension_t - 1; ++i) { + out << vec[i] << " "; + } + out << vec[dimension_t - 1]; + return out; +} + #endif // DRACO_CORE_VECTOR_D_H_ diff --git a/core/vector_d_test.cc b/core/vector_d_test.cc index 2079a7d..4ed6102 100644 --- a/core/vector_d_test.cc +++ b/core/vector_d_test.cc @@ -18,10 +18,26 @@ namespace { -using draco::Vector3f; +typedef draco::Vector2f Vector2f; +typedef draco::Vector3f Vector3f; +typedef draco::Vector4f Vector4f; +typedef draco::Vector5f Vector5f; +typedef draco::Vector2ui Vector2ui; +typedef draco::Vector3ui Vector3ui; +typedef draco::Vector4ui Vector4ui; +typedef draco::Vector5ui Vector5ui; class VectorDTest : public ::testing::Test { protected: + template + void TestSquaredDistance(const draco::VectorD v1, + const draco::VectorD v2, + const CoeffT result) { + CoeffT squared_distance = SquaredDistance(v1, v2); + ASSERT_EQ(squared_distance, result); + squared_distance = SquaredDistance(v2, v1); + ASSERT_EQ(squared_distance, result); + } }; TEST_F(VectorDTest, TestOperators) { @@ -69,4 +85,54 @@ TEST_F(VectorDTest, TestOperators) { ASSERT_EQ(v.Dot(v), 14); } +TEST_F(VectorDTest, TestSquaredDistance) { + // Test Vector2f: float, 2D. + Vector2f v1_2f(5.5, 10.5); + Vector2f v2_2f(3.5, 15.5); + float result_f = 29; + TestSquaredDistance(v1_2f, v2_2f, result_f); + + // Test Vector3f: float, 3D. + Vector3f v1_3f(5.5, 10.5, 2.3); + Vector3f v2_3f(3.5, 15.5, 0); + result_f = 34.29; + TestSquaredDistance(v1_3f, v2_3f, result_f); + + // Test Vector4f: float, 4D. + Vector4f v1_4f(5.5, 10.5, 2.3, 7.2); + Vector4f v2_4f(3.5, 15.5, 0, 9.9); + result_f = 41.58; + TestSquaredDistance(v1_4f, v2_4f, result_f); + + // Test Vector5f: float, 5D. + Vector5f v1_5f(5.5, 10.5, 2.3, 7.2, 1.0); + Vector5f v2_5f(3.5, 15.5, 0, 9.9, 0.2); + result_f = 42.22; + TestSquaredDistance(v1_5f, v2_5f, result_f); + + // Test Vector 2ui: uint32_t, 2D. + Vector2ui v1_2ui(5, 10); + Vector2ui v2_2ui(3, 15); + uint32_t result_ui = 29; + TestSquaredDistance(v1_2ui, v2_2ui, result_ui); + + // Test Vector 3ui: uint32_t, 3D. + Vector3ui v1_3ui(5, 10, 2); + Vector3ui v2_3ui(3, 15, 0); + result_ui = 33; + TestSquaredDistance(v1_3ui, v2_3ui, result_ui); + + // Test Vector 4ui: uint32_t, 4D. + Vector4ui v1_4ui(5, 10, 2, 7); + Vector4ui v2_4ui(3, 15, 0, 9); + result_ui = 37; + TestSquaredDistance(v1_4ui, v2_4ui, result_ui); + + // Test Vector 5ui: uint32_t, 5D. + Vector5ui v1_5ui(5, 10, 2, 7, 1); + Vector5ui v2_5ui(3, 15, 0, 9, 12); + result_ui = 158; + TestSquaredDistance(v1_5ui, v2_5ui, result_ui); +} + } // namespace diff --git a/io/obj_decoder.cc b/io/obj_decoder.cc index 3943558..8ebfe3f 100644 --- a/io/obj_decoder.cc +++ b/io/obj_decoder.cc @@ -311,22 +311,38 @@ bool ObjDecoder::ParseFace(bool *error) { ->SetPointMapEntry(vert_id, AttributeValueIndex(indices[0] - 1)); } else if (indices[0] < 0) { out_point_cloud_->attribute(pos_att_id_) - ->SetPointMapEntry(vert_id, AttributeValueIndex(num_positions_ + indices[0])); + ->SetPointMapEntry( + vert_id, AttributeValueIndex(num_positions_ + indices[0])); } + if (indices[1] > 0) { out_point_cloud_->attribute(tex_att_id_) ->SetPointMapEntry(vert_id, AttributeValueIndex(indices[1] - 1)); } else if (indices[1] < 0) { out_point_cloud_->attribute(tex_att_id_) - ->SetPointMapEntry(vert_id, AttributeValueIndex(num_tex_coords_ + indices[1])); + ->SetPointMapEntry( + vert_id, AttributeValueIndex(num_tex_coords_ + indices[1])); + } else if (tex_att_id_ >= 0) { + // Texture index not provided but expected. Insert 0 entry as the + // default value. + out_point_cloud_->attribute(tex_att_id_) + ->SetPointMapEntry(vert_id, AttributeValueIndex(0)); } + if (indices[2] > 0) { out_point_cloud_->attribute(norm_att_id_) ->SetPointMapEntry(vert_id, AttributeValueIndex(indices[2] - 1)); } else if (indices[2] < 0) { - out_point_cloud_->attribute(norm_att_id_) - ->SetPointMapEntry(vert_id, AttributeValueIndex(num_normals_ + indices[2])); + out_point_cloud_->attribute(norm_att_id_) + ->SetPointMapEntry(vert_id, + AttributeValueIndex(num_normals_ + indices[2])); + } else if (norm_att_id_ >= 0) { + // Normal index not provided but expected. Insert 0 entry as the default + // value. + out_point_cloud_->attribute(norm_att_id_) + ->SetPointMapEntry(vert_id, AttributeValueIndex(0)); } + if (material_att_id_ >= 0) { out_point_cloud_->attribute(material_att_id_) ->SetPointMapEntry(vert_id, AttributeValueIndex(last_material_id_)); diff --git a/io/obj_decoder_test.cc b/io/obj_decoder_test.cc index 45f0d89..2d0740b 100644 --- a/io/obj_decoder_test.cc +++ b/io/obj_decoder_test.cc @@ -48,5 +48,10 @@ TEST_F(ObjDecoderTest, ExtraVertexOBJ) { test_decoding(file_name); } +TEST_F(ObjDecoderTest, ParialAttributesOBJ) { + const std::string file_name = "cube_att_partial.obj"; + test_decoding(file_name); +} + } // namespace draco diff --git a/io/obj_encoder.cc b/io/obj_encoder.cc index 6ed7c2e..ad4920f 100644 --- a/io/obj_encoder.cc +++ b/io/obj_encoder.cc @@ -95,7 +95,7 @@ bool ObjEncoder::EncodePositions() { return false; buffer()->Encode("v ", 2); EncodeFloatList(&value[0], 3); - buffer()->Encode("\r\n", 2); + buffer()->Encode("\n", 1); } pos_att_ = att; return true; @@ -112,7 +112,7 @@ bool ObjEncoder::EncodeTextureCoordinates() { return false; buffer()->Encode("vt ", 3); EncodeFloatList(&value[0], 2); - buffer()->Encode("\r\n", 2); + buffer()->Encode("\n", 1); } tex_coord_att_ = att; return true; @@ -129,7 +129,7 @@ bool ObjEncoder::EncodeNormals() { return false; buffer()->Encode("vn ", 3); EncodeFloatList(&value[0], 3); - buffer()->Encode("\r\n", 2); + buffer()->Encode("\n", 1); } normal_att_ = att; return true; @@ -142,7 +142,7 @@ bool ObjEncoder::EncodeFaces() { if (!EncodeFaceCorner(i, j)) return false; } - buffer()->Encode("\r\n", 2); + buffer()->Encode("\n", 1); } return true; } diff --git a/io/ply_decoder.cc b/io/ply_decoder.cc index 3525d2a..6c6ee91 100644 --- a/io/ply_decoder.cc +++ b/io/ply_decoder.cc @@ -132,6 +132,11 @@ bool PlyDecoder::DecodeVertexData(const PlyElement *vertex_element) { // Decode vertex positions. { // TODO(ostava): For now assume the position types are float32. + if (x_prop->data_type() != DT_FLOAT32 || + y_prop->data_type() != DT_FLOAT32 || + z_prop->data_type() != DT_FLOAT32) { + return false; + } PlyPropertyReader x_reader(x_prop); PlyPropertyReader y_reader(y_prop); PlyPropertyReader z_reader(z_prop); @@ -149,6 +154,33 @@ bool PlyDecoder::DecodeVertexData(const PlyElement *vertex_element) { } } + // Decode normals if present. + const PlyProperty *const n_x_prop = vertex_element->GetPropertyByName("nx"); + const PlyProperty *const n_y_prop = vertex_element->GetPropertyByName("ny"); + const PlyProperty *const n_z_prop = vertex_element->GetPropertyByName("nz"); + if (n_x_prop != nullptr && n_y_prop != nullptr && n_z_prop != nullptr) { + // For now, all normal properties must be set and of type float32 + if (n_x_prop->data_type() == DT_FLOAT32 && + n_y_prop->data_type() == DT_FLOAT32 && + n_z_prop->data_type() == DT_FLOAT32) { + PlyPropertyReader x_reader(n_x_prop); + PlyPropertyReader y_reader(n_y_prop); + PlyPropertyReader z_reader(n_z_prop); + GeometryAttribute va; + va.Init(GeometryAttribute::NORMAL, nullptr, 3, DT_FLOAT32, false, + sizeof(float) * 3, 0); + const int att_id = out_point_cloud_->AddAttribute(va, true, num_vertices); + for (PointIndex::ValueType i = 0; i < num_vertices; ++i) { + std::array val; + val[0] = x_reader.ReadValue(i); + val[1] = y_reader.ReadValue(i); + val[2] = z_reader.ReadValue(i); + out_point_cloud_->attribute(att_id)->SetAttributeValue( + AttributeValueIndex(i), &val[0]); + } + } + } + // Decode color data if present. int num_colors = 0; const PlyProperty *const r_prop = vertex_element->GetPropertyByName("red"); diff --git a/io/ply_decoder_test.cc b/io/ply_decoder_test.cc index cbd95ca..f4eb3c5 100644 --- a/io/ply_decoder_test.cc +++ b/io/ply_decoder_test.cc @@ -32,10 +32,13 @@ class PlyDecoderTest : public ::testing::Test { } void test_decoding_method(const std::string &file_name, int num_faces, - uint32_t num_points) { - const std::unique_ptr mesh(DecodePly(file_name)); + uint32_t num_points, + std::unique_ptr *out_mesh) { + std::unique_ptr mesh(DecodePly(file_name)); ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name; ASSERT_EQ(mesh->num_faces(), num_faces); + if (out_mesh) + *out_mesh = std::move(mesh); const std::unique_ptr pc(DecodePly(file_name)); ASSERT_NE(pc, nullptr) << "Failed to load test model " << file_name; @@ -45,7 +48,18 @@ class PlyDecoderTest : public ::testing::Test { TEST_F(PlyDecoderTest, TestPlyDecoding) { const std::string file_name = "test_pos_color.ply"; - test_decoding_method(file_name, 224, 114); + test_decoding_method(file_name, 224, 114, nullptr); +} + +TEST_F(PlyDecoderTest, TestPlyNormals) { + const std::string file_name = "cube_att.ply"; + std::unique_ptr mesh; + test_decoding_method(file_name, 12, 3 * 8, &mesh); + ASSERT_NE(mesh, nullptr); + const int att_id = mesh->GetNamedAttributeId(GeometryAttribute::NORMAL); + ASSERT_GE(att_id, 0); + const PointAttribute *const att = mesh->attribute(att_id); + ASSERT_EQ(att->size(), 6); // 6 unique normal values. } } // namespace draco diff --git a/javascript/example/DRACOLoader.js b/javascript/example/DRACOLoader.js index 0f0a0b9..f0c9b9c 100644 --- a/javascript/example/DRACOLoader.js +++ b/javascript/example/DRACOLoader.js @@ -75,7 +75,8 @@ THREE.DRACOLoader.prototype = { /* * Example on how to retrieve mesh and attributes. */ - let numFaces, numPoints, numVertexCoordinates, numTextureCoordinates, numAttributes; + let numFaces, numPoints; + let numVertexCoordinates, numTextureCoordinates, numAttributes; // For output basic geometry information. let geometryInfoStr; if (geometryType == DracoModule.TRIANGULAR_MESH) { @@ -137,9 +138,11 @@ THREE.DRACOLoader.prototype = { let textCoordAttributeData; if (texCoordAttId != -1) { geometryInfoStr += "\nLoaded texture coordinate attribute.\n"; - const texCoordAttribute = wrapper.GetAttribute(dracoGeometry, texCoordAttId); + const texCoordAttribute = wrapper.GetAttribute(dracoGeometry, + texCoordAttId); textCoordAttributeData = new DracoModule.DracoFloat32Array(); - wrapper.GetAttributeFloatForAllPoints(dracoGeometry, texCoordAttribute, + wrapper.GetAttributeFloatForAllPoints(dracoGeometry, + texCoordAttribute, textCoordAttributeData); } @@ -191,7 +194,7 @@ THREE.DRACOLoader.prototype = { DracoModule.destroy(norAttributeData); if (texCoordAttId != -1) DracoModule.destroy(textCoordAttributeData); - + // For mesh, we need to generate the faces. if (geometryType == DracoModule.TRIANGULAR_MESH) { const numIndices = numFaces * 3; diff --git a/point_cloud/point_attribute.cc b/point_cloud/point_attribute.cc index 5af8944..bcffc18 100644 --- a/point_cloud/point_attribute.cc +++ b/point_cloud/point_attribute.cc @@ -32,15 +32,17 @@ PointAttribute::PointAttribute(const GeometryAttribute &att) num_unique_entries_(0), identity_mapping_(false) {} -void PointAttribute::Reset(size_t num_attribute_values) { +bool PointAttribute::Reset(size_t num_attribute_values) { if (attribute_buffer_ == nullptr) { attribute_buffer_ = std::unique_ptr(new DataBuffer()); } const int64_t entry_size = DataTypeLength(data_type()) * components_count(); - attribute_buffer_->Update(nullptr, num_attribute_values * entry_size); + if (!attribute_buffer_->Update(nullptr, num_attribute_values * entry_size)) + return false; // Assign the new buffer to the parent attribute. ResetBuffer(attribute_buffer_.get(), entry_size, 0); num_unique_entries_ = num_attribute_values; + return true; } AttributeValueIndex::ValueType PointAttribute::DeduplicateValues( diff --git a/point_cloud/point_attribute.h b/point_cloud/point_attribute.h index 856a962..d603509 100644 --- a/point_cloud/point_attribute.h +++ b/point_cloud/point_attribute.h @@ -39,7 +39,7 @@ class PointAttribute : public GeometryAttribute { PointAttribute &operator=(PointAttribute &&attribute) = default; // Prepares the attribute storage for the specified number of entries. - void Reset(size_t num_attribute_values); + bool Reset(size_t num_attribute_values); size_t size() const { return num_unique_entries_; } AttributeValueIndex mapped_index(PointIndex point_index) const { diff --git a/testdata/cube_att.ply b/testdata/cube_att.ply new file mode 100644 index 0000000..ea280ef --- /dev/null +++ b/testdata/cube_att.ply @@ -0,0 +1,50 @@ +ply +format ascii 1.0 +comment VCGLIB generated +element vertex 24 +property float x +property float y +property float z +property float nx +property float ny +property float nz +element face 12 +property list uchar int vertex_indices +property list uchar float texcoord +end_header +0 0 0 -1 0 0 +0 0 1 0 0 1 +0 1 0 0 1 0 +0 1 1 0 1 0 +1 0 0 0 -1 0 +1 0 1 0 -1 0 +1 1 0 1 0 0 +1 1 1 1 0 0 +0 0 0 0 -1 0 +0 0 0 0 0 -1 +1 1 0 0 1 0 +1 1 0 0 0 -1 +1 0 0 1 0 0 +1 0 0 0 0 -1 +0 1 0 -1 0 0 +0 1 0 0 0 -1 +0 1 1 0 0 1 +0 1 1 -1 0 0 +0 0 1 0 -1 0 +0 0 1 -1 0 0 +1 1 1 0 0 1 +1 1 1 0 1 0 +1 0 1 0 0 1 +1 0 1 1 0 0 +3 9 11 13 6 1 1 0 0 0 1 +3 9 15 11 6 1 1 1 0 0 0 +3 0 17 14 6 0 1 1 0 0 0 +3 0 19 17 6 0 1 1 1 1 0 +3 2 21 10 6 0 0 1 1 1 0 +3 2 3 21 6 0 0 0 1 1 1 +3 12 6 7 6 1 1 1 0 0 0 +3 12 7 23 6 1 1 0 0 0 1 +3 8 4 5 6 0 1 1 1 1 0 +3 8 5 18 6 0 1 1 0 0 0 +3 1 22 20 6 0 1 1 1 1 0 +3 1 20 16 6 0 1 1 0 0 0 diff --git a/testdata/cube_att_partial.obj b/testdata/cube_att_partial.obj new file mode 100644 index 0000000..4534459 --- /dev/null +++ b/testdata/cube_att_partial.obj @@ -0,0 +1,35 @@ +# Attributes are only partially specified on the faces. + +v 0.0 0.0 0.0 +v 0.0 0.0 1.0 +v 0.0 1.0 0.0 +v 0.0 1.0 1.0 +v 1.0 0.0 0.0 +v 1.0 0.0 1.0 +v 1.0 1.0 0.0 +v 1.0 1.0 1.0 + +vn 0.0 0.0 1.0 +vn 0.0 0.0 -1.0 +vn 0.0 1.0 0.0 +vn 0.0 -1.0 0.0 +vn 1.0 0.0 0.0 +vn -1.0 0.0 0.0 + +vt 0.0 0.0 +vt 0.0 1.0 +vt 1.0 0.0 +vt 1.0 1.0 + +f 1/4 7/1 5/2 +f 1/4/2 3/3/2 7/1/2 +f 1/2 4/3 3/1 +f 1//6 2//6 4//6 +f 3/1/3 8/4/3 7/3/3 +f 3/1/3 4/2/3 8/4/3 +f 5//5 7//5 8//5 +f 5/4/5 8/1/5 6/2/5 +f 1/2/4 5/4/4 6/3/4 +f 1/2/4 6/3/4 2/1/4 +f 2 6 8 +f 2 8 4