From 437958ef91920bde96ebd8481540406a33aaa947 Mon Sep 17 00:00:00 2001 From: Jackie9527 <80555200+Jackie9527@users.noreply.github.com> Date: Sat, 25 Feb 2023 10:10:35 +0800 Subject: [PATCH 01/49] bugfix fails to check if point in triangle. Signed-off-by: Jackie9527 <80555200+Jackie9527@users.noreply.github.com> --- code/Common/PolyTools.h | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/code/Common/PolyTools.h b/code/Common/PolyTools.h index 9837a2991..a5bd1090b 100644 --- a/code/Common/PolyTools.h +++ b/code/Common/PolyTools.h @@ -74,26 +74,8 @@ inline bool OnLeftSideOfLine2D(const T& p0, const T& p1,const T& p2) { * both aiVector3D and aiVector2D, but generally ignores the third coordinate.*/ template inline bool PointInTriangle2D(const T& p0, const T& p1,const T& p2, const T& pp) { - // Point in triangle test using baryzentric coordinates - const aiVector2D v0 = p1 - p0; - const aiVector2D v1 = p2 - p0; - const aiVector2D v2 = pp - p0; - - double dot00 = v0 * v0; - double dot11 = v1 * v1; - const double dot01 = v0 * v1; - const double dot02 = v0 * v2; - const double dot12 = v1 * v2; - const double denom = dot00 * dot11 - dot01 * dot01; - if (denom == 0.0) { - return false; - } - - const double invDenom = 1.0 / denom; - dot11 = (dot11 * dot02 - dot01 * dot12) * invDenom; - dot00 = (dot00 * dot12 - dot01 * dot02) * invDenom; - - return (dot11 > 0) && (dot00 > 0) && (dot11 + dot00 < 1); + // pp should be left side of the three triangle side, by ccw arrow + return OnLeftSideOfLine2D(p0, p1, pp) && OnLeftSideOfLine2D(p1, p2, pp) && OnLeftSideOfLine2D(p2, p0, pp); } From a1aace74e58460d0d5c6462cea81682b5647e4b7 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 28 Feb 2023 21:53:18 +0100 Subject: [PATCH 02/49] Fix: Use C++17 compliant utf8 encoding. --- samples/SharedCode/UTFConverter.cpp | 2 +- samples/SharedCode/UTFConverter.h | 43 ++++++++++++++++++----------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/samples/SharedCode/UTFConverter.cpp b/samples/SharedCode/UTFConverter.cpp index a1bff7e4b..e6c07e946 100644 --- a/samples/SharedCode/UTFConverter.cpp +++ b/samples/SharedCode/UTFConverter.cpp @@ -46,7 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace AssimpSamples { namespace SharedCode { -typename UTFConverter::UTFConverterImpl UTFConverter::impl_; +//typename UTFConverter::UTFConverterImpl UTFConverter::impl_; } } diff --git a/samples/SharedCode/UTFConverter.h b/samples/SharedCode/UTFConverter.h index 17e89ee4d..34b2293de 100644 --- a/samples/SharedCode/UTFConverter.h +++ b/samples/SharedCode/UTFConverter.h @@ -45,43 +45,54 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define ASSIMP_SAMPLES_SHARED_CODE_UTFCONVERTER_H #include -#include -#include +#include + +#ifdef ASSIMP_USE_HUNTER +#include +#else +#include "../contrib/utf8cpp/source/utf8.h" +#endif namespace AssimpSamples { namespace SharedCode { // Used to convert between multibyte and unicode strings. class UTFConverter { - using UTFConverterImpl = std::wstring_convert, wchar_t>; public: - UTFConverter(const char* s) : - s_(s), - ws_(impl_.from_bytes(s)) { + //utf8::utf16to8(start, end, back_inserter(str)); + + UTFConverter(const char* s) : s_(s), ws_() { + std::vector str; + utf8::utf8to16(s, s + std::strlen(s) + 1, back_inserter(str)); } - UTFConverter(const wchar_t* s) : - s_(impl_.to_bytes(s)), - ws_(s) { + + UTFConverter(const wchar_t* s) : s_(),ws_(s) { + std::vector str; + utf8::utf16to8(s, s + ws_.size() + 1, back_inserter(str)); } - UTFConverter(const std::string& s) : - s_(s), - ws_(impl_.from_bytes(s)) { + + UTFConverter(const std::string& s) : s_(s), ws_() { + std::vector str; + utf8::utf8to16(s.c_str(), s.c_str() + s.size() + 1, back_inserter(str)); } - UTFConverter(const std::wstring& s) : - s_(impl_.to_bytes(s)), - ws_(s) { + + UTFConverter(const std::wstring& s) : s_(), ws_(s) { + std::vector str; + utf8::utf16to8(s.c_str(), s.c_str() + ws_.size() + 1, back_inserter(str)); } + inline const char* c_str() const { return s_.c_str(); } + inline const std::string& str() const { return s_; } + inline const wchar_t* c_wstr() const { return ws_.c_str(); } private: - static UTFConverterImpl impl_; std::string s_; std::wstring ws_; }; From 60da5e7e963d5753a5a532cea1ff78600cf8185f Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 28 Feb 2023 23:27:46 +0100 Subject: [PATCH 03/49] Update UTFConverter.cpp --- samples/SharedCode/UTFConverter.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/samples/SharedCode/UTFConverter.cpp b/samples/SharedCode/UTFConverter.cpp index e6c07e946..383ae43f1 100644 --- a/samples/SharedCode/UTFConverter.cpp +++ b/samples/SharedCode/UTFConverter.cpp @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2020, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -46,7 +44,5 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace AssimpSamples { namespace SharedCode { -//typename UTFConverter::UTFConverterImpl UTFConverter::impl_; - } } From 534ee288c5d5864f871a924a44ef5462c2acf435 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 28 Feb 2023 23:28:24 +0100 Subject: [PATCH 04/49] Update UTFConverter.h --- samples/SharedCode/UTFConverter.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/samples/SharedCode/UTFConverter.h b/samples/SharedCode/UTFConverter.h index 34b2293de..8173d474c 100644 --- a/samples/SharedCode/UTFConverter.h +++ b/samples/SharedCode/UTFConverter.h @@ -3,9 +3,7 @@ Open Asset Import Library (assimp) --------------------------------------------------------------------------- -Copyright (c) 2006-2020, assimp team - - +Copyright (c) 2006-2023, assimp team All rights reserved. @@ -56,11 +54,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace AssimpSamples { namespace SharedCode { -// Used to convert between multibyte and unicode strings. +/// @brief Used to convert between multibyte and unicode strings. class UTFConverter { public: - //utf8::utf16to8(start, end, back_inserter(str)); - UTFConverter(const char* s) : s_(s), ws_() { std::vector str; utf8::utf8to16(s, s + std::strlen(s) + 1, back_inserter(str)); From 39efe4c832c02e8e6560b9922b0504716fd3c99c Mon Sep 17 00:00:00 2001 From: Jackie9527 <80555200+Jackie9527@users.noreply.github.com> Date: Sat, 25 Feb 2023 10:11:35 +0800 Subject: [PATCH 05/49] upgrade draco to 1.5.6 Signed-off-by: Jackie9527 <80555200+Jackie9527@users.noreply.github.com> --- contrib/draco/.cmake-format.py | 217 +- contrib/draco/.gitattributes | 1 + contrib/draco/.gitmodules | 12 + contrib/draco/BUILDING.md | 86 +- contrib/draco/CMakeLists.txt | 1193 +++--- contrib/draco/README.md | 150 +- contrib/draco/cmake/DracoConfig.cmake | 3 - contrib/draco/cmake/FindDraco.cmake | 56 - contrib/draco/cmake/compiler_flags.cmake | 220 - contrib/draco/cmake/compiler_tests.cmake | 103 - .../draco/cmake/draco-config.cmake.template | 5 +- contrib/draco/cmake/draco.pc.template | 9 +- .../draco/cmake/draco_build_definitions.cmake | 79 +- contrib/draco/cmake/draco_cpu_detection.cmake | 14 + contrib/draco/cmake/draco_dependencies.cmake | 136 + contrib/draco/cmake/draco_emscripten.cmake | 177 +- contrib/draco/cmake/draco_flags.cmake | 51 +- contrib/draco/cmake/draco_helpers.cmake | 14 + contrib/draco/cmake/draco_install.cmake | 126 +- contrib/draco/cmake/draco_intrinsics.cmake | 34 +- contrib/draco/cmake/draco_options.cmake | 185 +- contrib/draco/cmake/draco_sanitizer.cmake | 22 +- contrib/draco/cmake/draco_targets.cmake | 147 +- contrib/draco/cmake/draco_test_config.h.cmake | 15 + contrib/draco/cmake/draco_tests.cmake | 154 +- contrib/draco/cmake/draco_variables.cmake | 29 +- contrib/draco/cmake/sanitizers.cmake | 19 - .../cmake/toolchains/aarch64-linux-gnu.cmake | 14 + .../cmake/toolchains/android-ndk-common.cmake | 14 + contrib/draco/cmake/toolchains/android.cmake | 20 +- .../cmake/toolchains/arm-ios-common.cmake | 16 +- .../toolchains/arm-linux-gnueabihf.cmake | 14 + .../toolchains/arm64-android-ndk-libcpp.cmake | 14 + .../draco/cmake/toolchains/arm64-ios.cmake | 15 +- .../cmake/toolchains/arm64-linux-gcc.cmake | 14 + .../toolchains/armv7-android-ndk-libcpp.cmake | 14 + .../draco/cmake/toolchains/armv7-ios.cmake | 15 +- .../cmake/toolchains/armv7-linux-gcc.cmake | 14 + .../draco/cmake/toolchains/armv7s-ios.cmake | 15 +- contrib/draco/cmake/toolchains/i386-ios.cmake | 15 +- .../toolchains/x86-android-ndk-libcpp.cmake | 14 + .../x86_64-android-ndk-libcpp.cmake | 14 + .../draco/cmake/toolchains/x86_64-ios.cmake | 15 +- contrib/draco/cmake/util.cmake | 79 - .../draco/src/draco/animation/animation.cc | 47 + contrib/draco/src/draco/animation/animation.h | 149 + .../src/draco/animation/animation_test.cc | 71 + .../keyframe_animation_encoding_test.cc | 17 +- .../animation/keyframe_animation_test.cc | 6 +- .../src/draco/animation/node_animation_data.h | 150 + contrib/draco/src/draco/animation/skin.cc | 29 + contrib/draco/src/draco/animation/skin.h | 64 + .../draco/attributes/attribute_transform.cc | 7 +- .../draco/attributes/geometry_attribute.cc | 2 +- .../src/draco/attributes/geometry_attribute.h | 225 +- .../src/draco/attributes/point_attribute.cc | 43 + .../src/draco/attributes/point_attribute.h | 6 + .../attributes/attributes_encoder.cc | 16 +- .../attributes/kd_tree_attributes_decoder.cc | 73 +- .../attributes/kd_tree_attributes_decoder.h | 4 + .../attributes/normal_compression_utils.h | 38 +- .../compression/attributes/point_d_vector.h | 12 +- ..._constrained_multi_parallelogram_decoder.h | 7 +- ..._constrained_multi_parallelogram_encoder.h | 2 +- ...ction_scheme_multi_parallelogram_decoder.h | 4 +- ...esh_prediction_scheme_tex_coords_decoder.h | 58 +- ...ction_scheme_tex_coords_portable_encoder.h | 5 +- ...ion_scheme_tex_coords_portable_predictor.h | 64 +- .../prediction_scheme_encoder_factory.cc | 53 +- .../prediction_scheme_encoder_factory.h | 7 +- ...ahedron_canonicalized_decoding_transform.h | 6 +- ...octahedron_canonicalized_transform_test.cc | 10 +- ...eme_normal_octahedron_decoding_transform.h | 18 +- ...scheme_normal_octahedron_transform_test.cc | 8 +- .../prediction_scheme_wrap_transform_base.h | 4 +- .../sequential_integer_attribute_decoder.cc | 10 +- .../sequential_integer_attribute_encoder.cc | 8 +- .../sequential_normal_attribute_encoder.cc | 3 +- .../bit_coders/direct_bit_decoder.h | 9 +- .../compression/config/encoder_options.h | 4 + .../src/draco/compression/decode_test.cc | 76 + .../compression/draco_compression_options.cc | 59 + .../compression/draco_compression_options.h | 141 + .../draco_compression_options_test.cc | 45 + contrib/draco/src/draco/compression/encode.h | 1 - .../draco/src/draco/compression/encode_base.h | 2 +- .../src/draco/compression/encode_test.cc | 187 +- .../draco/src/draco/compression/entropy/ans.h | 1 - .../compression/entropy/rans_symbol_decoder.h | 7 + .../compression/entropy/rans_symbol_encoder.h | 4 +- .../compression/entropy/symbol_decoding.cc | 2 +- .../src/draco/compression/expert_encode.cc | 118 + .../src/draco/compression/expert_encode.h | 6 + .../mesh/mesh_edgebreaker_decoder_impl.cc | 54 +- .../mesh/mesh_edgebreaker_encoder.cc | 1 - .../mesh/mesh_edgebreaker_encoder_impl.cc | 2 +- .../mesh/mesh_edgebreaker_encoder_impl.h | 1 - .../mesh/mesh_edgebreaker_encoding_test.cc | 27 +- .../mesh/mesh_edgebreaker_shared.h | 2 - ...sh_edgebreaker_traversal_valence_decoder.h | 6 +- .../compression/mesh/mesh_encoder_test.cc | 8 +- .../mesh/mesh_sequential_decoder.cc | 6 +- .../mesh/mesh_sequential_encoder.cc | 4 - .../mesh/mesh_sequential_encoder.h | 1 - ...mesh_attribute_indices_encoding_observer.h | 2 +- .../mesh/traverser/mesh_traversal_sequencer.h | 2 +- .../dynamic_integer_points_kd_tree_decoder.h | 53 +- .../dynamic_integer_points_kd_tree_encoder.h | 5 +- .../algorithms/float_points_tree_encoder.h | 4 +- .../integer_points_kd_tree_decoder.h | 3 +- .../integer_points_kd_tree_encoder.h | 3 +- .../algorithms/quantize_points_3.h | 6 +- .../point_cloud_kd_tree_encoding_test.cc | 4 +- contrib/draco/src/draco/core/bounding_box.cc | 15 +- contrib/draco/src/draco/core/bounding_box.h | 5 + contrib/draco/src/draco/core/constants.h | 6 + contrib/draco/src/draco/core/data_buffer.cc | 2 +- contrib/draco/src/draco/core/data_buffer.h | 2 +- contrib/draco/src/draco/core/decoder_buffer.h | 14 +- .../src/draco/core/draco_index_type_vector.h | 15 +- .../draco/src/draco/core/draco_test_utils.cc | 26 +- .../draco/src/draco/core/draco_test_utils.h | 53 + contrib/draco/src/draco/core/draco_version.h | 4 +- contrib/draco/src/draco/core/macros.h | 21 +- contrib/draco/src/draco/core/math_utils.h | 24 + .../draco/src/draco/core/math_utils_test.cc | 4 +- contrib/draco/src/draco/core/options.cc | 3 +- contrib/draco/src/draco/core/options.h | 7 +- contrib/draco/src/draco/core/status.cc | 44 + contrib/draco/src/draco/core/status.h | 6 + contrib/draco/src/draco/core/status_test.cc | 7 + contrib/draco/src/draco/core/vector_d.h | 6 +- contrib/draco/src/draco/core/vector_d_test.cc | 60 - .../draco/src/draco/io/file_reader_factory.cc | 2 +- contrib/draco/src/draco/io/file_utils.cc | 37 +- contrib/draco/src/draco/io/file_utils.h | 15 +- contrib/draco/src/draco/io/file_utils_test.cc | 2 + .../draco/src/draco/io/file_writer_factory.cc | 1 + .../draco/src/draco/io/file_writer_utils.cc | 21 +- .../src/draco/io/file_writer_utils_test.cc | 49 + contrib/draco/src/draco/io/gltf_decoder.cc | 2893 +++++++++++++ contrib/draco/src/draco/io/gltf_decoder.h | 524 +++ .../draco/src/draco/io/gltf_decoder_test.cc | 1402 +++++++ contrib/draco/src/draco/io/gltf_encoder.cc | 3662 +++++++++++++++++ contrib/draco/src/draco/io/gltf_encoder.h | 134 + .../draco/src/draco/io/gltf_encoder_test.cc | 1717 ++++++++ .../draco/src/draco/io/gltf_test_helper.cc | 823 ++++ contrib/draco/src/draco/io/gltf_test_helper.h | 61 + contrib/draco/src/draco/io/gltf_utils.cc | 154 + contrib/draco/src/draco/io/gltf_utils.h | 186 + contrib/draco/src/draco/io/gltf_utils_test.cc | 366 ++ .../src/draco/io/image_compression_options.h | 31 + contrib/draco/src/draco/io/mesh_io.cc | 33 +- contrib/draco/src/draco/io/obj_decoder.cc | 132 +- contrib/draco/src/draco/io/obj_decoder.h | 22 +- .../draco/src/draco/io/obj_decoder_test.cc | 128 +- contrib/draco/src/draco/io/obj_encoder.cc | 169 +- contrib/draco/src/draco/io/obj_encoder.h | 15 + .../draco/src/draco/io/obj_encoder_test.cc | 34 + contrib/draco/src/draco/io/parser_utils.cc | 43 +- .../draco/src/draco/io/ply_decoder_test.cc | 1 + contrib/draco/src/draco/io/ply_encoder.cc | 13 +- contrib/draco/src/draco/io/ply_reader_test.cc | 10 +- contrib/draco/src/draco/io/scene_io.cc | 127 + contrib/draco/src/draco/io/scene_io.h | 55 + contrib/draco/src/draco/io/scene_io_test.cc | 86 + .../src/draco/io/stdio_file_reader_test.cc | 2 +- contrib/draco/src/draco/io/stl_decoder.cc | 77 + contrib/draco/src/draco/io/stl_decoder.h | 38 + .../draco/src/draco/io/stl_decoder_test.cc | 49 + contrib/draco/src/draco/io/stl_encoder.cc | 111 + contrib/draco/src/draco/io/stl_encoder.h | 52 + .../draco/src/draco/io/stl_encoder_test.cc | 78 + contrib/draco/src/draco/io/texture_io.cc | 94 + contrib/draco/src/draco/io/texture_io.h | 56 + contrib/draco/src/draco/io/texture_io_test.cc | 55 + contrib/draco/src/draco/io/tiny_gltf_utils.cc | 230 ++ contrib/draco/src/draco/io/tiny_gltf_utils.h | 140 + .../animation_decoder_webidl_wrapper.cc | 101 - .../animation_decoder_webidl_wrapper.h | 73 - .../animation_encoder_webidl_wrapper.cc | 89 - .../animation_encoder_webidl_wrapper.h | 66 - .../emscripten/decoder_webidl_wrapper.cc | 12 +- .../draco_animation_decoder_glue_wrapper.cc | 28 - .../draco_animation_web_decoder.idl | 52 - .../draco_animation_web_encoder.idl | 34 - .../draco/javascript/emscripten/version.js | 2 +- contrib/draco/src/draco/material/material.cc | 258 ++ contrib/draco/src/draco/material/material.h | 276 ++ .../src/draco/material/material_library.cc | 125 + .../src/draco/material/material_library.h | 104 + .../draco/material/material_library_test.cc | 155 + .../draco/src/draco/material/material_test.cc | 320 ++ .../src/draco/material/material_utils.cc | 14 + .../draco/src/draco/material/material_utils.h | 14 + .../src/draco/material/material_utils_test.cc | 24 + contrib/draco/src/draco/mesh/corner_table.cc | 1 + contrib/draco/src/draco/mesh/corner_table.h | 1 + .../src/draco/mesh/corner_table_iterators.h | 32 +- .../draco/src/draco/mesh/corner_table_test.cc | 126 + contrib/draco/src/draco/mesh/mesh.cc | 433 ++ contrib/draco/src/draco/mesh/mesh.h | 180 + .../src/draco/mesh/mesh_are_equivalent.cc | 52 + .../draco/mesh/mesh_are_equivalent_test.cc | 37 + .../draco/mesh/mesh_attribute_corner_table.cc | 14 +- .../draco/mesh/mesh_attribute_corner_table.h | 10 +- contrib/draco/src/draco/mesh/mesh_cleanup.cc | 12 +- contrib/draco/src/draco/mesh/mesh_cleanup.h | 16 +- .../draco/src/draco/mesh/mesh_cleanup_test.cc | 16 +- .../draco/mesh/mesh_connected_components.h | 161 + contrib/draco/src/draco/mesh/mesh_features.cc | 98 + contrib/draco/src/draco/mesh/mesh_features.h | 93 + .../src/draco/mesh/mesh_features_test.cc | 98 + contrib/draco/src/draco/mesh/mesh_indices.h | 37 + .../src/draco/mesh/mesh_misc_functions.h | 1 - contrib/draco/src/draco/mesh/mesh_splitter.cc | 451 ++ contrib/draco/src/draco/mesh/mesh_splitter.h | 109 + .../mesh_splitter_test.cc} | 23 +- .../draco/src/draco/mesh/mesh_stripifier.h | 2 - contrib/draco/src/draco/mesh/mesh_test.cc | 644 +++ contrib/draco/src/draco/mesh/mesh_utils.cc | 492 +++ contrib/draco/src/draco/mesh/mesh_utils.h | 102 + .../draco/src/draco/mesh/mesh_utils_test.cc | 391 ++ .../draco/mesh/triangle_soup_mesh_builder.cc | 16 +- .../draco/mesh/triangle_soup_mesh_builder.h | 63 + .../mesh/triangle_soup_mesh_builder_test.cc | 72 +- .../src/draco/metadata/geometry_metadata.cc | 15 +- .../src/draco/metadata/geometry_metadata.h | 2 + contrib/draco/src/draco/metadata/metadata.cc | 8 + contrib/draco/src/draco/metadata/metadata.h | 1 + .../src/draco/metadata/metadata_decoder.cc | 21 +- .../draco/src/draco/metadata/metadata_test.cc | 6 +- .../src/draco/metadata/property_table.cc | 183 + .../draco/src/draco/metadata/property_table.h | 243 ++ .../src/draco/metadata/property_table_test.cc | 624 +++ .../src/draco/metadata/structural_metadata.cc | 74 + .../src/draco/metadata/structural_metadata.h | 64 + .../metadata/structural_metadata_test.cc | 170 + .../src/draco/point_cloud/point_cloud.cc | 39 +- .../draco/src/draco/point_cloud/point_cloud.h | 10 + .../draco/point_cloud/point_cloud_builder.cc | 2 + .../draco/point_cloud/point_cloud_builder.h | 11 + .../src/draco/point_cloud/point_cloud_test.cc | 54 + .../draco/src/draco/scene/instance_array.cc | 45 + .../draco/src/draco/scene/instance_array.h | 61 + .../src/draco/scene/instance_array_test.cc | 179 + contrib/draco/src/draco/scene/light.cc | 45 + contrib/draco/src/draco/scene/light.h | 81 + contrib/draco/src/draco/scene/light_test.cc | 64 + contrib/draco/src/draco/scene/mesh_group.h | 138 + .../draco/src/draco/scene/mesh_group_test.cc | 196 + contrib/draco/src/draco/scene/scene.cc | 174 + contrib/draco/src/draco/scene/scene.h | 258 ++ .../src/draco/scene/scene_are_equivalent.cc | 109 + .../src/draco/scene/scene_are_equivalent.h | 42 + .../draco/scene/scene_are_equivalent_test.cc | 86 + contrib/draco/src/draco/scene/scene_indices.h | 72 + contrib/draco/src/draco/scene/scene_node.h | 105 + contrib/draco/src/draco/scene/scene_test.cc | 295 ++ contrib/draco/src/draco/scene/scene_utils.cc | 962 +++++ contrib/draco/src/draco/scene/scene_utils.h | 150 + .../draco/src/draco/scene/scene_utils_test.cc | 763 ++++ contrib/draco/src/draco/scene/trs_matrix.cc | 102 + contrib/draco/src/draco/scene/trs_matrix.h | 124 + .../draco/src/draco/scene/trs_matrix_test.cc | 79 + .../draco/src/draco/texture/source_image.cc | 29 + .../draco/src/draco/texture/source_image.h | 72 + contrib/draco/src/draco/texture/texture.h | 46 + .../src/draco/texture/texture_library.cc | 61 + .../draco/src/draco/texture/texture_library.h | 67 + .../src/draco/texture/texture_library_test.cc | 22 + .../draco/src/draco/texture/texture_map.cc | 86 + contrib/draco/src/draco/texture/texture_map.h | 175 + .../src/draco/texture/texture_map_test.cc | 0 .../src/draco/texture/texture_transform.cc | 79 + .../src/draco/texture/texture_transform.h | 75 + .../draco/texture/texture_transform_test.cc | 0 .../draco/src/draco/texture/texture_utils.cc | 144 + .../draco/src/draco/texture/texture_utils.h | 78 + .../src/draco/texture/texture_utils_test.cc | 163 + .../draco/src/draco/tools/draco_decoder.cc | 20 +- .../draco/src/draco/tools/draco_encoder.cc | 59 +- .../draco/src/draco/tools/draco_transcoder.cc | 130 + .../src/draco/tools/draco_transcoder_lib.cc | 86 + .../src/draco/tools/draco_transcoder_lib.h | 103 + .../draco/tools/draco_transcoder_lib_test.cc | 172 + contrib/draco/src/draco/tools/fuzz/build.sh | 2 +- .../draco/tools/install_test/CMakeLists.txt | 25 + .../src/draco/tools/install_test/main.cc | 44 + .../src/draco/tools/install_test/test.py | 456 ++ 290 files changed, 30940 insertions(+), 2350 deletions(-) create mode 100644 contrib/draco/.gitattributes create mode 100644 contrib/draco/.gitmodules delete mode 100644 contrib/draco/cmake/DracoConfig.cmake delete mode 100644 contrib/draco/cmake/FindDraco.cmake delete mode 100644 contrib/draco/cmake/compiler_flags.cmake delete mode 100644 contrib/draco/cmake/compiler_tests.cmake create mode 100644 contrib/draco/cmake/draco_dependencies.cmake delete mode 100644 contrib/draco/cmake/sanitizers.cmake delete mode 100644 contrib/draco/cmake/util.cmake create mode 100644 contrib/draco/src/draco/animation/animation.cc create mode 100644 contrib/draco/src/draco/animation/animation.h create mode 100644 contrib/draco/src/draco/animation/animation_test.cc create mode 100644 contrib/draco/src/draco/animation/node_animation_data.h create mode 100644 contrib/draco/src/draco/animation/skin.cc create mode 100644 contrib/draco/src/draco/animation/skin.h create mode 100644 contrib/draco/src/draco/compression/draco_compression_options.cc create mode 100644 contrib/draco/src/draco/compression/draco_compression_options.h create mode 100644 contrib/draco/src/draco/compression/draco_compression_options_test.cc create mode 100644 contrib/draco/src/draco/core/constants.h create mode 100644 contrib/draco/src/draco/core/status.cc create mode 100644 contrib/draco/src/draco/io/file_writer_utils_test.cc create mode 100644 contrib/draco/src/draco/io/gltf_decoder.cc create mode 100644 contrib/draco/src/draco/io/gltf_decoder.h create mode 100644 contrib/draco/src/draco/io/gltf_decoder_test.cc create mode 100644 contrib/draco/src/draco/io/gltf_encoder.cc create mode 100644 contrib/draco/src/draco/io/gltf_encoder.h create mode 100644 contrib/draco/src/draco/io/gltf_encoder_test.cc create mode 100644 contrib/draco/src/draco/io/gltf_test_helper.cc create mode 100644 contrib/draco/src/draco/io/gltf_test_helper.h create mode 100644 contrib/draco/src/draco/io/gltf_utils.cc create mode 100644 contrib/draco/src/draco/io/gltf_utils.h create mode 100644 contrib/draco/src/draco/io/gltf_utils_test.cc create mode 100644 contrib/draco/src/draco/io/image_compression_options.h create mode 100644 contrib/draco/src/draco/io/scene_io.cc create mode 100644 contrib/draco/src/draco/io/scene_io.h create mode 100644 contrib/draco/src/draco/io/scene_io_test.cc create mode 100644 contrib/draco/src/draco/io/stl_decoder.cc create mode 100644 contrib/draco/src/draco/io/stl_decoder.h create mode 100644 contrib/draco/src/draco/io/stl_decoder_test.cc create mode 100644 contrib/draco/src/draco/io/stl_encoder.cc create mode 100644 contrib/draco/src/draco/io/stl_encoder.h create mode 100644 contrib/draco/src/draco/io/stl_encoder_test.cc create mode 100644 contrib/draco/src/draco/io/texture_io.cc create mode 100644 contrib/draco/src/draco/io/texture_io.h create mode 100644 contrib/draco/src/draco/io/texture_io_test.cc create mode 100644 contrib/draco/src/draco/io/tiny_gltf_utils.cc create mode 100644 contrib/draco/src/draco/io/tiny_gltf_utils.h delete mode 100644 contrib/draco/src/draco/javascript/emscripten/animation_decoder_webidl_wrapper.cc delete mode 100644 contrib/draco/src/draco/javascript/emscripten/animation_decoder_webidl_wrapper.h delete mode 100644 contrib/draco/src/draco/javascript/emscripten/animation_encoder_webidl_wrapper.cc delete mode 100644 contrib/draco/src/draco/javascript/emscripten/animation_encoder_webidl_wrapper.h delete mode 100644 contrib/draco/src/draco/javascript/emscripten/draco_animation_decoder_glue_wrapper.cc delete mode 100644 contrib/draco/src/draco/javascript/emscripten/draco_animation_web_decoder.idl delete mode 100644 contrib/draco/src/draco/javascript/emscripten/draco_animation_web_encoder.idl create mode 100644 contrib/draco/src/draco/material/material.cc create mode 100644 contrib/draco/src/draco/material/material.h create mode 100644 contrib/draco/src/draco/material/material_library.cc create mode 100644 contrib/draco/src/draco/material/material_library.h create mode 100644 contrib/draco/src/draco/material/material_library_test.cc create mode 100644 contrib/draco/src/draco/material/material_test.cc create mode 100644 contrib/draco/src/draco/material/material_utils.cc create mode 100644 contrib/draco/src/draco/material/material_utils.h create mode 100644 contrib/draco/src/draco/material/material_utils_test.cc create mode 100644 contrib/draco/src/draco/mesh/corner_table_test.cc create mode 100644 contrib/draco/src/draco/mesh/mesh_connected_components.h create mode 100644 contrib/draco/src/draco/mesh/mesh_features.cc create mode 100644 contrib/draco/src/draco/mesh/mesh_features.h create mode 100644 contrib/draco/src/draco/mesh/mesh_features_test.cc create mode 100644 contrib/draco/src/draco/mesh/mesh_indices.h create mode 100644 contrib/draco/src/draco/mesh/mesh_splitter.cc create mode 100644 contrib/draco/src/draco/mesh/mesh_splitter.h rename contrib/draco/src/draco/{javascript/emscripten/draco_animation_encoder_glue_wrapper.cc => mesh/mesh_splitter_test.cc} (51%) create mode 100644 contrib/draco/src/draco/mesh/mesh_test.cc create mode 100644 contrib/draco/src/draco/mesh/mesh_utils.cc create mode 100644 contrib/draco/src/draco/mesh/mesh_utils.h create mode 100644 contrib/draco/src/draco/mesh/mesh_utils_test.cc create mode 100644 contrib/draco/src/draco/metadata/property_table.cc create mode 100644 contrib/draco/src/draco/metadata/property_table.h create mode 100644 contrib/draco/src/draco/metadata/property_table_test.cc create mode 100644 contrib/draco/src/draco/metadata/structural_metadata.cc create mode 100644 contrib/draco/src/draco/metadata/structural_metadata.h create mode 100644 contrib/draco/src/draco/metadata/structural_metadata_test.cc create mode 100644 contrib/draco/src/draco/scene/instance_array.cc create mode 100644 contrib/draco/src/draco/scene/instance_array.h create mode 100644 contrib/draco/src/draco/scene/instance_array_test.cc create mode 100644 contrib/draco/src/draco/scene/light.cc create mode 100644 contrib/draco/src/draco/scene/light.h create mode 100644 contrib/draco/src/draco/scene/light_test.cc create mode 100644 contrib/draco/src/draco/scene/mesh_group.h create mode 100644 contrib/draco/src/draco/scene/mesh_group_test.cc create mode 100644 contrib/draco/src/draco/scene/scene.cc create mode 100644 contrib/draco/src/draco/scene/scene.h create mode 100644 contrib/draco/src/draco/scene/scene_are_equivalent.cc create mode 100644 contrib/draco/src/draco/scene/scene_are_equivalent.h create mode 100644 contrib/draco/src/draco/scene/scene_are_equivalent_test.cc create mode 100644 contrib/draco/src/draco/scene/scene_indices.h create mode 100644 contrib/draco/src/draco/scene/scene_node.h create mode 100644 contrib/draco/src/draco/scene/scene_test.cc create mode 100644 contrib/draco/src/draco/scene/scene_utils.cc create mode 100644 contrib/draco/src/draco/scene/scene_utils.h create mode 100644 contrib/draco/src/draco/scene/scene_utils_test.cc create mode 100644 contrib/draco/src/draco/scene/trs_matrix.cc create mode 100644 contrib/draco/src/draco/scene/trs_matrix.h create mode 100644 contrib/draco/src/draco/scene/trs_matrix_test.cc create mode 100644 contrib/draco/src/draco/texture/source_image.cc create mode 100644 contrib/draco/src/draco/texture/source_image.h create mode 100644 contrib/draco/src/draco/texture/texture.h create mode 100644 contrib/draco/src/draco/texture/texture_library.cc create mode 100644 contrib/draco/src/draco/texture/texture_library.h create mode 100644 contrib/draco/src/draco/texture/texture_library_test.cc create mode 100644 contrib/draco/src/draco/texture/texture_map.cc create mode 100644 contrib/draco/src/draco/texture/texture_map.h create mode 100644 contrib/draco/src/draco/texture/texture_map_test.cc create mode 100644 contrib/draco/src/draco/texture/texture_transform.cc create mode 100644 contrib/draco/src/draco/texture/texture_transform.h create mode 100644 contrib/draco/src/draco/texture/texture_transform_test.cc create mode 100644 contrib/draco/src/draco/texture/texture_utils.cc create mode 100644 contrib/draco/src/draco/texture/texture_utils.h create mode 100644 contrib/draco/src/draco/texture/texture_utils_test.cc create mode 100644 contrib/draco/src/draco/tools/draco_transcoder.cc create mode 100644 contrib/draco/src/draco/tools/draco_transcoder_lib.cc create mode 100644 contrib/draco/src/draco/tools/draco_transcoder_lib.h create mode 100644 contrib/draco/src/draco/tools/draco_transcoder_lib_test.cc create mode 100644 contrib/draco/src/draco/tools/install_test/CMakeLists.txt create mode 100644 contrib/draco/src/draco/tools/install_test/main.cc create mode 100644 contrib/draco/src/draco/tools/install_test/test.py diff --git a/contrib/draco/.cmake-format.py b/contrib/draco/.cmake-format.py index 64f2495b4..5b36f67aa 100644 --- a/contrib/draco/.cmake-format.py +++ b/contrib/draco/.cmake-format.py @@ -1,102 +1,137 @@ -# Generated with cmake-format 0.5.1 -# How wide to allow formatted cmake files -line_width = 80 - -# How many spaces to tab for indent -tab_size = 2 - -# If arglists are longer than this, break them always -max_subargs_per_line = 10 - -# If true, separate flow control names from their parentheses with a space -separate_ctrl_name_with_space = False - -# If true, separate function names from parentheses with a space -separate_fn_name_with_space = False - -# If a statement is wrapped to more than one line, than dangle the closing -# parenthesis on its own line -dangle_parens = False - -# What character to use for bulleted lists -bullet_char = '*' - -# What character to use as punctuation after numerals in an enumerated list -enum_char = '.' - -# What style line endings to use in the output. -line_ending = u'unix' - -# Format command names consistently as 'lower' or 'upper' case -command_case = u'lower' - -# Format keywords consistently as 'lower' or 'upper' case -keyword_case = u'unchanged' - -# Specify structure for custom cmake functions -additional_commands = { - "foo": { - "flags": [ - "BAR", - "BAZ" - ], - "kwargs": { - "HEADERS": "*", - "DEPENDS": "*", - "SOURCES": "*" - } +with section('parse'): + # Specify structure for custom cmake functions + additional_commands = { + 'draco_add_emscripten_executable': { + 'kwargs': { + 'NAME': '*', + 'SOURCES': '*', + 'OUTPUT_NAME': '*', + 'DEFINES': '*', + 'INCLUDES': '*', + 'COMPILE_FLAGS': '*', + 'LINK_FLAGS': '*', + 'OBJLIB_DEPS': '*', + 'LIB_DEPS': '*', + 'GLUE_PATH': '*', + 'PRE_LINK_JS_SOURCES': '*', + 'POST_LINK_JS_SOURCES': '*', + 'FEATURES': '*', + }, + 'pargs': 0, + }, + 'draco_add_executable': { + 'kwargs': { + 'NAME': '*', + 'SOURCES': '*', + 'OUTPUT_NAME': '*', + 'TEST': 0, + 'DEFINES': '*', + 'INCLUDES': '*', + 'COMPILE_FLAGS': '*', + 'LINK_FLAGS': '*', + 'OBJLIB_DEPS': '*', + 'LIB_DEPS': '*', + }, + 'pargs': 0, + }, + 'draco_add_library': { + 'kwargs': { + 'NAME': '*', + 'TYPE': '*', + 'SOURCES': '*', + 'TEST': 0, + 'OUTPUT_NAME': '*', + 'DEFINES': '*', + 'INCLUDES': '*', + 'COMPILE_FLAGS': '*', + 'LINK_FLAGS': '*', + 'OBJLIB_DEPS': '*', + 'LIB_DEPS': '*', + 'PUBLIC_INCLUDES': '*', + }, + 'pargs': 0, + }, + 'draco_generate_emscripten_glue': { + 'kwargs': { + 'INPUT_IDL': '*', + 'OUTPUT_PATH': '*', + }, + 'pargs': 0, + }, + 'draco_get_required_emscripten_flags': { + 'kwargs': { + 'FLAG_LIST_VAR_COMPILER': '*', + 'FLAG_LIST_VAR_LINKER': '*', + }, + 'pargs': 0, + }, + 'draco_option': { + 'kwargs': { + 'NAME': '*', + 'HELPSTRING': '*', + 'VALUE': '*', + }, + 'pargs': 0, + }, + # Rules for built in CMake commands and those from dependencies. + 'list': { + 'kwargs': { + 'APPEND': '*', + 'FILTER': '*', + 'FIND': '*', + 'GET': '*', + 'INSERT': '*', + 'JOIN': '*', + 'LENGTH': '*', + 'POP_BACK': '*', + 'POP_FRONT': '*', + 'PREPEND': '*', + 'REMOVE_DUPLICATES': '*', + 'REMOVE_ITEM': '*', + 'REVERSE': '*', + 'SORT': '*', + 'SUBLIST': '*', + 'TRANSFORM': '*', + }, + }, + 'protobuf_generate': { + 'kwargs': { + 'IMPORT_DIRS': '*', + 'LANGUAGE': '*', + 'OUT_VAR': '*', + 'PROTOC_OUT_DIR': '*', + 'PROTOS': '*', + }, + }, } -} -# A list of command names which should always be wrapped -always_wrap = [] +with section('format'): + # Formatting options. -# Specify the order of wrapping algorithms during successive reflow attempts -algorithm_order = [0, 1, 2, 3, 4] + # How wide to allow formatted cmake files + line_width = 80 -# If true, the argument lists which are known to be sortable will be sorted -# lexicographicall -autosort = False + # How many spaces to tab for indent + tab_size = 2 -# enable comment markup parsing and reflow -enable_markup = True + # If true, separate flow control names from their parentheses with a space + separate_ctrl_name_with_space = False -# If comment markup is enabled, don't reflow the first comment block in -# eachlistfile. Use this to preserve formatting of your -# copyright/licensestatements. -first_comment_is_literal = False + # If true, separate function names from parentheses with a space + separate_fn_name_with_space = False -# If comment markup is enabled, don't reflow any comment block which matchesthis -# (regex) pattern. Default is `None` (disabled). -literal_comment_pattern = None + # If a statement is wrapped to more than one line, than dangle the closing + # parenthesis on its own line. + dangle_parens = False -# Regular expression to match preformat fences in comments -# default=r'^\s*([`~]{3}[`~]*)(.*)$' -fence_pattern = u'^\\s*([`~]{3}[`~]*)(.*)$' + # Do not sort argument lists. + enable_sort = False -# Regular expression to match rulers in comments -# default=r'^\s*[^\w\s]{3}.*[^\w\s]{3}$' -ruler_pattern = u'^\\s*[^\\w\\s]{3}.*[^\\w\\s]{3}$' + # What style line endings to use in the output. + line_ending = 'unix' -# If true, emit the unicode byte-order mark (BOM) at the start of the file -emit_byteorder_mark = False + # Format command names consistently as 'lower' or 'upper' case + command_case = 'canonical' -# If a comment line starts with at least this many consecutive hash characters, -# then don't lstrip() them off. This allows for lazy hash rulers where the first -# hash char is not separated by space -hashruler_min_length = 10 - -# If true, then insert a space between the first hash char and remaining hash -# chars in a hash ruler, and normalize its length to fill the column -canonicalize_hashrulers = True - -# Specify the encoding of the input file. Defaults to utf-8. -input_encoding = u'utf-8' - -# Specify the encoding of the output file. Defaults to utf-8. Note that cmake -# only claims to support utf-8 so be careful when using anything else -output_encoding = u'utf-8' - -# A dictionary containing any per-command configuration overrides. Currently -# only `command_case` is supported. -per_command = {} + # Format keywords consistently as 'lower' or 'upper' case + keyword_case = 'upper' diff --git a/contrib/draco/.gitattributes b/contrib/draco/.gitattributes new file mode 100644 index 000000000..96acfc612 --- /dev/null +++ b/contrib/draco/.gitattributes @@ -0,0 +1 @@ +*.obj eol=lf \ No newline at end of file diff --git a/contrib/draco/.gitmodules b/contrib/draco/.gitmodules new file mode 100644 index 000000000..25f0a1c03 --- /dev/null +++ b/contrib/draco/.gitmodules @@ -0,0 +1,12 @@ +[submodule "third_party/googletest"] + path = third_party/googletest + url = https://github.com/google/googletest.git +[submodule "third_party/eigen"] + path = third_party/eigen + url = https://gitlab.com/libeigen/eigen.git +[submodule "third_party/tinygltf"] + path = third_party/tinygltf + url = https://github.com/syoyo/tinygltf.git +[submodule "third_party/filesystem"] + path = third_party/filesystem + url = https://github.com/gulrak/filesystem diff --git a/contrib/draco/BUILDING.md b/contrib/draco/BUILDING.md index d33917b88..340b2b83b 100644 --- a/contrib/draco/BUILDING.md +++ b/contrib/draco/BUILDING.md @@ -4,8 +4,10 @@ _**Contents**_ * [Mac OS X](#mac-os-x) * [Windows](#windows) * [CMake Build Configuration](#cmake-build-configuration) + * [Transcoder](#transcoder) * [Debugging and Optimization](#debugging-and-optimization) * [Googletest Integration](#googletest-integration) + * [Third Party Libraries](#third-party-libraries) * [Javascript Encoder/Decoder](#javascript-encoderdecoder) * [WebAssembly Decoder](#webassembly-decoder) * [WebAssembly Mesh Only Decoder](#webassembly-mesh-only-decoder) @@ -72,6 +74,43 @@ C:\Users\nobody> cmake ../ -G "Visual Studio 16 2019" -A x64 CMake Build Configuration ------------------------- +Transcoder +---------- + +Before attempting to build Draco with transcoding support you must run an +additional Git command to obtain the submodules: + +~~~~~ bash +# Run this command from within your Draco clone. +$ git submodule update --init +# See below if you prefer to use existing versions of Draco dependencies. +~~~~~ + +In order to build the `draco_transcoder` target, the transcoding support needs +to be explicitly enabled when you run `cmake`, for example: + +~~~~~ bash +$ cmake ../ -DDRACO_TRANSCODER_SUPPORTED=ON +~~~~~ + +The above option is currently not compatible with our Javascript or WebAssembly +builds but all other use cases are supported. Note that binaries and libraries +built with the transcoder support may result in increased binary sizes of the +produced libraries and executables compared to the default CMake settings. + +The following CMake variables can be used to configure Draco to use local +copies of third party dependencies instead of git submodules. + +- `DRACO_EIGEN_PATH`: this path must contain an Eigen directory that includes + the Eigen sources. +- `DRACO_FILESYSTEM_PATH`: this path must contain the ghc directory where the + filesystem includes are located. +- `DRACO_TINYGLTF_PATH`: this path must contain tiny_gltf.h and its + dependencies. + +When not specified the Draco build requires the presence of the submodules that +are stored within `draco/third_party`. + Debugging and Optimization -------------------------- @@ -114,17 +153,52 @@ $ cmake ../ -DDRACO_SANITIZE=address Googletest Integration ---------------------- -Draco includes testing support built using Googletest. To enable Googletest unit -test support the DRACO_TESTS cmake variable must be turned on at cmake -generation time: +Draco includes testing support built using Googletest. The Googletest repository +is included as a submodule of the Draco git repository. Run the following +command to clone the Googletest repository: + +~~~~~ bash +$ git submodule update --init +~~~~~ + +To enable Googletest unit test support the DRACO_TESTS cmake variable must be +turned on at cmake generation time: ~~~~~ bash $ cmake ../ -DDRACO_TESTS=ON ~~~~~ -When cmake is used as shown in the above example the googletest directory must -be a sibling of the Draco repository root directory. To run the tests execute -`draco_tests` from your build output directory. +To run the tests execute `draco_tests` from your build output directory: + +~~~~~ bash +$ ./draco_tests +~~~~~ + +Draco can be configured to use a local Googletest installation. The +`DRACO_GOOGLETEST_PATH` variable overrides the behavior described above and +configures Draco to use the Googletest at the specified path. + +Third Party Libraries +--------------------- + +When Draco is built with transcoding and/or testing support enabled the project +has dependencies on third party libraries: + +- [Eigen](https://eigen.tuxfamily.org/) + - Provides various math utilites. +- [Googletest](https://github.com/google/googletest) + - Provides testing support. +- [Gulrak/filesystem](https://github.com/gulrak/filesystem) + - Provides C++17 std::filesystem emulation for pre-C++17 environments. +- [TinyGLTF](https://github.com/syoyo/tinygltf) + - Provides GLTF I/O support. + +These dependencies are managed as Git submodules. To obtain the dependencies +run the following command in your Draco repository: + +~~~~~ bash +$ git submodule update --init +~~~~~ WebAssembly Decoder ------------------- diff --git a/contrib/draco/CMakeLists.txt b/contrib/draco/CMakeLists.txt index 6ea9b21fd..a93267d25 100644 --- a/contrib/draco/CMakeLists.txt +++ b/contrib/draco/CMakeLists.txt @@ -1,7 +1,18 @@ -cmake_minimum_required(VERSION 3.12 FATAL_ERROR) +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. -# Draco requires C++11. -set(CMAKE_CXX_STANDARD 11) +cmake_minimum_required(VERSION 3.12 FATAL_ERROR) project(draco C CXX) if(NOT CMAKE_BUILD_TYPE) @@ -10,21 +21,23 @@ endif() set(draco_root "${CMAKE_CURRENT_SOURCE_DIR}") set(draco_src_root "${draco_root}/src/draco") -set(draco_build "${Assimp_BINARY_DIR}") +set(draco_build "${CMAKE_BINARY_DIR}") if("${draco_root}" STREQUAL "${draco_build}") message( - FATAL_ERROR "Building from within the Draco source tree is not supported.\n" - "Hint: Run these commands\n" - "$ rm -rf CMakeCache.txt CMakeFiles\n" - "$ mkdir -p ../draco_build\n" "$ cd ../draco_build\n" - "And re-run CMake from the draco_build directory.") + FATAL_ERROR + "Building from within the Draco source tree is not supported.\n" + "Hint: Run these commands\n" + "$ rm -rf CMakeCache.txt CMakeFiles\n" + "$ mkdir -p ../draco_build\n" + "$ cd ../draco_build\n" + "And re-run CMake from the draco_build directory.") endif() -include(CMakePackageConfigHelpers) include(FindPythonInterp) include("${draco_root}/cmake/draco_build_definitions.cmake") include("${draco_root}/cmake/draco_cpu_detection.cmake") +include("${draco_root}/cmake/draco_dependencies.cmake") include("${draco_root}/cmake/draco_emscripten.cmake") include("${draco_root}/cmake/draco_flags.cmake") include("${draco_root}/cmake/draco_helpers.cmake") @@ -49,6 +62,7 @@ draco_track_configuration_variable(DRACO_GENERATED_SOURCES_DIRECTORY) # Controls use of std::mutex and absl::Mutex in ThreadPool. draco_track_configuration_variable(DRACO_THREADPOOL_USE_STD_MUTEX) + if(DRACO_VERBOSE) draco_dump_cmake_flag_variables() draco_dump_tracked_configuration_variables() @@ -68,29 +82,32 @@ draco_reset_target_lists() draco_setup_options() draco_set_build_definitions() draco_set_cxx_flags() +draco_set_exe_linker_flags() draco_generate_features_h() # Draco source file listing variables. -list(APPEND draco_attributes_sources - "${draco_src_root}/attributes/attribute_octahedron_transform.cc" - "${draco_src_root}/attributes/attribute_octahedron_transform.h" - "${draco_src_root}/attributes/attribute_quantization_transform.cc" - "${draco_src_root}/attributes/attribute_quantization_transform.h" - "${draco_src_root}/attributes/attribute_transform.cc" - "${draco_src_root}/attributes/attribute_transform.h" - "${draco_src_root}/attributes/attribute_transform_data.h" - "${draco_src_root}/attributes/attribute_transform_type.h" - "${draco_src_root}/attributes/geometry_attribute.cc" - "${draco_src_root}/attributes/geometry_attribute.h" - "${draco_src_root}/attributes/geometry_indices.h" - "${draco_src_root}/attributes/point_attribute.cc" - "${draco_src_root}/attributes/point_attribute.h") +list( + APPEND draco_attributes_sources + "${draco_src_root}/attributes/attribute_octahedron_transform.cc" + "${draco_src_root}/attributes/attribute_octahedron_transform.h" + "${draco_src_root}/attributes/attribute_quantization_transform.cc" + "${draco_src_root}/attributes/attribute_quantization_transform.h" + "${draco_src_root}/attributes/attribute_transform.cc" + "${draco_src_root}/attributes/attribute_transform.h" + "${draco_src_root}/attributes/attribute_transform_data.h" + "${draco_src_root}/attributes/attribute_transform_type.h" + "${draco_src_root}/attributes/geometry_attribute.cc" + "${draco_src_root}/attributes/geometry_attribute.h" + "${draco_src_root}/attributes/geometry_indices.h" + "${draco_src_root}/attributes/point_attribute.cc" + "${draco_src_root}/attributes/point_attribute.h") list( APPEND draco_compression_attributes_dec_sources "${draco_src_root}/compression/attributes/attributes_decoder.cc" "${draco_src_root}/compression/attributes/attributes_decoder.h" + "${draco_src_root}/compression/attributes/attributes_decoder_interface.h" "${draco_src_root}/compression/attributes/kd_tree_attributes_decoder.cc" "${draco_src_root}/compression/attributes/kd_tree_attributes_decoder.h" "${draco_src_root}/compression/attributes/kd_tree_attributes_shared.h" @@ -107,7 +124,7 @@ list( "${draco_src_root}/compression/attributes/sequential_normal_attribute_decoder.h" "${draco_src_root}/compression/attributes/sequential_quantization_attribute_decoder.cc" "${draco_src_root}/compression/attributes/sequential_quantization_attribute_decoder.h" - ) +) list( APPEND @@ -128,7 +145,7 @@ list( "${draco_src_root}/compression/attributes/sequential_normal_attribute_encoder.h" "${draco_src_root}/compression/attributes/sequential_quantization_attribute_encoder.cc" "${draco_src_root}/compression/attributes/sequential_quantization_attribute_encoder.h" - ) +) list( @@ -160,7 +177,7 @@ list( "${draco_src_root}/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_transform_base.h" "${draco_src_root}/compression/attributes/prediction_schemes/prediction_scheme_wrap_decoding_transform.h" "${draco_src_root}/compression/attributes/prediction_schemes/prediction_scheme_wrap_transform_base.h" - ) +) list( APPEND @@ -192,7 +209,7 @@ list( "${draco_src_root}/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_transform_base.h" "${draco_src_root}/compression/attributes/prediction_schemes/prediction_scheme_wrap_encoding_transform.h" "${draco_src_root}/compression/attributes/prediction_schemes/prediction_scheme_wrap_transform_base.h" - ) +) list( APPEND @@ -217,27 +234,34 @@ list( "${draco_src_root}/compression/bit_coders/symbol_bit_encoder.cc" "${draco_src_root}/compression/bit_coders/symbol_bit_encoder.h") -list(APPEND draco_enc_config_sources - "${draco_src_root}/compression/config/compression_shared.h" - "${draco_src_root}/compression/config/draco_options.h" - "${draco_src_root}/compression/config/encoder_options.h" - "${draco_src_root}/compression/config/encoding_features.h") +list( + APPEND draco_enc_config_sources + "${draco_src_root}/compression/config/compression_shared.h" + "${draco_src_root}/compression/config/draco_options.h" + "${draco_src_root}/compression/config/encoder_options.h" + "${draco_src_root}/compression/config/encoding_features.h") -list(APPEND draco_dec_config_sources - "${draco_src_root}/compression/config/compression_shared.h" - "${draco_src_root}/compression/config/decoder_options.h" - "${draco_src_root}/compression/config/draco_options.h") +list( + APPEND draco_dec_config_sources + "${draco_src_root}/compression/config/compression_shared.h" + "${draco_src_root}/compression/config/decoder_options.h" + "${draco_src_root}/compression/config/draco_options.h") + +list(APPEND draco_compression_options_sources + "${draco_src_root}/compression/draco_compression_options.cc" + "${draco_src_root}/compression/draco_compression_options.h") list(APPEND draco_compression_decode_sources "${draco_src_root}/compression/decode.cc" "${draco_src_root}/compression/decode.h") -list(APPEND draco_compression_encode_sources - "${draco_src_root}/compression/encode.cc" - "${draco_src_root}/compression/encode.h" - "${draco_src_root}/compression/encode_base.h" - "${draco_src_root}/compression/expert_encode.cc" - "${draco_src_root}/compression/expert_encode.h") +list( + APPEND draco_compression_encode_sources + "${draco_src_root}/compression/encode.cc" + "${draco_src_root}/compression/encode.h" + "${draco_src_root}/compression/encode_base.h" + "${draco_src_root}/compression/expert_encode.cc" + "${draco_src_root}/compression/expert_encode.h") list( APPEND @@ -291,7 +315,7 @@ list( "${draco_src_root}/compression/point_cloud/point_cloud_kd_tree_decoder.h" "${draco_src_root}/compression/point_cloud/point_cloud_sequential_decoder.cc" "${draco_src_root}/compression/point_cloud/point_cloud_sequential_decoder.h" - ) +) list( APPEND @@ -302,112 +326,126 @@ list( "${draco_src_root}/compression/point_cloud/point_cloud_kd_tree_encoder.h" "${draco_src_root}/compression/point_cloud/point_cloud_sequential_encoder.cc" "${draco_src_root}/compression/point_cloud/point_cloud_sequential_encoder.h" - ) +) -list(APPEND draco_compression_entropy_sources - "${draco_src_root}/compression/entropy/ans.h" - "${draco_src_root}/compression/entropy/rans_symbol_coding.h" - "${draco_src_root}/compression/entropy/rans_symbol_decoder.h" - "${draco_src_root}/compression/entropy/rans_symbol_encoder.h" - "${draco_src_root}/compression/entropy/shannon_entropy.cc" - "${draco_src_root}/compression/entropy/shannon_entropy.h" - "${draco_src_root}/compression/entropy/symbol_decoding.cc" - "${draco_src_root}/compression/entropy/symbol_decoding.h" - "${draco_src_root}/compression/entropy/symbol_encoding.cc" - "${draco_src_root}/compression/entropy/symbol_encoding.h") +list( + APPEND draco_compression_entropy_sources + "${draco_src_root}/compression/entropy/ans.h" + "${draco_src_root}/compression/entropy/rans_symbol_coding.h" + "${draco_src_root}/compression/entropy/rans_symbol_decoder.h" + "${draco_src_root}/compression/entropy/rans_symbol_encoder.h" + "${draco_src_root}/compression/entropy/shannon_entropy.cc" + "${draco_src_root}/compression/entropy/shannon_entropy.h" + "${draco_src_root}/compression/entropy/symbol_decoding.cc" + "${draco_src_root}/compression/entropy/symbol_decoding.h" + "${draco_src_root}/compression/entropy/symbol_encoding.cc" + "${draco_src_root}/compression/entropy/symbol_encoding.h") -list(APPEND draco_core_sources - "${draco_src_root}/core/bit_utils.cc" - "${draco_src_root}/core/bit_utils.h" - "${draco_src_root}/core/bounding_box.cc" - "${draco_src_root}/core/bounding_box.h" - "${draco_src_root}/core/cycle_timer.cc" - "${draco_src_root}/core/cycle_timer.h" - "${draco_src_root}/core/data_buffer.cc" - "${draco_src_root}/core/data_buffer.h" - "${draco_src_root}/core/decoder_buffer.cc" - "${draco_src_root}/core/decoder_buffer.h" - "${draco_src_root}/core/divide.cc" - "${draco_src_root}/core/divide.h" - "${draco_src_root}/core/draco_index_type.h" - "${draco_src_root}/core/draco_index_type_vector.h" - "${draco_src_root}/core/draco_types.cc" - "${draco_src_root}/core/draco_types.h" - "${draco_src_root}/core/encoder_buffer.cc" - "${draco_src_root}/core/encoder_buffer.h" - "${draco_src_root}/core/hash_utils.cc" - "${draco_src_root}/core/hash_utils.h" - "${draco_src_root}/core/macros.h" - "${draco_src_root}/core/math_utils.h" - "${draco_src_root}/core/options.cc" - "${draco_src_root}/core/options.h" - "${draco_src_root}/core/quantization_utils.cc" - "${draco_src_root}/core/quantization_utils.h" - "${draco_src_root}/core/status.h" - "${draco_src_root}/core/status_or.h" - "${draco_src_root}/core/varint_decoding.h" - "${draco_src_root}/core/varint_encoding.h" - "${draco_src_root}/core/vector_d.h") +list( + APPEND draco_core_sources + "${draco_src_root}/core/bit_utils.cc" + "${draco_src_root}/core/bit_utils.h" + "${draco_src_root}/core/bounding_box.cc" + "${draco_src_root}/core/bounding_box.h" + "${draco_src_root}/core/constants.h" + "${draco_src_root}/core/cycle_timer.cc" + "${draco_src_root}/core/cycle_timer.h" + "${draco_src_root}/core/data_buffer.cc" + "${draco_src_root}/core/data_buffer.h" + "${draco_src_root}/core/decoder_buffer.cc" + "${draco_src_root}/core/decoder_buffer.h" + "${draco_src_root}/core/divide.cc" + "${draco_src_root}/core/divide.h" + "${draco_src_root}/core/draco_index_type.h" + "${draco_src_root}/core/draco_index_type_vector.h" + "${draco_src_root}/core/draco_types.cc" + "${draco_src_root}/core/draco_types.h" + "${draco_src_root}/core/draco_version.h" + "${draco_src_root}/core/encoder_buffer.cc" + "${draco_src_root}/core/encoder_buffer.h" + "${draco_src_root}/core/hash_utils.cc" + "${draco_src_root}/core/hash_utils.h" + "${draco_src_root}/core/macros.h" + "${draco_src_root}/core/math_utils.h" + "${draco_src_root}/core/options.cc" + "${draco_src_root}/core/options.h" + "${draco_src_root}/core/quantization_utils.cc" + "${draco_src_root}/core/quantization_utils.h" + "${draco_src_root}/core/status.h" + "${draco_src_root}/core/status_or.h" + "${draco_src_root}/core/varint_decoding.h" + "${draco_src_root}/core/varint_encoding.h" + "${draco_src_root}/core/vector_d.h") -list(APPEND draco_io_sources - "${draco_src_root}/io/file_reader_factory.cc" - "${draco_src_root}/io/file_reader_factory.h" - "${draco_src_root}/io/file_reader_interface.h" - "${draco_src_root}/io/file_utils.cc" - "${draco_src_root}/io/file_utils.h" - "${draco_src_root}/io/file_writer_factory.cc" - "${draco_src_root}/io/file_writer_factory.h" - "${draco_src_root}/io/file_writer_interface.h" - "${draco_src_root}/io/file_writer_utils.h" - "${draco_src_root}/io/file_writer_utils.cc" - "${draco_src_root}/io/mesh_io.cc" - "${draco_src_root}/io/mesh_io.h" - "${draco_src_root}/io/obj_decoder.cc" - "${draco_src_root}/io/obj_decoder.h" - "${draco_src_root}/io/obj_encoder.cc" - "${draco_src_root}/io/obj_encoder.h" - "${draco_src_root}/io/parser_utils.cc" - "${draco_src_root}/io/parser_utils.h" - "${draco_src_root}/io/ply_decoder.cc" - "${draco_src_root}/io/ply_decoder.h" - "${draco_src_root}/io/ply_encoder.cc" - "${draco_src_root}/io/ply_encoder.h" - "${draco_src_root}/io/ply_property_reader.h" - "${draco_src_root}/io/ply_property_writer.h" - "${draco_src_root}/io/ply_reader.cc" - "${draco_src_root}/io/ply_reader.h" - "${draco_src_root}/io/point_cloud_io.cc" - "${draco_src_root}/io/point_cloud_io.h" - "${draco_src_root}/io/stdio_file_reader.cc" - "${draco_src_root}/io/stdio_file_reader.h" - "${draco_src_root}/io/stdio_file_writer.cc" - "${draco_src_root}/io/stdio_file_writer.h") +list( + APPEND draco_io_sources + "${draco_src_root}/io/file_reader_factory.cc" + "${draco_src_root}/io/file_reader_factory.h" + "${draco_src_root}/io/file_reader_interface.h" + "${draco_src_root}/io/file_utils.cc" + "${draco_src_root}/io/file_utils.h" + "${draco_src_root}/io/file_writer_factory.cc" + "${draco_src_root}/io/file_writer_factory.h" + "${draco_src_root}/io/file_writer_interface.h" + "${draco_src_root}/io/file_writer_utils.h" + "${draco_src_root}/io/file_writer_utils.cc" + "${draco_src_root}/io/mesh_io.cc" + "${draco_src_root}/io/mesh_io.h" + "${draco_src_root}/io/obj_decoder.cc" + "${draco_src_root}/io/obj_decoder.h" + "${draco_src_root}/io/obj_encoder.cc" + "${draco_src_root}/io/obj_encoder.h" + "${draco_src_root}/io/parser_utils.cc" + "${draco_src_root}/io/parser_utils.h" + "${draco_src_root}/io/ply_decoder.cc" + "${draco_src_root}/io/ply_decoder.h" + "${draco_src_root}/io/ply_encoder.cc" + "${draco_src_root}/io/ply_encoder.h" + "${draco_src_root}/io/ply_property_reader.h" + "${draco_src_root}/io/ply_property_writer.h" + "${draco_src_root}/io/ply_reader.cc" + "${draco_src_root}/io/ply_reader.h" + "${draco_src_root}/io/stl_decoder.cc" + "${draco_src_root}/io/stl_decoder.h" + "${draco_src_root}/io/stl_encoder.cc" + "${draco_src_root}/io/stl_encoder.h" + "${draco_src_root}/io/point_cloud_io.cc" + "${draco_src_root}/io/point_cloud_io.h" + "${draco_src_root}/io/stdio_file_reader.cc" + "${draco_src_root}/io/stdio_file_reader.h" + "${draco_src_root}/io/stdio_file_writer.cc" + "${draco_src_root}/io/stdio_file_writer.h") -list(APPEND draco_mesh_sources - "${draco_src_root}/mesh/corner_table.cc" - "${draco_src_root}/mesh/corner_table.h" - "${draco_src_root}/mesh/corner_table_iterators.h" - "${draco_src_root}/mesh/mesh.cc" - "${draco_src_root}/mesh/mesh.h" - "${draco_src_root}/mesh/mesh_are_equivalent.cc" - "${draco_src_root}/mesh/mesh_are_equivalent.h" - "${draco_src_root}/mesh/mesh_attribute_corner_table.cc" - "${draco_src_root}/mesh/mesh_attribute_corner_table.h" - "${draco_src_root}/mesh/mesh_cleanup.cc" - "${draco_src_root}/mesh/mesh_cleanup.h" - "${draco_src_root}/mesh/mesh_misc_functions.cc" - "${draco_src_root}/mesh/mesh_misc_functions.h" - "${draco_src_root}/mesh/mesh_stripifier.cc" - "${draco_src_root}/mesh/mesh_stripifier.h" - "${draco_src_root}/mesh/triangle_soup_mesh_builder.cc" - "${draco_src_root}/mesh/triangle_soup_mesh_builder.h" - "${draco_src_root}/mesh/valence_cache.h") +list( + APPEND draco_mesh_sources + "${draco_src_root}/mesh/corner_table.cc" + "${draco_src_root}/mesh/corner_table.h" + "${draco_src_root}/mesh/corner_table_iterators.h" + "${draco_src_root}/mesh/mesh.cc" + "${draco_src_root}/mesh/mesh.h" + "${draco_src_root}/mesh/mesh_are_equivalent.cc" + "${draco_src_root}/mesh/mesh_are_equivalent.h" + "${draco_src_root}/mesh/mesh_attribute_corner_table.cc" + "${draco_src_root}/mesh/mesh_attribute_corner_table.h" + "${draco_src_root}/mesh/mesh_cleanup.cc" + "${draco_src_root}/mesh/mesh_cleanup.h" + "${draco_src_root}/mesh/mesh_features.cc" + "${draco_src_root}/mesh/mesh_features.h" + "${draco_src_root}/mesh/mesh_indices.h" + "${draco_src_root}/mesh/mesh_misc_functions.cc" + "${draco_src_root}/mesh/mesh_misc_functions.h" + "${draco_src_root}/mesh/mesh_stripifier.cc" + "${draco_src_root}/mesh/mesh_stripifier.h" + "${draco_src_root}/mesh/triangle_soup_mesh_builder.cc" + "${draco_src_root}/mesh/triangle_soup_mesh_builder.h" + "${draco_src_root}/mesh/valence_cache.h") -list(APPEND draco_point_cloud_sources - "${draco_src_root}/point_cloud/point_cloud.cc" - "${draco_src_root}/point_cloud/point_cloud.h" - "${draco_src_root}/point_cloud/point_cloud_builder.cc" - "${draco_src_root}/point_cloud/point_cloud_builder.h") +list( + APPEND draco_point_cloud_sources + "${draco_src_root}/point_cloud/point_cloud.cc" + "${draco_src_root}/point_cloud/point_cloud.h" + "${draco_src_root}/point_cloud/point_cloud_builder.cc" + "${draco_src_root}/point_cloud/point_cloud_builder.h") list( APPEND @@ -424,7 +462,7 @@ list( "${draco_src_root}/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_decoder.h" "${draco_src_root}/compression/point_cloud/algorithms/float_points_tree_decoder.cc" "${draco_src_root}/compression/point_cloud/algorithms/float_points_tree_decoder.h" - ) +) list( APPEND @@ -433,13 +471,18 @@ list( "${draco_src_root}/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_encoder.h" "${draco_src_root}/compression/point_cloud/algorithms/float_points_tree_encoder.cc" "${draco_src_root}/compression/point_cloud/algorithms/float_points_tree_encoder.h" - ) +) -list(APPEND draco_metadata_sources - "${draco_src_root}/metadata/geometry_metadata.cc" - "${draco_src_root}/metadata/geometry_metadata.h" - "${draco_src_root}/metadata/metadata.cc" - "${draco_src_root}/metadata/metadata.h") +list( + APPEND draco_metadata_sources + "${draco_src_root}/metadata/geometry_metadata.cc" + "${draco_src_root}/metadata/geometry_metadata.h" + "${draco_src_root}/metadata/metadata.cc" + "${draco_src_root}/metadata/metadata.h" + "${draco_src_root}/metadata/property_table.cc" + "${draco_src_root}/metadata/property_table.h" + "${draco_src_root}/metadata/structural_metadata.cc" + "${draco_src_root}/metadata/structural_metadata.h") list(APPEND draco_metadata_enc_sources "${draco_src_root}/metadata/metadata_encoder.cc" @@ -465,7 +508,7 @@ list( APPEND draco_js_dec_sources "${draco_src_root}/javascript/emscripten/decoder_webidl_wrapper.cc" "${draco_src_root}/javascript/emscripten/draco_decoder_glue_wrapper.cc" - ) +) list( APPEND draco_js_enc_sources @@ -477,14 +520,14 @@ list( draco_animation_js_dec_sources "${draco_src_root}/javascript/emscripten/animation_decoder_webidl_wrapper.cc" "${draco_src_root}/javascript/emscripten/draco_animation_decoder_glue_wrapper.cc" - ) +) list( APPEND draco_animation_js_enc_sources "${draco_src_root}/javascript/emscripten/animation_encoder_webidl_wrapper.cc" "${draco_src_root}/javascript/emscripten/draco_animation_encoder_glue_wrapper.cc" - ) +) list(APPEND draco_unity_plug_sources "${draco_src_root}/unity/draco_unity_plugin.cc" @@ -494,49 +537,133 @@ list(APPEND draco_maya_plug_sources "${draco_src_root}/maya/draco_maya_plugin.cc" "${draco_src_root}/maya/draco_maya_plugin.h") +if(DRACO_TRANSCODER_SUPPORTED) + list( + APPEND draco_animation_sources + "${draco_src_root}/animation/animation.cc" + "${draco_src_root}/animation/animation.h" + "${draco_src_root}/animation/node_animation_data.h" + "${draco_src_root}/animation/skin.cc" + "${draco_src_root}/animation/skin.h") + + list( + APPEND draco_io_sources + "${draco_src_root}/io/gltf_decoder.cc" + "${draco_src_root}/io/gltf_decoder.h" + "${draco_src_root}/io/gltf_encoder.cc" + "${draco_src_root}/io/gltf_encoder.h" + "${draco_src_root}/io/gltf_utils.cc" + "${draco_src_root}/io/gltf_utils.h" + "${draco_src_root}/io/image_compression_options.h" + "${draco_src_root}/io/scene_io.cc" + "${draco_src_root}/io/scene_io.h" + "${draco_src_root}/io/texture_io.cc" + "${draco_src_root}/io/texture_io.h" + "${draco_src_root}/io/tiny_gltf_utils.cc" + "${draco_src_root}/io/tiny_gltf_utils.h") + + list( + APPEND draco_material_sources + "${draco_src_root}/material/material.cc" + "${draco_src_root}/material/material.h" + "${draco_src_root}/material/material_library.cc" + "${draco_src_root}/material/material_library.h") + + list( + APPEND draco_mesh_sources + "${draco_src_root}/mesh/mesh_connected_components.h" + "${draco_src_root}/mesh/mesh_splitter.cc" + "${draco_src_root}/mesh/mesh_splitter.h" + "${draco_src_root}/mesh/mesh_utils.cc" + "${draco_src_root}/mesh/mesh_utils.h") + + list( + APPEND draco_scene_sources + "${draco_src_root}/scene/instance_array.cc" + "${draco_src_root}/scene/instance_array.h" + "${draco_src_root}/scene/light.cc" + "${draco_src_root}/scene/light.h" + "${draco_src_root}/scene/mesh_group.h" + "${draco_src_root}/scene/scene.cc" + "${draco_src_root}/scene/scene.h" + "${draco_src_root}/scene/scene_are_equivalent.cc" + "${draco_src_root}/scene/scene_are_equivalent.h" + "${draco_src_root}/scene/scene_indices.h" + "${draco_src_root}/scene/scene_node.h" + "${draco_src_root}/scene/scene_utils.cc" + "${draco_src_root}/scene/scene_utils.h" + "${draco_src_root}/scene/trs_matrix.cc" + "${draco_src_root}/scene/trs_matrix.h") + + list( + APPEND draco_texture_sources + "${draco_src_root}/texture/source_image.cc" + "${draco_src_root}/texture/source_image.h" + "${draco_src_root}/texture/texture.h" + "${draco_src_root}/texture/texture_library.cc" + "${draco_src_root}/texture/texture_library.h" + "${draco_src_root}/texture/texture_map.cc" + "${draco_src_root}/texture/texture_map.h" + "${draco_src_root}/texture/texture_transform.cc" + "${draco_src_root}/texture/texture_transform.h" + "${draco_src_root}/texture/texture_utils.cc" + "${draco_src_root}/texture/texture_utils.h") + + +endif() + # # Draco targets. # if(EMSCRIPTEN AND DRACO_JS_GLUE) # Draco decoder and encoder "executable" targets in various flavors for - # Emsscripten. - list(APPEND draco_decoder_src - ${draco_attributes_sources} - ${draco_compression_attributes_dec_sources} - ${draco_compression_attributes_pred_schemes_dec_sources} - ${draco_compression_bit_coders_sources} - ${draco_compression_decode_sources} - ${draco_compression_entropy_sources} - ${draco_compression_mesh_traverser_sources} - ${draco_compression_mesh_dec_sources} - ${draco_compression_point_cloud_dec_sources} - ${draco_core_sources} - ${draco_dec_config_sources} - ${draco_js_dec_sources} - ${draco_mesh_sources} - ${draco_metadata_dec_sources} - ${draco_metadata_sources} - ${draco_point_cloud_sources} - ${draco_points_dec_sources}) + # Emscripten. - list(APPEND draco_encoder_src - ${draco_attributes_sources} - ${draco_compression_attributes_enc_sources} - ${draco_compression_attributes_pred_schemes_enc_sources} - ${draco_compression_bit_coders_sources} - ${draco_compression_encode_sources} - ${draco_compression_entropy_sources} - ${draco_compression_mesh_traverser_sources} - ${draco_compression_mesh_enc_sources} - ${draco_compression_point_cloud_enc_sources} - ${draco_core_sources} - ${draco_enc_config_sources} - ${draco_js_enc_sources} - ${draco_mesh_sources} - ${draco_metadata_enc_sources} - ${draco_metadata_sources} - ${draco_point_cloud_sources} - ${draco_points_enc_sources}) + if(DRACO_TRANSCODER_SUPPORTED) + message(FATAL_ERROR "The transcoder is not supported in Emscripten.") + endif() + + list( + APPEND draco_decoder_src + ${draco_attributes_sources} + ${draco_compression_attributes_dec_sources} + ${draco_compression_attributes_pred_schemes_dec_sources} + ${draco_compression_bit_coders_sources} + ${draco_compression_decode_sources} + ${draco_compression_entropy_sources} + ${draco_compression_mesh_traverser_sources} + ${draco_compression_mesh_dec_sources} + ${draco_compression_options_sources} + ${draco_compression_point_cloud_dec_sources} + ${draco_core_sources} + ${draco_dec_config_sources} + ${draco_js_dec_sources} + ${draco_mesh_sources} + ${draco_metadata_dec_sources} + ${draco_metadata_sources} + ${draco_point_cloud_sources} + ${draco_points_dec_sources}) + + list( + APPEND draco_encoder_src + ${draco_attributes_sources} + ${draco_compression_attributes_enc_sources} + ${draco_compression_attributes_pred_schemes_enc_sources} + ${draco_compression_bit_coders_sources} + ${draco_compression_encode_sources} + ${draco_compression_entropy_sources} + ${draco_compression_mesh_traverser_sources} + ${draco_compression_mesh_enc_sources} + ${draco_compression_options_sources} + ${draco_compression_point_cloud_enc_sources} + ${draco_core_sources} + ${draco_enc_config_sources} + ${draco_js_enc_sources} + ${draco_mesh_sources} + ${draco_metadata_enc_sources} + ${draco_metadata_sources} + ${draco_point_cloud_sources} + ${draco_points_enc_sources}) list(APPEND draco_js_dec_idl "${draco_src_root}/javascript/emscripten/draco_web_decoder.idl") @@ -561,10 +688,10 @@ if(EMSCRIPTEN AND DRACO_JS_GLUE) set(draco_decoder_glue_path "${draco_build}/glue_decoder") set(draco_encoder_glue_path "${draco_build}/glue_encoder") - draco_generate_emscripten_glue(INPUT_IDL ${draco_js_dec_idl} OUTPUT_PATH - ${draco_decoder_glue_path}) - draco_generate_emscripten_glue(INPUT_IDL ${draco_js_enc_idl} OUTPUT_PATH - ${draco_encoder_glue_path}) + draco_generate_emscripten_glue(INPUT_IDL ${draco_js_dec_idl} + OUTPUT_PATH ${draco_decoder_glue_path}) + draco_generate_emscripten_glue(INPUT_IDL ${draco_js_enc_idl} + OUTPUT_PATH ${draco_encoder_glue_path}) if(DRACO_DECODER_ATTRIBUTE_DEDUPLICATION) list(APPEND draco_decoder_features @@ -572,45 +699,28 @@ if(EMSCRIPTEN AND DRACO_JS_GLUE) "DRACO_ATTRIBUTE_VALUES_DEDUPLICATION_SUPPORTED") endif() - draco_add_emscripten_executable(NAME - draco_decoder - SOURCES - ${draco_decoder_src} - DEFINES - ${draco_defines} - FEATURES - ${draco_decoder_features} - INCLUDES - ${draco_include_paths} - LINK_FLAGS - "-sEXPORT_NAME=\"DracoDecoderModule\"" - GLUE_PATH - ${draco_decoder_glue_path} - PRE_LINK_JS_SOURCES - ${draco_pre_link_js_sources} - POST_LINK_JS_SOURCES - ${draco_post_link_js_decoder_sources}) + draco_add_emscripten_executable( + NAME draco_decoder + SOURCES ${draco_decoder_src} + DEFINES ${draco_defines} + FEATURES ${draco_decoder_features} + INCLUDES ${draco_include_paths} + LINK_FLAGS "-sEXPORT_NAME=\"DracoDecoderModule\"" + GLUE_PATH ${draco_decoder_glue_path} + PRE_LINK_JS_SOURCES ${draco_pre_link_js_sources} + POST_LINK_JS_SOURCES ${draco_post_link_js_decoder_sources}) draco_add_emscripten_executable( - NAME - draco_encoder - SOURCES - ${draco_encoder_src} - DEFINES - ${draco_defines} - FEATURES - DRACO_ATTRIBUTE_INDICES_DEDUPLICATION_SUPPORTED - DRACO_ATTRIBUTE_VALUES_DEDUPLICATION_SUPPORTED - INCLUDES - ${draco_include_paths} - LINK_FLAGS - "-sEXPORT_NAME=\"DracoEncoderModule\"" - GLUE_PATH - ${draco_encoder_glue_path} - PRE_LINK_JS_SOURCES - ${draco_pre_link_js_sources} - POST_LINK_JS_SOURCES - ${draco_post_link_js_sources}) + NAME draco_encoder + SOURCES ${draco_encoder_src} + DEFINES ${draco_defines} + FEATURES DRACO_ATTRIBUTE_INDICES_DEDUPLICATION_SUPPORTED + DRACO_ATTRIBUTE_VALUES_DEDUPLICATION_SUPPORTED + INCLUDES ${draco_include_paths} + LINK_FLAGS "-sEXPORT_NAME=\"DracoEncoderModule\"" + GLUE_PATH ${draco_encoder_glue_path} + PRE_LINK_JS_SOURCES ${draco_pre_link_js_sources} + POST_LINK_JS_SOURCES ${draco_post_link_js_sources}) if(DRACO_ANIMATION_ENCODING) set(draco_anim_decoder_glue_path "${draco_build}/glue_animation_decoder") @@ -622,186 +732,270 @@ if(EMSCRIPTEN AND DRACO_JS_GLUE) OUTPUT_PATH ${draco_anim_encoder_glue_path}) draco_add_emscripten_executable( - NAME - draco_animation_decoder - SOURCES - ${draco_animation_dec_sources} - ${draco_animation_js_dec_sources} - ${draco_animation_sources} - ${draco_decoder_src} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - LINK_FLAGS - "-sEXPORT_NAME=\"DracoAnimationDecoderModule\"" - GLUE_PATH - ${draco_anim_decoder_glue_path} - PRE_LINK_JS_SOURCES - ${draco_pre_link_js_sources} - POST_LINK_JS_SOURCES - ${draco_post_link_js_decoder_sources}) + NAME draco_animation_decoder + SOURCES ${draco_animation_dec_sources} ${draco_animation_js_dec_sources} + ${draco_animation_sources} ${draco_decoder_src} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + LINK_FLAGS "-sEXPORT_NAME=\"DracoAnimationDecoderModule\"" + GLUE_PATH ${draco_anim_decoder_glue_path} + PRE_LINK_JS_SOURCES ${draco_pre_link_js_sources} + POST_LINK_JS_SOURCES ${draco_post_link_js_decoder_sources}) draco_add_emscripten_executable( - NAME - draco_animation_encoder - SOURCES - ${draco_animation_enc_sources} - ${draco_animation_js_enc_sources} - ${draco_animation_sources} - ${draco_encoder_src} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - LINK_FLAGS - "-sEXPORT_NAME=\"DracoAnimationEncoderModule\"" - GLUE_PATH - ${draco_anim_encoder_glue_path} - PRE_LINK_JS_SOURCES - ${draco_pre_link_js_sources} - POST_LINK_JS_SOURCES - ${draco_post_link_js_sources}) + NAME draco_animation_encoder + SOURCES ${draco_animation_enc_sources} ${draco_animation_js_enc_sources} + ${draco_animation_sources} ${draco_encoder_src} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + LINK_FLAGS "-sEXPORT_NAME=\"DracoAnimationEncoderModule\"" + GLUE_PATH ${draco_anim_encoder_glue_path} + PRE_LINK_JS_SOURCES ${draco_pre_link_js_sources} + POST_LINK_JS_SOURCES ${draco_post_link_js_sources}) endif() else() # Standard Draco libs, encoder and decoder. Object collections that mirror the # Draco directory structure. - draco_add_library(NAME draco_attributes TYPE OBJECT SOURCES - ${draco_attributes_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME - draco_compression_attributes_dec - OBJECT - ${draco_compression_attributes_dec_sources} - TYPE - OBJECT - SOURCES - ${draco_compression_attributes_dec_sources} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths}) - draco_add_library(NAME draco_compression_attributes_enc TYPE OBJECT SOURCES - ${draco_compression_attributes_enc_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_attributes_pred_schemes_dec TYPE - OBJECT SOURCES - ${draco_compression_attributes_pred_schemes_dec_sources}) - draco_add_library(NAME draco_compression_attributes_pred_schemes_enc TYPE - OBJECT SOURCES - ${draco_compression_attributes_pred_schemes_enc_sources} - DEFINES ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_bit_coders TYPE OBJECT SOURCES - ${draco_compression_bit_coders_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_enc_config TYPE OBJECT SOURCES - ${draco_enc_config_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_dec_config TYPE OBJECT SOURCES - ${draco_dec_config_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_decode TYPE OBJECT SOURCES - ${draco_compression_decode_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_encode TYPE OBJECT SOURCES - ${draco_compression_encode_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_entropy TYPE OBJECT SOURCES - ${draco_compression_entropy_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_mesh_traverser TYPE OBJECT SOURCES - ${draco_compression_mesh_traverser_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_mesh_dec TYPE OBJECT SOURCES - ${draco_compression_mesh_dec_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_mesh_enc TYPE OBJECT SOURCES - ${draco_compression_mesh_enc_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_point_cloud_dec TYPE OBJECT SOURCES - ${draco_compression_point_cloud_dec_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_compression_point_cloud_enc TYPE OBJECT SOURCES - ${draco_compression_point_cloud_enc_sources} DEFINES - ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_core TYPE OBJECT SOURCES ${draco_core_sources} - DEFINES ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_io TYPE OBJECT SOURCES ${draco_io_sources} - DEFINES ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_mesh TYPE OBJECT SOURCES ${draco_mesh_sources} - DEFINES ${draco_defines} INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_metadata_dec TYPE OBJECT SOURCES - ${draco_metadata_dec_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_metadata_enc TYPE OBJECT SOURCES - ${draco_metadata_enc_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_metadata TYPE OBJECT SOURCES - ${draco_metadata_sources} DEFINES ${draco_defines} INCLUDES - ${draco_include_paths}) - draco_add_library(NAME draco_animation_dec TYPE OBJECT SOURCES - ${draco_animation_dec_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_animation_enc TYPE OBJECT SOURCES - ${draco_animation_enc_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME draco_animation TYPE OBJECT SOURCES - ${draco_animation_sources} DEFINES ${draco_defines} INCLUDES - ${draco_include_paths}) - draco_add_library(NAME draco_point_cloud TYPE OBJECT SOURCES - ${draco_point_cloud_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) - draco_add_library(NAME - draco_points_dec - TYPE - OBJECT - SOURCES - ${draco_points_common_sources} - ${draco_points_dec_sources} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths}) - draco_add_library(NAME - draco_points_enc - TYPE - OBJECT - SOURCES - ${draco_points_common_sources} - ${draco_points_enc_sources} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths}) + draco_add_library( + NAME draco_attributes + TYPE OBJECT + SOURCES ${draco_attributes_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_attributes_dec OBJECT + ${draco_compression_attributes_dec_sources} + TYPE OBJECT + SOURCES ${draco_compression_attributes_dec_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_attributes_enc + TYPE OBJECT + SOURCES ${draco_compression_attributes_enc_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_attributes_pred_schemes_dec + TYPE OBJECT + SOURCES ${draco_compression_attributes_pred_schemes_dec_sources}) + draco_add_library( + NAME draco_compression_attributes_pred_schemes_enc + TYPE OBJECT + SOURCES ${draco_compression_attributes_pred_schemes_enc_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_bit_coders + TYPE OBJECT + SOURCES ${draco_compression_bit_coders_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_enc_config + TYPE OBJECT + SOURCES ${draco_enc_config_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_dec_config + TYPE OBJECT + SOURCES ${draco_dec_config_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_decode + TYPE OBJECT + SOURCES ${draco_compression_decode_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_encode + TYPE OBJECT + SOURCES ${draco_compression_encode_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_entropy + TYPE OBJECT + SOURCES ${draco_compression_entropy_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_mesh_traverser + TYPE OBJECT + SOURCES ${draco_compression_mesh_traverser_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_mesh_dec + TYPE OBJECT + SOURCES ${draco_compression_mesh_dec_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_mesh_enc + TYPE OBJECT + SOURCES ${draco_compression_mesh_enc_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_options + TYPE OBJECT + SOURCES ${draco_compression_options_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_point_cloud_dec + TYPE OBJECT + SOURCES ${draco_compression_point_cloud_dec_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_compression_point_cloud_enc + TYPE OBJECT + SOURCES ${draco_compression_point_cloud_enc_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_core + TYPE OBJECT + SOURCES ${draco_core_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_io + TYPE OBJECT + SOURCES ${draco_io_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_mesh + TYPE OBJECT + SOURCES ${draco_mesh_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_metadata_dec + TYPE OBJECT + SOURCES ${draco_metadata_dec_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_metadata_enc + TYPE OBJECT + SOURCES ${draco_metadata_enc_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_metadata + TYPE OBJECT + SOURCES ${draco_metadata_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_animation_dec + TYPE OBJECT + SOURCES ${draco_animation_dec_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_animation_enc + TYPE OBJECT + SOURCES ${draco_animation_enc_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_animation + TYPE OBJECT + SOURCES ${draco_animation_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_point_cloud + TYPE OBJECT + SOURCES ${draco_point_cloud_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_points_dec + TYPE OBJECT + SOURCES ${draco_points_common_sources} ${draco_points_dec_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_points_enc + TYPE OBJECT + SOURCES ${draco_points_common_sources} ${draco_points_enc_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) - set(draco_object_library_deps - draco_attributes - draco_compression_attributes_dec - draco_compression_attributes_enc - draco_compression_attributes_pred_schemes_dec - draco_compression_attributes_pred_schemes_enc - draco_compression_bit_coders - draco_compression_decode - draco_compression_encode - draco_compression_entropy - draco_compression_mesh_dec - draco_compression_mesh_enc - draco_compression_point_cloud_dec - draco_compression_point_cloud_enc - draco_core - draco_dec_config - draco_enc_config - draco_io - draco_mesh - draco_metadata - draco_metadata_dec - draco_metadata_enc - draco_animation - draco_animation_dec - draco_animation_enc - draco_point_cloud - draco_points_dec - draco_points_enc) + if(DRACO_TRANSCODER_SUPPORTED) + if(MSVC) + # TODO(https://github.com/google/draco/issues/826) + set_source_files_properties("${draco_src_root}/io/gltf_decoder.cc" + PROPERTIES COMPILE_OPTIONS "/Od") + endif() + + draco_add_library( + NAME draco_material + TYPE OBJECT + SOURCES ${draco_material_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + + draco_add_library( + NAME draco_scene + TYPE OBJECT + SOURCES ${draco_scene_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + + draco_add_library( + NAME draco_texture + TYPE OBJECT + SOURCES ${draco_texture_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) + + endif() + + list( + APPEND draco_object_library_deps + draco_attributes + draco_compression_attributes_dec + draco_compression_attributes_enc + draco_compression_attributes_pred_schemes_dec + draco_compression_attributes_pred_schemes_enc + draco_compression_bit_coders + draco_compression_decode + draco_compression_encode + draco_compression_entropy + draco_compression_mesh_dec + draco_compression_mesh_enc + draco_compression_options + draco_compression_point_cloud_dec + draco_compression_point_cloud_enc + draco_core + draco_dec_config + draco_enc_config + draco_io + draco_mesh + draco_metadata + draco_metadata_dec + draco_metadata_enc + draco_animation + draco_animation_dec + draco_animation_enc + draco_point_cloud + draco_points_dec + draco_points_enc) + + if(DRACO_TRANSCODER_SUPPORTED) + list(APPEND draco_object_library_deps draco_material draco_scene + draco_texture) + + endif() # Library targets that consume the object collections. if(MSVC) @@ -809,56 +1003,48 @@ else() # that the exported symbols are part of the DLL target. The unfortunate side # effect of this is that a single configuration cannot output both the # static library and the DLL: This results in an either/or situation. - # Windows users of the draco build can have a DLL and an import library, - # or they can have a static library; they cannot have both from a single + # Windows users of the draco build can have a DLL and an import library, or + # they can have a static library; they cannot have both from a single # configuration of the build. if(BUILD_SHARED_LIBS) set(draco_lib_type SHARED) else() set(draco_lib_type STATIC) endif() - draco_add_library(NAME - draco - OUTPUT_NAME - draco - TYPE - ${draco_lib_type} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - OBJLIB_DEPS - ${draco_object_library_deps}) + draco_add_library( + NAME draco + OUTPUT_NAME draco + TYPE ${draco_lib_type} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + OBJLIB_DEPS ${draco_object_library_deps} + LIB_DEPS ${draco_lib_deps}) + add_library(draco::draco ALIAS draco) else() - draco_add_library(NAME - draco_static - OUTPUT_NAME - draco - TYPE - STATIC - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - OBJLIB_DEPS - ${draco_object_library_deps}) + draco_add_library( + NAME draco_static + OUTPUT_NAME draco + TYPE STATIC + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + OBJLIB_DEPS ${draco_object_library_deps} + LIB_DEPS ${draco_lib_deps}) if(BUILD_SHARED_LIBS) - draco_add_library(NAME - draco_shared - SOURCES - "${draco_src_root}/core/draco_version.h" - OUTPUT_NAME - draco - TYPE - SHARED - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - LIB_DEPS - draco_static) + draco_add_library( + NAME draco_shared + SOURCES "${draco_src_root}/core/draco_version.h" + OUTPUT_NAME draco + TYPE SHARED + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + LIB_DEPS draco_static) + add_library(draco::draco ALIAS draco_shared) + set_target_properties(draco_shared PROPERTIES EXPORT_NAME draco) + else() + add_library(draco::draco ALIAS draco_static) + set_target_properties(draco_static PROPERTIES EXPORT_NAME draco) endif() endif() @@ -869,22 +1055,20 @@ else() set(unity_decoder_lib_type MODULE) endif() - draco_add_library(NAME draco_unity_plugin TYPE OBJECT SOURCES - ${draco_unity_plug_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_unity_plugin + TYPE OBJECT + SOURCES ${draco_unity_plug_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) - draco_add_library(NAME - dracodec_unity - TYPE - ${unity_decoder_lib_type} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - OBJLIB_DEPS - draco_unity_plugin - LIB_DEPS - ${draco_plugin_dependency}) + draco_add_library( + NAME dracodec_unity + TYPE ${unity_decoder_lib_type} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + OBJLIB_DEPS draco_unity_plugin + LIB_DEPS ${draco_plugin_dependency}) # For Mac, we need to build a .bundle for the unity plugin. if(APPLE) @@ -893,22 +1077,20 @@ else() endif() if(DRACO_MAYA_PLUGIN) - draco_add_library(NAME draco_maya_plugin TYPE OBJECT SOURCES - ${draco_maya_plug_sources} DEFINES ${draco_defines} - INCLUDES ${draco_include_paths}) + draco_add_library( + NAME draco_maya_plugin + TYPE OBJECT + SOURCES ${draco_maya_plug_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths}) - draco_add_library(NAME - draco_maya_wrapper - TYPE - MODULE - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - OBJLIB_DEPS - draco_maya_plugin - LIB_DEPS - ${draco_plugin_dependency}) + draco_add_library( + NAME draco_maya_wrapper + TYPE MODULE + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + OBJLIB_DEPS draco_maya_plugin + LIB_DEPS ${draco_plugin_dependency}) # For Mac, we need to build a .bundle for the plugin. if(APPLE) @@ -917,29 +1099,44 @@ else() endif() # Draco app targets. - draco_add_executable(NAME - draco_decoder - SOURCES - "${draco_src_root}/tools/draco_decoder.cc" - ${draco_io_sources} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - LIB_DEPS - ${draco_dependency}) + draco_add_executable( + NAME draco_decoder + SOURCES "${draco_src_root}/tools/draco_decoder.cc" ${draco_io_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + LIB_DEPS ${draco_dependency}) - draco_add_executable(NAME - draco_encoder - SOURCES - "${draco_src_root}/tools/draco_encoder.cc" - ${draco_io_sources} - DEFINES - ${draco_defines} - INCLUDES - ${draco_include_paths} - LIB_DEPS - ${draco_dependency}) + draco_add_executable( + NAME draco_encoder + SOURCES "${draco_src_root}/tools/draco_encoder.cc" ${draco_io_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + LIB_DEPS ${draco_dependency}) + + if(DRACO_TRANSCODER_SUPPORTED) + draco_add_executable( + NAME draco_transcoder + SOURCES "${draco_src_root}/tools/draco_transcoder.cc" + "${draco_src_root}/tools/draco_transcoder_lib.cc" + "${draco_src_root}/tools/draco_transcoder_lib.h" + ${draco_io_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + LIB_DEPS ${draco_dependency}) + + if(DRACO_SIMPLIFIER_SUPPORTED) + draco_add_executable( + NAME draco_simplifier + SOURCES ${draco_pipeline_proto_header} + "${draco_src_root}/tools/draco_simplifier.cc" + "${draco_src_root}/tools/draco_simplifier_lib.cc" + "${draco_src_root}/tools/draco_simplifier_lib.h" + ${draco_io_sources} + DEFINES ${draco_defines} + INCLUDES ${draco_include_paths} + LIB_DEPS ${draco_dependency}) + endif() + endif() draco_setup_install_target() draco_setup_test_targets() diff --git a/contrib/draco/README.md b/contrib/draco/README.md index 0d980b387..4cc717c8d 100644 --- a/contrib/draco/README.md +++ b/contrib/draco/README.md @@ -2,10 +2,93 @@

-[![Build Status](https://github.com/google/draco/workflows/Build/badge.svg)](https://github.com/google/draco/actions?query=workflow%3ABuild) +[![draco-ci](https://github.com/google/draco/workflows/draco-ci/badge.svg?branch=master)](https://github.com/google/draco/actions/workflows/ci.yml) News ======= + +Attention GStatic users: the Draco team strongly recommends using the versioned +URLs for accessing Draco GStatic content. If you are using the URLs that include +the `v1/decoders` substring within the URL, edge caching and GStatic propagation +delays can result in transient errors that can be difficult to diagnose when +new Draco releases are launched. To avoid the issue pin your sites to a +versioned release. + +### Version 1.5.6 release: +* Using the versioned www.gstatic.com WASM and Javascript decoders continues + to be recommended. To use v1.5.6, use this URL: + * https://www.gstatic.com/draco/versioned/decoders/1.5.6/* +* The CMake flag DRACO_DEBUG_MSVC_WARNINGS has been replaced with + DRACO_DEBUG_COMPILER_WARNINGS, and the behavior has changed. It is now a + boolean flag defined in draco_options.cmake. +* Bug fixes. +* Security fixes. + +### Version 1.5.5 release: +* Using the versioned www.gstatic.com WASM and Javascript decoders continues + to be recommended. To use v1.5.5, use this URL: + * https://www.gstatic.com/draco/versioned/decoders/1.5.5/* +* Bug fix: https://github.com/google/draco/issues/935 + +### Version 1.5.4 release: +* Using the versioned www.gstatic.com WASM and Javascript decoders continues + to be recommended. To use v1.5.4, use this URL: + * https://www.gstatic.com/draco/versioned/decoders/1.5.4/* +* Added partial support for glTF extensions EXT_mesh_features and + EXT_structural_metadata. +* Bug fixes. +* Security fixes. + +### Version 1.5.3 release: +* Using the versioned www.gstatic.com WASM and Javascript decoders continues + to be recommended. To use v1.5.3, use this URL: + * https://www.gstatic.com/draco/versioned/decoders/1.5.3/* +* Bug fixes. + +### Version 1.5.2 release +* This is the same as v1.5.1 with the following two bug fixes: + * Fixes DRACO_TRANSCODER_SUPPORTED enabled builds. + * ABI version updated. + +### Version 1.5.1 release +* Adds assertion enabled Emscripten builds to the release, and a subset of the + assertion enabled builds to GStatic. See the file listing below. +* Custom paths to third party dependencies are now supported. See BUILDING.md + for more information. +* The CMake configuration file draco-config.cmake is now tested and known to + work for using Draco in Linux, MacOS, and Windows CMake projects. See the + `install_test` subdirectory of `src/draco/tools` for more information. +* Bug fixes. + +### Version 1.5.0 release +* Adds the draco_transcoder tool. See the section below on the glTF transcoding + tool, and BUILDING.md for build and dependency information. +* Some changes to configuration variables have been made for this release: + - The DRACO_GLTF flag has been renamed to DRACO_GLTF_BITSTREAM to help + increase understanding of its purpose, which is to limit Draco features to + those included in the Draco glTF specification. + - Variables exported in CMake via draco-config.cmake and find-draco.cmake + (formerly FindDraco.cmake) have been renamed. It's unlikely that this + impacts any existing projects as the aforementioned files were not formed + correctly. See [PR775](https://github.com/google/draco/pull/775) for full + details of the changes. +* A CMake version file has been added. +* The CMake install target now uses absolute paths direct from CMake instead + of building them using CMAKE_INSTALL_PREFIX. This was done to make Draco + easier to use for downstream packagers and should have little to no impact on + users picking up Draco from source. +* Certain MSVC warnings have had their levels changed via compiler flag to + reduce the amount of noise output by the MSVC compilers. Set MSVC warning + level to 4, or define DRACO_DEBUG_MSVC_WARNINGS at CMake configuration time + to restore previous behavior. +* Bug fixes. + +### Version 1.4.3 release +* Using the versioned www.gstatic.com WASM and Javascript decoders continues + to be recommended. To use v1.4.3, use this URL: + * https://www.gstatic.com/draco/versioned/decoders/1.4.3/* +* Bug fixes + ### Version 1.4.1 release * Using the versioned www.gstatic.com WASM and Javascript decoders is now recommended. To use v1.4.1, use this URL: @@ -129,6 +212,7 @@ _**Contents**_ * [Encoding Tool](#encoding-tool) * [Encoding Point Clouds](#encoding-point-clouds) * [Decoding Tool](#decoding-tool) + * [glTF Transcoding Tool](#gltf-transcoding-tool) * [C++ Decoder API](#c-decoder-api) * [Javascript Encoder API](#javascript-encoder-api) * [Javascript Decoder API](#javascript-decoder-api) @@ -136,6 +220,7 @@ _**Contents**_ * [Metadata API](#metadata-api) * [NPM Package](#npm-package) * [three.js Renderer Example](#threejs-renderer-example) + * [GStatic Javascript Builds](#gstatic-javascript-builds) * [Support](#support) * [License](#license) * [References](#references) @@ -170,16 +255,18 @@ Command Line Applications ------------------------ The default target created from the build files will be the `draco_encoder` -and `draco_decoder` command line applications. For both applications, if you -run them without any arguments or `-h`, the applications will output usage and -options. +and `draco_decoder` command line applications. Additionally, `draco_transcoder` +is generated when CMake is run with the DRACO_TRANSCODER_SUPPORTED variable set +to ON (see [BUILDING](BUILDING.md#transcoder) for more details). For all +applications, if you run them without any arguments or `-h`, the applications +will output usage and options. Encoding Tool ------------- -`draco_encoder` will read OBJ or PLY files as input, and output Draco-encoded -files. We have included Stanford's [Bunny] mesh for testing. The basic command -line looks like this: +`draco_encoder` will read OBJ, STL or PLY files as input, and output +Draco-encoded files. We have included Stanford's [Bunny] mesh for testing. The +basic command line looks like this: ~~~~~ bash ./draco_encoder -i testdata/bun_zipper.ply -o out.drc @@ -232,15 +319,34 @@ and denser point clouds. Decoding Tool ------------- -`draco_decoder` will read Draco files as input, and output OBJ or PLY files. -The basic command line looks like this: +`draco_decoder` will read Draco files as input, and output OBJ, STL or PLY +files. The basic command line looks like this: ~~~~~ bash ./draco_decoder -i in.drc -o out.obj ~~~~~ +glTF Transcoding Tool +--------------------- + +`draco_transcoder` can be used to add Draco compression to glTF assets. The +basic command line looks like this: + +~~~~~ bash +./draco_transcoder -i in.glb -o out.glb +~~~~~ + +This command line will add geometry compression to all meshes in the `in.glb` +file. Quantization values for different glTF attributes can be specified +similarly to the `draco_encoder` tool. For example `-qp` can be used to define +quantization of the position attribute: + +~~~~~ bash +./draco_transcoder -i in.glb -o out.glb -qp 12 +~~~~~ + C++ Decoder API -------------- +--------------- If you'd like to add decoding to your applications you will need to include the `draco_dec` library. In order to use the Draco decoder you need to @@ -442,6 +548,30 @@ Javascript decoder using the `three.js` renderer. Please see the [javascript/example/README.md](javascript/example/README.md) file for more information. +GStatic Javascript Builds +========================= + +Prebuilt versions of the Emscripten-built Draco javascript decoders are hosted +on www.gstatic.com in version labeled directories: + +https://www.gstatic.com/draco/versioned/decoders/VERSION/* + +As of the v1.4.3 release the files available are: + +- [draco_decoder.js](https://www.gstatic.com/draco/versioned/decoders/1.4.3/draco_decoder.js) +- [draco_decoder.wasm](https://www.gstatic.com/draco/versioned/decoders/1.4.3/draco_decoder.wasm) +- [draco_decoder_gltf.js](https://www.gstatic.com/draco/versioned/decoders/1.4.3/draco_decoder_gltf.js) +- [draco_decoder_gltf.wasm](https://www.gstatic.com/draco/versioned/decoders/1.4.3/draco_decoder_gltf.wasm) +- [draco_wasm_wrapper.js](https://www.gstatic.com/draco/versioned/decoders/1.4.3/draco_wasm_wrapper.js) +- [draco_wasm_wrapper_gltf.js](https://www.gstatic.com/draco/versioned/decoders/1.4.3/draco_wasm_wrapper_gltf.js) + +Beginning with the v1.5.1 release assertion enabled builds of the following +files are available: + +- [draco_decoder.js](https://www.gstatic.com/draco/versioned/decoders/1.5.1/with_asserts/draco_decoder.js) +- [draco_decoder.wasm](https://www.gstatic.com/draco/versioned/decoders/1.5.1/with_asserts/draco_decoder.wasm) +- [draco_wasm_wrapper.js](https://www.gstatic.com/draco/versioned/decoders/1.5.1/with_asserts/draco_wasm_wrapper.js) + Support ======= diff --git a/contrib/draco/cmake/DracoConfig.cmake b/contrib/draco/cmake/DracoConfig.cmake deleted file mode 100644 index be5e1faef..000000000 --- a/contrib/draco/cmake/DracoConfig.cmake +++ /dev/null @@ -1,3 +0,0 @@ -@PACKAGE_INIT@ -set_and_check(draco_INCLUDE_DIR "@PACKAGE_draco_include_install_dir@") -set_and_check(draco_LIBRARY_DIR "@PACKAGE_draco_lib_install_dir@") diff --git a/contrib/draco/cmake/FindDraco.cmake b/contrib/draco/cmake/FindDraco.cmake deleted file mode 100644 index 0a9193065..000000000 --- a/contrib/draco/cmake/FindDraco.cmake +++ /dev/null @@ -1,56 +0,0 @@ -# Finddraco -# -# Locates draco and sets the following variables: -# -# draco_FOUND draco_INCLUDE_DIRS draco_LIBARY_DIRS draco_LIBRARIES -# draco_VERSION_STRING -# -# draco_FOUND is set to YES only when all other variables are successfully -# configured. - -unset(draco_FOUND) -unset(draco_INCLUDE_DIRS) -unset(draco_LIBRARY_DIRS) -unset(draco_LIBRARIES) -unset(draco_VERSION_STRING) - -mark_as_advanced(draco_FOUND) -mark_as_advanced(draco_INCLUDE_DIRS) -mark_as_advanced(draco_LIBRARY_DIRS) -mark_as_advanced(draco_LIBRARIES) -mark_as_advanced(draco_VERSION_STRING) - -set(draco_version_file_no_prefix "draco/src/draco/core/draco_version.h") - -# Set draco_INCLUDE_DIRS -find_path(draco_INCLUDE_DIRS NAMES "${draco_version_file_no_prefix}") - -# Extract the version string from draco_version.h. -if(draco_INCLUDE_DIRS) - set(draco_version_file - "${draco_INCLUDE_DIRS}/draco/src/draco/core/draco_version.h") - file(STRINGS "${draco_version_file}" draco_version REGEX "kdracoVersion") - list(GET draco_version 0 draco_version) - string(REPLACE "static const char kdracoVersion[] = " "" draco_version - "${draco_version}") - string(REPLACE ";" "" draco_version "${draco_version}") - string(REPLACE "\"" "" draco_version "${draco_version}") - set(draco_VERSION_STRING ${draco_version}) -endif() - -# Find the library. -if(BUILD_SHARED_LIBS) - find_library(draco_LIBRARIES NAMES draco.dll libdraco.dylib libdraco.so) -else() - find_library(draco_LIBRARIES NAMES draco.lib libdraco.a) -endif() - -# Store path to library. -get_filename_component(draco_LIBRARY_DIRS ${draco_LIBRARIES} DIRECTORY) - -if(draco_INCLUDE_DIRS - AND draco_LIBRARY_DIRS - AND draco_LIBRARIES - AND draco_VERSION_STRING) - set(draco_FOUND YES) -endif() diff --git a/contrib/draco/cmake/compiler_flags.cmake b/contrib/draco/cmake/compiler_flags.cmake deleted file mode 100644 index 8750e6f7d..000000000 --- a/contrib/draco/cmake/compiler_flags.cmake +++ /dev/null @@ -1,220 +0,0 @@ -if(DRACO_CMAKE_COMPILER_FLAGS_CMAKE_) - return() -endif() -set(DRACO_CMAKE_COMPILER_FLAGS_CMAKE_ 1) - -include(CheckCCompilerFlag) -include(CheckCXXCompilerFlag) -include("${draco_root}/cmake/compiler_tests.cmake") - -# Strings used to cache failed C/CXX flags. -set(DRACO_FAILED_C_FLAGS) -set(DRACO_FAILED_CXX_FLAGS) - -# Checks C compiler for support of $c_flag. Adds $c_flag to $CMAKE_C_FLAGS when -# the compile test passes. Caches $c_flag in $DRACO_FAILED_C_FLAGS when the test -# fails. -macro(add_c_flag_if_supported c_flag) - unset(C_FLAG_FOUND CACHE) - string(FIND "${CMAKE_C_FLAGS}" "${c_flag}" C_FLAG_FOUND) - unset(C_FLAG_FAILED CACHE) - string(FIND "${DRACO_FAILED_C_FLAGS}" "${c_flag}" C_FLAG_FAILED) - - if(${C_FLAG_FOUND} EQUAL -1 AND ${C_FLAG_FAILED} EQUAL -1) - unset(C_FLAG_SUPPORTED CACHE) - message("Checking C compiler flag support for: " ${c_flag}) - check_c_compiler_flag("${c_flag}" C_FLAG_SUPPORTED) - if(${C_FLAG_SUPPORTED}) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${c_flag}" CACHE STRING "") - else() - set(DRACO_FAILED_C_FLAGS - "${DRACO_FAILED_C_FLAGS} ${c_flag}" - CACHE STRING "" FORCE) - endif() - endif() -endmacro() - -# Checks C++ compiler for support of $cxx_flag. Adds $cxx_flag to -# $CMAKE_CXX_FLAGS when the compile test passes. Caches $c_flag in -# $DRACO_FAILED_CXX_FLAGS when the test fails. -macro(add_cxx_flag_if_supported cxx_flag) - unset(CXX_FLAG_FOUND CACHE) - string(FIND "${CMAKE_CXX_FLAGS}" "${cxx_flag}" CXX_FLAG_FOUND) - unset(CXX_FLAG_FAILED CACHE) - string(FIND "${DRACO_FAILED_CXX_FLAGS}" "${cxx_flag}" CXX_FLAG_FAILED) - - if(${CXX_FLAG_FOUND} EQUAL -1 AND ${CXX_FLAG_FAILED} EQUAL -1) - unset(CXX_FLAG_SUPPORTED CACHE) - message("Checking CXX compiler flag support for: " ${cxx_flag}) - check_cxx_compiler_flag("${cxx_flag}" CXX_FLAG_SUPPORTED) - if(${CXX_FLAG_SUPPORTED}) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${cxx_flag}" CACHE STRING "") - else() - set(DRACO_FAILED_CXX_FLAGS - "${DRACO_FAILED_CXX_FLAGS} ${cxx_flag}" - CACHE STRING "" FORCE) - endif() - endif() -endmacro() - -# Convenience method for adding a flag to both the C and C++ compiler command -# lines. -macro(add_compiler_flag_if_supported flag) - add_c_flag_if_supported(${flag}) - add_cxx_flag_if_supported(${flag}) -endmacro() - -# Checks C compiler for support of $c_flag and terminates generation when -# support is not present. -macro(require_c_flag c_flag update_c_flags) - unset(C_FLAG_FOUND CACHE) - string(FIND "${CMAKE_C_FLAGS}" "${c_flag}" C_FLAG_FOUND) - - if(${C_FLAG_FOUND} EQUAL -1) - unset(HAVE_C_FLAG CACHE) - message("Checking C compiler flag support for: " ${c_flag}) - check_c_compiler_flag("${c_flag}" HAVE_C_FLAG) - if(NOT ${HAVE_C_FLAG}) - message( - FATAL_ERROR "${PROJECT_NAME} requires support for C flag: ${c_flag}.") - endif() - if(${update_c_flags}) - set(CMAKE_C_FLAGS "${c_flag} ${CMAKE_C_FLAGS}" CACHE STRING "" FORCE) - endif() - endif() -endmacro() - -# Checks CXX compiler for support of $cxx_flag and terminates generation when -# support is not present. -macro(require_cxx_flag cxx_flag update_cxx_flags) - unset(CXX_FLAG_FOUND CACHE) - string(FIND "${CMAKE_CXX_FLAGS}" "${cxx_flag}" CXX_FLAG_FOUND) - - if(${CXX_FLAG_FOUND} EQUAL -1) - unset(HAVE_CXX_FLAG CACHE) - message("Checking CXX compiler flag support for: " ${cxx_flag}) - check_cxx_compiler_flag("${cxx_flag}" HAVE_CXX_FLAG) - if(NOT ${HAVE_CXX_FLAG}) - message( - FATAL_ERROR - "${PROJECT_NAME} requires support for CXX flag: ${cxx_flag}.") - endif() - if(${update_cxx_flags}) - set(CMAKE_CXX_FLAGS - "${cxx_flag} ${CMAKE_CXX_FLAGS}" - CACHE STRING "" FORCE) - endif() - endif() -endmacro() - -# Checks for support of $flag by both the C and CXX compilers. Terminates -# generation when support is not present in both compilers. -macro(require_compiler_flag flag update_cmake_flags) - require_c_flag(${flag} ${update_cmake_flags}) - require_cxx_flag(${flag} ${update_cmake_flags}) -endmacro() - -# Checks only non-MSVC targets for support of $c_flag and terminates generation -# when support is not present. -macro(require_c_flag_nomsvc c_flag update_c_flags) - if(NOT MSVC) - require_c_flag(${c_flag} ${update_c_flags}) - endif() -endmacro() - -# Checks only non-MSVC targets for support of $cxx_flag and terminates -# generation when support is not present. -macro(require_cxx_flag_nomsvc cxx_flag update_cxx_flags) - if(NOT MSVC) - require_cxx_flag(${cxx_flag} ${update_cxx_flags}) - endif() -endmacro() - -# Checks only non-MSVC targets for support of $flag by both the C and CXX -# compilers. Terminates generation when support is not present in both -# compilers. -macro(require_compiler_flag_nomsvc flag update_cmake_flags) - require_c_flag_nomsvc(${flag} ${update_cmake_flags}) - require_cxx_flag_nomsvc(${flag} ${update_cmake_flags}) -endmacro() - -# Adds $flag to assembler command line. -macro(append_as_flag flag) - unset(AS_FLAG_FOUND CACHE) - string(FIND "${DRACO_AS_FLAGS}" "${flag}" AS_FLAG_FOUND) - - if(${AS_FLAG_FOUND} EQUAL -1) - set(DRACO_AS_FLAGS "${DRACO_AS_FLAGS} ${flag}") - endif() -endmacro() - -# Adds $flag to the C compiler command line. -macro(append_c_flag flag) - unset(C_FLAG_FOUND CACHE) - string(FIND "${CMAKE_C_FLAGS}" "${flag}" C_FLAG_FOUND) - - if(${C_FLAG_FOUND} EQUAL -1) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${flag}") - endif() -endmacro() - -# Adds $flag to the CXX compiler command line. -macro(append_cxx_flag flag) - unset(CXX_FLAG_FOUND CACHE) - string(FIND "${CMAKE_CXX_FLAGS}" "${flag}" CXX_FLAG_FOUND) - - if(${CXX_FLAG_FOUND} EQUAL -1) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${flag}") - endif() -endmacro() - -# Adds $flag to the C and CXX compiler command lines. -macro(append_compiler_flag flag) - append_c_flag(${flag}) - append_cxx_flag(${flag}) -endmacro() - -# Adds $flag to the executable linker command line. -macro(append_exe_linker_flag flag) - unset(LINKER_FLAG_FOUND CACHE) - string(FIND "${CMAKE_EXE_LINKER_FLAGS}" "${flag}" LINKER_FLAG_FOUND) - - if(${LINKER_FLAG_FOUND} EQUAL -1) - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${flag}") - endif() -endmacro() - -# Adds $flag to the link flags for $target. -function(append_link_flag_to_target target flags) - unset(target_link_flags) - get_target_property(target_link_flags ${target} LINK_FLAGS) - - if(target_link_flags) - unset(link_flag_found) - string(FIND "${target_link_flags}" "${flags}" link_flag_found) - - if(NOT ${link_flag_found} EQUAL -1) - return() - endif() - - set(target_link_flags "${target_link_flags} ${flags}") - else() - set(target_link_flags "${flags}") - endif() - - set_target_properties(${target} PROPERTIES LINK_FLAGS ${target_link_flags}) -endfunction() - -# Adds $flag to executable linker flags, and makes sure C/CXX builds still work. -macro(require_linker_flag flag) - append_exe_linker_flag(${flag}) - - unset(c_passed) - draco_check_c_compiles("LINKER_FLAG_C_TEST(${flag})" "" c_passed) - unset(cxx_passed) - draco_check_cxx_compiles("LINKER_FLAG_CXX_TEST(${flag})" "" cxx_passed) - - if(NOT c_passed OR NOT cxx_passed) - message(FATAL_ERROR "Linker flag test for ${flag} failed.") - endif() -endmacro() diff --git a/contrib/draco/cmake/compiler_tests.cmake b/contrib/draco/cmake/compiler_tests.cmake deleted file mode 100644 index e781a6537..000000000 --- a/contrib/draco/cmake/compiler_tests.cmake +++ /dev/null @@ -1,103 +0,0 @@ -if(DRACO_CMAKE_COMPILER_TESTS_CMAKE_) - return() -endif() -set(DRACO_CMAKE_COMPILER_TESTS_CMAKE_ 1) - -include(CheckCSourceCompiles) -include(CheckCXXSourceCompiles) - -# The basic main() macro used in all compile tests. -set(DRACO_C_MAIN "\nint main(void) { return 0; }") -set(DRACO_CXX_MAIN "\nint main() { return 0; }") - -# Strings containing the names of passed and failed tests. -set(DRACO_C_PASSED_TESTS) -set(DRACO_C_FAILED_TESTS) -set(DRACO_CXX_PASSED_TESTS) -set(DRACO_CXX_FAILED_TESTS) - -macro(draco_push_var var new_value) - set(SAVED_${var} ${var}) - set(${var} ${new_value}) -endmacro() - -macro(draco_pop_var var) - set(var ${SAVED_${var}}) - unset(SAVED_${var}) -endmacro() - -# Confirms $test_source compiles and stores $test_name in one of -# $DRACO_C_PASSED_TESTS or $DRACO_C_FAILED_TESTS depending on out come. When the -# test passes $result_var is set to 1. When it fails $result_var is unset. The -# test is not run if the test name is found in either of the passed or failed -# test variables. -macro(draco_check_c_compiles test_name test_source result_var) - unset(C_TEST_PASSED CACHE) - unset(C_TEST_FAILED CACHE) - string(FIND "${DRACO_C_PASSED_TESTS}" "${test_name}" C_TEST_PASSED) - string(FIND "${DRACO_C_FAILED_TESTS}" "${test_name}" C_TEST_FAILED) - if(${C_TEST_PASSED} EQUAL -1 AND ${C_TEST_FAILED} EQUAL -1) - unset(C_TEST_COMPILED CACHE) - message("Running C compiler test: ${test_name}") - check_c_source_compiles("${test_source} ${DRACO_C_MAIN}" C_TEST_COMPILED) - set(${result_var} ${C_TEST_COMPILED}) - - if(${C_TEST_COMPILED}) - set(DRACO_C_PASSED_TESTS "${DRACO_C_PASSED_TESTS} ${test_name}") - else() - set(DRACO_C_FAILED_TESTS "${DRACO_C_FAILED_TESTS} ${test_name}") - message("C Compiler test ${test_name} failed.") - endif() - elseif(NOT ${C_TEST_PASSED} EQUAL -1) - set(${result_var} 1) - else() # ${C_TEST_FAILED} NOT EQUAL -1 - unset(${result_var}) - endif() -endmacro() - -# Confirms $test_source compiles and stores $test_name in one of -# $DRACO_CXX_PASSED_TESTS or $DRACO_CXX_FAILED_TESTS depending on out come. When -# the test passes $result_var is set to 1. When it fails $result_var is unset. -# The test is not run if the test name is found in either of the passed or -# failed test variables. -macro(draco_check_cxx_compiles test_name test_source result_var) - unset(CXX_TEST_PASSED CACHE) - unset(CXX_TEST_FAILED CACHE) - string(FIND "${DRACO_CXX_PASSED_TESTS}" "${test_name}" CXX_TEST_PASSED) - string(FIND "${DRACO_CXX_FAILED_TESTS}" "${test_name}" CXX_TEST_FAILED) - if(${CXX_TEST_PASSED} EQUAL -1 AND ${CXX_TEST_FAILED} EQUAL -1) - unset(CXX_TEST_COMPILED CACHE) - message("Running CXX compiler test: ${test_name}") - check_cxx_source_compiles("${test_source} ${DRACO_CXX_MAIN}" - CXX_TEST_COMPILED) - set(${result_var} ${CXX_TEST_COMPILED}) - - if(${CXX_TEST_COMPILED}) - set(DRACO_CXX_PASSED_TESTS "${DRACO_CXX_PASSED_TESTS} ${test_name}") - else() - set(DRACO_CXX_FAILED_TESTS "${DRACO_CXX_FAILED_TESTS} ${test_name}") - message("CXX Compiler test ${test_name} failed.") - endif() - elseif(NOT ${CXX_TEST_PASSED} EQUAL -1) - set(${result_var} 1) - else() # ${CXX_TEST_FAILED} NOT EQUAL -1 - unset(${result_var}) - endif() -endmacro() - -# Convenience macro that confirms $test_source compiles as C and C++. -# $result_var is set to 1 when both tests are successful, and 0 when one or both -# tests fail. Note: This macro is intended to be used to write to result -# variables that are expanded via configure_file(). $result_var is set to 1 or 0 -# to allow direct usage of the value in generated source files. -macro(draco_check_source_compiles test_name test_source result_var) - unset(C_PASSED) - unset(CXX_PASSED) - draco_check_c_compiles(${test_name} ${test_source} C_PASSED) - draco_check_cxx_compiles(${test_name} ${test_source} CXX_PASSED) - if(${C_PASSED} AND ${CXX_PASSED}) - set(${result_var} 1) - else() - set(${result_var} 0) - endif() -endmacro() diff --git a/contrib/draco/cmake/draco-config.cmake.template b/contrib/draco/cmake/draco-config.cmake.template index ca4a456bf..ed86823ea 100644 --- a/contrib/draco/cmake/draco-config.cmake.template +++ b/contrib/draco/cmake/draco-config.cmake.template @@ -1,2 +1,3 @@ -set(DRACO_INCLUDE_DIRS "@DRACO_INCLUDE_DIRS@") -set(DRACO_LIBRARIES "draco") +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/draco-targets.cmake") diff --git a/contrib/draco/cmake/draco.pc.template b/contrib/draco/cmake/draco.pc.template index b8ae48212..050219ccb 100644 --- a/contrib/draco/cmake/draco.pc.template +++ b/contrib/draco/cmake/draco.pc.template @@ -1,11 +1,6 @@ -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - Name: @PROJECT_NAME@ Description: Draco geometry de(com)pression library. Version: @DRACO_VERSION@ -Cflags: -I${includedir} -Libs: -L${libdir} -ldraco +Cflags: -I@includes_path@ +Libs: -L@libs_path@ -ldraco Libs.private: @CMAKE_THREAD_LIBS_INIT@ diff --git a/contrib/draco/cmake/draco_build_definitions.cmake b/contrib/draco/cmake/draco_build_definitions.cmake index f7354c15f..4dc232333 100644 --- a/contrib/draco/cmake/draco_build_definitions.cmake +++ b/contrib/draco/cmake/draco_build_definitions.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_BUILD_DEFINITIONS_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_BUILD_DEFINITIONS_CMAKE_ @@ -17,10 +31,6 @@ macro(set_draco_target) endif() set(draco_plugin_dependency draco_static) endif() - - if(BUILD_SHARED_LIBS) - set(CMAKE_POSITION_INDEPENDENT_CODE ON) - endif() endmacro() # Configures flags and sets build system globals. @@ -36,23 +46,37 @@ macro(draco_set_build_definitions) endif() draco_load_version_info() - set(DRACO_SOVERSION 1) + + # Library version info. See the libtool docs for updating the values: + # https://www.gnu.org/software/libtool/manual/libtool.html#Updating-version-info + # + # c=, r=, a= + # + # libtool generates a .so file as .so.[c-a].a.r, while -version-info c:r:a is + # passed to libtool. + # + # We set DRACO_SOVERSION = [c-a].a.r + set(LT_CURRENT 8) + set(LT_REVISION 0) + set(LT_AGE 0) + math(EXPR DRACO_SOVERSION_MAJOR "${LT_CURRENT} - ${LT_AGE}") + set(DRACO_SOVERSION "${DRACO_SOVERSION_MAJOR}.${LT_AGE}.${LT_REVISION}") + unset(LT_CURRENT) + unset(LT_REVISION) + unset(LT_AGE) list(APPEND draco_include_paths "${draco_root}" "${draco_root}/src" "${draco_build}") - if(DRACO_ABSL) - list(APPEND draco_include_path "${draco_root}/third_party/abseil-cpp") + if(DRACO_TRANSCODER_SUPPORTED) + draco_setup_eigen() + draco_setup_filesystem() + draco_setup_tinygltf() + + endif() - list(APPEND draco_gtest_include_paths - "${draco_root}/../googletest/googlemock/include" - "${draco_root}/../googletest/googlemock" - "${draco_root}/../googletest/googletest/include" - "${draco_root}/../googletest/googletest") - list(APPEND draco_test_include_paths ${draco_include_paths} - ${draco_gtest_include_paths}) list(APPEND draco_defines "DRACO_CMAKE=1" "DRACO_FLAGS_SRCDIR=\"${draco_root}\"" "DRACO_FLAGS_TMPDIR=\"/tmp\"") @@ -63,11 +87,22 @@ macro(draco_set_build_definitions) if(BUILD_SHARED_LIBS) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) endif() - else() + endif() + + if(NOT MSVC) if(${CMAKE_SIZEOF_VOID_P} EQUAL 8) # Ensure 64-bit platforms can support large files. list(APPEND draco_defines "_LARGEFILE_SOURCE" "_FILE_OFFSET_BITS=64") endif() + + if(NOT DRACO_DEBUG_COMPILER_WARNINGS) + if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + list(APPEND draco_clang_cxx_flags + "-Wno-implicit-const-int-float-conversion") + else() + list(APPEND draco_base_cxx_flags "-Wno-deprecated-declarations") + endif() + endif() endif() if(ANDROID) @@ -102,13 +137,9 @@ macro(draco_set_build_definitions) set(draco_neon_source_file_suffix "neon.cc") set(draco_sse4_source_file_suffix "sse4.cc") - if((${CMAKE_CXX_COMPILER_ID} - STREQUAL - "GNU" - AND ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 5) - OR (${CMAKE_CXX_COMPILER_ID} - STREQUAL - "Clang" + if((${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" AND ${CMAKE_CXX_COMPILER_VERSION} + VERSION_LESS 5) + OR (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang" AND ${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 4)) message( WARNING "GNU/GCC < v5 or Clang/LLVM < v4, ENABLING COMPATIBILITY MODE.") @@ -117,7 +148,9 @@ macro(draco_set_build_definitions) if(EMSCRIPTEN) draco_check_emscripten_environment() - draco_get_required_emscripten_flags(FLAG_LIST_VAR draco_base_cxx_flags) + draco_get_required_emscripten_flags( + FLAG_LIST_VAR_COMPILER draco_base_cxx_flags + FLAG_LIST_VAR_LINKER draco_base_exe_linker_flags) endif() draco_configure_sanitizer() diff --git a/contrib/draco/cmake/draco_cpu_detection.cmake b/contrib/draco/cmake/draco_cpu_detection.cmake index 96e4a289b..c3b77b80c 100644 --- a/contrib/draco/cmake/draco_cpu_detection.cmake +++ b/contrib/draco/cmake/draco_cpu_detection.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_CPU_DETECTION_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_CPU_DETECTION_CMAKE_ diff --git a/contrib/draco/cmake/draco_dependencies.cmake b/contrib/draco/cmake/draco_dependencies.cmake new file mode 100644 index 000000000..91ee0839b --- /dev/null +++ b/contrib/draco/cmake/draco_dependencies.cmake @@ -0,0 +1,136 @@ +# Copyright 2022 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +if(DRACO_CMAKE_DRACO_DEPENDENCIES_CMAKE) + return() +endif() +set(DRACO_CMAKE_DRACO_DEPENDENCIES_CMAKE 1) + +include("${draco_root}/cmake/draco_variables.cmake") + +# Each variable holds a user specified custom path to a local copy of the +# sources that belong to each project that Draco depends on. When paths are +# empty the build will be generated pointing to the Draco git submodules. +# Otherwise the paths specified by the user will be used in the build +# configuration. + +# Path to the Eigen. The path must contain the Eigen directory. +set(DRACO_EIGEN_PATH) +draco_track_configuration_variable(DRACO_EIGEN_PATH) + +# Path to the gulrak/filesystem installation. The path specified must contain +# the ghc subdirectory that houses the filesystem includes. +set(DRACO_FILESYSTEM_PATH) +draco_track_configuration_variable(DRACO_FILESYSTEM_PATH) + +# Path to the googletest installation. The path must be to the root of the +# Googletest project directory. +set(DRACO_GOOGLETEST_PATH) +draco_track_configuration_variable(DRACO_GOOGLETEST_PATH) + +# Path to the syoyo/tinygltf installation. The path must be to the root of the +# project directory. +set(DRACO_TINYGLTF_PATH) +draco_track_configuration_variable(DRACO_TINYGLTF_PATH) + +# Utility macro for killing the build due to a missing submodule directory. +macro(draco_die_missing_submodule dir) + message(FATAL_ERROR "${dir} missing, run git submodule update --init") +endmacro() + +# Determines the Eigen location and updates the build configuration accordingly. +macro(draco_setup_eigen) + if(DRACO_EIGEN_PATH) + set(eigen_path "${DRACO_EIGEN_PATH}") + + if(NOT IS_DIRECTORY "${eigen_path}") + message(FATAL_ERROR "DRACO_EIGEN_PATH does not exist.") + endif() + else() + set(eigen_path "${draco_root}/third_party/eigen") + + if(NOT IS_DIRECTORY "${eigen_path}") + draco_die_missing_submodule("${eigen_path}") + endif() + endif() + + set(eigen_include_path "${eigen_path}/Eigen") + + if(NOT EXISTS "${eigen_path}/Eigen") + message(FATAL_ERROR "The eigen path does not contain an Eigen directory.") + endif() + + list(APPEND draco_include_paths "${eigen_path}") +endmacro() + +# Determines the gulrak/filesystem location and updates the build configuration +# accordingly. +macro(draco_setup_filesystem) + if(DRACO_FILESYSTEM_PATH) + set(fs_path "${DRACO_FILESYSTEM_PATH}") + + if(NOT IS_DIRECTORY "${fs_path}") + message(FATAL_ERROR "DRACO_FILESYSTEM_PATH does not exist.") + endif() + else() + set(fs_path "${draco_root}/third_party/filesystem/include") + + if(NOT IS_DIRECTORY "${fs_path}") + draco_die_missing_submodule("${fs_path}") + endif() + endif() + + list(APPEND draco_include_paths "${fs_path}") +endmacro() + +# Determines the Googletest location and sets up include and source list vars +# for the draco_tests build. +macro(draco_setup_googletest) + if(DRACO_GOOGLETEST_PATH) + set(gtest_path "${DRACO_GOOGLETEST_PATH}") + if(NOT IS_DIRECTORY "${gtest_path}") + message(FATAL_ERROR "DRACO_GOOGLETEST_PATH does not exist.") + endif() + else() + set(gtest_path "${draco_root}/third_party/googletest") + endif() + + list(APPEND draco_test_include_paths ${draco_include_paths} + "${gtest_path}/include" "${gtest_path}/googlemock" + "${gtest_path}/googletest/include" "${gtest_path}/googletest") + + list(APPEND draco_gtest_all "${gtest_path}/googletest/src/gtest-all.cc") + list(APPEND draco_gtest_main "${gtest_path}/googletest/src/gtest_main.cc") +endmacro() + + +# Determines the location of TinyGLTF and updates the build configuration +# accordingly. +macro(draco_setup_tinygltf) + if(DRACO_TINYGLTF_PATH) + set(tinygltf_path "${DRACO_TINYGLTF_PATH}") + + if(NOT IS_DIRECTORY "${tinygltf_path}") + message(FATAL_ERROR "DRACO_TINYGLTF_PATH does not exist.") + endif() + else() + set(tinygltf_path "${draco_root}/third_party/tinygltf") + + if(NOT IS_DIRECTORY "${tinygltf_path}") + draco_die_missing_submodule("${tinygltf_path}") + endif() + endif() + + list(APPEND draco_include_paths "${tinygltf_path}") +endmacro() diff --git a/contrib/draco/cmake/draco_emscripten.cmake b/contrib/draco/cmake/draco_emscripten.cmake index 10c935043..c9616ae86 100644 --- a/contrib/draco/cmake/draco_emscripten.cmake +++ b/contrib/draco/cmake/draco_emscripten.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_EMSCRIPTEN_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_EMSCRIPTEN_CMAKE_ @@ -18,39 +32,64 @@ endmacro() # Obtains the required Emscripten flags for Draco targets. macro(draco_get_required_emscripten_flags) - set(em_FLAG_LIST_VAR) + set(em_FLAG_LIST_VAR_COMPILER) + set(em_FLAG_LIST_VAR_LINKER) set(em_flags) - set(em_single_arg_opts FLAG_LIST_VAR) + set(em_single_arg_opts FLAG_LIST_VAR_COMPILER FLAG_LIST_VAR_LINKER) set(em_multi_arg_opts) cmake_parse_arguments(em "${em_flags}" "${em_single_arg_opts}" "${em_multi_arg_opts}" ${ARGN}) - if(NOT em_FLAG_LIST_VAR) - message(FATAL "draco_get_required_emscripten_flags: FLAG_LIST_VAR required") + if(NOT em_FLAG_LIST_VAR_COMPILER) + message( + FATAL + "draco_get_required_emscripten_flags: FLAG_LIST_VAR_COMPILER required") + endif() + + if(NOT em_FLAG_LIST_VAR_LINKER) + message( + FATAL + "draco_get_required_emscripten_flags: FLAG_LIST_VAR_LINKER required") endif() if(DRACO_JS_GLUE) unset(required_flags) - list(APPEND ${em_FLAG_LIST_VAR} "-sALLOW_MEMORY_GROWTH=1") - list(APPEND ${em_FLAG_LIST_VAR} "-Wno-almost-asm") - list(APPEND ${em_FLAG_LIST_VAR} "--memory-init-file" "0") - list(APPEND ${em_FLAG_LIST_VAR} "-fno-omit-frame-pointer") - list(APPEND ${em_FLAG_LIST_VAR} "-sMODULARIZE=1") - list(APPEND ${em_FLAG_LIST_VAR} "-sNO_FILESYSTEM=1") - list(APPEND ${em_FLAG_LIST_VAR} "-sEXPORTED_RUNTIME_METHODS=[]") - list(APPEND ${em_FLAG_LIST_VAR} "-sPRECISE_F32=1") - list(APPEND ${em_FLAG_LIST_VAR} "-sNODEJS_CATCH_EXIT=0") - list(APPEND ${em_FLAG_LIST_VAR} "-sNODEJS_CATCH_REJECTION=0") + # TODO(tomfinegan): Revisit splitting of compile/link flags for Emscripten, + # and drop -Wno-unused-command-line-argument. Emscripten complains about + # what are supposedly link-only flags sent with compile commands, but then + # proceeds to produce broken code if the warnings are heeded. + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} + "-Wno-unused-command-line-argument") + + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-Wno-almost-asm") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "--memory-init-file" "0") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-fno-omit-frame-pointer") + + # According to Emscripten the following flags are linker only, but sending + # these flags (en masse) to only the linker results in a broken Emscripten + # build with an empty DracoDecoderModule. + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sALLOW_MEMORY_GROWTH=1") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sMODULARIZE=1") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sFILESYSTEM=0") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} + "-sEXPORTED_FUNCTIONS=[\"_free\",\"_malloc\"]") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sPRECISE_F32=1") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sNODEJS_CATCH_EXIT=0") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sNODEJS_CATCH_REJECTION=0") if(DRACO_FAST) - list(APPEND ${em_FLAG_LIST_VAR} "--llvm-lto" "1") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "--llvm-lto" "1") endif() + + # The WASM flag is reported as linker only. if(DRACO_WASM) - list(APPEND ${em_FLAG_LIST_VAR} "-sWASM=1") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sWASM=1") else() - list(APPEND ${em_FLAG_LIST_VAR} "-sWASM=0") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sWASM=0") endif() + + # The LEGACY_VM_SUPPORT flag is reported as linker only. if(DRACO_IE_COMPATIBLE) - list(APPEND ${em_FLAG_LIST_VAR} "-sLEGACY_VM_SUPPORT=1") + list(APPEND ${em_FLAG_LIST_VAR_COMPILER} "-sLEGACY_VM_SUPPORT=1") endif() endif() endmacro() @@ -66,10 +105,11 @@ macro(draco_generate_emscripten_glue) "${glue_multi_arg_opts}" ${ARGN}) if(DRACO_VERBOSE GREATER 1) - message("--------- draco_generate_emscripten_glue -----------\n" - "glue_INPUT_IDL=${glue_INPUT_IDL}\n" - "glue_OUTPUT_PATH=${glue_OUTPUT_PATH}\n" ] - "----------------------------------------------------\n") + message( + "--------- draco_generate_emscripten_glue -----------\n" + "glue_INPUT_IDL=${glue_INPUT_IDL}\n" + "glue_OUTPUT_PATH=${glue_OUTPUT_PATH}\n" + "----------------------------------------------------\n") endif() if(NOT glue_INPUT_IDL OR NOT glue_OUTPUT_PATH) @@ -79,22 +119,22 @@ macro(draco_generate_emscripten_glue) endif() # Generate the glue source. - execute_process(COMMAND ${PYTHON_EXECUTABLE} - $ENV{EMSCRIPTEN}/tools/webidl_binder.py - ${glue_INPUT_IDL} ${glue_OUTPUT_PATH}) + execute_process( + COMMAND ${PYTHON_EXECUTABLE} $ENV{EMSCRIPTEN}/tools/webidl_binder.py + ${glue_INPUT_IDL} ${glue_OUTPUT_PATH}) if(NOT EXISTS "${glue_OUTPUT_PATH}.cpp") message(FATAL_ERROR "JS glue generation failed for ${glue_INPUT_IDL}.") endif() # Create a dependency so that it regenerated on edits. - add_custom_command(OUTPUT "${glue_OUTPUT_PATH}.cpp" - COMMAND ${PYTHON_EXECUTABLE} - $ENV{EMSCRIPTEN}/tools/webidl_binder.py - ${glue_INPUT_IDL} ${glue_OUTPUT_PATH} - DEPENDS ${draco_js_dec_idl} - COMMENT "Generating ${glue_OUTPUT_PATH}.cpp." - WORKING_DIRECTORY ${draco_build} - VERBATIM) + add_custom_command( + OUTPUT "${glue_OUTPUT_PATH}.cpp" + COMMAND ${PYTHON_EXECUTABLE} $ENV{EMSCRIPTEN}/tools/webidl_binder.py + ${glue_INPUT_IDL} ${glue_OUTPUT_PATH} + DEPENDS ${draco_js_dec_idl} + COMMENT "Generating ${glue_OUTPUT_PATH}.cpp." + WORKING_DIRECTORY ${draco_build} + VERBATIM) endmacro() # Wrapper for draco_add_executable() that handles the extra work necessary for @@ -120,8 +160,14 @@ macro(draco_add_emscripten_executable) unset(emexe_LINK_FLAGS) set(optional_args) set(single_value_args NAME GLUE_PATH) - set(multi_value_args SOURCES DEFINES FEATURES INCLUDES LINK_FLAGS - PRE_LINK_JS_SOURCES POST_LINK_JS_SOURCES) + set(multi_value_args + SOURCES + DEFINES + FEATURES + INCLUDES + LINK_FLAGS + PRE_LINK_JS_SOURCES + POST_LINK_JS_SOURCES) cmake_parse_arguments(emexe "${optional_args}" "${single_value_args}" "${multi_value_args}" ${ARGN}) @@ -136,49 +182,50 @@ macro(draco_add_emscripten_executable) endif() if(DRACO_VERBOSE GREATER 1) - message("--------- draco_add_emscripten_executable ---------\n" - "emexe_NAME=${emexe_NAME}\n" - "emexe_SOURCES=${emexe_SOURCES}\n" - "emexe_DEFINES=${emexe_DEFINES}\n" - "emexe_INCLUDES=${emexe_INCLUDES}\n" - "emexe_LINK_FLAGS=${emexe_LINK_FLAGS}\n" - "emexe_GLUE_PATH=${emexe_GLUE_PATH}\n" - "emexe_FEATURES=${emexe_FEATURES}\n" - "emexe_PRE_LINK_JS_SOURCES=${emexe_PRE_LINK_JS_SOURCES}\n" - "emexe_POST_LINK_JS_SOURCES=${emexe_POST_LINK_JS_SOURCES}\n" - "----------------------------------------------------\n") + message( + "--------- draco_add_emscripten_executable ---------\n" + "emexe_NAME=${emexe_NAME}\n" + "emexe_SOURCES=${emexe_SOURCES}\n" + "emexe_DEFINES=${emexe_DEFINES}\n" + "emexe_INCLUDES=${emexe_INCLUDES}\n" + "emexe_LINK_FLAGS=${emexe_LINK_FLAGS}\n" + "emexe_GLUE_PATH=${emexe_GLUE_PATH}\n" + "emexe_FEATURES=${emexe_FEATURES}\n" + "emexe_PRE_LINK_JS_SOURCES=${emexe_PRE_LINK_JS_SOURCES}\n" + "emexe_POST_LINK_JS_SOURCES=${emexe_POST_LINK_JS_SOURCES}\n" + "----------------------------------------------------\n") endif() # The Emscripten linker needs the C++ flags in addition to whatever has been # passed in with the target. list(APPEND emexe_LINK_FLAGS ${DRACO_CXX_FLAGS}) - if(DRACO_GLTF) - draco_add_executable(NAME - ${emexe_NAME} - OUTPUT_NAME - ${emexe_NAME}_gltf - SOURCES - ${emexe_SOURCES} - DEFINES - ${emexe_DEFINES} - INCLUDES - ${emexe_INCLUDES} - LINK_FLAGS - ${emexe_LINK_FLAGS}) + if(DRACO_GLTF_BITSTREAM) + # Add "_gltf" suffix to target output name. + draco_add_executable( + NAME ${emexe_NAME} + OUTPUT_NAME ${emexe_NAME}_gltf + SOURCES ${emexe_SOURCES} + DEFINES ${emexe_DEFINES} + INCLUDES ${emexe_INCLUDES} + LINK_FLAGS ${emexe_LINK_FLAGS}) else() - draco_add_executable(NAME ${emexe_NAME} SOURCES ${emexe_SOURCES} DEFINES - ${emexe_DEFINES} INCLUDES ${emexe_INCLUDES} LINK_FLAGS - ${emexe_LINK_FLAGS}) + draco_add_executable( + NAME ${emexe_NAME} + SOURCES ${emexe_SOURCES} + DEFINES ${emexe_DEFINES} + INCLUDES ${emexe_INCLUDES} + LINK_FLAGS ${emexe_LINK_FLAGS}) endif() foreach(feature ${emexe_FEATURES}) draco_enable_feature(FEATURE ${feature} TARGETS ${emexe_NAME}) endforeach() - set_property(SOURCE ${emexe_SOURCES} - APPEND - PROPERTY OBJECT_DEPENDS "${emexe_GLUE_PATH}.cpp") + set_property( + SOURCE ${emexe_SOURCES} + APPEND + PROPERTY OBJECT_DEPENDS "${emexe_GLUE_PATH}.cpp") em_link_pre_js(${emexe_NAME} ${emexe_PRE_LINK_JS_SOURCES}) em_link_post_js(${emexe_NAME} "${emexe_GLUE_PATH}.js" ${emexe_POST_LINK_JS_SOURCES}) diff --git a/contrib/draco/cmake/draco_flags.cmake b/contrib/draco/cmake/draco_flags.cmake index 0397859a4..f3b24c6e1 100644 --- a/contrib/draco/cmake/draco_flags.cmake +++ b/contrib/draco/cmake/draco_flags.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_FLAGS_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_FLAGS_CMAKE_ @@ -24,7 +38,7 @@ macro(draco_set_compiler_flags_for_sources) endif() set_source_files_properties(${compiler_SOURCES} PROPERTIES COMPILE_FLAGS - ${compiler_FLAGS}) + ${compiler_FLAGS}) if(DRACO_VERBOSE GREATER 1) foreach(source ${compiler_SOURCES}) @@ -85,8 +99,8 @@ macro(draco_test_cxx_flag) # are passed as a list it will remove the list separators, and attempt to run # a compile command using list entries concatenated together as a single # argument. Avoid the problem by forcing the argument to be a string. - draco_set_and_stringify(SOURCE_VARS all_cxx_flags DEST all_cxx_flags) - check_cxx_compiler_flag("${all_cxx_flags}" draco_all_cxx_flags_pass) + draco_set_and_stringify(SOURCE_VARS all_cxx_flags DEST all_cxx_flags_string) + check_cxx_compiler_flag("${all_cxx_flags_string}" draco_all_cxx_flags_pass) if(cxx_test_FLAG_REQUIRED AND NOT draco_all_cxx_flags_pass) draco_die("Flag test failed for required flag(s): " @@ -245,3 +259,34 @@ macro(draco_set_cxx_flags) draco_test_cxx_flag(FLAG_LIST_VAR_NAMES ${cxx_flag_lists}) endif() endmacro() + +# Collects Draco built-in and user-specified linker flags and tests them. Halts +# configuration and reports the error when any flags cause the build to fail. +# +# Note: draco_test_exe_linker_flag() does the real work of setting the flags and +# running the test compile commands. +macro(draco_set_exe_linker_flags) + unset(linker_flag_lists) + + if(DRACO_VERBOSE) + message("draco_set_exe_linker_flags: " + "draco_base_exe_linker_flags=${draco_base_exe_linker_flags}") + endif() + + if(draco_base_exe_linker_flags) + list(APPEND linker_flag_lists draco_base_exe_linker_flags) + endif() + + if(linker_flag_lists) + unset(test_linker_flags) + + if(DRACO_VERBOSE) + message("draco_set_exe_linker_flags: " + "linker_flag_lists=${linker_flag_lists}") + endif() + + draco_set_and_stringify(DEST test_linker_flags SOURCE_VARS + ${linker_flag_lists}) + draco_test_exe_linker_flag(FLAG_LIST_VAR_NAME test_linker_flags) + endif() +endmacro() diff --git a/contrib/draco/cmake/draco_helpers.cmake b/contrib/draco/cmake/draco_helpers.cmake index 0b3b804cf..69e24c5be 100644 --- a/contrib/draco/cmake/draco_helpers.cmake +++ b/contrib/draco/cmake/draco_helpers.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_HELPERS_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_HELPERS_CMAKE_ diff --git a/contrib/draco/cmake/draco_install.cmake b/contrib/draco/cmake/draco_install.cmake index 09bfb591d..3be1ba163 100644 --- a/contrib/draco/cmake/draco_install.cmake +++ b/contrib/draco/cmake/draco_install.cmake @@ -1,32 +1,32 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_INSTALL_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_INSTALL_CMAKE_ set(DRACO_CMAKE_DRACO_INSTALL_CMAKE_ 1) +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) + # Sets up the draco install targets. Must be called after the static library # target is created. macro(draco_setup_install_target) - include(GNUInstallDirs) - - # pkg-config: draco.pc - set(prefix "${CMAKE_INSTALL_PREFIX}") - set(exec_prefix "\${prefix}") - set(libdir "\${prefix}/${CMAKE_INSTALL_LIBDIR}") - set(includedir "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") - set(draco_lib_name "draco") - - configure_file("${draco_root}/cmake/draco.pc.template" - "${draco_build}/draco.pc" @ONLY NEWLINE_STYLE UNIX) - install(FILES "${draco_build}/draco.pc" - DESTINATION "${prefix}/${CMAKE_INSTALL_LIBDIR}/pkgconfig") - - # CMake config: draco-config.cmake - set(DRACO_INCLUDE_DIRS "${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") - configure_file("${draco_root}/cmake/draco-config.cmake.template" - "${draco_build}/draco-config.cmake" @ONLY NEWLINE_STYLE UNIX) - install( - FILES "${draco_build}/draco-config.cmake" - DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/cmake") + set(bin_path "${CMAKE_INSTALL_BINDIR}") + set(data_path "${CMAKE_INSTALL_DATAROOTDIR}") + set(includes_path "${CMAKE_INSTALL_INCLUDEDIR}") + set(libs_path "${CMAKE_INSTALL_LIBDIR}") foreach(file ${draco_sources}) if(file MATCHES "h$") @@ -34,46 +34,88 @@ macro(draco_setup_install_target) endif() endforeach() + list(REMOVE_DUPLICATES draco_api_includes) + # Strip $draco_src_root from the file paths: we need to install relative to # $include_directory. list(TRANSFORM draco_api_includes REPLACE "${draco_src_root}/" "") - set(include_directory "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}") foreach(draco_api_include ${draco_api_includes}) get_filename_component(file_directory ${draco_api_include} DIRECTORY) - set(target_directory "${include_directory}/draco/${file_directory}") + set(target_directory "${includes_path}/draco/${file_directory}") install(FILES ${draco_src_root}/${draco_api_include} DESTINATION "${target_directory}") endforeach() - install( - FILES "${draco_build}/draco/draco_features.h" - DESTINATION "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}/draco/") + install(FILES "${draco_build}/draco/draco_features.h" + DESTINATION "${includes_path}/draco/") - install(TARGETS draco_decoder DESTINATION - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}") - install(TARGETS draco_encoder DESTINATION - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}") + install(TARGETS draco_decoder DESTINATION "${bin_path}") + install(TARGETS draco_encoder DESTINATION "${bin_path}") + + if(DRACO_TRANSCODER_SUPPORTED) + install(TARGETS draco_transcoder DESTINATION "${bin_path}") + endif() if(MSVC) - install(TARGETS draco DESTINATION - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") + install( + TARGETS draco + EXPORT dracoExport + RUNTIME DESTINATION "${bin_path}" + ARCHIVE DESTINATION "${libs_path}" + LIBRARY DESTINATION "${libs_path}") else() - install(TARGETS draco_static DESTINATION - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") + install( + TARGETS draco_static + EXPORT dracoExport + DESTINATION "${libs_path}") + if(BUILD_SHARED_LIBS) - install(TARGETS draco_shared DESTINATION - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") + install( + TARGETS draco_shared + EXPORT dracoExport + RUNTIME DESTINATION "${bin_path}" + ARCHIVE DESTINATION "${libs_path}" + LIBRARY DESTINATION "${libs_path}") endif() endif() if(DRACO_UNITY_PLUGIN) - install(TARGETS dracodec_unity DESTINATION - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") - endif() - if(DRACO_MAYA_PLUGIN) - install(TARGETS draco_maya_wrapper DESTINATION - "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") + install(TARGETS dracodec_unity DESTINATION "${libs_path}") endif() + if(DRACO_MAYA_PLUGIN) + install(TARGETS draco_maya_wrapper DESTINATION "${libs_path}") + endif() + + # pkg-config: draco.pc + configure_file("${draco_root}/cmake/draco.pc.template" + "${draco_build}/draco.pc" @ONLY NEWLINE_STYLE UNIX) + install(FILES "${draco_build}/draco.pc" DESTINATION "${libs_path}/pkgconfig") + + # CMake config: draco-config.cmake + configure_package_config_file( + "${draco_root}/cmake/draco-config.cmake.template" + "${draco_build}/draco-config.cmake" + INSTALL_DESTINATION "${data_path}/cmake/draco") + + write_basic_package_version_file( + "${draco_build}/draco-config-version.cmake" + VERSION ${DRACO_VERSION} + COMPATIBILITY AnyNewerVersion) + + export( + EXPORT dracoExport + NAMESPACE draco:: + FILE "${draco_build}/draco-targets.cmake") + + install( + EXPORT dracoExport + NAMESPACE draco:: + FILE draco-targets.cmake + DESTINATION "${data_path}/cmake/draco") + + install(FILES "${draco_build}/draco-config.cmake" + "${draco_build}/draco-config-version.cmake" + DESTINATION "${data_path}/cmake/draco") endmacro() diff --git a/contrib/draco/cmake/draco_intrinsics.cmake b/contrib/draco/cmake/draco_intrinsics.cmake index 9011c0de5..178df97a6 100644 --- a/contrib/draco/cmake/draco_intrinsics.cmake +++ b/contrib/draco/cmake/draco_intrinsics.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_INTRINSICS_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_INTRINSICS_CMAKE_ @@ -61,17 +75,15 @@ macro(draco_process_intrinsics_sources) unset(sse4_sources) list(APPEND sse4_sources ${arg_SOURCES}) - list(FILTER sse4_sources INCLUDE REGEX - "${draco_sse4_source_file_suffix}$") + list(FILTER sse4_sources INCLUDE REGEX "${draco_sse4_source_file_suffix}$") if(sse4_sources) unset(sse4_flags) - draco_get_intrinsics_flag_for_suffix(SUFFIX - ${draco_sse4_source_file_suffix} - VARIABLE sse4_flags) + draco_get_intrinsics_flag_for_suffix( + SUFFIX ${draco_sse4_source_file_suffix} VARIABLE sse4_flags) if(sse4_flags) draco_set_compiler_flags_for_sources(SOURCES ${sse4_sources} FLAGS - ${sse4_flags}) + ${sse4_flags}) endif() endif() endif() @@ -79,17 +91,15 @@ macro(draco_process_intrinsics_sources) if(DRACO_ENABLE_NEON AND draco_have_neon) unset(neon_sources) list(APPEND neon_sources ${arg_SOURCES}) - list(FILTER neon_sources INCLUDE REGEX - "${draco_neon_source_file_suffix}$") + list(FILTER neon_sources INCLUDE REGEX "${draco_neon_source_file_suffix}$") if(neon_sources AND DRACO_NEON_INTRINSICS_FLAG) unset(neon_flags) - draco_get_intrinsics_flag_for_suffix(SUFFIX - ${draco_neon_source_file_suffix} - VARIABLE neon_flags) + draco_get_intrinsics_flag_for_suffix( + SUFFIX ${draco_neon_source_file_suffix} VARIABLE neon_flags) if(neon_flags) draco_set_compiler_flags_for_sources(SOURCES ${neon_sources} FLAGS - ${neon_flags}) + ${neon_flags}) endif() endif() endif() diff --git a/contrib/draco/cmake/draco_options.cmake b/contrib/draco/cmake/draco_options.cmake index 832bfb69f..085149774 100644 --- a/contrib/draco/cmake/draco_options.cmake +++ b/contrib/draco/cmake/draco_options.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_OPTIONS_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_OPTIONS_CMAKE_ @@ -18,17 +32,22 @@ macro(draco_option) cmake_parse_arguments(option "${optional_args}" "${single_value_args}" "${multi_value_args}" ${ARGN}) - if(NOT (option_NAME AND option_HELPSTRING AND DEFINED option_VALUE)) + if(NOT + (option_NAME + AND option_HELPSTRING + AND DEFINED option_VALUE)) message(FATAL_ERROR "draco_option: NAME HELPSTRING and VALUE required.") endif() option(${option_NAME} ${option_HELPSTRING} ${option_VALUE}) if(DRACO_VERBOSE GREATER 2) - message("--------- draco_option ---------\n" "option_NAME=${option_NAME}\n" - "option_HELPSTRING=${option_HELPSTRING}\n" - "option_VALUE=${option_VALUE}\n" - "------------------------------------------\n") + message( + "--------- draco_option ---------\n" + "option_NAME=${option_NAME}\n" + "option_HELPSTRING=${option_HELPSTRING}\n" + "option_VALUE=${option_VALUE}\n" + "------------------------------------------\n") endif() list(APPEND draco_options ${option_NAME}) @@ -44,33 +63,74 @@ endmacro() # Set default options. macro(draco_set_default_options) - draco_option(NAME DRACO_FAST HELPSTRING "Try to build faster libs." VALUE OFF) - draco_option(NAME DRACO_JS_GLUE HELPSTRING - "Enable JS Glue and JS targets when using Emscripten." VALUE ON) - draco_option(NAME DRACO_IE_COMPATIBLE HELPSTRING - "Enable support for older IE builds when using Emscripten." VALUE - OFF) - draco_option(NAME DRACO_MESH_COMPRESSION HELPSTRING "Enable mesh compression." - VALUE ON) - draco_option(NAME DRACO_POINT_CLOUD_COMPRESSION HELPSTRING - "Enable point cloud compression." VALUE ON) - draco_option(NAME DRACO_PREDICTIVE_EDGEBREAKER HELPSTRING - "Enable predictive edgebreaker." VALUE ON) - draco_option(NAME DRACO_STANDARD_EDGEBREAKER HELPSTRING - "Enable stand edgebreaker." VALUE ON) - draco_option(NAME DRACO_BACKWARDS_COMPATIBILITY HELPSTRING - "Enable backwards compatibility." VALUE ON) - draco_option(NAME DRACO_DECODER_ATTRIBUTE_DEDUPLICATION HELPSTRING - "Enable attribute deduping." VALUE OFF) - draco_option(NAME DRACO_TESTS HELPSTRING "Enables tests." VALUE OFF) - draco_option(NAME DRACO_WASM HELPSTRING "Enables WASM support." VALUE OFF) - draco_option(NAME DRACO_UNITY_PLUGIN HELPSTRING - "Build plugin library for Unity." VALUE OFF) - draco_option(NAME DRACO_ANIMATION_ENCODING HELPSTRING "Enable animation." - VALUE OFF) - draco_option(NAME DRACO_GLTF HELPSTRING "Support GLTF." VALUE OFF) - draco_option(NAME DRACO_MAYA_PLUGIN HELPSTRING - "Build plugin library for Maya." VALUE OFF) + draco_option( + NAME DRACO_FAST + HELPSTRING "Try to build faster libs." + VALUE OFF) + draco_option( + NAME DRACO_JS_GLUE + HELPSTRING "Enable JS Glue and JS targets when using Emscripten." + VALUE ON) + draco_option( + NAME DRACO_IE_COMPATIBLE + HELPSTRING "Enable support for older IE builds when using Emscripten." + VALUE OFF) + draco_option( + NAME DRACO_MESH_COMPRESSION + HELPSTRING "Enable mesh compression." + VALUE ON) + draco_option( + NAME DRACO_POINT_CLOUD_COMPRESSION + HELPSTRING "Enable point cloud compression." + VALUE ON) + draco_option( + NAME DRACO_PREDICTIVE_EDGEBREAKER + HELPSTRING "Enable predictive edgebreaker." + VALUE ON) + draco_option( + NAME DRACO_STANDARD_EDGEBREAKER + HELPSTRING "Enable stand edgebreaker." + VALUE ON) + draco_option( + NAME DRACO_BACKWARDS_COMPATIBILITY + HELPSTRING "Enable backwards compatibility." + VALUE ON) + draco_option( + NAME DRACO_DECODER_ATTRIBUTE_DEDUPLICATION + HELPSTRING "Enable attribute deduping." + VALUE OFF) + draco_option( + NAME DRACO_TESTS + HELPSTRING "Enables tests." + VALUE OFF) + draco_option( + NAME DRACO_WASM + HELPSTRING "Enables WASM support." + VALUE OFF) + draco_option( + NAME DRACO_UNITY_PLUGIN + HELPSTRING "Build plugin library for Unity." + VALUE OFF) + draco_option( + NAME DRACO_ANIMATION_ENCODING + HELPSTRING "Enable animation." + VALUE OFF) + draco_option( + NAME DRACO_GLTF_BITSTREAM + HELPSTRING "Draco GLTF extension bitstream specified features only." + VALUE OFF) + draco_option( + NAME DRACO_MAYA_PLUGIN + HELPSTRING "Build plugin library for Maya." + VALUE OFF) + draco_option( + NAME DRACO_TRANSCODER_SUPPORTED + HELPSTRING "Enable the Draco transcoder." + VALUE OFF) + draco_option( + NAME DRACO_DEBUG_COMPILER_WARNINGS + HELPSTRING "Turn on more warnings." + VALUE OFF) draco_check_deprecated_options() endmacro() @@ -117,14 +177,16 @@ macro(draco_check_deprecated_options) DRACO_MAYA_PLUGIN) draco_handle_deprecated_option(OLDNAME BUILD_USD_PLUGIN NEWNAME BUILD_SHARED_LIBS) + draco_handle_deprecated_option(OLDNAME DRACO_GLTF NEWNAME + DRACO_GLTF_BITSTREAM) endmacro() # Macro for setting Draco features based on user configuration. Features enabled # by this macro are Draco global. macro(draco_set_optional_features) - if(DRACO_GLTF) - # Override settings when building for GLTF. + if(DRACO_GLTF_BITSTREAM) + # Enable only the features included in the Draco GLTF bitstream spec. draco_enable_feature(FEATURE "DRACO_MESH_COMPRESSION_SUPPORTED") draco_enable_feature(FEATURE "DRACO_NORMAL_ENCODING_SUPPORTED") draco_enable_feature(FEATURE "DRACO_STANDARD_EDGEBREAKER_SUPPORTED") @@ -170,6 +232,11 @@ macro(draco_set_optional_features) set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() + if(DRACO_TRANSCODER_SUPPORTED) + draco_enable_feature(FEATURE "DRACO_TRANSCODER_SUPPORTED") + endif() + + endmacro() # Macro that handles tracking of Draco preprocessor symbols for the purpose of @@ -221,8 +288,56 @@ function(draco_generate_features_h) file(APPEND "${draco_features_file_name}.new" "#define ${feature}\n") endforeach() + if(MSVC) + if(NOT DRACO_DEBUG_COMPILER_WARNINGS) + file(APPEND "${draco_features_file_name}.new" + "// Enable DRACO_DEBUG_COMPILER_WARNINGS at CMake generation \n" + "// time to remove these pragmas.\n") + + # warning C4018: '': signed/unsigned mismatch. + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4018)\n") + + # warning C4146: unary minus operator applied to unsigned type, result + # still unsigned + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4146)\n") + + # warning C4244: 'return': conversion from '' to '', possible + # loss of data. + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4244)\n") + + # warning C4267: 'initializing' conversion from '' to '', + # possible loss of data. + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4267)\n") + + # warning C4305: 'context' : truncation from 'type1' to 'type2'. + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4305)\n") + + # warning C4661: 'identifier' : no suitable definition provided for + # explicit template instantiation request. + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4661)\n") + + # warning C4800: Implicit conversion from 'type' to bool. Possible + # information loss. + # Also, in older MSVC releases: + # warning C4800: 'type' : forcing value to bool 'true' or 'false' + # (performance warning). + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4800)\n") + + # warning C4804: '': unsafe use of type '' in operation. + file(APPEND "${draco_features_file_name}.new" + "#pragma warning(disable:4804)\n") + endif() + endif() + file(APPEND "${draco_features_file_name}.new" - "\n#endif // DRACO_FEATURES_H_") + "\n#endif // DRACO_FEATURES_H_\n") # Will replace ${draco_features_file_name} only if the file content has # changed. This prevents forced Draco rebuilds after CMake runs. diff --git a/contrib/draco/cmake/draco_sanitizer.cmake b/contrib/draco/cmake/draco_sanitizer.cmake index d2e41a6cb..77d141481 100644 --- a/contrib/draco/cmake/draco_sanitizer.cmake +++ b/contrib/draco/cmake/draco_sanitizer.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_SANITIZER_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_SANITIZER_CMAKE_ @@ -5,7 +19,9 @@ set(DRACO_CMAKE_DRACO_SANITIZER_CMAKE_ 1) # Handles the details of enabling sanitizers. macro(draco_configure_sanitizer) - if(DRACO_SANITIZE AND NOT EMSCRIPTEN AND NOT MSVC) + if(DRACO_SANITIZE + AND NOT EMSCRIPTEN + AND NOT MSVC) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") if(DRACO_SANITIZE MATCHES "cfi") list(APPEND SAN_CXX_FLAGS "-flto" "-fno-sanitize-trap=cfi") @@ -13,8 +29,8 @@ macro(draco_configure_sanitizer) "-fuse-ld=gold") endif() - if(${CMAKE_SIZEOF_VOID_P} EQUAL 4 - AND DRACO_SANITIZE MATCHES "integer|undefined") + if(${CMAKE_SIZEOF_VOID_P} EQUAL 4 AND DRACO_SANITIZE MATCHES + "integer|undefined") list(APPEND SAN_LINKER_FLAGS "--rtlib=compiler-rt" "-lgcc_s") endif() endif() diff --git a/contrib/draco/cmake/draco_targets.cmake b/contrib/draco/cmake/draco_targets.cmake index 0456c4d7b..c8c79f511 100644 --- a/contrib/draco/cmake/draco_targets.cmake +++ b/contrib/draco/cmake/draco_targets.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_TARGETS_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_TARGETS_CMAKE_ @@ -51,26 +65,33 @@ macro(draco_add_executable) unset(exe_LIB_DEPS) set(optional_args TEST) set(single_value_args NAME OUTPUT_NAME) - set(multi_value_args SOURCES DEFINES INCLUDES COMPILE_FLAGS LINK_FLAGS - OBJLIB_DEPS LIB_DEPS) + set(multi_value_args + SOURCES + DEFINES + INCLUDES + COMPILE_FLAGS + LINK_FLAGS + OBJLIB_DEPS + LIB_DEPS) cmake_parse_arguments(exe "${optional_args}" "${single_value_args}" "${multi_value_args}" ${ARGN}) if(DRACO_VERBOSE GREATER 1) - message("--------- draco_add_executable ---------\n" - "exe_TEST=${exe_TEST}\n" - "exe_TEST_DEFINES_MAIN=${exe_TEST_DEFINES_MAIN}\n" - "exe_NAME=${exe_NAME}\n" - "exe_OUTPUT_NAME=${exe_OUTPUT_NAME}\n" - "exe_SOURCES=${exe_SOURCES}\n" - "exe_DEFINES=${exe_DEFINES}\n" - "exe_INCLUDES=${exe_INCLUDES}\n" - "exe_COMPILE_FLAGS=${exe_COMPILE_FLAGS}\n" - "exe_LINK_FLAGS=${exe_LINK_FLAGS}\n" - "exe_OBJLIB_DEPS=${exe_OBJLIB_DEPS}\n" - "exe_LIB_DEPS=${exe_LIB_DEPS}\n" - "------------------------------------------\n") + message( + "--------- draco_add_executable ---------\n" + "exe_TEST=${exe_TEST}\n" + "exe_TEST_DEFINES_MAIN=${exe_TEST_DEFINES_MAIN}\n" + "exe_NAME=${exe_NAME}\n" + "exe_OUTPUT_NAME=${exe_OUTPUT_NAME}\n" + "exe_SOURCES=${exe_SOURCES}\n" + "exe_DEFINES=${exe_DEFINES}\n" + "exe_INCLUDES=${exe_INCLUDES}\n" + "exe_COMPILE_FLAGS=${exe_COMPILE_FLAGS}\n" + "exe_LINK_FLAGS=${exe_LINK_FLAGS}\n" + "exe_OBJLIB_DEPS=${exe_OBJLIB_DEPS}\n" + "exe_LIB_DEPS=${exe_LIB_DEPS}\n" + "------------------------------------------\n") endif() if(NOT (exe_NAME AND exe_SOURCES)) @@ -87,7 +108,12 @@ macro(draco_add_executable) endif() add_executable(${exe_NAME} ${exe_SOURCES}) - set_target_properties(${exe_NAME} PROPERTIES VERSION ${DRACO_VERSION}) + + target_compile_features(${exe_NAME} PUBLIC cxx_std_11) + + if(NOT EMSCRIPTEN) + set_target_properties(${exe_NAME} PROPERTIES VERSION ${DRACO_VERSION}) + endif() if(exe_OUTPUT_NAME) set_target_properties(${exe_NAME} PROPERTIES OUTPUT_NAME ${exe_OUTPUT_NAME}) @@ -104,8 +130,8 @@ macro(draco_add_executable) endif() if(exe_COMPILE_FLAGS OR DRACO_CXX_FLAGS) - target_compile_options(${exe_NAME} - PRIVATE ${exe_COMPILE_FLAGS} ${DRACO_CXX_FLAGS}) + target_compile_options(${exe_NAME} PRIVATE ${exe_COMPILE_FLAGS} + ${DRACO_CXX_FLAGS}) endif() if(exe_LINK_FLAGS OR DRACO_EXE_LINKER_FLAGS) @@ -113,8 +139,8 @@ macro(draco_add_executable) list(APPEND exe_LINK_FLAGS "${DRACO_EXE_LINKER_FLAGS}") # LINK_FLAGS is managed as a string. draco_set_and_stringify(SOURCE "${exe_LINK_FLAGS}" DEST exe_LINK_FLAGS) - set_target_properties(${exe_NAME} - PROPERTIES LINK_FLAGS "${exe_LINK_FLAGS}") + set_target_properties(${exe_NAME} PROPERTIES LINK_FLAGS + "${exe_LINK_FLAGS}") else() target_link_options(${exe_NAME} PRIVATE ${exe_LINK_FLAGS} ${DRACO_EXE_LINKER_FLAGS}) @@ -136,12 +162,7 @@ macro(draco_add_executable) endif() if(exe_LIB_DEPS) - unset(exe_static) - if("${CMAKE_EXE_LINKER_FLAGS} ${DRACO_EXE_LINKER_FLAGS}" MATCHES "static") - set(exe_static ON) - endif() - - if(exe_static AND CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + if(CMAKE_CXX_COMPILER_ID MATCHES "^Clang|^GNU") # Third party dependencies can introduce dependencies on system and test # libraries. Since the target created here is an executable, and CMake # does not provide a method of controlling order of link dependencies, @@ -149,6 +170,10 @@ macro(draco_add_executable) # ensure that dependencies of third party targets can be resolved when # those dependencies happen to be resolved by dependencies of the current # target. + # TODO(tomfinegan): For portability use LINK_GROUP with RESCAN instead of + # directly (ab)using compiler/linker specific flags once CMake v3.24 is in + # wider use. See: + # https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#genex:LINK_GROUP list(INSERT exe_LIB_DEPS 0 -Wl,--start-group) list(APPEND exe_LIB_DEPS -Wl,--end-group) endif() @@ -209,27 +234,36 @@ macro(draco_add_library) unset(lib_TARGET_PROPERTIES) set(optional_args TEST) set(single_value_args NAME OUTPUT_NAME TYPE) - set(multi_value_args SOURCES DEFINES INCLUDES COMPILE_FLAGS LINK_FLAGS - OBJLIB_DEPS LIB_DEPS PUBLIC_INCLUDES TARGET_PROPERTIES) + set(multi_value_args + SOURCES + DEFINES + INCLUDES + COMPILE_FLAGS + LINK_FLAGS + OBJLIB_DEPS + LIB_DEPS + PUBLIC_INCLUDES + TARGET_PROPERTIES) cmake_parse_arguments(lib "${optional_args}" "${single_value_args}" "${multi_value_args}" ${ARGN}) if(DRACO_VERBOSE GREATER 1) - message("--------- draco_add_library ---------\n" - "lib_TEST=${lib_TEST}\n" - "lib_NAME=${lib_NAME}\n" - "lib_OUTPUT_NAME=${lib_OUTPUT_NAME}\n" - "lib_TYPE=${lib_TYPE}\n" - "lib_SOURCES=${lib_SOURCES}\n" - "lib_DEFINES=${lib_DEFINES}\n" - "lib_INCLUDES=${lib_INCLUDES}\n" - "lib_COMPILE_FLAGS=${lib_COMPILE_FLAGS}\n" - "lib_LINK_FLAGS=${lib_LINK_FLAGS}\n" - "lib_OBJLIB_DEPS=${lib_OBJLIB_DEPS}\n" - "lib_LIB_DEPS=${lib_LIB_DEPS}\n" - "lib_PUBLIC_INCLUDES=${lib_PUBLIC_INCLUDES}\n" - "---------------------------------------\n") + message( + "--------- draco_add_library ---------\n" + "lib_TEST=${lib_TEST}\n" + "lib_NAME=${lib_NAME}\n" + "lib_OUTPUT_NAME=${lib_OUTPUT_NAME}\n" + "lib_TYPE=${lib_TYPE}\n" + "lib_SOURCES=${lib_SOURCES}\n" + "lib_DEFINES=${lib_DEFINES}\n" + "lib_INCLUDES=${lib_INCLUDES}\n" + "lib_COMPILE_FLAGS=${lib_COMPILE_FLAGS}\n" + "lib_LINK_FLAGS=${lib_LINK_FLAGS}\n" + "lib_OBJLIB_DEPS=${lib_OBJLIB_DEPS}\n" + "lib_LIB_DEPS=${lib_LIB_DEPS}\n" + "lib_PUBLIC_INCLUDES=${lib_PUBLIC_INCLUDES}\n" + "---------------------------------------\n") endif() if(NOT (lib_NAME AND lib_TYPE)) @@ -256,14 +290,24 @@ macro(draco_add_library) endif() add_library(${lib_NAME} ${lib_TYPE} ${lib_SOURCES}) + + target_compile_features(${lib_NAME} PUBLIC cxx_std_11) + + target_include_directories(${lib_NAME} PUBLIC $) + + if(BUILD_SHARED_LIBS) + # Enable PIC for all targets in shared configurations. + set_target_properties(${lib_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON) + endif() + if(lib_SOURCES) draco_process_intrinsics_sources(TARGET ${lib_NAME} SOURCES ${lib_SOURCES}) endif() if(lib_OUTPUT_NAME) if(NOT (BUILD_SHARED_LIBS AND MSVC)) - set_target_properties(${lib_NAME} - PROPERTIES OUTPUT_NAME ${lib_OUTPUT_NAME}) + set_target_properties(${lib_NAME} PROPERTIES OUTPUT_NAME + ${lib_OUTPUT_NAME}) endif() endif() @@ -280,8 +324,8 @@ macro(draco_add_library) endif() if(lib_COMPILE_FLAGS OR DRACO_CXX_FLAGS) - target_compile_options(${lib_NAME} - PRIVATE ${lib_COMPILE_FLAGS} ${DRACO_CXX_FLAGS}) + target_compile_options(${lib_NAME} PRIVATE ${lib_COMPILE_FLAGS} + ${DRACO_CXX_FLAGS}) endif() if(lib_LINK_FLAGS) @@ -320,11 +364,12 @@ macro(draco_add_library) set_target_properties(${lib_NAME} PROPERTIES PREFIX "") endif() - # VERSION and SOVERSION as necessary - if(NOT lib_TYPE STREQUAL STATIC AND NOT lib_TYPE STREQUAL MODULE) - set_target_properties(${lib_NAME} PROPERTIES VERSION ${DRACO_VERSION}) - if(NOT MSVC) - set_target_properties(${lib_NAME} PROPERTIES SOVERSION ${DRACO_SOVERSION}) + if(NOT EMSCRIPTEN) + # VERSION and SOVERSION as necessary + if((lib_TYPE STREQUAL BUNDLE OR lib_TYPE STREQUAL SHARED) AND NOT MSVC) + set_target_properties( + ${lib_NAME} PROPERTIES VERSION ${DRACO_SOVERSION} + SOVERSION ${DRACO_SOVERSION_MAJOR}) endif() endif() diff --git a/contrib/draco/cmake/draco_test_config.h.cmake b/contrib/draco/cmake/draco_test_config.h.cmake index 77a574123..9bb174569 100644 --- a/contrib/draco/cmake/draco_test_config.h.cmake +++ b/contrib/draco/cmake/draco_test_config.h.cmake @@ -1,3 +1,17 @@ +// Copyright 2021 The Draco Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #ifndef DRACO_TESTING_DRACO_TEST_CONFIG_H_ #define DRACO_TESTING_DRACO_TEST_CONFIG_H_ @@ -9,5 +23,6 @@ #define DRACO_TEST_DATA_DIR "${DRACO_TEST_DATA_DIR}" #define DRACO_TEST_TEMP_DIR "${DRACO_TEST_TEMP_DIR}" +#define DRACO_TEST_ROOT_DIR "${DRACO_TEST_ROOT_DIR}" #endif // DRACO_TESTING_DRACO_TEST_CONFIG_H_ diff --git a/contrib/draco/cmake/draco_tests.cmake b/contrib/draco/cmake/draco_tests.cmake index a6dfc5b57..1d905a969 100644 --- a/contrib/draco/cmake/draco_tests.cmake +++ b/contrib/draco/cmake/draco_tests.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_TESTS_CMAKE) return() endif() @@ -10,6 +24,13 @@ set(draco_factory_test_sources "${draco_src_root}/io/file_reader_factory_test.cc" "${draco_src_root}/io/file_writer_factory_test.cc") +list( + APPEND draco_test_common_sources + "${draco_src_root}/core/draco_test_base.h" + "${draco_src_root}/core/draco_test_utils.cc" + "${draco_src_root}/core/draco_test_utils.h" + "${draco_src_root}/core/status.cc") + list( APPEND draco_test_sources @@ -30,22 +51,23 @@ list( "${draco_src_root}/compression/point_cloud/point_cloud_kd_tree_encoding_test.cc" "${draco_src_root}/compression/point_cloud/point_cloud_sequential_encoding_test.cc" "${draco_src_root}/core/buffer_bit_coding_test.cc" - "${draco_src_root}/core/draco_test_base.h" - "${draco_src_root}/core/draco_test_utils.cc" - "${draco_src_root}/core/draco_test_utils.h" "${draco_src_root}/core/math_utils_test.cc" "${draco_src_root}/core/quantization_utils_test.cc" "${draco_src_root}/core/status_test.cc" "${draco_src_root}/core/vector_d_test.cc" "${draco_src_root}/io/file_reader_test_common.h" "${draco_src_root}/io/file_utils_test.cc" + "${draco_src_root}/io/file_writer_utils_test.cc" "${draco_src_root}/io/stdio_file_reader_test.cc" "${draco_src_root}/io/stdio_file_writer_test.cc" "${draco_src_root}/io/obj_decoder_test.cc" "${draco_src_root}/io/obj_encoder_test.cc" "${draco_src_root}/io/ply_decoder_test.cc" "${draco_src_root}/io/ply_reader_test.cc" + "${draco_src_root}/io/stl_decoder_test.cc" + "${draco_src_root}/io/stl_encoder_test.cc" "${draco_src_root}/io/point_cloud_io_test.cc" + "${draco_src_root}/mesh/corner_table_test.cc" "${draco_src_root}/mesh/mesh_are_equivalent_test.cc" "${draco_src_root}/mesh/mesh_cleanup_test.cc" "${draco_src_root}/mesh/triangle_soup_mesh_builder_test.cc" @@ -54,47 +76,71 @@ list( "${draco_src_root}/point_cloud/point_cloud_builder_test.cc" "${draco_src_root}/point_cloud/point_cloud_test.cc") -list(APPEND draco_gtest_all - "${draco_root}/../googletest/googletest/src/gtest-all.cc") -list(APPEND draco_gtest_main - "${draco_root}/../googletest/googletest/src/gtest_main.cc") +if(DRACO_TRANSCODER_SUPPORTED) + list( + APPEND draco_test_sources + "${draco_src_root}/animation/animation_test.cc" + "${draco_src_root}/io/gltf_decoder_test.cc" + "${draco_src_root}/io/gltf_encoder_test.cc" + "${draco_src_root}/io/gltf_utils_test.cc" + "${draco_src_root}/io/gltf_test_helper.cc" + "${draco_src_root}/io/gltf_test_helper.h" + "${draco_src_root}/io/scene_io_test.cc" + "${draco_src_root}/io/texture_io_test.cc" + "${draco_src_root}/material/material_library_test.cc" + "${draco_src_root}/material/material_test.cc" + "${draco_src_root}/metadata/property_table_test.cc" + "${draco_src_root}/metadata/structural_metadata_test.cc" + "${draco_src_root}/scene/instance_array_test.cc" + "${draco_src_root}/scene/light_test.cc" + "${draco_src_root}/scene/mesh_group_test.cc" + "${draco_src_root}/scene/scene_test.cc" + "${draco_src_root}/scene/scene_are_equivalent_test.cc" + "${draco_src_root}/scene/scene_utils_test.cc" + "${draco_src_root}/scene/trs_matrix_test.cc" + "${draco_src_root}/texture/texture_library_test.cc" + "${draco_src_root}/texture/texture_map_test.cc" + "${draco_src_root}/texture/texture_transform_test.cc") + +endif() macro(draco_setup_test_targets) if(DRACO_TESTS) + draco_setup_googletest() + if(NOT (EXISTS ${draco_gtest_all} AND EXISTS ${draco_gtest_main})) - message(FATAL "googletest must be a sibling directory of ${draco_root}.") + message(FATAL_ERROR "googletest missing, run git submodule update --init") endif() list(APPEND draco_test_defines GTEST_HAS_PTHREAD=0) - draco_add_library(TEST - NAME - draco_gtest - TYPE - STATIC - SOURCES - ${draco_gtest_all} - DEFINES - ${draco_defines} - ${draco_test_defines} - INCLUDES - ${draco_test_include_paths}) + draco_add_library( + TEST + NAME draco_test_common + TYPE STATIC + SOURCES ${draco_test_common_sources} + DEFINES ${draco_defines} ${draco_test_defines} + INCLUDES ${draco_test_include_paths}) - draco_add_library(TEST - NAME - draco_gtest_main - TYPE - STATIC - SOURCES - ${draco_gtest_main} - DEFINES - ${draco_defines} - ${draco_test_defines} - INCLUDES - ${draco_test_include_paths}) + draco_add_library( + TEST + NAME draco_gtest + TYPE STATIC + SOURCES ${draco_gtest_all} + DEFINES ${draco_defines} ${draco_test_defines} + INCLUDES ${draco_test_include_paths}) + + draco_add_library( + TEST + NAME draco_gtest_main + TYPE STATIC + SOURCES ${draco_gtest_main} + DEFINES ${draco_defines} ${draco_test_defines} + INCLUDES ${draco_test_include_paths}) set(DRACO_TEST_DATA_DIR "${draco_root}/testdata") set(DRACO_TEST_TEMP_DIR "${draco_build}/draco_test_temp") + set(DRACO_TEST_ROOT_DIR "${draco_root}") file(MAKE_DIRECTORY "${DRACO_TEST_TEMP_DIR}") # Sets DRACO_TEST_DATA_DIR and DRACO_TEST_TEMP_DIR. @@ -102,32 +148,24 @@ macro(draco_setup_test_targets) "${draco_build}/testing/draco_test_config.h") # Create the test targets. - draco_add_executable(NAME - draco_tests - SOURCES - ${draco_test_sources} - DEFINES - ${draco_defines} - ${draco_test_defines} - INCLUDES - ${draco_test_include_paths} - LIB_DEPS - draco_static - draco_gtest - draco_gtest_main) + draco_add_executable( + TEST + NAME draco_tests + SOURCES ${draco_test_sources} + DEFINES ${draco_defines} ${draco_test_defines} + INCLUDES ${draco_test_include_paths} + LIB_DEPS ${draco_dependency} draco_gtest draco_gtest_main + draco_test_common) + + draco_add_executable( + TEST + NAME draco_factory_tests + SOURCES ${draco_factory_test_sources} + DEFINES ${draco_defines} ${draco_test_defines} + INCLUDES ${draco_test_include_paths} + LIB_DEPS ${draco_dependency} draco_gtest draco_gtest_main + draco_test_common) + - draco_add_executable(NAME - draco_factory_tests - SOURCES - ${draco_factory_test_sources} - DEFINES - ${draco_defines} - ${draco_test_defines} - INCLUDES - ${draco_test_include_paths} - LIB_DEPS - draco_static - draco_gtest - draco_gtest_main) endif() endmacro() diff --git a/contrib/draco/cmake/draco_variables.cmake b/contrib/draco/cmake/draco_variables.cmake index 8dbc77a53..6d1b6a99d 100644 --- a/contrib/draco/cmake/draco_variables.cmake +++ b/contrib/draco/cmake/draco_variables.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_DRACO_VARIABLES_CMAKE_) return() endif() # DRACO_CMAKE_DRACO_VARIABLES_CMAKE_ @@ -14,8 +28,7 @@ macro(draco_variable_must_be_directory variable_name) if("${${variable_name}}" STREQUAL "") message( - FATAL_ERROR - "Empty variable ${variable_name} is required to build draco.") + FATAL_ERROR "Empty variable ${variable_name} is required to build draco.") endif() if(NOT IS_DIRECTORY "${${variable_name}}") @@ -44,11 +57,13 @@ macro(draco_dump_cmake_flag_variables) list(APPEND flag_variables "CMAKE_CXX_FLAGS_INIT" "CMAKE_CXX_FLAGS" "CMAKE_EXE_LINKER_FLAGS_INIT" "CMAKE_EXE_LINKER_FLAGS") if(CMAKE_BUILD_TYPE) - list(APPEND flag_variables "CMAKE_BUILD_TYPE" - "CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}_INIT" - "CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}" - "CMAKE_EXE_LINKER_FLAGS_${CMAKE_BUILD_TYPE}_INIT" - "CMAKE_EXE_LINKER_FLAGS_${CMAKE_BUILD_TYPE}") + list( + APPEND flag_variables + "CMAKE_BUILD_TYPE" + "CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}_INIT" + "CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE}" + "CMAKE_EXE_LINKER_FLAGS_${CMAKE_BUILD_TYPE}_INIT" + "CMAKE_EXE_LINKER_FLAGS_${CMAKE_BUILD_TYPE}") endif() foreach(flag_variable ${flag_variables}) message("${flag_variable}:${${flag_variable}}") diff --git a/contrib/draco/cmake/sanitizers.cmake b/contrib/draco/cmake/sanitizers.cmake deleted file mode 100644 index e720bc045..000000000 --- a/contrib/draco/cmake/sanitizers.cmake +++ /dev/null @@ -1,19 +0,0 @@ -if(DRACO_CMAKE_SANITIZERS_CMAKE_) - return() -endif() -set(DRACO_CMAKE_SANITIZERS_CMAKE_ 1) - -if(MSVC OR NOT SANITIZE) - return() -endif() - -include("${draco_root}/cmake/compiler_flags.cmake") - -string(TOLOWER ${SANITIZE} SANITIZE) - -# Require the sanitizer requested. -require_linker_flag("-fsanitize=${SANITIZE}") -require_compiler_flag("-fsanitize=${SANITIZE}" YES) - -# Make callstacks accurate. -require_compiler_flag("-fno-omit-frame-pointer -fno-optimize-sibling-calls" YES) diff --git a/contrib/draco/cmake/toolchains/aarch64-linux-gnu.cmake b/contrib/draco/cmake/toolchains/aarch64-linux-gnu.cmake index 87e0b4a45..a55da20fa 100644 --- a/contrib/draco/cmake/toolchains/aarch64-linux-gnu.cmake +++ b/contrib/draco/cmake/toolchains/aarch64-linux-gnu.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_AARCH64_LINUX_GNU_CMAKE_) return() endif() # DRACO_CMAKE_TOOLCHAINS_AARCH64_LINUX_GNU_CMAKE_ diff --git a/contrib/draco/cmake/toolchains/android-ndk-common.cmake b/contrib/draco/cmake/toolchains/android-ndk-common.cmake index 5126d6e29..80396af48 100644 --- a/contrib/draco/cmake/toolchains/android-ndk-common.cmake +++ b/contrib/draco/cmake/toolchains/android-ndk-common.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ANDROID_NDK_COMMON_CMAKE_) return() endif() diff --git a/contrib/draco/cmake/toolchains/android.cmake b/contrib/draco/cmake/toolchains/android.cmake index b8f576d5e..ba50576b7 100644 --- a/contrib/draco/cmake/toolchains/android.cmake +++ b/contrib/draco/cmake/toolchains/android.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ANDROID_CMAKE_) return() endif() # DRACO_CMAKE_TOOLCHAINS_ANDROID_CMAKE_ @@ -16,9 +30,9 @@ if(NOT ANDROID_ABI) set(ANDROID_ABI arm64-v8a) endif() -# Force arm mode for 32-bit targets (instead of the default thumb) to improve -# performance. -if(NOT ANDROID_ARM_MODE) +# Force arm mode for 32-bit arm targets (instead of the default thumb) to +# improve performance. +if(ANDROID_ABI MATCHES "^armeabi" AND NOT ANDROID_ARM_MODE) set(ANDROID_ARM_MODE arm) endif() diff --git a/contrib/draco/cmake/toolchains/arm-ios-common.cmake b/contrib/draco/cmake/toolchains/arm-ios-common.cmake index 65326d1c2..fab54bb39 100644 --- a/contrib/draco/cmake/toolchains/arm-ios-common.cmake +++ b/contrib/draco/cmake/toolchains/arm-ios-common.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARM_IOS_COMMON_CMAKE_) return() endif() @@ -13,5 +27,3 @@ set(CMAKE_C_COMPILER clang) set(CMAKE_C_COMPILER_ARG1 "-arch ${CMAKE_SYSTEM_PROCESSOR}") set(CMAKE_CXX_COMPILER clang++) set(CMAKE_CXX_COMPILER_ARG1 "-arch ${CMAKE_SYSTEM_PROCESSOR}") - -# TODO(tomfinegan): Handle bit code embedding. diff --git a/contrib/draco/cmake/toolchains/arm-linux-gnueabihf.cmake b/contrib/draco/cmake/toolchains/arm-linux-gnueabihf.cmake index 6e45969e9..f1f83d67c 100644 --- a/contrib/draco/cmake/toolchains/arm-linux-gnueabihf.cmake +++ b/contrib/draco/cmake/toolchains/arm-linux-gnueabihf.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARM_LINUX_GNUEABIHF_CMAKE_) return() endif() # DRACO_CMAKE_TOOLCHAINS_ARM_LINUX_GNUEABIHF_CMAKE_ diff --git a/contrib/draco/cmake/toolchains/arm64-android-ndk-libcpp.cmake b/contrib/draco/cmake/toolchains/arm64-android-ndk-libcpp.cmake index 4b6d366f0..80d452f97 100644 --- a/contrib/draco/cmake/toolchains/arm64-android-ndk-libcpp.cmake +++ b/contrib/draco/cmake/toolchains/arm64-android-ndk-libcpp.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARM64_ANDROID_NDK_LIBCPP_CMAKE_) return() endif() diff --git a/contrib/draco/cmake/toolchains/arm64-ios.cmake b/contrib/draco/cmake/toolchains/arm64-ios.cmake index c4ec7e3fa..5365d70f1 100644 --- a/contrib/draco/cmake/toolchains/arm64-ios.cmake +++ b/contrib/draco/cmake/toolchains/arm64-ios.cmake @@ -1,10 +1,23 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARM64_IOS_CMAKE_) return() endif() set(DRACO_CMAKE_TOOLCHAINS_ARM64_IOS_CMAKE_ 1) if(XCODE) - # TODO(tomfinegan): Handle arm builds in Xcode. message(FATAL_ERROR "This toolchain does not support Xcode.") endif() diff --git a/contrib/draco/cmake/toolchains/arm64-linux-gcc.cmake b/contrib/draco/cmake/toolchains/arm64-linux-gcc.cmake index 046ff0139..a332760b2 100644 --- a/contrib/draco/cmake/toolchains/arm64-linux-gcc.cmake +++ b/contrib/draco/cmake/toolchains/arm64-linux-gcc.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARM64_LINUX_GCC_CMAKE_) return() endif() diff --git a/contrib/draco/cmake/toolchains/armv7-android-ndk-libcpp.cmake b/contrib/draco/cmake/toolchains/armv7-android-ndk-libcpp.cmake index 80ee98b18..bedcc0cad 100644 --- a/contrib/draco/cmake/toolchains/armv7-android-ndk-libcpp.cmake +++ b/contrib/draco/cmake/toolchains/armv7-android-ndk-libcpp.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARMV7_ANDROID_NDK_LIBCPP_CMAKE_) return() endif() diff --git a/contrib/draco/cmake/toolchains/armv7-ios.cmake b/contrib/draco/cmake/toolchains/armv7-ios.cmake index 8ddd6997b..43e208b1f 100644 --- a/contrib/draco/cmake/toolchains/armv7-ios.cmake +++ b/contrib/draco/cmake/toolchains/armv7-ios.cmake @@ -1,10 +1,23 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARMV7_IOS_CMAKE_) return() endif() set(DRACO_CMAKE_TOOLCHAINS_ARMV7_IOS_CMAKE_ 1) if(XCODE) - # TODO(tomfinegan): Handle arm builds in Xcode. message(FATAL_ERROR "This toolchain does not support Xcode.") endif() diff --git a/contrib/draco/cmake/toolchains/armv7-linux-gcc.cmake b/contrib/draco/cmake/toolchains/armv7-linux-gcc.cmake index 9c9472319..730a87f4b 100644 --- a/contrib/draco/cmake/toolchains/armv7-linux-gcc.cmake +++ b/contrib/draco/cmake/toolchains/armv7-linux-gcc.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARMV7_LINUX_GCC_CMAKE_) return() endif() diff --git a/contrib/draco/cmake/toolchains/armv7s-ios.cmake b/contrib/draco/cmake/toolchains/armv7s-ios.cmake index b433025ba..472756117 100644 --- a/contrib/draco/cmake/toolchains/armv7s-ios.cmake +++ b/contrib/draco/cmake/toolchains/armv7s-ios.cmake @@ -1,10 +1,23 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_ARMV7S_IOS_CMAKE_) return() endif() set(DRACO_CMAKE_TOOLCHAINS_ARMV7S_IOS_CMAKE_ 1) if(XCODE) - # TODO(tomfinegan): Handle arm builds in Xcode. message(FATAL_ERROR "This toolchain does not support Xcode.") endif() diff --git a/contrib/draco/cmake/toolchains/i386-ios.cmake b/contrib/draco/cmake/toolchains/i386-ios.cmake index e9a105591..38989d225 100644 --- a/contrib/draco/cmake/toolchains/i386-ios.cmake +++ b/contrib/draco/cmake/toolchains/i386-ios.cmake @@ -1,10 +1,23 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_i386_IOS_CMAKE_) return() endif() set(DRACO_CMAKE_TOOLCHAINS_i386_IOS_CMAKE_ 1) if(XCODE) - # TODO(tomfinegan): Handle arm builds in Xcode. message(FATAL_ERROR "This toolchain does not support Xcode.") endif() diff --git a/contrib/draco/cmake/toolchains/x86-android-ndk-libcpp.cmake b/contrib/draco/cmake/toolchains/x86-android-ndk-libcpp.cmake index d43383640..6f63f2c31 100644 --- a/contrib/draco/cmake/toolchains/x86-android-ndk-libcpp.cmake +++ b/contrib/draco/cmake/toolchains/x86-android-ndk-libcpp.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_X86_ANDROID_NDK_LIBCPP_CMAKE_) return() endif() diff --git a/contrib/draco/cmake/toolchains/x86_64-android-ndk-libcpp.cmake b/contrib/draco/cmake/toolchains/x86_64-android-ndk-libcpp.cmake index d6fabeacc..7a630f4d4 100644 --- a/contrib/draco/cmake/toolchains/x86_64-android-ndk-libcpp.cmake +++ b/contrib/draco/cmake/toolchains/x86_64-android-ndk-libcpp.cmake @@ -1,3 +1,17 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_X86_64_ANDROID_NDK_LIBCPP_CMAKE_) return() endif() diff --git a/contrib/draco/cmake/toolchains/x86_64-ios.cmake b/contrib/draco/cmake/toolchains/x86_64-ios.cmake index 4c50a72a2..6946ce410 100644 --- a/contrib/draco/cmake/toolchains/x86_64-ios.cmake +++ b/contrib/draco/cmake/toolchains/x86_64-ios.cmake @@ -1,10 +1,23 @@ +# Copyright 2021 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + if(DRACO_CMAKE_TOOLCHAINS_X86_64_IOS_CMAKE_) return() endif() set(DRACO_CMAKE_TOOLCHAINS_X86_64_IOS_CMAKE_ 1) if(XCODE) - # TODO(tomfinegan): Handle arm builds in Xcode. message(FATAL_ERROR "This toolchain does not support Xcode.") endif() diff --git a/contrib/draco/cmake/util.cmake b/contrib/draco/cmake/util.cmake deleted file mode 100644 index 813146a62..000000000 --- a/contrib/draco/cmake/util.cmake +++ /dev/null @@ -1,79 +0,0 @@ -if(DRACO_CMAKE_UTIL_CMAKE_) - return() -endif() -set(DRACO_CMAKE_UTIL_CMAKE_ 1) - -# Creates dummy source file in $draco_build_dir named $basename.$extension and -# returns the full path to the dummy source file via the $out_file_path -# parameter. -function(create_dummy_source_file basename extension out_file_path) - set(dummy_source_file "${draco_build_dir}/${basename}.${extension}") - file(WRITE "${dummy_source_file}.new" - "// Generated file. DO NOT EDIT!\n" - "// ${target_name} needs a ${extension} file to force link language, \n" - "// or to silence a harmless CMake warning: Ignore me.\n" - "void ${target_name}_dummy_function(void) {}\n") - - # Will replace ${dummy_source_file} only if the file content has changed. - # This prevents forced Draco rebuilds after CMake runs. - configure_file("${dummy_source_file}.new" "${dummy_source_file}") - file(REMOVE "${dummy_source_file}.new") - - set(${out_file_path} ${dummy_source_file} PARENT_SCOPE) -endfunction() - -# Convenience function for adding a dummy source file to $target_name using -# $extension as the file extension. Wraps create_dummy_source_file(). -function(add_dummy_source_file_to_target target_name extension) - create_dummy_source_file("${target_name}" "${extension}" "dummy_source_file") - target_sources(${target_name} PRIVATE ${dummy_source_file}) -endfunction() - -# Extracts the version number from $version_file and returns it to the user via -# $version_string_out_var. This is achieved by finding the first instance of the -# kDracoVersion variable and then removing everything but the string literal -# assigned to the variable. Quotes and semicolon are stripped from the returned -# string. -function(extract_version_string version_file version_string_out_var) - file(STRINGS "${version_file}" draco_version REGEX "kDracoVersion") - list(GET draco_version 0 draco_version) - string(REPLACE "static const char kDracoVersion[] = " "" draco_version - "${draco_version}") - string(REPLACE ";" "" draco_version "${draco_version}") - string(REPLACE "\"" "" draco_version "${draco_version}") - set("${version_string_out_var}" "${draco_version}" PARENT_SCOPE) -endfunction() - -# Sets CMake compiler launcher to $launcher_name when $launcher_name is found in -# $PATH. Warns user about ignoring build flag $launcher_flag when $launcher_name -# is not found in $PATH. -function(set_compiler_launcher launcher_flag launcher_name) - find_program(launcher_path "${launcher_name}") - if(launcher_path) - set(CMAKE_C_COMPILER_LAUNCHER "${launcher_path}" PARENT_SCOPE) - set(CMAKE_CXX_COMPILER_LAUNCHER "${launcher_path}" PARENT_SCOPE) - message("--- Using ${launcher_name} as compiler launcher.") - else() - message( - WARNING "--- Cannot find ${launcher_name}, ${launcher_flag} ignored.") - endif() -endfunction() - -# Terminates CMake execution when $var_name is unset in the environment. Sets -# CMake variable to the value of the environment variable when the variable is -# present in the environment. -macro(require_variable var_name) - if("$ENV{${var_name}}" STREQUAL "") - message(FATAL_ERROR "${var_name} must be set in environment.") - endif() - set_variable_if_unset(${var_name} "") -endmacro() - -# Sets $var_name to $default_value if not already set. -macro(set_variable_if_unset var_name default_value) - if(NOT "$ENV{${var_name}}" STREQUAL "") - set(${var_name} $ENV{${var_name}}) - elseif(NOT ${var_name}) - set(${var_name} ${default_value}) - endif() -endmacro() diff --git a/contrib/draco/src/draco/animation/animation.cc b/contrib/draco/src/draco/animation/animation.cc new file mode 100644 index 000000000..471cf2942 --- /dev/null +++ b/contrib/draco/src/draco/animation/animation.cc @@ -0,0 +1,47 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/animation/animation.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +void Animation::Copy(const Animation &src) { + name_ = src.name_; + channels_.clear(); + for (int i = 0; i < src.NumChannels(); ++i) { + std::unique_ptr new_channel(new AnimationChannel()); + new_channel->Copy(*src.GetChannel(i)); + channels_.push_back(std::move(new_channel)); + } + + samplers_.clear(); + for (int i = 0; i < src.NumSamplers(); ++i) { + std::unique_ptr new_sampler(new AnimationSampler()); + new_sampler->Copy(*src.GetSampler(i)); + samplers_.push_back(std::move(new_sampler)); + } + + node_animation_data_.clear(); + for (int i = 0; i < src.NumNodeAnimationData(); ++i) { + std::unique_ptr new_data(new NodeAnimationData()); + new_data->Copy(*src.GetNodeAnimationData(i)); + node_animation_data_.push_back(std::move(new_data)); + } +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/animation/animation.h b/contrib/draco/src/draco/animation/animation.h new file mode 100644 index 000000000..3713f9886 --- /dev/null +++ b/contrib/draco/src/draco/animation/animation.h @@ -0,0 +1,149 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_ANIMATION_ANIMATION_H_ +#define DRACO_ANIMATION_ANIMATION_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/animation/node_animation_data.h" +#include "draco/core/status.h" + +namespace draco { + +// Struct to hold information about an animation's sampler. +struct AnimationSampler { + enum class SamplerInterpolation { LINEAR, STEP, CUBICSPLINE }; + + static std::string InterpolationToString(SamplerInterpolation value) { + switch (value) { + case SamplerInterpolation::STEP: + return "STEP"; + case SamplerInterpolation::CUBICSPLINE: + return "CUBICSPLINE"; + default: + return "LINEAR"; + } + } + + AnimationSampler() + : input_index(-1), + interpolation_type(SamplerInterpolation::LINEAR), + output_index(-1) {} + + void Copy(const AnimationSampler &src) { + input_index = src.input_index; + interpolation_type = src.interpolation_type; + output_index = src.output_index; + } + + int input_index; + SamplerInterpolation interpolation_type; + int output_index; +}; + +// Struct to hold information about an animation's channel. +struct AnimationChannel { + enum class ChannelTransformation { TRANSLATION, ROTATION, SCALE, WEIGHTS }; + + static std::string TransformationToString(ChannelTransformation value) { + switch (value) { + case ChannelTransformation::ROTATION: + return "rotation"; + case ChannelTransformation::SCALE: + return "scale"; + case ChannelTransformation::WEIGHTS: + return "weights"; + default: + return "translation"; + } + } + + AnimationChannel() + : target_index(-1), + transformation_type(ChannelTransformation::TRANSLATION), + sampler_index(-1) {} + + void Copy(const AnimationChannel &src) { + target_index = src.target_index; + transformation_type = src.transformation_type; + sampler_index = src.sampler_index; + } + + int target_index; + ChannelTransformation transformation_type; + int sampler_index; +}; + +// This class is used to hold data and information of glTF animations. +class Animation { + public: + Animation() {} + + void Copy(const Animation &src); + + const std::string &GetName() const { return name_; } + void SetName(const std::string &name) { name_ = name; } + + // Returns the number of channels in an animation. + int NumChannels() const { return channels_.size(); } + // Returns the number of samplers in an animation. + int NumSamplers() const { return samplers_.size(); } + // Returns the number of accessors in an animation. + int NumNodeAnimationData() const { return node_animation_data_.size(); } + + // Returns a channel in the animation. + AnimationChannel *GetChannel(int index) { return channels_[index].get(); } + const AnimationChannel *GetChannel(int index) const { + return channels_[index].get(); + } + // Returns a sampler in the animation. + AnimationSampler *GetSampler(int index) { return samplers_[index].get(); } + const AnimationSampler *GetSampler(int index) const { + return samplers_[index].get(); + } + // Returns an accessor in the animation. + NodeAnimationData *GetNodeAnimationData(int index) { + return node_animation_data_[index].get(); + } + const NodeAnimationData *GetNodeAnimationData(int index) const { + return node_animation_data_[index].get(); + } + + void AddNodeAnimationData( + std::unique_ptr node_animation_data) { + node_animation_data_.push_back(std::move(node_animation_data)); + } + void AddSampler(std::unique_ptr sampler) { + samplers_.push_back(std::move(sampler)); + } + void AddChannel(std::unique_ptr channel) { + channels_.push_back(std::move(channel)); + } + + private: + std::string name_; + std::vector> samplers_; + std::vector> channels_; + std::vector> node_animation_data_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_ANIMATION_ANIMATION_H_ diff --git a/contrib/draco/src/draco/animation/animation_test.cc b/contrib/draco/src/draco/animation/animation_test.cc new file mode 100644 index 000000000..473938bca --- /dev/null +++ b/contrib/draco/src/draco/animation/animation_test.cc @@ -0,0 +1,71 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/animation/animation.h" + +#include "draco/core/draco_test_base.h" +#include "draco/draco_features.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED +TEST(AnimationTest, TestCopy) { + // Test copying of animation data. + draco::Animation src_anim; + ASSERT_TRUE(src_anim.GetName().empty()); + src_anim.SetName("Walking"); + ASSERT_EQ(src_anim.GetName(), "Walking"); + + std::unique_ptr src_sampler_0( + new draco::AnimationSampler()); + src_sampler_0->interpolation_type = + draco::AnimationSampler::SamplerInterpolation::CUBICSPLINE; + std::unique_ptr src_sampler_1( + new draco::AnimationSampler()); + src_sampler_1->Copy(*src_sampler_0); + + ASSERT_EQ(src_sampler_0->interpolation_type, + src_sampler_1->interpolation_type); + + src_sampler_1->interpolation_type = + draco::AnimationSampler::SamplerInterpolation::STEP; + + src_anim.AddSampler(std::move(src_sampler_0)); + src_anim.AddSampler(std::move(src_sampler_1)); + ASSERT_EQ(src_anim.NumSamplers(), 2); + + std::unique_ptr src_channel( + new draco::AnimationChannel()); + src_channel->transformation_type = + draco::AnimationChannel::ChannelTransformation::WEIGHTS; + src_anim.AddChannel(std::move(src_channel)); + ASSERT_EQ(src_anim.NumChannels(), 1); + + draco::Animation dst_anim; + dst_anim.Copy(src_anim); + + ASSERT_EQ(dst_anim.GetName(), src_anim.GetName()); + ASSERT_EQ(dst_anim.NumSamplers(), 2); + ASSERT_EQ(dst_anim.NumChannels(), 1); + + ASSERT_EQ(dst_anim.GetSampler(0)->interpolation_type, + src_anim.GetSampler(0)->interpolation_type); + ASSERT_EQ(dst_anim.GetSampler(1)->interpolation_type, + src_anim.GetSampler(1)->interpolation_type); + ASSERT_EQ(dst_anim.GetChannel(0)->transformation_type, + src_anim.GetChannel(0)->transformation_type); +} +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/contrib/draco/src/draco/animation/keyframe_animation_encoding_test.cc b/contrib/draco/src/draco/animation/keyframe_animation_encoding_test.cc index 4a6491f9d..fcd0eaa6f 100644 --- a/contrib/draco/src/draco/animation/keyframe_animation_encoding_test.cc +++ b/contrib/draco/src/draco/animation/keyframe_animation_encoding_test.cc @@ -26,8 +26,9 @@ class KeyframeAnimationEncodingTest : public ::testing::Test { bool CreateAndAddTimestamps(int32_t num_frames) { timestamps_.resize(num_frames); - for (int i = 0; i < timestamps_.size(); ++i) + for (int i = 0; i < timestamps_.size(); ++i) { timestamps_[i] = static_cast(i); + } return keyframe_animation_.SetTimestamps(timestamps_); } @@ -35,8 +36,9 @@ class KeyframeAnimationEncodingTest : public ::testing::Test { uint32_t num_components) { // Create and add animation data with. animation_data_.resize(num_frames * num_components); - for (int i = 0; i < animation_data_.size(); ++i) + for (int i = 0; i < animation_data_.size(); ++i) { animation_data_[i] = static_cast(i); + } return keyframe_animation_.AddKeyframes(draco::DT_FLOAT32, num_components, animation_data_); } @@ -49,7 +51,7 @@ class KeyframeAnimationEncodingTest : public ::testing::Test { ASSERT_EQ(animation0.num_animations(), animation1.num_animations()); if (quantized) { - // TODO(hemmer) : Add test for stable quantization. + // TODO(b/199760123) : Add test for stable quantization. // Quantization will result in slightly different values. // Skip comparing values. return; @@ -109,9 +111,8 @@ class KeyframeAnimationEncodingTest : public ::testing::Test { } } - ASSERT_TRUE( - encoder.EncodeKeyframeAnimation(keyframe_animation_, options, &buffer) - .ok()); + DRACO_ASSERT_OK( + encoder.EncodeKeyframeAnimation(keyframe_animation_, options, &buffer)); draco::DecoderBuffer dec_decoder; draco::KeyframeAnimationDecoder decoder; @@ -122,8 +123,8 @@ class KeyframeAnimationEncodingTest : public ::testing::Test { std::unique_ptr decoded_animation( new KeyframeAnimation()); DecoderOptions dec_options; - ASSERT_TRUE( - decoder.Decode(dec_options, &dec_buffer, decoded_animation.get()).ok()); + DRACO_ASSERT_OK( + decoder.Decode(dec_options, &dec_buffer, decoded_animation.get())); // Verify if animation before and after compression is identical. CompareAnimationData(keyframe_animation_, diff --git a/contrib/draco/src/draco/animation/keyframe_animation_test.cc b/contrib/draco/src/draco/animation/keyframe_animation_test.cc index bc92b25ff..94566972b 100644 --- a/contrib/draco/src/draco/animation/keyframe_animation_test.cc +++ b/contrib/draco/src/draco/animation/keyframe_animation_test.cc @@ -24,8 +24,9 @@ class KeyframeAnimationTest : public ::testing::Test { bool CreateAndAddTimestamps(int32_t num_frames) { timestamps_.resize(num_frames); - for (int i = 0; i < timestamps_.size(); ++i) + for (int i = 0; i < timestamps_.size(); ++i) { timestamps_[i] = static_cast(i); + } return keyframe_animation_.SetTimestamps(timestamps_); } @@ -33,8 +34,9 @@ class KeyframeAnimationTest : public ::testing::Test { uint32_t num_components) { // Create and add animation data with. animation_data_.resize(num_frames * num_components); - for (int i = 0; i < animation_data_.size(); ++i) + for (int i = 0; i < animation_data_.size(); ++i) { animation_data_[i] = static_cast(i); + } return keyframe_animation_.AddKeyframes(draco::DT_FLOAT32, num_components, animation_data_); } diff --git a/contrib/draco/src/draco/animation/node_animation_data.h b/contrib/draco/src/draco/animation/node_animation_data.h new file mode 100644 index 000000000..7799e3376 --- /dev/null +++ b/contrib/draco/src/draco/animation/node_animation_data.h @@ -0,0 +1,150 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_ANIMATION_NODE_ANIMATION_DATA_H_ +#define DRACO_ANIMATION_NODE_ANIMATION_DATA_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include "draco/core/hash_utils.h" +#include "draco/core/status.h" +#include "draco/core/status_or.h" + +namespace draco { + +// This class is used to store information and data for animations that only +// affect the nodes. +// TODO(fgalligan): Think about changing the name of this class now that Skin +// is using it. +class NodeAnimationData { + public: + enum class Type { SCALAR, VEC3, VEC4, MAT4 }; + + NodeAnimationData() : type_(Type::SCALAR), count_(0), normalized_(false) {} + + void Copy(const NodeAnimationData &src) { + type_ = src.type_; + count_ = src.count_; + normalized_ = src.normalized_; + data_ = src.data_; + } + + Type type() const { return type_; } + int count() const { return count_; } + bool normalized() const { return normalized_; } + + std::vector *GetMutableData() { return &data_; } + const std::vector *GetData() const { return &data_; } + + void SetType(Type type) { type_ = type; } + void SetCount(int count) { count_ = count; } + void SetNormalized(bool normalized) { normalized_ = normalized; } + + int ComponentSize() const { return sizeof(float); } + int NumComponents() const { + switch (type_) { + case Type::SCALAR: + return 1; + case Type::VEC3: + return 3; + case Type::MAT4: + return 16; + default: + return 4; + } + } + + std::string TypeAsString() const { + switch (type_) { + case Type::SCALAR: + return "SCALAR"; + case Type::VEC3: + return "VEC3"; + case Type::MAT4: + return "MAT4"; + default: + return "VEC4"; + } + } + + bool operator==(const NodeAnimationData &nad) const { + return type_ == nad.type_ && count_ == nad.count_ && + normalized_ == nad.normalized_ && data_ == nad.data_; + } + + private: + Type type_; + int count_; + bool normalized_; + std::vector data_; +}; + +// Wrapper class for hashing NodeAnimationData. When using different containers, +// this class is preferable instead of copying the data in NodeAnimationData +// every time. +class NodeAnimationDataHash { + public: + NodeAnimationDataHash() = delete; + NodeAnimationDataHash &operator=(const NodeAnimationDataHash &) = delete; + NodeAnimationDataHash(NodeAnimationDataHash &&) = delete; + NodeAnimationDataHash &operator=(NodeAnimationDataHash &&) = delete; + + explicit NodeAnimationDataHash(const NodeAnimationData *nad) + : node_animation_data_(nad) { + hash_ = NodeAnimationDataHash::HashNodeAnimationData(*node_animation_data_); + } + + NodeAnimationDataHash(const NodeAnimationDataHash &nadh) { + node_animation_data_ = nadh.node_animation_data_; + hash_ = nadh.hash_; + } + + bool operator==(const NodeAnimationDataHash &nadh) const { + return *node_animation_data_ == *nadh.node_animation_data_; + } + + struct Hash { + size_t operator()(const NodeAnimationDataHash &nadh) const { + return nadh.hash_; + } + }; + + const NodeAnimationData *GetNodeAnimationData() { + return node_animation_data_; + } + + private: + // Returns a hash of |nad|. + static size_t HashNodeAnimationData(const NodeAnimationData &nad) { + size_t hash = 79; // Magic number. + hash = HashCombine(static_cast(nad.type()), hash); + hash = HashCombine(nad.count(), hash); + hash = HashCombine(nad.normalized(), hash); + const uint64_t data_hash = + FingerprintString(reinterpret_cast(nad.GetData()->data()), + nad.GetData()->size() * sizeof(float)); + hash = HashCombine(data_hash, hash); + return hash; + } + + const NodeAnimationData *node_animation_data_; + size_t hash_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_ANIMATION_NODE_ANIMATION_DATA_H_ diff --git a/contrib/draco/src/draco/animation/skin.cc b/contrib/draco/src/draco/animation/skin.cc new file mode 100644 index 000000000..f232978c2 --- /dev/null +++ b/contrib/draco/src/draco/animation/skin.cc @@ -0,0 +1,29 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/animation/skin.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +void Skin::Copy(const Skin &s) { + inverse_bind_matrices_.Copy(s.GetInverseBindMatrices()); + joints_ = s.GetJoints(); + joint_root_index_ = s.GetJointRoot(); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/animation/skin.h b/contrib/draco/src/draco/animation/skin.h new file mode 100644 index 000000000..81ca997eb --- /dev/null +++ b/contrib/draco/src/draco/animation/skin.h @@ -0,0 +1,64 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_ANIMATION_SKIN_H_ +#define DRACO_ANIMATION_SKIN_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include + +#include "draco/animation/node_animation_data.h" +#include "draco/scene/scene_indices.h" + +namespace draco { + +// This class is used to store information on animation skins. +class Skin { + public: + Skin() : joint_root_index_(-1) {} + + void Copy(const Skin &s); + + NodeAnimationData &GetInverseBindMatrices() { return inverse_bind_matrices_; } + const NodeAnimationData &GetInverseBindMatrices() const { + return inverse_bind_matrices_; + } + + int AddJoint(SceneNodeIndex index) { + joints_.push_back(index); + return joints_.size() - 1; + } + int NumJoints() const { return joints_.size(); } + SceneNodeIndex GetJoint(int index) const { return joints_[index]; } + SceneNodeIndex &GetJoint(int index) { return joints_[index]; } + const std::vector &GetJoints() const { return joints_; } + + void SetJointRoot(SceneNodeIndex index) { joint_root_index_ = index; } + SceneNodeIndex GetJointRoot() const { return joint_root_index_; } + + private: + NodeAnimationData inverse_bind_matrices_; + + // List of node indices that make up the joint hierarchy. + std::vector joints_; + SceneNodeIndex joint_root_index_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_ANIMATION_SKIN_H_ diff --git a/contrib/draco/src/draco/attributes/attribute_transform.cc b/contrib/draco/src/draco/attributes/attribute_transform.cc index 174e6b822..fb2ed1829 100644 --- a/contrib/draco/src/draco/attributes/attribute_transform.cc +++ b/contrib/draco/src/draco/attributes/attribute_transform.cc @@ -28,12 +28,13 @@ std::unique_ptr AttributeTransform::InitTransformedAttribute( const PointAttribute &src_attribute, int num_entries) { const int num_components = GetTransformedNumComponents(src_attribute); const DataType dt = GetTransformedDataType(src_attribute); - GeometryAttribute va; - va.Init(src_attribute.attribute_type(), nullptr, num_components, dt, false, + GeometryAttribute ga; + ga.Init(src_attribute.attribute_type(), nullptr, num_components, dt, false, num_components * DataTypeLength(dt), 0); - std::unique_ptr transformed_attribute(new PointAttribute(va)); + std::unique_ptr transformed_attribute(new PointAttribute(ga)); transformed_attribute->Reset(num_entries); transformed_attribute->SetIdentityMapping(); + transformed_attribute->set_unique_id(src_attribute.unique_id()); return transformed_attribute; } diff --git a/contrib/draco/src/draco/attributes/geometry_attribute.cc b/contrib/draco/src/draco/attributes/geometry_attribute.cc index b62478426..141130f43 100644 --- a/contrib/draco/src/draco/attributes/geometry_attribute.cc +++ b/contrib/draco/src/draco/attributes/geometry_attribute.cc @@ -26,7 +26,7 @@ GeometryAttribute::GeometryAttribute() unique_id_(0) {} void GeometryAttribute::Init(GeometryAttribute::Type attribute_type, - DataBuffer *buffer, int8_t num_components, + DataBuffer *buffer, uint8_t num_components, DataType data_type, bool normalized, int64_t byte_stride, int64_t byte_offset) { buffer_ = buffer; diff --git a/contrib/draco/src/draco/attributes/geometry_attribute.h b/contrib/draco/src/draco/attributes/geometry_attribute.h index f4d099b1b..28f743fa0 100644 --- a/contrib/draco/src/draco/attributes/geometry_attribute.h +++ b/contrib/draco/src/draco/attributes/geometry_attribute.h @@ -15,12 +15,18 @@ #ifndef DRACO_ATTRIBUTES_GEOMETRY_ATTRIBUTE_H_ #define DRACO_ATTRIBUTES_GEOMETRY_ATTRIBUTE_H_ +#include #include +#include #include #include "draco/attributes/geometry_indices.h" #include "draco/core/data_buffer.h" #include "draco/core/hash_utils.h" +#include "draco/draco_features.h" +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/status.h" +#endif namespace draco { @@ -51,6 +57,16 @@ class GeometryAttribute { // predefined use case. Such attributes are often used for a shader specific // data. GENERIC, +#ifdef DRACO_TRANSCODER_SUPPORTED + // TODO(ostava): Adding a new attribute would be bit-stream change for GLTF. + // Older decoders wouldn't know what to do with this attribute type. This + // should be open-sourced only when we are ready to increase our bit-stream + // version. + TANGENT, + MATERIAL, + JOINTS, + WEIGHTS, +#endif // Total number of different attribute types. // Always keep behind all named attributes. NAMED_ATTRIBUTES_COUNT, @@ -58,7 +74,7 @@ class GeometryAttribute { GeometryAttribute(); // Initializes and enables the attribute. - void Init(Type attribute_type, DataBuffer *buffer, int8_t num_components, + void Init(Type attribute_type, DataBuffer *buffer, uint8_t num_components, DataType data_type, bool normalized, int64_t byte_stride, int64_t byte_offset); bool IsValid() const { return buffer_ != nullptr; } @@ -129,6 +145,17 @@ class GeometryAttribute { buffer_->Write(byte_pos, value, byte_stride()); } +#ifdef DRACO_TRANSCODER_SUPPORTED + // Sets a value of an attribute entry. The input |value| must have + // |input_num_components| entries and it will be automatically converted to + // the internal format used by the geometry attribute. If the conversion is + // not possible, an error status will be returned. + template + Status ConvertAndSetAttributeValue(AttributeValueIndex avi, + int input_num_components, + const InputT *value); +#endif + // DEPRECATED: Use // ConvertValue(AttributeValueIndex att_id, // int out_num_components, @@ -233,10 +260,11 @@ class GeometryAttribute { // Returns the number of components that are stored for each entry. // For position attribute this is usually three (x,y,z), // while texture coordinates have two components (u,v). - int8_t num_components() const { return num_components_; } + uint8_t num_components() const { return num_components_; } // Indicates whether the data type should be normalized before interpretation, // that is, it should be divided by the max value of the data type. bool normalized() const { return normalized_; } + void set_normalized(bool normalized) { normalized_ = normalized; } // The buffer storing the entire data of the attribute. const DataBuffer *buffer() const { return buffer_; } // Returns the number of bytes between two attribute entries, this is, at @@ -260,7 +288,7 @@ class GeometryAttribute { // T is the stored attribute data type. // OutT is the desired data type of the attribute. template - bool ConvertTypedValue(AttributeValueIndex att_id, int8_t out_num_components, + bool ConvertTypedValue(AttributeValueIndex att_id, uint8_t out_num_components, OutT *out_value) const { const uint8_t *src_address = GetAddress(att_id); @@ -270,29 +298,10 @@ class GeometryAttribute { return false; } const T in_value = *reinterpret_cast(src_address); - - // Make sure the in_value fits within the range of values that OutT - // is able to represent. Perform the check only for integral types. - if (std::is_integral::value && std::is_integral::value) { - static constexpr OutT kOutMin = - std::is_signed::value ? std::numeric_limits::lowest() : 0; - if (in_value < kOutMin || in_value > std::numeric_limits::max()) { - return false; - } + if (!ConvertComponentValue(in_value, normalized_, + out_value + i)) { + return false; } - - out_value[i] = static_cast(in_value); - // When converting integer to floating point, normalize the value if - // necessary. - if (std::is_integral::value && std::is_floating_point::value && - normalized_) { - out_value[i] /= static_cast(std::numeric_limits::max()); - } - // TODO(ostava): Add handling of normalized attributes when converting - // between different integer representations. If the attribute is - // normalized, integer values should be converted as if they represent 0-1 - // range. E.g. when we convert uint16 to uint8, the range <0, 2^16 - 1> - // should be converted to range <0, 2^8 - 1>. src_address += sizeof(T); } // Fill empty data for unused output components if needed. @@ -302,12 +311,128 @@ class GeometryAttribute { return true; } +#ifdef DRACO_TRANSCODER_SUPPORTED + // Function that converts input |value| from type T to the internal attribute + // representation defined by OutT and |num_components_|. + template + Status ConvertAndSetAttributeTypedValue(AttributeValueIndex avi, + int8_t input_num_components, + const T *value) { + uint8_t *address = GetAddress(avi); + + // Convert all components available in both the original and output formats. + for (int i = 0; i < num_components_; ++i) { + if (!IsAddressValid(address)) { + return ErrorStatus("GeometryAttribute: Invalid address."); + } + OutT *const out_value = reinterpret_cast(address); + if (i < input_num_components) { + if (!ConvertComponentValue(*(value + i), normalized_, + out_value)) { + return ErrorStatus( + "GeometryAttribute: Failed to convert component value."); + } + } else { + *out_value = static_cast(0); + } + address += sizeof(OutT); + } + return OkStatus(); + } +#endif // DRACO_TRANSCODER_SUPPORTED + + // Converts |in_value| of type T into |out_value| of type OutT. If + // |normalized| is true, any conversion between floating point and integer + // values will be treating integers as normalized types (the entire integer + // range will be used to represent 0-1 floating point range). + template + static bool ConvertComponentValue(const T &in_value, bool normalized, + OutT *out_value) { + // Make sure the |in_value| can be represented as an integral type OutT. + if (std::is_integral::value) { + // Make sure the |in_value| fits within the range of values that OutT + // is able to represent. Perform the check only for integral types. + if (!std::is_same::value && std::is_integral::value) { + static constexpr OutT kOutMin = + std::is_signed::value ? std::numeric_limits::min() : 0; + if (in_value < kOutMin || in_value > std::numeric_limits::max()) { + return false; + } + } + + // Check conversion of floating point |in_value| to integral value OutT. + if (std::is_floating_point::value) { + // Make sure the floating point |in_value| is not NaN and not Inf as + // integral type OutT is unable to represent these values. + if (sizeof(in_value) > sizeof(double)) { + if (std::isnan(static_cast(in_value)) || + std::isinf(static_cast(in_value))) { + return false; + } + } else if (sizeof(in_value) > sizeof(float)) { + if (std::isnan(static_cast(in_value)) || + std::isinf(static_cast(in_value))) { + return false; + } + } else { + if (std::isnan(static_cast(in_value)) || + std::isinf(static_cast(in_value))) { + return false; + } + } + + // Make sure the floating point |in_value| fits within the range of + // values that integral type OutT is able to represent. + if (in_value < std::numeric_limits::min() || + in_value >= std::numeric_limits::max()) { + return false; + } + } + } + + if (std::is_integral::value && std::is_floating_point::value && + normalized) { + // When converting integer to floating point, normalize the value if + // necessary. + *out_value = static_cast(in_value); + *out_value /= static_cast(std::numeric_limits::max()); + } else if (std::is_floating_point::value && + std::is_integral::value && normalized) { + // Converting from floating point to a normalized integer. + if (in_value > 1 || in_value < 0) { + // Normalized float values need to be between 0 and 1. + return false; + } + // TODO(ostava): Consider allowing float to normalized integer conversion + // for 64-bit integer types. Currently it doesn't work because we don't + // have a floating point type that could store all 64 bit integers. + if (sizeof(OutT) > 4) { + return false; + } + // Expand the float to the range of the output integer and round it to the + // nearest representable value. Use doubles for the math to ensure the + // integer values are represented properly during the conversion process. + *out_value = static_cast(std::floor( + in_value * static_cast(std::numeric_limits::max()) + + 0.5)); + } else { + *out_value = static_cast(in_value); + } + + // TODO(ostava): Add handling of normalized attributes when converting + // between different integer representations. If the attribute is + // normalized, integer values should be converted as if they represent 0-1 + // range. E.g. when we convert uint16 to uint8, the range <0, 2^16 - 1> + // should be converted to range <0, 2^8 - 1>. + return true; + } + DataBuffer *buffer_; // The buffer descriptor is stored at the time the buffer is attached to this // attribute. The purpose is to detect if any changes happened to the buffer // since the time it was attached. DataBufferDescriptor buffer_descriptor_; - int8_t num_components_; + uint8_t num_components_; DataType data_type_; bool normalized_; int64_t byte_stride_; @@ -323,6 +448,54 @@ class GeometryAttribute { friend struct GeometryAttributeHasher; }; +#ifdef DRACO_TRANSCODER_SUPPORTED +template +Status GeometryAttribute::ConvertAndSetAttributeValue(AttributeValueIndex avi, + int input_num_components, + const InputT *value) { + switch (this->data_type()) { + case DT_INT8: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_UINT8: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_INT16: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_UINT16: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_INT32: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_UINT32: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_INT64: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_UINT64: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_FLOAT32: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_FLOAT64: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + case DT_BOOL: + return ConvertAndSetAttributeTypedValue( + avi, input_num_components, value); + default: + break; + } + return ErrorStatus( + "GeometryAttribute::SetAndConvertAttributeValue: Unsupported " + "attribute type."); +} +#endif + // Hashing support // Function object for using Attribute as a hash key. diff --git a/contrib/draco/src/draco/attributes/point_attribute.cc b/contrib/draco/src/draco/attributes/point_attribute.cc index b28f860c1..e54ab5427 100644 --- a/contrib/draco/src/draco/attributes/point_attribute.cc +++ b/contrib/draco/src/draco/attributes/point_attribute.cc @@ -222,4 +222,47 @@ AttributeValueIndex::ValueType PointAttribute::DeduplicateFormattedValues( } #endif +#ifdef DRACO_TRANSCODER_SUPPORTED +void PointAttribute::RemoveUnusedValues() { + if (is_mapping_identity()) { + return; // For identity mapping, all values are always used. + } + // For explicit mapping we need to check if any point is mapped to a value. + // If not we can delete the value. + IndexTypeVector is_value_used(size(), false); + int num_used_values = 0; + for (PointIndex pi(0); pi < indices_map_.size(); ++pi) { + const AttributeValueIndex avi = indices_map_[pi]; + if (!is_value_used[avi]) { + is_value_used[avi] = true; + num_used_values++; + } + } + if (num_used_values == size()) { + return; // All values are used. + } + + // Remap the values and update the point to value mapping. + IndexTypeVector + old_to_new_value_map(size(), kInvalidAttributeValueIndex); + AttributeValueIndex new_avi(0); + for (AttributeValueIndex avi(0); avi < size(); ++avi) { + if (!is_value_used[avi]) { + continue; + } + if (avi != new_avi) { + SetAttributeValue(new_avi, GetAddress(avi)); + } + old_to_new_value_map[avi] = new_avi++; + } + + // Remap all points to the new attribute values. + for (PointIndex pi(0); pi < indices_map_.size(); ++pi) { + indices_map_[pi] = old_to_new_value_map[indices_map_[pi]]; + } + + num_unique_entries_ = num_used_values; +} +#endif + } // namespace draco diff --git a/contrib/draco/src/draco/attributes/point_attribute.h b/contrib/draco/src/draco/attributes/point_attribute.h index ee3662031..d55c50c8a 100644 --- a/contrib/draco/src/draco/attributes/point_attribute.h +++ b/contrib/draco/src/draco/attributes/point_attribute.h @@ -133,6 +133,12 @@ class PointAttribute : public GeometryAttribute { return attribute_transform_data_.get(); } +#ifdef DRACO_TRANSCODER_SUPPORTED + // Removes unused values from the attribute. Value is unused when no point + // is mapped to the value. Only applicable when the mapping is not identity. + void RemoveUnusedValues(); +#endif + private: #ifdef DRACO_ATTRIBUTE_VALUES_DEDUPLICATION_SUPPORTED template diff --git a/contrib/draco/src/draco/compression/attributes/attributes_encoder.cc b/contrib/draco/src/draco/compression/attributes/attributes_encoder.cc index 797c62f30..480e3ff34 100644 --- a/contrib/draco/src/draco/compression/attributes/attributes_encoder.cc +++ b/contrib/draco/src/draco/compression/attributes/attributes_encoder.cc @@ -15,14 +15,16 @@ #include "draco/compression/attributes/attributes_encoder.h" #include "draco/core/varint_encoding.h" +#include "draco/draco_features.h" namespace draco { AttributesEncoder::AttributesEncoder() : point_cloud_encoder_(nullptr), point_cloud_(nullptr) {} -AttributesEncoder::AttributesEncoder(int att_id) : AttributesEncoder() { - AddAttributeId(att_id); +AttributesEncoder::AttributesEncoder(int point_attrib_id) + : AttributesEncoder() { + AddAttributeId(point_attrib_id); } bool AttributesEncoder::Init(PointCloudEncoder *encoder, const PointCloud *pc) { @@ -37,7 +39,15 @@ bool AttributesEncoder::EncodeAttributesEncoderData(EncoderBuffer *out_buffer) { for (uint32_t i = 0; i < num_attributes(); ++i) { const int32_t att_id = point_attribute_ids_[i]; const PointAttribute *const pa = point_cloud_->attribute(att_id); - out_buffer->Encode(static_cast(pa->attribute_type())); + GeometryAttribute::Type type = pa->attribute_type(); +#ifdef DRACO_TRANSCODER_SUPPORTED + // Attribute types TANGENT, MATERIAL, JOINTS, and WEIGHTS are not supported + // in the official bitstream. They will be encoded as GENERIC. + if (type > GeometryAttribute::GENERIC) { + type = GeometryAttribute::GENERIC; + } +#endif + out_buffer->Encode(static_cast(type)); out_buffer->Encode(static_cast(pa->data_type())); out_buffer->Encode(static_cast(pa->num_components())); out_buffer->Encode(static_cast(pa->normalized())); diff --git a/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.cc b/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.cc index e4d53485d..51c41cf7a 100644 --- a/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.cc +++ b/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.cc @@ -72,16 +72,19 @@ class PointAttributeVectorOutputIterator { Self &operator*() { return *this; } // Still needed in some cases. - // TODO(hemmer): remove. + // TODO(b/199760123): Remove. // hardcoded to 3 based on legacy usage. const Self &operator=(const VectorD &val) { DRACO_DCHECK_EQ(attributes_.size(), 1); // Expect only ONE attribute. AttributeTuple &att = attributes_[0]; PointAttribute *attribute = std::get<0>(att); + const AttributeValueIndex avi = attribute->mapped_index(point_id_); + if (avi >= static_cast(attribute->size())) { + return *this; + } const uint32_t &offset = std::get<1>(att); DRACO_DCHECK_EQ(offset, 0); // expected to be zero - attribute->SetAttributeValue(attribute->mapped_index(point_id_), - &val[0] + offset); + attribute->SetAttributeValue(avi, &val[0] + offset); return *this; } // Additional operator taking std::vector as argument. @@ -89,6 +92,10 @@ class PointAttributeVectorOutputIterator { for (auto index = 0; index < attributes_.size(); index++) { AttributeTuple &att = attributes_[index]; PointAttribute *attribute = std::get<0>(att); + const AttributeValueIndex avi = attribute->mapped_index(point_id_); + if (avi >= static_cast(attribute->size())) { + return *this; + } const uint32_t &offset = std::get<1>(att); const uint32_t &data_size = std::get<3>(att); const uint32_t &num_components = std::get<4>(att); @@ -103,10 +110,6 @@ class PointAttributeVectorOutputIterator { // redirect to copied data data_source = reinterpret_cast(data_); } - const AttributeValueIndex avi = attribute->mapped_index(point_id_); - if (avi >= static_cast(attribute->size())) { - return *this; - } attribute->SetAttributeValue(avi, data_source); } return *this; @@ -195,54 +198,55 @@ bool KdTreeAttributesDecoder::DecodePortableAttributes( data_size, num_components); total_dimensionality += num_components; } - PointAttributeVectorOutputIterator out_it(atts); + typedef PointAttributeVectorOutputIterator OutIt; + OutIt out_it(atts); switch (compression_level) { case 0: { - DynamicIntegerPointsKdTreeDecoder<0> decoder(total_dimensionality); - if (!decoder.DecodePoints(in_buffer, out_it)) { + if (!DecodePoints<0, OutIt>(total_dimensionality, num_points, in_buffer, + &out_it)) { return false; } break; } case 1: { - DynamicIntegerPointsKdTreeDecoder<1> decoder(total_dimensionality); - if (!decoder.DecodePoints(in_buffer, out_it)) { + if (!DecodePoints<1, OutIt>(total_dimensionality, num_points, in_buffer, + &out_it)) { return false; } break; } case 2: { - DynamicIntegerPointsKdTreeDecoder<2> decoder(total_dimensionality); - if (!decoder.DecodePoints(in_buffer, out_it)) { + if (!DecodePoints<2, OutIt>(total_dimensionality, num_points, in_buffer, + &out_it)) { return false; } break; } case 3: { - DynamicIntegerPointsKdTreeDecoder<3> decoder(total_dimensionality); - if (!decoder.DecodePoints(in_buffer, out_it)) { + if (!DecodePoints<3, OutIt>(total_dimensionality, num_points, in_buffer, + &out_it)) { return false; } break; } case 4: { - DynamicIntegerPointsKdTreeDecoder<4> decoder(total_dimensionality); - if (!decoder.DecodePoints(in_buffer, out_it)) { + if (!DecodePoints<4, OutIt>(total_dimensionality, num_points, in_buffer, + &out_it)) { return false; } break; } case 5: { - DynamicIntegerPointsKdTreeDecoder<5> decoder(total_dimensionality); - if (!decoder.DecodePoints(in_buffer, out_it)) { + if (!DecodePoints<5, OutIt>(total_dimensionality, num_points, in_buffer, + &out_it)) { return false; } break; } case 6: { - DynamicIntegerPointsKdTreeDecoder<6> decoder(total_dimensionality); - if (!decoder.DecodePoints(in_buffer, out_it)) { + if (!DecodePoints<6, OutIt>(total_dimensionality, num_points, in_buffer, + &out_it)) { return false; } break; @@ -253,6 +257,19 @@ bool KdTreeAttributesDecoder::DecodePortableAttributes( return true; } +template +bool KdTreeAttributesDecoder::DecodePoints(int total_dimensionality, + int num_expected_points, + DecoderBuffer *in_buffer, + OutIteratorT *out_iterator) { + DynamicIntegerPointsKdTreeDecoder decoder(total_dimensionality); + if (!decoder.DecodePoints(in_buffer, *out_iterator, num_expected_points) || + decoder.num_decoded_points() != num_expected_points) { + return false; + } + return true; +} + bool KdTreeAttributesDecoder::DecodeDataNeededByPortableTransforms( DecoderBuffer *in_buffer) { if (in_buffer->bitstream_version() >= DRACO_BITSTREAM_VERSION(2, 3)) { @@ -336,6 +353,10 @@ bool KdTreeAttributesDecoder::DecodeDataNeededByPortableTransforms( return false; } if (method == KdTreeAttributesEncodingMethod::kKdTreeQuantizationEncoding) { + // This method only supports one attribute with exactly three components. + if (atts.size() != 1 || std::get<4>(atts[0]) != 3) { + return false; + } uint8_t compression_level = 0; if (!in_buffer->Decode(&compression_level)) { return false; @@ -376,7 +397,7 @@ bool KdTreeAttributesDecoder::DecodeDataNeededByPortableTransforms( GetDecoder()->point_cloud()->attribute(att_id); attr->Reset(num_points); attr->SetIdentityMapping(); - }; + } PointAttributeVectorOutputIterator out_it(atts); @@ -455,7 +476,11 @@ bool KdTreeAttributesDecoder::TransformAttributeBackToSignedType( att->GetValue(avi, &unsigned_val[0]); for (int c = 0; c < att->num_components(); ++c) { // Up-cast |unsigned_val| to int32_t to ensure we don't overflow it for - // smaller data types. + // smaller data types. But first check that the up-casting does not cause + // signed integer overflow. + if (unsigned_val[c] > std::numeric_limits::max()) { + return false; + } signed_val[c] = static_cast( static_cast(unsigned_val[c]) + min_signed_values_[num_processed_signed_components + c]); diff --git a/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.h b/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.h index 87338d6b0..4af367a1a 100644 --- a/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.h +++ b/contrib/draco/src/draco/compression/attributes/kd_tree_attributes_decoder.h @@ -31,6 +31,10 @@ class KdTreeAttributesDecoder : public AttributesDecoder { bool TransformAttributesToOriginalFormat() override; private: + template + bool DecodePoints(int total_dimensionality, int num_expected_points, + DecoderBuffer *in_buffer, OutIteratorT *out_iterator); + template bool TransformAttributeBackToSignedType(PointAttribute *att, int num_processed_signed_components); diff --git a/contrib/draco/src/draco/compression/attributes/normal_compression_utils.h b/contrib/draco/src/draco/compression/attributes/normal_compression_utils.h index 8a6f25b66..b717d0dbe 100644 --- a/contrib/draco/src/draco/compression/attributes/normal_compression_utils.h +++ b/contrib/draco/src/draco/compression/attributes/normal_compression_utils.h @@ -61,7 +61,7 @@ class OctahedronToolBox { return false; } quantization_bits_ = q; - max_quantized_value_ = (1 << quantization_bits_) - 1; + max_quantized_value_ = (1u << quantization_bits_) - 1; max_value_ = max_quantized_value_ - 1; dequantization_scale_ = 2.f / max_value_; center_value_ = max_value_ / 2; @@ -208,7 +208,9 @@ class OctahedronToolBox { DRACO_DCHECK_LE(t, center_value_); DRACO_DCHECK_GE(s, -center_value_); DRACO_DCHECK_GE(t, -center_value_); - return std::abs(s) + std::abs(t) <= center_value_; + const uint32_t st = + static_cast(std::abs(s)) + static_cast(std::abs(t)); + return st <= center_value_; } void InvertDiamond(int32_t *s, int32_t *t) const { @@ -230,19 +232,29 @@ class OctahedronToolBox { sign_t = (*t > 0) ? 1 : -1; } - const int32_t corner_point_s = sign_s * center_value_; - const int32_t corner_point_t = sign_t * center_value_; - *s = 2 * *s - corner_point_s; - *t = 2 * *t - corner_point_t; + // Perform the addition and subtraction using unsigned integers to avoid + // signed integer overflows for bad data. Note that the result will be + // unchanged for non-overflowing cases. + const uint32_t corner_point_s = sign_s * center_value_; + const uint32_t corner_point_t = sign_t * center_value_; + uint32_t us = *s; + uint32_t ut = *t; + us = us + us - corner_point_s; + ut = ut + ut - corner_point_t; if (sign_s * sign_t >= 0) { - int32_t temp = *s; - *s = -*t; - *t = -temp; + uint32_t temp = us; + us = -ut; + ut = -temp; } else { - std::swap(*s, *t); + std::swap(us, ut); } - *s = (*s + corner_point_s) / 2; - *t = (*t + corner_point_t) / 2; + us = us + corner_point_s; + ut = ut + corner_point_t; + + *s = us; + *t = ut; + *s /= 2; + *t /= 2; } void InvertDirection(int32_t *s, int32_t *t) const { @@ -318,7 +330,7 @@ class OctahedronToolBox { // Remaining coordinate can be computed by projecting the (y, z) values onto // the surface of the octahedron. - const float x = 1.f - abs(y) - abs(z); + const float x = 1.f - std::abs(y) - std::abs(z); // |x| is essentially a signed distance from the diagonal edges of the // diamond shown on the figure above. It is positive for all points in the diff --git a/contrib/draco/src/draco/compression/attributes/point_d_vector.h b/contrib/draco/src/draco/compression/attributes/point_d_vector.h index 3b115d500..6ceb454ae 100644 --- a/contrib/draco/src/draco/compression/attributes/point_d_vector.h +++ b/contrib/draco/src/draco/compression/attributes/point_d_vector.h @@ -16,7 +16,9 @@ #ifndef DRACO_COMPRESSION_ATTRIBUTES_POINT_D_VECTOR_H_ #define DRACO_COMPRESSION_ATTRIBUTES_POINT_D_VECTOR_H_ +#include #include +#include #include #include @@ -99,11 +101,17 @@ class PointDVector { data_(n_items * dimensionality), data0_(data_.data()) {} // random access iterator - class PointDVectorIterator - : public std::iterator { + class PointDVectorIterator { friend class PointDVector; public: + // Iterator traits expected by std libraries. + using iterator_category = std::random_access_iterator_tag; + using value_type = size_t; + using difference_type = size_t; + using pointer = PointDVector *; + using reference = PointDVector &; + // std::iter_swap is called inside of std::partition and needs this // specialized support PseudoPointD operator*() const { diff --git a/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_decoder.h b/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_decoder.h index 36c124baa..17899d054 100644 --- a/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_decoder.h +++ b/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_decoder.h @@ -22,6 +22,7 @@ #include "draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_decoder.h" #include "draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_parallelogram_shared.h" #include "draco/compression/bit_coders/rans_bit_decoder.h" +#include "draco/core/math_utils.h" #include "draco/core/varint_decoding.h" #include "draco/draco_features.h" @@ -161,7 +162,8 @@ bool MeshPredictionSchemeConstrainedMultiParallelogramDecoder< if (!is_crease) { ++num_used_parallelograms; for (int j = 0; j < num_components; ++j) { - multi_pred_vals[j] += pred_vals[i][j]; + multi_pred_vals[j] = + AddAsUnsigned(multi_pred_vals[j], pred_vals[i][j]); } } } @@ -210,6 +212,9 @@ bool MeshPredictionSchemeConstrainedMultiParallelogramDecoder< if (!DecodeVarint(&num_flags, buffer)) { return false; } + if (num_flags > this->mesh_data().corner_table()->num_corners()) { + return false; + } if (num_flags > 0) { is_crease_edge_[i].resize(num_flags); RAnsBitDecoder decoder; diff --git a/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_encoder.h b/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_encoder.h index 77df8ee24..736598b15 100644 --- a/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_encoder.h +++ b/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_constrained_multi_parallelogram_encoder.h @@ -392,7 +392,7 @@ bool MeshPredictionSchemeConstrainedMultiParallelogramEncoder< RAnsBitEncoder encoder; encoder.StartEncoding(); // Encode the crease edge flags in the reverse vertex order that is needed - // be the decoder. Note that for the currently supported mode, each vertex + // by the decoder. Note that for the currently supported mode, each vertex // has exactly |num_used_parallelograms| edges that need to be encoded. for (int j = static_cast(is_crease_edge_[i].size()) - num_used_parallelograms; diff --git a/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_multi_parallelogram_decoder.h b/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_multi_parallelogram_decoder.h index fc82e0a8f..9825c7261 100644 --- a/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_multi_parallelogram_decoder.h +++ b/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_multi_parallelogram_decoder.h @@ -18,6 +18,7 @@ #include "draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_decoder.h" #include "draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_parallelogram_shared.h" +#include "draco/core/math_utils.h" #include "draco/draco_features.h" namespace draco { @@ -89,7 +90,8 @@ bool MeshPredictionSchemeMultiParallelogramDecoder(data[data_offset + 1])); } - void ComputePredictedValue(CornerIndex corner_id, const DataTypeT *data, + bool ComputePredictedValue(CornerIndex corner_id, const DataTypeT *data, int data_id); private: @@ -123,6 +123,10 @@ bool MeshPredictionSchemeTexCoordsDecoder:: ComputeOriginalValues(const CorrType *in_corr, DataTypeT *out_data, int /* size */, int num_components, const PointIndex *entry_to_point_id_map) { + if (num_components != 2) { + // Corrupt/malformed input. Two output components are req'd. + return false; + } num_components_ = num_components; entry_to_point_id_map_ = entry_to_point_id_map; predicted_value_ = @@ -133,7 +137,9 @@ bool MeshPredictionSchemeTexCoordsDecoder:: static_cast(this->mesh_data().data_to_corner_map()->size()); for (int p = 0; p < corner_map_size; ++p) { const CornerIndex corner_id = this->mesh_data().data_to_corner_map()->at(p); - ComputePredictedValue(corner_id, out_data, p); + if (!ComputePredictedValue(corner_id, out_data, p)) { + return false; + } const int dst_offset = p * num_components; this->transform().ComputeOriginalValue( @@ -159,6 +165,11 @@ bool MeshPredictionSchemeTexCoordsDecoder:: if (num_orientations == 0) { return false; } + if (num_orientations > this->mesh_data().corner_table()->num_corners()) { + // We can't have more orientations than the maximum number of decoded + // values. + return false; + } orientations_.resize(num_orientations); bool last_orientation = true; RAnsBitDecoder decoder; @@ -177,7 +188,7 @@ bool MeshPredictionSchemeTexCoordsDecoder:: } template -void MeshPredictionSchemeTexCoordsDecoder:: +bool MeshPredictionSchemeTexCoordsDecoder:: ComputePredictedValue(CornerIndex corner_id, const DataTypeT *data, int data_id) { // Compute the predicted UV coordinate from the positions on all corners @@ -206,9 +217,17 @@ void MeshPredictionSchemeTexCoordsDecoder:: const Vector2f p_uv = GetTexCoordForEntryId(prev_data_id, data); if (p_uv == n_uv) { // We cannot do a reliable prediction on degenerated UV triangles. - predicted_value_[0] = static_cast(p_uv[0]); - predicted_value_[1] = static_cast(p_uv[1]); - return; + // Technically floats > INT_MAX are undefined, but compilers will + // convert those values to INT_MIN. We are being explicit here for asan. + for (const int i : {0, 1}) { + if (std::isnan(p_uv[i]) || static_cast(p_uv[i]) > INT_MAX || + static_cast(p_uv[i]) < INT_MIN) { + predicted_value_[i] = INT_MIN; + } else { + predicted_value_[i] = static_cast(p_uv[i]); + } + } + return true; } // Get positions at all corners. @@ -282,32 +301,40 @@ void MeshPredictionSchemeTexCoordsDecoder:: const float pnvs = pn_uv[1] * s + n_uv[1]; const float pnvt = pn_uv[1] * t; Vector2f predicted_uv; + if (orientations_.empty()) { + return false; + } // When decoding the data, we already know which orientation to use. const bool orientation = orientations_.back(); orientations_.pop_back(); - if (orientation) + if (orientation) { predicted_uv = Vector2f(pnus - pnvt, pnvs + pnut); - else + } else { predicted_uv = Vector2f(pnus + pnvt, pnvs - pnut); - + } if (std::is_integral::value) { // Round the predicted value for integer types. - if (std::isnan(predicted_uv[0])) { + // Technically floats > INT_MAX are undefined, but compilers will + // convert those values to INT_MIN. We are being explicit here for asan. + const double u = floor(predicted_uv[0] + 0.5); + if (std::isnan(u) || u > INT_MAX || u < INT_MIN) { predicted_value_[0] = INT_MIN; } else { - predicted_value_[0] = static_cast(floor(predicted_uv[0] + 0.5)); + predicted_value_[0] = static_cast(u); } - if (std::isnan(predicted_uv[1])) { + const double v = floor(predicted_uv[1] + 0.5); + if (std::isnan(v) || v > INT_MAX || v < INT_MIN) { predicted_value_[1] = INT_MIN; } else { - predicted_value_[1] = static_cast(floor(predicted_uv[1] + 0.5)); + predicted_value_[1] = static_cast(v); } } else { predicted_value_[0] = static_cast(predicted_uv[0]); predicted_value_[1] = static_cast(predicted_uv[1]); } - return; + + return true; } // Else we don't have available textures on both corners. For such case we // can't use positions for predicting the uv value and we resort to delta @@ -330,12 +357,13 @@ void MeshPredictionSchemeTexCoordsDecoder:: for (int i = 0; i < num_components_; ++i) { predicted_value_[i] = 0; } - return; + return true; } } for (int i = 0; i < num_components_; ++i) { predicted_value_[i] = data[data_offset + i]; } + return true; } } // namespace draco diff --git a/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_encoder.h b/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_encoder.h index 741ec66dc..44fcc7a6a 100644 --- a/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_encoder.h +++ b/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_encoder.h @@ -98,7 +98,10 @@ bool MeshPredictionSchemeTexCoordsPortableEncoder(this->mesh_data().data_to_corner_map()->size() - 1); p >= 0; --p) { const CornerIndex corner_id = this->mesh_data().data_to_corner_map()->at(p); - predictor_.template ComputePredictedValue(corner_id, in_data, p); + if (!predictor_.template ComputePredictedValue(corner_id, in_data, + p)) { + return false; + } const int dst_offset = p * num_components; this->transform().ComputeCorrection(in_data + dst_offset, diff --git a/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_predictor.h b/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_predictor.h index f05e5ddd7..26262fb13 100644 --- a/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_predictor.h +++ b/contrib/draco/src/draco/compression/attributes/prediction_schemes/mesh_prediction_scheme_tex_coords_portable_predictor.h @@ -17,6 +17,9 @@ #include +#include +#include + #include "draco/attributes/point_attribute.h" #include "draco/core/math_utils.h" #include "draco/core/vector_d.h" @@ -105,10 +108,14 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor< next_data_id = mesh_data_.vertex_to_data_map()->at(next_vert_id); prev_data_id = mesh_data_.vertex_to_data_map()->at(prev_vert_id); + typedef VectorD Vec2; + typedef VectorD Vec3; + typedef VectorD Vec2u; + if (prev_data_id < data_id && next_data_id < data_id) { // Both other corners have available UV coordinates for prediction. - const VectorD n_uv = GetTexCoordForEntryId(next_data_id, data); - const VectorD p_uv = GetTexCoordForEntryId(prev_data_id, data); + const Vec2 n_uv = GetTexCoordForEntryId(next_data_id, data); + const Vec2 p_uv = GetTexCoordForEntryId(prev_data_id, data); if (p_uv == n_uv) { // We cannot do a reliable prediction on degenerated UV triangles. predicted_value_[0] = p_uv[0]; @@ -117,9 +124,9 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor< } // Get positions at all corners. - const VectorD tip_pos = GetPositionForEntryId(data_id); - const VectorD next_pos = GetPositionForEntryId(next_data_id); - const VectorD prev_pos = GetPositionForEntryId(prev_data_id); + const Vec3 tip_pos = GetPositionForEntryId(data_id); + const Vec3 next_pos = GetPositionForEntryId(next_data_id); + const Vec3 prev_pos = GetPositionForEntryId(prev_data_id); // We use the positions of the above triangle to predict the texture // coordinate on the tip corner C. // To convert the triangle into the UV coordinate system we first compute @@ -135,17 +142,17 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor< // Where next_pos is point (N), prev_pos is point (P) and tip_pos is the // position of predicted coordinate (C). // - const VectorD pn = prev_pos - next_pos; + const Vec3 pn = prev_pos - next_pos; const uint64_t pn_norm2_squared = pn.SquaredNorm(); if (pn_norm2_squared != 0) { // Compute the projection of C onto PN by computing dot product of CN with // PN and normalizing it by length of PN. This gives us a factor |s| where // |s = PN.Dot(CN) / PN.SquaredNorm2()|. This factor can be used to // compute X in UV space |X_UV| as |X_UV = N_UV + s * PN_UV|. - const VectorD cn = tip_pos - next_pos; + const Vec3 cn = tip_pos - next_pos; const int64_t cn_dot_pn = pn.Dot(cn); - const VectorD pn_uv = p_uv - n_uv; + const Vec2 pn_uv = p_uv - n_uv; // Because we perform all computations with integers, we don't explicitly // compute the normalized factor |s|, but rather we perform all operations // over UV vectors in a non-normalized coordinate system scaled with a @@ -153,19 +160,30 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor< // // x_uv = X_UV * PN.Norm2Squared() // - const VectorD x_uv = - n_uv * pn_norm2_squared + (cn_dot_pn * pn_uv); - + const int64_t n_uv_absmax_element = + std::max(std::abs(n_uv[0]), std::abs(n_uv[1])); + if (n_uv_absmax_element > + std::numeric_limits::max() / pn_norm2_squared) { + // Return false if the below multiplication would overflow. + return false; + } + const int64_t pn_uv_absmax_element = + std::max(std::abs(pn_uv[0]), std::abs(pn_uv[1])); + if (cn_dot_pn > + std::numeric_limits::max() / pn_uv_absmax_element) { + // Return false if squared length calculation would overflow. + return false; + } + const Vec2 x_uv = n_uv * pn_norm2_squared + (cn_dot_pn * pn_uv); const int64_t pn_absmax_element = std::max(std::max(std::abs(pn[0]), std::abs(pn[1])), std::abs(pn[2])); if (cn_dot_pn > std::numeric_limits::max() / pn_absmax_element) { - // return false if squared length calculation would overflow. + // Return false if squared length calculation would overflow. return false; } // Compute squared length of vector CX in position coordinate system: - const VectorD x_pos = - next_pos + (cn_dot_pn * pn) / pn_norm2_squared; + const Vec3 x_pos = next_pos + (cn_dot_pn * pn) / pn_norm2_squared; const uint64_t cx_norm2_squared = (tip_pos - x_pos).SquaredNorm(); // Compute vector CX_UV in the uv space by rotating vector PN_UV by 90 @@ -182,7 +200,7 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor< // // cx_uv = CX.Norm2() * PN.Norm2() * Rot(PN_UV) // - VectorD cx_uv(pn_uv[1], -pn_uv[0]); // Rotated PN_UV. + Vec2 cx_uv(pn_uv[1], -pn_uv[0]); // Rotated PN_UV. // Compute CX.Norm2() * PN.Norm2() const uint64_t norm_squared = IntSqrt(cx_norm2_squared * pn_norm2_squared); @@ -191,17 +209,15 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor< // Predicted uv coordinate is then computed by either adding or // subtracting CX_UV to/from X_UV. - VectorD predicted_uv; + Vec2 predicted_uv; if (is_encoder_t) { // When encoding, compute both possible vectors and determine which one // results in a better prediction. // Both vectors need to be transformed back from the scaled space to // the real UV coordinate space. - const VectorD predicted_uv_0((x_uv + cx_uv) / - pn_norm2_squared); - const VectorD predicted_uv_1((x_uv - cx_uv) / - pn_norm2_squared); - const VectorD c_uv = GetTexCoordForEntryId(data_id, data); + const Vec2 predicted_uv_0((x_uv + cx_uv) / pn_norm2_squared); + const Vec2 predicted_uv_1((x_uv - cx_uv) / pn_norm2_squared); + const Vec2 c_uv = GetTexCoordForEntryId(data_id, data); if ((c_uv - predicted_uv_0).SquaredNorm() < (c_uv - predicted_uv_1).SquaredNorm()) { predicted_uv = predicted_uv_0; @@ -217,10 +233,12 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor< } const bool orientation = orientations_.back(); orientations_.pop_back(); + // Perform operations in unsigned type to avoid signed integer overflow. + // Note that the result will be the same (for non-overflowing values). if (orientation) { - predicted_uv = (x_uv + cx_uv) / pn_norm2_squared; + predicted_uv = Vec2(Vec2u(x_uv) + Vec2u(cx_uv)) / pn_norm2_squared; } else { - predicted_uv = (x_uv - cx_uv) / pn_norm2_squared; + predicted_uv = Vec2(Vec2u(x_uv) - Vec2u(cx_uv)) / pn_norm2_squared; } } predicted_value_[0] = static_cast(predicted_uv[0]); diff --git a/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.cc b/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.cc index f410a6cd2..2338f2f76 100644 --- a/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.cc +++ b/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.cc @@ -18,22 +18,58 @@ namespace draco { PredictionSchemeMethod SelectPredictionMethod( int att_id, const PointCloudEncoder *encoder) { - if (encoder->options()->GetSpeed() >= 10) { + return SelectPredictionMethod(att_id, *encoder->options(), encoder); +} + +PredictionSchemeMethod SelectPredictionMethod( + int att_id, const EncoderOptions &options, + const PointCloudEncoder *encoder) { + if (options.GetSpeed() >= 10) { // Selected fastest, though still doing some compression. return PREDICTION_DIFFERENCE; } if (encoder->GetGeometryType() == TRIANGULAR_MESH) { // Use speed setting to select the best encoding method. + const int att_quant = + options.GetAttributeInt(att_id, "quantization_bits", -1); const PointAttribute *const att = encoder->point_cloud()->attribute(att_id); - if (att->attribute_type() == GeometryAttribute::TEX_COORD) { - if (encoder->options()->GetSpeed() < 4) { + if (att_quant != -1 && + att->attribute_type() == GeometryAttribute::TEX_COORD && + att->num_components() == 2) { + // Texture coordinate predictor needs a position attribute that is either + // integer or quantized. For numerical reasons, we require the position + // quantization to be at most 21 bits and the 2*position_quantization + + // uv_quantization < 64 (TODO(b/231259902)). + const PointAttribute *const pos_att = + encoder->point_cloud()->GetNamedAttribute( + GeometryAttribute::POSITION); + bool is_pos_att_valid = false; + if (pos_att) { + if (IsDataTypeIntegral(pos_att->data_type())) { + is_pos_att_valid = true; + } else { + // Check quantization of the position attribute. + const int pos_att_id = encoder->point_cloud()->GetNamedAttributeId( + GeometryAttribute::POSITION); + const int pos_quant = + options.GetAttributeInt(pos_att_id, "quantization_bits", -1); + // Must be quantized but the quantization is restricted to 21 bits and + // 2*|pos_quant|+|att_quant| must be smaller than 64 bits. + if (pos_quant > 0 && pos_quant <= 21 && + 2 * pos_quant + att_quant < 64) { + is_pos_att_valid = true; + } + } + } + + if (is_pos_att_valid && options.GetSpeed() < 4) { // Use texture coordinate prediction for speeds 0, 1, 2, 3. return MESH_PREDICTION_TEX_COORDS_PORTABLE; } } if (att->attribute_type() == GeometryAttribute::NORMAL) { #ifdef DRACO_NORMAL_ENCODING_SUPPORTED - if (encoder->options()->GetSpeed() < 4) { + if (options.GetSpeed() < 4) { // Use geometric normal prediction for speeds 0, 1, 2, 3. // For this prediction, the position attribute needs to be either // integer or quantized as well. @@ -43,8 +79,8 @@ PredictionSchemeMethod SelectPredictionMethod( encoder->point_cloud()->GetNamedAttribute( GeometryAttribute::POSITION); if (pos_att && (IsDataTypeIntegral(pos_att->data_type()) || - encoder->options()->GetAttributeInt( - pos_att_id, "quantization_bits", -1) > 0)) { + options.GetAttributeInt(pos_att_id, "quantization_bits", + -1) > 0)) { return MESH_PREDICTION_GEOMETRIC_NORMAL; } } @@ -52,11 +88,10 @@ PredictionSchemeMethod SelectPredictionMethod( return PREDICTION_DIFFERENCE; // default } // Handle other attribute types. - if (encoder->options()->GetSpeed() >= 8) { + if (options.GetSpeed() >= 8) { return PREDICTION_DIFFERENCE; } - if (encoder->options()->GetSpeed() >= 2 || - encoder->point_cloud()->num_points() < 40) { + if (options.GetSpeed() >= 2 || encoder->point_cloud()->num_points() < 40) { // Parallelogram prediction is used for speeds 2 - 7 or when the overhead // of using constrained multi-parallelogram would be too high. return MESH_PREDICTION_PARALLELOGRAM; diff --git a/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.h b/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.h index 40a7683aa..11db5a62e 100644 --- a/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.h +++ b/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_encoder_factory.h @@ -38,6 +38,10 @@ namespace draco { PredictionSchemeMethod SelectPredictionMethod(int att_id, const PointCloudEncoder *encoder); +PredictionSchemeMethod SelectPredictionMethod(int att_id, + const EncoderOptions &options, + const PointCloudEncoder *encoder); + // Factory class for creating mesh prediction schemes. template struct MeshPredictionSchemeEncoderFactory { @@ -97,10 +101,11 @@ CreatePredictionSchemeForEncoder(PredictionSchemeMethod method, int att_id, // template nature of the prediction schemes). const MeshEncoder *const mesh_encoder = static_cast(encoder); + const uint16_t bitstream_version = kDracoMeshBitstreamVersion; auto ret = CreateMeshPredictionScheme< MeshEncoder, PredictionSchemeEncoder, MeshPredictionSchemeEncoderFactory>( - mesh_encoder, method, att_id, transform, kDracoMeshBitstreamVersion); + mesh_encoder, method, att_id, transform, bitstream_version); if (ret) { return ret; } diff --git a/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_decoding_transform.h b/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_decoding_transform.h index 5a6c7c2dd..e9e345343 100644 --- a/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_decoding_transform.h +++ b/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_decoding_transform.h @@ -21,6 +21,7 @@ #include "draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_transform_base.h" #include "draco/core/decoder_buffer.h" #include "draco/core/macros.h" +#include "draco/core/math_utils.h" #include "draco/core/vector_d.h" namespace draco { @@ -98,9 +99,8 @@ class PredictionSchemeNormalOctahedronCanonicalizedDecodingTransform if (!pred_is_in_bottom_left) { pred = this->RotatePoint(pred, rotation_count); } - Point2 orig = pred + corr; - orig[0] = this->ModMax(orig[0]); - orig[1] = this->ModMax(orig[1]); + Point2 orig(this->ModMax(AddAsUnsigned(pred[0], corr[0])), + this->ModMax(AddAsUnsigned(pred[1], corr[1]))); if (!pred_is_in_bottom_left) { const int32_t reverse_rotation_count = (4 - rotation_count) % 4; orig = this->RotatePoint(orig, reverse_rotation_count); diff --git a/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_transform_test.cc b/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_transform_test.cc index 8c8932f77..298758d8c 100644 --- a/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_transform_test.cc +++ b/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_canonicalized_transform_test.cc @@ -25,10 +25,10 @@ class PredictionSchemeNormalOctahedronCanonicalizedTransformTest Transform; typedef Transform::Point2 Point2; - void TestComputeCorrection(const Transform &transform, const int32_t &ox, - const int32_t &oy, const int32_t &px, - const int32_t &py, const int32_t &cx, - const int32_t &cy) { + void TestComputeCorrection(const Transform &transform, const int32_t ox, + const int32_t oy, const int32_t px, + const int32_t py, const int32_t cx, + const int32_t cy) { const int32_t o[2] = {ox + 7, oy + 7}; const int32_t p[2] = {px + 7, py + 7}; int32_t corr[2] = {500, 500}; @@ -38,7 +38,7 @@ class PredictionSchemeNormalOctahedronCanonicalizedTransformTest } void TestGetRotationCount(const Transform &transform, const Point2 &pred, - const int32_t &rot_dir) { + const int32_t rot_dir) { const int32_t rotation_count = transform.GetRotationCount(pred); ASSERT_EQ(rot_dir, rotation_count); } diff --git a/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_decoding_transform.h b/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_decoding_transform.h index a1bc4a327..d3705c8ad 100644 --- a/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_decoding_transform.h +++ b/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_decoding_transform.h @@ -80,19 +80,31 @@ class PredictionSchemeNormalOctahedronDecodingTransform private: Point2 ComputeOriginalValue(Point2 pred, const Point2 &corr) const { const Point2 t(this->center_value(), this->center_value()); - pred = pred - t; + typedef typename std::make_unsigned::type UnsignedDataTypeT; + typedef VectorD Point2u; + + // Perform the addition in unsigned type to avoid signed integer overflow. + // Note that the result will be the same (for non-overflowing values). + pred = Point2(Point2u(pred) - Point2u(t)); const bool pred_is_in_diamond = this->IsInDiamond(pred[0], pred[1]); if (!pred_is_in_diamond) { this->InvertDiamond(&pred[0], &pred[1]); } - Point2 orig = pred + corr; + + // Perform the addition in unsigned type to avoid signed integer overflow. + // Note that the result will be the same (for non-overflowing values). + Point2 orig(Point2u(pred) + Point2u(corr)); + orig[0] = this->ModMax(orig[0]); orig[1] = this->ModMax(orig[1]); if (!pred_is_in_diamond) { this->InvertDiamond(&orig[0], &orig[1]); } - orig = orig + t; + + // Perform the addition in unsigned type to avoid signed integer overflow. + // Note that the result will be the same (for non-overflowing values). + orig = Point2(Point2u(orig) + Point2u(t)); return orig; } }; diff --git a/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_transform_test.cc b/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_transform_test.cc index 1001b19fa..1403973c4 100644 --- a/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_transform_test.cc +++ b/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_normal_octahedron_transform_test.cc @@ -23,10 +23,10 @@ class PredictionSchemeNormalOctahedronTransformTest : public ::testing::Test { Transform; typedef Transform::Point2 Point2; - void TestComputeCorrection(const Transform &transform, const int32_t &ox, - const int32_t &oy, const int32_t &px, - const int32_t &py, const int32_t &cx, - const int32_t &cy) { + void TestComputeCorrection(const Transform &transform, const int32_t ox, + const int32_t oy, const int32_t px, + const int32_t py, const int32_t cx, + const int32_t cy) { const int32_t o[2] = {ox + 7, oy + 7}; const int32_t p[2] = {px + 7, py + 7}; int32_t corr[2] = {500, 500}; diff --git a/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_wrap_transform_base.h b/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_wrap_transform_base.h index 26f61fbaf..bba3de09c 100644 --- a/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_wrap_transform_base.h +++ b/contrib/draco/src/draco/compression/attributes/prediction_schemes/prediction_scheme_wrap_transform_base.h @@ -70,10 +70,10 @@ class PredictionSchemeWrapTransformBase { clamped_value_[i] = predicted_val[i]; } } - return &clamped_value_[0]; + return clamped_value_.data(); } - // TODO(hemmer): Consider refactoring to avoid this dummy. + // TODO(b/199760123): Consider refactoring to avoid this dummy. int quantization_bits() const { DRACO_DCHECK(false); return -1; diff --git a/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_decoder.cc b/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_decoder.cc index 83f42125a..17f32fc16 100644 --- a/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_decoder.cc +++ b/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_decoder.cc @@ -148,8 +148,9 @@ bool SequentialIntegerAttributeDecoder::DecodeIntegerValues( return false; } for (size_t i = 0; i < num_values; ++i) { - if (!in_buffer->Decode(portable_attribute_data + i, num_bytes)) + if (!in_buffer->Decode(portable_attribute_data + i, num_bytes)) { return false; + } } } } @@ -228,12 +229,13 @@ void SequentialIntegerAttributeDecoder::StoreTypedValues(uint32_t num_values) { void SequentialIntegerAttributeDecoder::PreparePortableAttribute( int num_entries, int num_components) { - GeometryAttribute va; - va.Init(attribute()->attribute_type(), nullptr, num_components, DT_INT32, + GeometryAttribute ga; + ga.Init(attribute()->attribute_type(), nullptr, num_components, DT_INT32, false, num_components * DataTypeLength(DT_INT32), 0); - std::unique_ptr port_att(new PointAttribute(va)); + std::unique_ptr port_att(new PointAttribute(ga)); port_att->SetIdentityMapping(); port_att->Reset(num_entries); + port_att->set_unique_id(attribute()->unique_id()); SetPortableAttribute(std::move(port_att)); } diff --git a/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_encoder.cc b/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_encoder.cc index e66a0a8a4..5f673be42 100644 --- a/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_encoder.cc +++ b/contrib/draco/src/draco/compression/attributes/sequential_integer_attribute_encoder.cc @@ -138,9 +138,11 @@ bool SequentialIntegerAttributeEncoder::EncodeValues( // All integer values are initialized. Process them using the prediction // scheme if we have one. if (prediction_scheme_) { - prediction_scheme_->ComputeCorrectionValues( - portable_attribute_data, &encoded_data[0], num_values, num_components, - point_ids.data()); + if (!prediction_scheme_->ComputeCorrectionValues( + portable_attribute_data, &encoded_data[0], num_values, + num_components, point_ids.data())) { + return false; + } } if (prediction_scheme_ == nullptr || diff --git a/contrib/draco/src/draco/compression/attributes/sequential_normal_attribute_encoder.cc b/contrib/draco/src/draco/compression/attributes/sequential_normal_attribute_encoder.cc index 2e20e89e6..3c5ef0ebc 100644 --- a/contrib/draco/src/draco/compression/attributes/sequential_normal_attribute_encoder.cc +++ b/contrib/draco/src/draco/compression/attributes/sequential_normal_attribute_encoder.cc @@ -20,8 +20,9 @@ namespace draco { bool SequentialNormalAttributeEncoder::Init(PointCloudEncoder *encoder, int attribute_id) { - if (!SequentialIntegerAttributeEncoder::Init(encoder, attribute_id)) + if (!SequentialIntegerAttributeEncoder::Init(encoder, attribute_id)) { return false; + } // Currently this encoder works only for 3-component normal vectors. if (attribute()->num_components() != 3) { return false; diff --git a/contrib/draco/src/draco/compression/bit_coders/direct_bit_decoder.h b/contrib/draco/src/draco/compression/bit_coders/direct_bit_decoder.h index b9fbc2d6f..6273692a2 100644 --- a/contrib/draco/src/draco/compression/bit_coders/direct_bit_decoder.h +++ b/contrib/draco/src/draco/compression/bit_coders/direct_bit_decoder.h @@ -47,14 +47,13 @@ class DirectBitDecoder { // Decode the next |nbits| and return the sequence in |value|. |nbits| must be // > 0 and <= 32. - void DecodeLeastSignificantBits32(int nbits, uint32_t *value) { + bool DecodeLeastSignificantBits32(int nbits, uint32_t *value) { DRACO_DCHECK_EQ(true, nbits <= 32); DRACO_DCHECK_EQ(true, nbits > 0); const int remaining = 32 - num_used_bits_; if (nbits <= remaining) { if (pos_ == bits_.end()) { - *value = 0; - return; + return false; } *value = (*pos_ << num_used_bits_) >> (32 - nbits); num_used_bits_ += nbits; @@ -64,8 +63,7 @@ class DirectBitDecoder { } } else { if (pos_ + 1 == bits_.end()) { - *value = 0; - return; + return false; } const uint32_t value_l = ((*pos_) << num_used_bits_); num_used_bits_ = nbits - remaining; @@ -73,6 +71,7 @@ class DirectBitDecoder { const uint32_t value_r = (*pos_) >> (32 - num_used_bits_); *value = (value_l >> (32 - num_used_bits_ - remaining)) | value_r; } + return true; } void EndDecoding() {} diff --git a/contrib/draco/src/draco/compression/config/encoder_options.h b/contrib/draco/src/draco/compression/config/encoder_options.h index ed1b02068..e8a55bbba 100644 --- a/contrib/draco/src/draco/compression/config/encoder_options.h +++ b/contrib/draco/src/draco/compression/config/encoder_options.h @@ -65,6 +65,10 @@ class EncoderOptionsBase : public DracoOptions { this->SetGlobalInt("encoding_speed", encoding_speed); this->SetGlobalInt("decoding_speed", decoding_speed); } + bool IsSpeedSet() const { + return this->IsGlobalOptionSet("encoding_speed") || + this->IsGlobalOptionSet("decoding_speed"); + } // Sets a given feature as supported or unsupported by the target decoder. // Encoder will always use only supported features when encoding the input diff --git a/contrib/draco/src/draco/compression/decode_test.cc b/contrib/draco/src/draco/compression/decode_test.cc index 198714690..8f3e7f4e9 100644 --- a/contrib/draco/src/draco/compression/decode_test.cc +++ b/contrib/draco/src/draco/compression/decode_test.cc @@ -17,9 +17,11 @@ #include #include +#include "draco/compression/encode.h" #include "draco/core/draco_test_base.h" #include "draco/core/draco_test_utils.h" #include "draco/io/file_utils.h" +#include "draco/io/obj_encoder.h" namespace { @@ -166,4 +168,78 @@ TEST_F(DecodeTest, TestSkipAttributeTransformWithNoQuantization) { ASSERT_EQ(pos_att->GetAttributeTransformData(), nullptr); } +TEST_F(DecodeTest, TestSkipAttributeTransformUniqueId) { + // Tests that decoders preserve unique id of attributes even when their + // attribute transforms are skipped. + const std::string file_name = "cube_att.obj"; + auto src_mesh = draco::ReadMeshFromTestFile(file_name); + ASSERT_NE(src_mesh, nullptr); + + constexpr int kPosUniqueId = 7; + constexpr int kNormUniqueId = 42; + // Set unique ids for some of the attributes. + src_mesh + ->attribute( + src_mesh->GetNamedAttributeId(draco::GeometryAttribute::POSITION)) + ->set_unique_id(kPosUniqueId); + src_mesh + ->attribute( + src_mesh->GetNamedAttributeId(draco::GeometryAttribute::NORMAL)) + ->set_unique_id(kNormUniqueId); + + draco::EncoderBuffer encoder_buffer; + draco::Encoder encoder; + encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 10); + encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 11); + encoder.EncodeMeshToBuffer(*src_mesh, &encoder_buffer); + + // Create a draco decoding buffer. + draco::DecoderBuffer buffer; + buffer.Init(encoder_buffer.data(), encoder_buffer.size()); + + // First we decode the mesh without skipping the attribute transforms. + draco::Decoder decoder_no_skip; + std::unique_ptr mesh_no_skip = + decoder_no_skip.DecodeMeshFromBuffer(&buffer).value(); + ASSERT_NE(mesh_no_skip, nullptr); + + // Now we decode it again while skipping some attributes. + draco::Decoder decoder_skip; + // Make sure we skip dequantization for the position and normal attribute. + decoder_skip.SetSkipAttributeTransform(draco::GeometryAttribute::POSITION); + decoder_skip.SetSkipAttributeTransform(draco::GeometryAttribute::NORMAL); + + // Decode the input data into a geometry. + buffer.Init(encoder_buffer.data(), encoder_buffer.size()); + std::unique_ptr mesh_skip = + decoder_skip.DecodeMeshFromBuffer(&buffer).value(); + ASSERT_NE(mesh_skip, nullptr); + + // Compare the unique ids. + const draco::PointAttribute *const pos_att_no_skip = + mesh_no_skip->GetNamedAttribute(draco::GeometryAttribute::POSITION); + ASSERT_NE(pos_att_no_skip, nullptr); + ASSERT_EQ(pos_att_no_skip->data_type(), draco::DataType::DT_FLOAT32); + + const draco::PointAttribute *const pos_att_skip = + mesh_skip->GetNamedAttribute(draco::GeometryAttribute::POSITION); + ASSERT_NE(pos_att_skip, nullptr); + ASSERT_EQ(pos_att_skip->data_type(), draco::DataType::DT_INT32); + + const draco::PointAttribute *const norm_att_no_skip = + mesh_no_skip->GetNamedAttribute(draco::GeometryAttribute::NORMAL); + ASSERT_NE(norm_att_no_skip, nullptr); + ASSERT_EQ(norm_att_no_skip->data_type(), draco::DataType::DT_FLOAT32); + + const draco::PointAttribute *const norm_att_skip = + mesh_skip->GetNamedAttribute(draco::GeometryAttribute::NORMAL); + ASSERT_NE(norm_att_skip, nullptr); + ASSERT_EQ(norm_att_skip->data_type(), draco::DataType::DT_INT32); + + ASSERT_EQ(pos_att_skip->unique_id(), pos_att_no_skip->unique_id()); + ASSERT_EQ(norm_att_skip->unique_id(), norm_att_no_skip->unique_id()); + std::cout << pos_att_skip->unique_id() << " " << norm_att_skip->unique_id() + << std::endl; +} + } // namespace diff --git a/contrib/draco/src/draco/compression/draco_compression_options.cc b/contrib/draco/src/draco/compression/draco_compression_options.cc new file mode 100644 index 000000000..08171c678 --- /dev/null +++ b/contrib/draco/src/draco/compression/draco_compression_options.cc @@ -0,0 +1,59 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/compression/draco_compression_options.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +SpatialQuantizationOptions::SpatialQuantizationOptions(int quantization_bits) { + SetQuantizationBits(quantization_bits); +} + +void SpatialQuantizationOptions::SetQuantizationBits(int quantization_bits) { + mode_ = LOCAL_QUANTIZATION_BITS; + quantization_bits_ = quantization_bits; +} + +bool SpatialQuantizationOptions::AreQuantizationBitsDefined() const { + return mode_ == LOCAL_QUANTIZATION_BITS; +} + +SpatialQuantizationOptions &SpatialQuantizationOptions::SetGrid(float spacing) { + mode_ = GLOBAL_GRID; + spacing_ = spacing; + return *this; +} + +bool SpatialQuantizationOptions::operator==( + const SpatialQuantizationOptions &other) const { + if (mode_ != other.mode_) { + return false; + } + if (mode_ == LOCAL_QUANTIZATION_BITS) { + if (quantization_bits_ != other.quantization_bits_) { + return false; + } + } else if (mode_ == GLOBAL_GRID) { + if (spacing_ != other.spacing_) { + return false; + } + } + return true; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/compression/draco_compression_options.h b/contrib/draco/src/draco/compression/draco_compression_options.h new file mode 100644 index 000000000..31a4418ed --- /dev/null +++ b/contrib/draco/src/draco/compression/draco_compression_options.h @@ -0,0 +1,141 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_COMPRESSION_DRACO_COMPRESSION_OPTIONS_H_ +#define DRACO_COMPRESSION_DRACO_COMPRESSION_OPTIONS_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/status.h" + +namespace draco { + +// Quantization options for positions. Currently there are two modes for +// quantizing positions: +// +// 1. Quantization bits: +// - User defined number of quantization bits that is evenly distributed +// to cover the compressed geometry. +// 2. Grid: +// - Positions are snapped to a global grid defined by grid spacing. +// - This method is primarily intended to be used when the location of +// quantized vertices needs to be consistent between multiple +// geometries. +class SpatialQuantizationOptions { + public: + explicit SpatialQuantizationOptions(int quantization_bits); + + // Sets quantization bits that are going to be used for the compressed + // geometry. If the geometry is a scene, the same number of quantization bits + // is going to be applied to each mesh of the scene. Quantized values are + // going to be distributed within the bounds of individual meshes. + void SetQuantizationBits(int quantization_bits); + + // If this returns true, quantization_bits() should be used to get the + // desired number of quantization bits for compression. Otherwise the grid + // mode is selected and spacing() should be used to get the desired grid + // spacing. + bool AreQuantizationBitsDefined() const; + const int quantization_bits() const { return quantization_bits_; } + + // Defines quantization grid used for the compressed geometry. All vertices + // are going to be snapped to the nearest grid vertex that corresponds to an + // integer quantized position. |spacing| defines the distance between two grid + // vertices. E.g. a grid with |spacing| = 10 would have grid vertices at + // locations {10 * i, 10 * j, 10 * k} where i, j, k are integer numbers. + SpatialQuantizationOptions &SetGrid(float spacing); + + const float spacing() const { return spacing_; } + + bool operator==(const SpatialQuantizationOptions &other) const; + + private: + enum Mode { LOCAL_QUANTIZATION_BITS, GLOBAL_GRID }; + Mode mode_ = LOCAL_QUANTIZATION_BITS; + int quantization_bits_; // Default quantization bits for positions. + float spacing_ = 0.f; +}; + +// TODO(fgalligan): Add support for unified_position_quantization. +// Struct to hold Draco compression options. +struct DracoCompressionOptions { + int compression_level = 7; // compression level [0-10], most=10, least=0. + SpatialQuantizationOptions quantization_position{11}; + int quantization_bits_normal = 8; + int quantization_bits_tex_coord = 10; + int quantization_bits_color = 8; + int quantization_bits_generic = 8; + int quantization_bits_tangent = 8; + int quantization_bits_weight = 8; + bool find_non_degenerate_texture_quantization = false; + + bool operator==(const DracoCompressionOptions &other) const { + return compression_level == other.compression_level && + quantization_position == other.quantization_position && + quantization_bits_normal == other.quantization_bits_normal && + quantization_bits_tex_coord == other.quantization_bits_tex_coord && + quantization_bits_color == other.quantization_bits_color && + quantization_bits_generic == other.quantization_bits_generic && + quantization_bits_tangent == other.quantization_bits_tangent && + quantization_bits_weight == other.quantization_bits_weight && + find_non_degenerate_texture_quantization == + other.find_non_degenerate_texture_quantization; + } + + bool operator!=(const DracoCompressionOptions &other) const { + return !(*this == other); + } + + Status Check() const { + DRACO_RETURN_IF_ERROR( + Validate("Compression level", compression_level, 0, 10)); + if (quantization_position.AreQuantizationBitsDefined()) { + DRACO_RETURN_IF_ERROR(Validate("Position quantization", + quantization_position.quantization_bits(), + 0, 30)); + } else { + if (quantization_position.spacing() <= 0.f) { + return ErrorStatus("Position quantization spacing is invalid."); + } + } + DRACO_RETURN_IF_ERROR( + Validate("Normals quantization", quantization_bits_normal, 0, 30)); + DRACO_RETURN_IF_ERROR( + Validate("Tex coord quantization", quantization_bits_tex_coord, 0, 30)); + DRACO_RETURN_IF_ERROR( + Validate("Color quantization", quantization_bits_color, 0, 30)); + DRACO_RETURN_IF_ERROR( + Validate("Generic quantization", quantization_bits_generic, 0, 30)); + DRACO_RETURN_IF_ERROR( + Validate("Tangent quantization", quantization_bits_tangent, 0, 30)); + DRACO_RETURN_IF_ERROR( + Validate("Weights quantization", quantization_bits_weight, 0, 30)); + return OkStatus(); + } + + static Status Validate(const std::string &name, int value, int min, int max) { + if (value < min || value > max) { + const std::string range = + "[" + std::to_string(min) + "-" + std::to_string(max) + "]."; + return Status(Status::DRACO_ERROR, name + " is out of range " + range); + } + return OkStatus(); + } +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_COMPRESSION_DRACO_COMPRESSION_OPTIONS_H_ diff --git a/contrib/draco/src/draco/compression/draco_compression_options_test.cc b/contrib/draco/src/draco/compression/draco_compression_options_test.cc new file mode 100644 index 000000000..415295211 --- /dev/null +++ b/contrib/draco/src/draco/compression/draco_compression_options_test.cc @@ -0,0 +1,45 @@ +#include "draco/compression/draco_compression_options.h" + +#include "draco/core/draco_test_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace { + +TEST(DracoCompressionOptionsTest, TestPositionQuantizationBits) { + // Test verifies that we can define draco compression options using + // quantization bits. + draco::SpatialQuantizationOptions options(10); + + // Quantization bits should be used by default. + ASSERT_TRUE(options.AreQuantizationBitsDefined()); + ASSERT_EQ(options.quantization_bits(), 10); + + // Change the quantization bits. + options.SetQuantizationBits(9); + ASSERT_TRUE(options.AreQuantizationBitsDefined()); + ASSERT_EQ(options.quantization_bits(), 9); + + // If we select the grid, quantization bits should not be used. + options.SetGrid(0.5f); + ASSERT_FALSE(options.AreQuantizationBitsDefined()); +} + +TEST(DracoCompressionOptionsTest, TestPositionQuantizationGrid) { + // Test verifies that we can define draco compression options using + // quantization grid. + draco::SpatialQuantizationOptions options(10); + + // Quantization bits should be used by default. + ASSERT_TRUE(options.AreQuantizationBitsDefined()); + + // Set the grid parameters. + options.SetGrid(0.25f); + ASSERT_FALSE(options.AreQuantizationBitsDefined()); + + ASSERT_EQ(options.spacing(), 0.25f); +} + +} // namespace + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/compression/encode.h b/contrib/draco/src/draco/compression/encode.h index bce8b34c2..00ccb9b2e 100644 --- a/contrib/draco/src/draco/compression/encode.h +++ b/contrib/draco/src/draco/compression/encode.h @@ -129,7 +129,6 @@ class Encoder // call of EncodePointCloudToBuffer or EncodeMeshToBuffer is going to fail. void SetEncodingMethod(int encoding_method); - protected: // Creates encoder options for the expert encoder used during the actual // encoding. EncoderOptions CreateExpertEncoderOptions(const PointCloud &pc) const; diff --git a/contrib/draco/src/draco/compression/encode_base.h b/contrib/draco/src/draco/compression/encode_base.h index c501bc4fa..6211efc22 100644 --- a/contrib/draco/src/draco/compression/encode_base.h +++ b/contrib/draco/src/draco/compression/encode_base.h @@ -98,7 +98,7 @@ class EncoderBase { "Invalid prediction scheme for attribute type."); } } - // TODO(hemmer): Try to enable more prediction schemes for normals. + // TODO(b/199760123): Try to enable more prediction schemes for normals. if (att_type == GeometryAttribute::NORMAL) { if (!(prediction_scheme == PREDICTION_DIFFERENCE || prediction_scheme == MESH_PREDICTION_GEOMETRIC_NORMAL)) { diff --git a/contrib/draco/src/draco/compression/encode_test.cc b/contrib/draco/src/draco/compression/encode_test.cc index fde4f6f5b..00d834703 100644 --- a/contrib/draco/src/draco/compression/encode_test.cc +++ b/contrib/draco/src/draco/compression/encode_test.cc @@ -26,6 +26,7 @@ #include "draco/core/draco_test_base.h" #include "draco/core/draco_test_utils.h" #include "draco/core/vector_d.h" +#include "draco/io/file_utils.h" #include "draco/io/obj_decoder.h" #include "draco/mesh/triangle_soup_mesh_builder.h" #include "draco/point_cloud/point_cloud_builder.h" @@ -213,16 +214,14 @@ class EncodeTest : public ::testing::Test { draco::Decoder decoder; if (mesh) { - auto maybe_mesh = decoder.DecodeMeshFromBuffer(&decoder_buffer); - ASSERT_TRUE(maybe_mesh.ok()); - auto decoded_mesh = std::move(maybe_mesh).value(); + DRACO_ASSIGN_OR_ASSERT(auto decoded_mesh, + decoder.DecodeMeshFromBuffer(&decoder_buffer)); ASSERT_NE(decoded_mesh, nullptr); ASSERT_EQ(decoded_mesh->num_points(), encoder.num_encoded_points()); ASSERT_EQ(decoded_mesh->num_faces(), encoder.num_encoded_faces()); } else { - auto maybe_pc = decoder.DecodePointCloudFromBuffer(&decoder_buffer); - ASSERT_TRUE(maybe_pc.ok()); - auto decoded_pc = std::move(maybe_pc).value(); + DRACO_ASSIGN_OR_ASSERT( + auto decoded_pc, decoder.DecodePointCloudFromBuffer(&decoder_buffer)); ASSERT_EQ(decoded_pc->num_points(), encoder.num_encoded_points()); } } @@ -274,7 +273,7 @@ TEST_F(EncodeTest, TestLinesObj) { encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 16); draco::EncoderBuffer buffer; - ASSERT_TRUE(encoder.EncodePointCloudToBuffer(*pc, &buffer).ok()); + DRACO_ASSERT_OK(encoder.EncodePointCloudToBuffer(*pc, &buffer)); } TEST_F(EncodeTest, TestQuantizedInfinity) { @@ -315,7 +314,7 @@ TEST_F(EncodeTest, TestUnquantizedInfinity) { encoder.SetEncodingMethod(draco::POINT_CLOUD_SEQUENTIAL_ENCODING); draco::EncoderBuffer buffer; - ASSERT_TRUE(encoder.EncodePointCloudToBuffer(*pc, &buffer).ok()); + DRACO_ASSERT_OK(encoder.EncodePointCloudToBuffer(*pc, &buffer)); } TEST_F(EncodeTest, TestQuantizedAndUnquantizedAttributes) { @@ -330,7 +329,7 @@ TEST_F(EncodeTest, TestQuantizedAndUnquantizedAttributes) { encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 11); encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 0); draco::EncoderBuffer buffer; - ASSERT_TRUE(encoder.EncodePointCloudToBuffer(*pc, &buffer).ok()); + DRACO_ASSERT_OK(encoder.EncodePointCloudToBuffer(*pc, &buffer)); } TEST_F(EncodeTest, TestKdTreeEncoding) { @@ -348,7 +347,7 @@ TEST_F(EncodeTest, TestKdTreeEncoding) { // Now set quantization for the position attribute which should make // the encoder happy. encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 16); - ASSERT_TRUE(encoder.EncodePointCloudToBuffer(*pc, &buffer).ok()); + DRACO_ASSERT_OK(encoder.EncodePointCloudToBuffer(*pc, &buffer)); } TEST_F(EncodeTest, TestTrackingOfNumberOfEncodedEntries) { @@ -373,7 +372,7 @@ TEST_F(EncodeTest, TestTrackingOfNumberOfEncodedEntriesNotSet) { draco::EncoderBuffer buffer; draco::Encoder encoder; - ASSERT_TRUE(encoder.EncodeMeshToBuffer(*mesh, &buffer).ok()); + DRACO_ASSERT_OK(encoder.EncodeMeshToBuffer(*mesh, &buffer)); ASSERT_EQ(encoder.num_encoded_points(), 0); ASSERT_EQ(encoder.num_encoded_faces(), 0); } @@ -404,4 +403,170 @@ TEST_F(EncodeTest, TestNoPosQuantizationNormalCoding) { ASSERT_NE(decoded_mesh, nullptr); } +#ifdef DRACO_TRANSCODER_SUPPORTED +TEST_F(EncodeTest, TestDracoCompressionOptions) { + // This test verifies that we can set the encoder's compression options via + // draco::Mesh's compression options. + const auto mesh = draco::ReadMeshFromTestFile("test_nm.obj"); + ASSERT_NE(mesh, nullptr); + + // First set compression level and quantization manually. + draco::Encoder encoder_manual; + draco::EncoderBuffer buffer_manual; + encoder_manual.SetAttributeQuantization(draco::GeometryAttribute::POSITION, + 8); + encoder_manual.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 7); + encoder_manual.SetSpeedOptions(4, 4); + + DRACO_ASSERT_OK(encoder_manual.EncodeMeshToBuffer(*mesh, &buffer_manual)); + + // Now do the same with options provided via DracoCompressionOptions. + draco::DracoCompressionOptions compression_options; + compression_options.compression_level = 6; + compression_options.quantization_position.SetQuantizationBits(8); + compression_options.quantization_bits_normal = 7; + mesh->SetCompressionOptions(compression_options); + mesh->SetCompressionEnabled(true); + + draco::Encoder encoder_auto; + draco::EncoderBuffer buffer_auto; + DRACO_ASSERT_OK(encoder_auto.EncodeMeshToBuffer(*mesh, &buffer_auto)); + + // Ensure that both encoders produce the same result. + ASSERT_EQ(buffer_manual.size(), buffer_auto.size()); + + // Now change some of the mesh's compression settings and ensure the + // compression changes as well. + compression_options.compression_level = 7; + mesh->SetCompressionOptions(compression_options); + buffer_auto.Clear(); + DRACO_ASSERT_OK(encoder_auto.EncodeMeshToBuffer(*mesh, &buffer_auto)); + ASSERT_NE(buffer_manual.size(), buffer_auto.size()); + + // Check that |mesh| compression options do not override the encoder options. + mesh->GetCompressionOptions().compression_level = 10; + mesh->GetCompressionOptions().quantization_position.SetQuantizationBits(10); + mesh->GetCompressionOptions().quantization_bits_normal = 10; + draco::EncoderBuffer buffer; + DRACO_ASSERT_OK(encoder_manual.EncodeMeshToBuffer(*mesh, &buffer)); + ASSERT_EQ(buffer.size(), buffer_manual.size()); +} + +TEST_F(EncodeTest, TestDracoCompressionOptionsManualOverride) { + // This test verifies that we can use encoder's option to override compression + // options provided in draco::Mesh's compression options. + const auto mesh = draco::ReadMeshFromTestFile("test_nm.obj"); + ASSERT_NE(mesh, nullptr); + + // Set some compression options. + draco::DracoCompressionOptions compression_options; + compression_options.compression_level = 6; + compression_options.quantization_position.SetQuantizationBits(8); + compression_options.quantization_bits_normal = 7; + mesh->SetCompressionOptions(compression_options); + mesh->SetCompressionEnabled(true); + + draco::Encoder encoder; + draco::EncoderBuffer buffer_no_override; + DRACO_ASSERT_OK(encoder.EncodeMeshToBuffer(*mesh, &buffer_no_override)); + + // Now override some options and ensure the compression is different. + encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 5); + draco::EncoderBuffer buffer_with_override; + DRACO_ASSERT_OK(encoder.EncodeMeshToBuffer(*mesh, &buffer_with_override)); + ASSERT_LT(buffer_with_override.size(), buffer_no_override.size()); +} + +TEST_F(EncodeTest, TestDracoCompressionOptionsGridQuantization) { + // Test verifies that we can set position quantization via grid spacing. + + // 1x1x1 cube. + const auto mesh = draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + mesh->SetCompressionEnabled(true); + + // Set grid quantization for positions. + draco::DracoCompressionOptions compression_options; + // This should result in 10x10x10 quantization. + compression_options.quantization_position.SetGrid(0.1); + mesh->SetCompressionOptions(compression_options); + + draco::ExpertEncoder encoder(*mesh); + draco::EncoderBuffer buffer; + DRACO_ASSERT_OK(encoder.EncodeToBuffer(&buffer)); + + // The grid options should be reflected in the |encoder|. Check that the + // computed values are correct. + const int pos_att_id = + mesh->GetNamedAttributeId(draco::GeometryAttribute::POSITION); + draco::Vector3f origin; + encoder.options().GetAttributeVector(pos_att_id, "quantization_origin", 3, + &origin[0]); + ASSERT_EQ(origin, draco::Vector3f(0.f, 0.f, 0.f)); + + // We need 4 quantization bits (for 10 values). + ASSERT_EQ( + encoder.options().GetAttributeInt(pos_att_id, "quantization_bits", -1), + 4); + + // The quantization range should be ((1 << quantization_bits) - 1) * spacing. + ASSERT_NEAR(encoder.options().GetAttributeFloat(pos_att_id, + "quantization_range", 0.f), + 15.f * 0.1f, 1e-6f); +} + +TEST_F(EncodeTest, TestDracoCompressionOptionsGridQuantizationWithOffset) { + // Test verifies that we can set position quantization via grid spacing when + // the geometry is not perfectly aligned with the quantization grid. + + // 1x1x1 cube. + const auto mesh = draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + // Move all positions a bit. + auto *pos_att = mesh->attribute( + mesh->GetNamedAttributeId(draco::GeometryAttribute::POSITION)); + for (draco::AttributeValueIndex avi(0); avi < pos_att->size(); ++avi) { + draco::Vector3f pos; + pos_att->GetValue(avi, &pos[0]); + pos = pos + draco::Vector3f(-0.55f, 0.65f, 10.75f); + pos_att->SetAttributeValue(avi, &pos[0]); + } + + mesh->SetCompressionEnabled(true); + + // Set grid quantization for positions. + draco::DracoCompressionOptions compression_options; + // This should result in 16x16x16 quantization if the grid was perfectly + // aligned but since it is not we should expect 17 or 18 values per component. + compression_options.quantization_position.SetGrid(0.0625f); + mesh->SetCompressionOptions(compression_options); + + draco::ExpertEncoder encoder(*mesh); + draco::EncoderBuffer buffer; + DRACO_ASSERT_OK(encoder.EncodeToBuffer(&buffer)); + + // The grid options should be reflected in the |encoder|. Check that the + // computed values are correct. + const int pos_att_id = + mesh->GetNamedAttributeId(draco::GeometryAttribute::POSITION); + draco::Vector3f origin; + encoder.options().GetAttributeVector(pos_att_id, "quantization_origin", 3, + &origin[0]); + // The origin is the first lower value on the quantization grid for each + // component of the mesh. + ASSERT_EQ(origin, draco::Vector3f(-0.5625f, 0.625f, 10.75f)); + + // We need 5 quantization bits (for 17-18 values). + ASSERT_EQ( + encoder.options().GetAttributeInt(pos_att_id, "quantization_bits", -1), + 5); + + // The quantization range should be ((1 << quantization_bits) - 1) * spacing. + ASSERT_NEAR(encoder.options().GetAttributeFloat(pos_att_id, + "quantization_range", 0.f), + 31.f * 0.0625f, 1e-6f); +} +#endif // DRACO_TRANSCODER_SUPPORTED + } // namespace diff --git a/contrib/draco/src/draco/compression/entropy/ans.h b/contrib/draco/src/draco/compression/entropy/ans.h index c71d58975..313546fee 100644 --- a/contrib/draco/src/draco/compression/entropy/ans.h +++ b/contrib/draco/src/draco/compression/entropy/ans.h @@ -391,7 +391,6 @@ class RAnsEncoder { ans_.buf[ans_.buf_offset++] = ans_.state % DRACO_ANS_IO_BASE; ans_.state /= DRACO_ANS_IO_BASE; } - // TODO(ostava): The division and multiplication should be optimized. ans_.state = (ans_.state / p) * rans_precision + ans_.state % p + sym->cum_prob; } diff --git a/contrib/draco/src/draco/compression/entropy/rans_symbol_decoder.h b/contrib/draco/src/draco/compression/entropy/rans_symbol_decoder.h index 10cdc6781..3b408c079 100644 --- a/contrib/draco/src/draco/compression/entropy/rans_symbol_decoder.h +++ b/contrib/draco/src/draco/compression/entropy/rans_symbol_decoder.h @@ -75,6 +75,13 @@ bool RAnsSymbolDecoder::Create( return false; } } + // Check that decoded number of symbols is not unreasonably high. Remaining + // buffer size must be at least |num_symbols| / 64 bytes to contain the + // probability table. The |prob_data| below is one byte but it can be + // theoretically stored for each 64th symbol. + if (num_symbols_ / 64 > buffer->remaining_size()) { + return false; + } probability_table_.resize(num_symbols_); if (num_symbols_ == 0) { return true; diff --git a/contrib/draco/src/draco/compression/entropy/rans_symbol_encoder.h b/contrib/draco/src/draco/compression/entropy/rans_symbol_encoder.h index 4e07ec871..4b738b50a 100644 --- a/contrib/draco/src/draco/compression/entropy/rans_symbol_encoder.h +++ b/contrib/draco/src/draco/compression/entropy/rans_symbol_encoder.h @@ -125,8 +125,8 @@ bool RAnsSymbolEncoder::Create( for (int i = 0; i < num_symbols; ++i) { sorted_probabilities[i] = i; } - std::sort(sorted_probabilities.begin(), sorted_probabilities.end(), - ProbabilityLess(&probability_table_)); + std::stable_sort(sorted_probabilities.begin(), sorted_probabilities.end(), + ProbabilityLess(&probability_table_)); if (total_rans_prob < rans_precision_) { // This happens rather infrequently, just add the extra needed precision // to the most frequent symbol. diff --git a/contrib/draco/src/draco/compression/entropy/symbol_decoding.cc b/contrib/draco/src/draco/compression/entropy/symbol_decoding.cc index 93d29971c..79e811818 100644 --- a/contrib/draco/src/draco/compression/entropy/symbol_decoding.cc +++ b/contrib/draco/src/draco/compression/entropy/symbol_decoding.cc @@ -72,7 +72,7 @@ bool DecodeTaggedSymbols(uint32_t num_values, int num_components, int value_id = 0; for (uint32_t i = 0; i < num_values; i += num_components) { // Decode the tag. - const int bit_length = tag_decoder.DecodeSymbol(); + const uint32_t bit_length = tag_decoder.DecodeSymbol(); // Decode the actual value. for (int j = 0; j < num_components; ++j) { uint32_t val; diff --git a/contrib/draco/src/draco/compression/expert_encode.cc b/contrib/draco/src/draco/compression/expert_encode.cc index f9aec15eb..a3e649193 100644 --- a/contrib/draco/src/draco/compression/expert_encode.cc +++ b/contrib/draco/src/draco/compression/expert_encode.cc @@ -14,6 +14,12 @@ // #include "draco/compression/expert_encode.h" +#include +#include +#include +#include +#include + #include "draco/compression/mesh/mesh_edgebreaker_encoder.h" #include "draco/compression/mesh/mesh_sequential_encoder.h" #ifdef DRACO_POINT_CLOUD_COMPRESSION_SUPPORTED @@ -21,6 +27,9 @@ #include "draco/compression/point_cloud/point_cloud_sequential_encoder.h" #endif +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/bit_utils.h" +#endif namespace draco { ExpertEncoder::ExpertEncoder(const PointCloud &point_cloud) @@ -101,6 +110,11 @@ Status ExpertEncoder::EncodePointCloudToBuffer(const PointCloud &pc, Status ExpertEncoder::EncodeMeshToBuffer(const Mesh &m, EncoderBuffer *out_buffer) { +#ifdef DRACO_TRANSCODER_SUPPORTED + // Apply DracoCompressionOptions associated with the mesh. + DRACO_RETURN_IF_ERROR(ApplyCompressionOptions(m)); +#endif // DRACO_TRANSCODER_SUPPORTED + std::unique_ptr encoder; // Select the encoding method only based on the provided options. int encoding_method = options().GetGlobalInt("encoding_method", -1); @@ -118,6 +132,7 @@ Status ExpertEncoder::EncodeMeshToBuffer(const Mesh &m, encoder = std::unique_ptr(new MeshSequentialEncoder()); } encoder->SetMesh(m); + DRACO_RETURN_IF_ERROR(encoder->Encode(options(), out_buffer)); set_num_encoded_points(encoder->num_encoded_points()); @@ -179,4 +194,107 @@ Status ExpertEncoder::SetAttributePredictionScheme( return status; } +#ifdef DRACO_TRANSCODER_SUPPORTED +Status ExpertEncoder::ApplyCompressionOptions(const Mesh &mesh) { + if (!mesh.IsCompressionEnabled()) { + return OkStatus(); + } + const auto &compression_options = mesh.GetCompressionOptions(); + + // Set any encoder options that haven't been explicitly set by users (don't + // override existing options). + if (!options().IsSpeedSet()) { + options().SetSpeed(10 - compression_options.compression_level, + 10 - compression_options.compression_level); + } + + for (int ai = 0; ai < mesh.num_attributes(); ++ai) { + if (options().IsAttributeOptionSet(ai, "quantization_bits")) { + continue; // Don't override options that have been set. + } + int quantization_bits = 0; + const auto type = mesh.attribute(ai)->attribute_type(); + switch (type) { + case GeometryAttribute::POSITION: + if (compression_options.quantization_position + .AreQuantizationBitsDefined()) { + quantization_bits = + compression_options.quantization_position.quantization_bits(); + } else { + DRACO_RETURN_IF_ERROR(ApplyGridQuantization(mesh, ai)); + } + break; + case GeometryAttribute::TEX_COORD: + quantization_bits = compression_options.quantization_bits_tex_coord; + break; + case GeometryAttribute::NORMAL: + quantization_bits = compression_options.quantization_bits_normal; + break; + case GeometryAttribute::COLOR: + quantization_bits = compression_options.quantization_bits_color; + break; + case GeometryAttribute::TANGENT: + quantization_bits = compression_options.quantization_bits_tangent; + break; + case GeometryAttribute::WEIGHTS: + quantization_bits = compression_options.quantization_bits_weight; + break; + case GeometryAttribute::GENERIC: + quantization_bits = compression_options.quantization_bits_generic; + break; + default: + break; + } + if (quantization_bits > 0) { + options().SetAttributeInt(ai, "quantization_bits", quantization_bits); + } + } + return OkStatus(); +} + +Status ExpertEncoder::ApplyGridQuantization(const Mesh &mesh, + int attribute_index) { + const auto compression_options = mesh.GetCompressionOptions(); + if (mesh.attribute(attribute_index)->num_components() != 3) { + return ErrorStatus( + "Invalid number of components: Grid quantization is currently " + "supported only for 3D positions."); + } + const float spacing = compression_options.quantization_position.spacing(); + // Compute quantization properties based on the grid spacing. + const auto &bbox = mesh.ComputeBoundingBox(); + // Snap min and max points of the |bbox| to the quantization grid vertices. + Vector3f min_pos; + int num_values = 0; // Number of values that we need to encode. + for (int c = 0; c < 3; ++c) { + // Min / max position on grid vertices in grid coordinates. + const float min_grid_pos = floor(bbox.GetMinPoint()[c] / spacing); + const float max_grid_pos = ceil(bbox.GetMaxPoint()[c] / spacing); + + // Min pos on grid vertex in mesh coordinates. + min_pos[c] = min_grid_pos * spacing; + + const float component_num_values = + static_cast(max_grid_pos) - static_cast(min_grid_pos) + 1; + if (component_num_values > num_values) { + num_values = component_num_values; + } + } + // Now compute the number of bits needed to encode |num_values|. + int bits = MostSignificantBit(num_values); + if ((1 << bits) < num_values) { + // If the |num_values| is larger than number of values representable by + // |bits|, we need to use one more bit. This will be almost always true + // unless |num_values| was equal to 1 << |bits|. + bits++; + } + // Compute the range in mesh coordinates that matches the quantization bits. + // Note there are n-1 intervals between the |n| quantization values. + const float range = ((1 << bits) - 1) * spacing; + SetAttributeExplicitQuantization(attribute_index, bits, 3, min_pos.data(), + range); + return OkStatus(); +} +#endif // DRACO_TRANSCODER_SUPPORTED + } // namespace draco diff --git a/contrib/draco/src/draco/compression/expert_encode.h b/contrib/draco/src/draco/compression/expert_encode.h index ea59393d3..5c1485e1e 100644 --- a/contrib/draco/src/draco/compression/expert_encode.h +++ b/contrib/draco/src/draco/compression/expert_encode.h @@ -138,6 +138,12 @@ class ExpertEncoder : public EncoderBase { Status EncodeMeshToBuffer(const Mesh &m, EncoderBuffer *out_buffer); +#ifdef DRACO_TRANSCODER_SUPPORTED + // Applies compression options stored in |mesh|. + Status ApplyCompressionOptions(const Mesh &mesh); + Status ApplyGridQuantization(const Mesh &mesh, int attribute_index); +#endif // DRACO_TRANSCODER_SUPPORTED + const PointCloud *point_cloud_; const Mesh *mesh_; }; diff --git a/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_decoder_impl.cc b/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_decoder_impl.cc index 0bbbea4af..21ad9959c 100644 --- a/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_decoder_impl.cc +++ b/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_decoder_impl.cc @@ -454,7 +454,7 @@ bool MeshEdgebreakerDecoderImpl::DecodeConnectivity() { #endif // Decode connectivity of non-position attributes. - if (attribute_data_.size() > 0) { + if (!attribute_data_.empty()) { #ifdef DRACO_BACKWARDS_COMPATIBILITY_SUPPORTED if (decoder_->bitstream_version() < DRACO_BITSTREAM_VERSION(2, 1)) { for (CornerIndex ci(0); ci < corner_table_->num_corners(); ci += 3) { @@ -484,7 +484,10 @@ bool MeshEdgebreakerDecoderImpl::DecodeConnectivity() { attribute_data_[i].connectivity_data.AddSeamEdge(CornerIndex(c)); } // Recompute vertices from the newly added seam edges. - attribute_data_[i].connectivity_data.RecomputeVertices(nullptr, nullptr); + if (!attribute_data_[i].connectivity_data.RecomputeVertices(nullptr, + nullptr)) { + return false; + } } pos_encoding_data_.Init(corner_table_->num_vertices()); @@ -574,6 +577,17 @@ int MeshEdgebreakerDecoderImpl::DecodeConnectivity( const CornerIndex corner_b = corner_table_->Next(corner_table_->LeftMostCorner(vertex_x)); + if (corner_a == corner_b) { + // All matched corners must be different. + return -1; + } + if (corner_table_->Opposite(corner_a) != kInvalidCornerIndex || + corner_table_->Opposite(corner_b) != kInvalidCornerIndex) { + // One of the corners is already opposite to an existing face, which + // should not happen unless the input was tampered with. + return -1; + } + // New tip corner. const CornerIndex corner(3 * face.value()); // Update opposite corner mappings. @@ -616,6 +630,11 @@ int MeshEdgebreakerDecoderImpl::DecodeConnectivity( return -1; } const CornerIndex corner_a = active_corner_stack.back(); + if (corner_table_->Opposite(corner_a) != kInvalidCornerIndex) { + // Active corner is already opposite to an existing face, which should + // not happen unless the input was tampered with. + return -1; + } // First corner on the new face is either corner "l" or "r". const CornerIndex corner(3 * face.value()); @@ -681,10 +700,14 @@ int MeshEdgebreakerDecoderImpl::DecodeConnectivity( } const CornerIndex corner_a = active_corner_stack.back(); + if (corner_a == corner_b) { + // All matched corners must be different. + return -1; + } if (corner_table_->Opposite(corner_a) != kInvalidCornerIndex || corner_table_->Opposite(corner_b) != kInvalidCornerIndex) { // One of the corners is already opposite to an existing face, which - // should not happen unless the input was tempered with. + // should not happen unless the input was tampered with. return -1; } @@ -713,9 +736,15 @@ int MeshEdgebreakerDecoderImpl::DecodeConnectivity( // Also update the vertex id at corner "n" and all corners that are // connected to it in the CCW direction. + const CornerIndex first_corner = corner_n; while (corner_n != kInvalidCornerIndex) { corner_table_->MapCornerToVertex(corner_n, vertex_p); corner_n = corner_table_->SwingLeft(corner_n); + if (corner_n == first_corner) { + // We reached the start again which should not happen for split + // symbols. + return -1; + } } // Make sure the old vertex n is now mapped to an invalid corner (make it // isolated). @@ -800,7 +829,7 @@ int MeshEdgebreakerDecoderImpl::DecodeConnectivity( return -1; // Unexpected number of decoded vertices. } // Decode start faces and connect them to the faces from the active stack. - while (active_corner_stack.size() > 0) { + while (!active_corner_stack.empty()) { const CornerIndex corner = active_corner_stack.back(); active_corner_stack.pop_back(); const bool interior_face = @@ -842,6 +871,18 @@ int MeshEdgebreakerDecoderImpl::DecodeConnectivity( const CornerIndex corner_c = corner_table_->Next(corner_table_->LeftMostCorner(vert_x)); + if (corner == corner_b || corner == corner_c || corner_b == corner_c) { + // All matched corners must be different. + return -1; + } + if (corner_table_->Opposite(corner) != kInvalidCornerIndex || + corner_table_->Opposite(corner_b) != kInvalidCornerIndex || + corner_table_->Opposite(corner_c) != kInvalidCornerIndex) { + // One of the corners is already opposite to an existing face, which + // should not happen unless the input was tampered with. + return -1; + } + const VertexIndex vert_p = corner_table_->Vertex(corner_table_->Next(corner_c)); @@ -894,6 +935,11 @@ int MeshEdgebreakerDecoderImpl::DecodeConnectivity( VertexCornersIterator vcit(corner_table_.get(), src_vert); for (; !vcit.End(); ++vcit) { const CornerIndex cid = vcit.Corner(); + if (corner_table_->Vertex(cid) != src_vert) { + // Vertex mapped to |cid| was not |src_vert|. This indicates corrupted + // data and we should terminate the decoding. + return -1; + } corner_table_->MapCornerToVertex(cid, invalid_vert); } corner_table_->SetLeftMostCorner(invalid_vert, diff --git a/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder.cc b/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder.cc index 5aff5d8cc..a7f381480 100644 --- a/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder.cc +++ b/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder.cc @@ -31,7 +31,6 @@ bool MeshEdgebreakerEncoder::InitializeEncoder() { impl_ = nullptr; // For tiny meshes it's usually better to use the basic edgebreaker as the // overhead of the predictive one may turn out to be too big. - // TODO(b/111065939): Check if this can be improved. const bool is_tiny_mesh = mesh()->num_faces() < 1000; int selected_edgebreaker_method = diff --git a/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.cc b/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.cc index 0791dc670..4bf6aa920 100644 --- a/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.cc +++ b/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.cc @@ -408,7 +408,7 @@ Status MeshEdgebreakerEncoderImpl::EncodeConnectivity() { init_face_connectivity_corners.begin(), init_face_connectivity_corners.end()); // Encode connectivity for all non-position attributes. - if (attribute_data_.size() > 0) { + if (!attribute_data_.empty()) { // Use the same order of corner that will be used by the decoder. visited_faces_.assign(mesh_->num_faces(), false); for (CornerIndex ci : processed_connectivity_corners_) { diff --git a/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.h b/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.h index fb3377163..979e1d373 100644 --- a/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.h +++ b/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoder_impl.h @@ -177,7 +177,6 @@ class MeshEdgebreakerEncoderImpl : public MeshEdgebreakerEncoderImplInterface { uint32_t num_split_symbols_; // Struct holding data used for encoding each non-position attribute. - // TODO(ostava): This should be probably renamed to something better. struct AttributeData { AttributeData() : attribute_index(-1), is_connectivity_used(true) {} int attribute_index; diff --git a/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoding_test.cc b/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoding_test.cc index 831388245..523303b09 100644 --- a/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoding_test.cc +++ b/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_encoding_test.cc @@ -44,7 +44,7 @@ class MeshEdgebreakerEncodingTest : public ::testing::Test { EncoderOptions encoder_options = EncoderOptions::CreateDefaultOptions(); encoder_options.SetSpeed(10 - compression_level, 10 - compression_level); encoder.SetMesh(*mesh); - ASSERT_TRUE(encoder.Encode(encoder_options, &buffer).ok()); + DRACO_ASSERT_OK(encoder.Encode(encoder_options, &buffer)); DecoderBuffer dec_buffer; dec_buffer.Init(buffer.data(), buffer.size()); @@ -52,15 +52,14 @@ class MeshEdgebreakerEncodingTest : public ::testing::Test { std::unique_ptr decoded_mesh(new Mesh()); DecoderOptions dec_options; - ASSERT_TRUE( - decoder.Decode(dec_options, &dec_buffer, decoded_mesh.get()).ok()); + DRACO_ASSERT_OK( + decoder.Decode(dec_options, &dec_buffer, decoded_mesh.get())); // Cleanup the input mesh to make sure that input and output can be // compared (edgebreaker method discards degenerated triangles and isolated // vertices). const MeshCleanupOptions options; - MeshCleanup cleanup; - ASSERT_TRUE(cleanup(mesh, options)) << "Failed to clean the input mesh."; + DRACO_ASSERT_OK(MeshCleanup::Cleanup(mesh, options)); MeshAreEquivalent eq; ASSERT_TRUE(eq(*mesh, *decoded_mesh.get())) @@ -102,8 +101,8 @@ TEST_F(MeshEdgebreakerEncodingTest, TestEncoderReuse) { EncoderOptions encoder_options = EncoderOptions::CreateDefaultOptions(); encoder.SetMesh(*mesh); EncoderBuffer buffer_0, buffer_1; - ASSERT_TRUE(encoder.Encode(encoder_options, &buffer_0).ok()); - ASSERT_TRUE(encoder.Encode(encoder_options, &buffer_1).ok()); + DRACO_ASSERT_OK(encoder.Encode(encoder_options, &buffer_0)); + DRACO_ASSERT_OK(encoder.Encode(encoder_options, &buffer_1)); // Make sure both buffer are identical. ASSERT_EQ(buffer_0.size(), buffer_1.size()); @@ -123,7 +122,7 @@ TEST_F(MeshEdgebreakerEncodingTest, TestDecoderReuse) { EncoderOptions encoder_options = EncoderOptions::CreateDefaultOptions(); encoder.SetMesh(*mesh); EncoderBuffer buffer; - ASSERT_TRUE(encoder.Encode(encoder_options, &buffer).ok()); + DRACO_ASSERT_OK(encoder.Encode(encoder_options, &buffer)); DecoderBuffer dec_buffer; dec_buffer.Init(buffer.data(), buffer.size()); @@ -133,13 +132,13 @@ TEST_F(MeshEdgebreakerEncodingTest, TestDecoderReuse) { // Decode the mesh two times. std::unique_ptr decoded_mesh_0(new Mesh()); DecoderOptions dec_options; - ASSERT_TRUE( - decoder.Decode(dec_options, &dec_buffer, decoded_mesh_0.get()).ok()); + DRACO_ASSERT_OK( + decoder.Decode(dec_options, &dec_buffer, decoded_mesh_0.get())); dec_buffer.Init(buffer.data(), buffer.size()); std::unique_ptr decoded_mesh_1(new Mesh()); - ASSERT_TRUE( - decoder.Decode(dec_options, &dec_buffer, decoded_mesh_1.get()).ok()); + DRACO_ASSERT_OK( + decoder.Decode(dec_options, &dec_buffer, decoded_mesh_1.get())); // Make sure both of the meshes are identical. MeshAreEquivalent eq; @@ -169,7 +168,7 @@ TEST_F(MeshEdgebreakerEncodingTest, TestSingleConnectivityEncoding) { encoder.SetAttributeQuantization(GeometryAttribute::TEX_COORD, 8); encoder.SetAttributeQuantization(GeometryAttribute::NORMAL, 8); encoder.SetEncodingMethod(MESH_EDGEBREAKER_ENCODING); - ASSERT_TRUE(encoder.EncodeMeshToBuffer(*mesh, &buffer).ok()); + DRACO_ASSERT_OK(encoder.EncodeMeshToBuffer(*mesh, &buffer)); DecoderBuffer dec_buffer; dec_buffer.Init(buffer.data(), buffer.size()); @@ -216,7 +215,7 @@ TEST_F(MeshEdgebreakerEncodingTest, TestWrongAttributeOrder) { encoder.SetAttributeQuantization(GeometryAttribute::POSITION, 8); encoder.SetAttributeQuantization(GeometryAttribute::NORMAL, 8); encoder.SetEncodingMethod(MESH_EDGEBREAKER_ENCODING); - ASSERT_TRUE(encoder.EncodeMeshToBuffer(*mesh, &buffer).ok()); + DRACO_ASSERT_OK(encoder.EncodeMeshToBuffer(*mesh, &buffer)); DecoderBuffer dec_buffer; dec_buffer.Init(buffer.data(), buffer.size()); diff --git a/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_shared.h b/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_shared.h index cb3c29dd6..c650bc352 100644 --- a/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_shared.h +++ b/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_shared.h @@ -50,8 +50,6 @@ namespace draco { // \ / S \ / / E \ // *-------* *-------* // -// TODO(ostava): Get rid of the topology bit pattern. It's important only for -// encoding but the algorithms should use EdgebreakerSymbol instead. enum EdgebreakerTopologyBitPattern { TOPOLOGY_C = 0x0, // 0 TOPOLOGY_S = 0x1, // 1 0 0 diff --git a/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_traversal_valence_decoder.h b/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_traversal_valence_decoder.h index c00373727..89553e909 100644 --- a/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_traversal_valence_decoder.h +++ b/contrib/draco/src/draco/compression/mesh/mesh_edgebreaker_traversal_valence_decoder.h @@ -129,7 +129,11 @@ class MeshEdgebreakerTraversalValenceDecoder if (context_counter < 0) { return TOPOLOGY_INVALID; } - const int symbol_id = context_symbols_[active_context_][context_counter]; + const uint32_t symbol_id = + context_symbols_[active_context_][context_counter]; + if (symbol_id > 4) { + return TOPOLOGY_INVALID; + } last_symbol_ = edge_breaker_symbol_to_topology_id[symbol_id]; } else { #ifdef DRACO_BACKWARDS_COMPATIBILITY_SUPPORTED diff --git a/contrib/draco/src/draco/compression/mesh/mesh_encoder_test.cc b/contrib/draco/src/draco/compression/mesh/mesh_encoder_test.cc index 55f683696..2dfdb58ef 100644 --- a/contrib/draco/src/draco/compression/mesh/mesh_encoder_test.cc +++ b/contrib/draco/src/draco/compression/mesh/mesh_encoder_test.cc @@ -78,9 +78,10 @@ class MeshEncoderTest : public ::testing::TestWithParam { encoder.SetAttributeQuantization(i, 12); } EncoderBuffer buffer; - ASSERT_TRUE(encoder.EncodeToBuffer(&buffer).ok()) - << "Failed encoding test mesh " << file_name << " with method " - << GetParam().encoding_method; + const Status status = encoder.EncodeToBuffer(&buffer); + EXPECT_TRUE(status.ok()) << "Failed encoding test mesh " << file_name + << " with method " << GetParam().encoding_method; + DRACO_ASSERT_OK(status); // Check that the encoded mesh was really encoded with the selected method. DecoderBuffer decoder_buffer; decoder_buffer.Init(buffer.data(), buffer.size()); @@ -88,6 +89,7 @@ class MeshEncoderTest : public ::testing::TestWithParam { uint8_t encoded_method; ASSERT_TRUE(decoder_buffer.Decode(&encoded_method)); ASSERT_EQ(encoded_method, method); + if (!FLAGS_update_golden_files) { EXPECT_TRUE( CompareGoldenFile(golden_file_name, buffer.data(), buffer.size())) diff --git a/contrib/draco/src/draco/compression/mesh/mesh_sequential_decoder.cc b/contrib/draco/src/draco/compression/mesh/mesh_sequential_decoder.cc index be349f543..595a487a4 100644 --- a/contrib/draco/src/draco/compression/mesh/mesh_sequential_decoder.cc +++ b/contrib/draco/src/draco/compression/mesh/mesh_sequential_decoder.cc @@ -96,7 +96,7 @@ bool MeshSequentialDecoder::DecodeConnectivity() { } mesh()->AddFace(face); } - } else if (mesh()->num_points() < (1 << 21) && + } else if (num_points < (1 << 21) && bitstream_version() >= DRACO_BITSTREAM_VERSION(2, 2)) { // Decode indices as uint32_t. for (uint32_t i = 0; i < num_faces; ++i) { @@ -158,6 +158,10 @@ bool MeshSequentialDecoder::DecodeAndDecompressIndices(uint32_t num_faces) { index_diff = -index_diff; } const int32_t index_value = index_diff + last_index_value; + if (index_value < 0) { + // Negative indices are not allowed. + return false; + } face[j] = index_value; last_index_value = index_value; } diff --git a/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.cc b/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.cc index 02ac7779e..fd8b11392 100644 --- a/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.cc +++ b/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.cc @@ -32,8 +32,6 @@ Status MeshSequentialEncoder::EncodeConnectivity() { EncodeVarint(static_cast(mesh()->num_points()), buffer()); // We encode all attributes in the original (possibly duplicated) format. - // TODO(ostava): This may not be optimal if we have only one attribute or if - // all attributes share the same index mapping. if (options()->GetGlobalBool("compress_connectivity", false)) { // 0 = Encode compressed indices. buffer()->Encode(static_cast(0)); @@ -44,8 +42,6 @@ Status MeshSequentialEncoder::EncodeConnectivity() { // 1 = Encode indices directly. buffer()->Encode(static_cast(1)); // Store vertex indices using a smallest data type that fits their range. - // TODO(ostava): This can be potentially improved by using a tighter - // fit that is not bound by a bit-length of any particular data type. if (mesh()->num_points() < 256) { // Serialize indices as uint8_t. for (FaceIndex i(0); i < num_faces; ++i) { diff --git a/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.h b/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.h index 672609642..6e2b05877 100644 --- a/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.h +++ b/contrib/draco/src/draco/compression/mesh/mesh_sequential_encoder.h @@ -33,7 +33,6 @@ namespace draco { // Class that encodes mesh data using a simple binary representation of mesh's // connectivity and geometry. -// TODO(ostava): Use a better name. class MeshSequentialEncoder : public MeshEncoder { public: MeshSequentialEncoder(); diff --git a/contrib/draco/src/draco/compression/mesh/traverser/mesh_attribute_indices_encoding_observer.h b/contrib/draco/src/draco/compression/mesh/traverser/mesh_attribute_indices_encoding_observer.h index e66dd14b2..dd9738ba2 100644 --- a/contrib/draco/src/draco/compression/mesh/traverser/mesh_attribute_indices_encoding_observer.h +++ b/contrib/draco/src/draco/compression/mesh/traverser/mesh_attribute_indices_encoding_observer.h @@ -25,7 +25,7 @@ namespace draco { // values based on the traversal of the encoded mesh. The class should be used // as the TraversalObserverT member of a Traverser class such as the // DepthFirstTraverser (depth_first_traverser.h). -// TODO(hemmer): rename to AttributeIndicesCodingTraverserObserver +// TODO(b/199760123): Rename to AttributeIndicesCodingTraverserObserver. template class MeshAttributeIndicesEncodingObserver { public: diff --git a/contrib/draco/src/draco/compression/mesh/traverser/mesh_traversal_sequencer.h b/contrib/draco/src/draco/compression/mesh/traverser/mesh_traversal_sequencer.h index ebe1d5f7a..e55c93a79 100644 --- a/contrib/draco/src/draco/compression/mesh/traverser/mesh_traversal_sequencer.h +++ b/contrib/draco/src/draco/compression/mesh/traverser/mesh_traversal_sequencer.h @@ -25,7 +25,7 @@ namespace draco { // Sequencer that generates point sequence in an order given by a deterministic // traversal on the mesh surface. Note that all attributes encoded with this // sequence must share the same connectivity. -// TODO(hemmer): Consider refactoring such that this is an observer. +// TODO(b/199760123): Consider refactoring such that this is an observer. template class MeshTraversalSequencer : public PointsSequencer { public: diff --git a/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_decoder.h b/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_decoder.h index 87bc2b7ef..55bafe7c4 100644 --- a/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_decoder.h +++ b/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_decoder.h @@ -18,8 +18,10 @@ #define DRACO_COMPRESSION_POINT_CLOUD_ALGORITHMS_DYNAMIC_INTEGER_POINTS_KD_TREE_DECODER_H_ #include +#include #include #include +#include #include "draco/compression/bit_coders/adaptive_rans_bit_decoder.h" #include "draco/compression/bit_coders/direct_bit_decoder.h" @@ -92,17 +94,29 @@ class DynamicIntegerPointsKdTreeDecoder { base_stack_(32 * dimension + 1, VectorUint32(dimension, 0)), levels_stack_(32 * dimension + 1, VectorUint32(dimension, 0)) {} - // Decodes a integer point cloud from |buffer|. + // Decodes an integer point cloud from |buffer|. Optional |oit_max_points| can + // be used to tell the decoder the maximum number of points accepted by the + // iterator. template bool DecodePoints(DecoderBuffer *buffer, OutputIteratorT &oit); + template + bool DecodePoints(DecoderBuffer *buffer, OutputIteratorT &oit, + uint32_t oit_max_points); + #ifndef DRACO_OLD_GCC template bool DecodePoints(DecoderBuffer *buffer, OutputIteratorT &&oit); + template + bool DecodePoints(DecoderBuffer *buffer, OutputIteratorT &&oit, + uint32_t oit_max_points); #endif // DRACO_OLD_GCC const uint32_t dimension() const { return dimension_; } + // Returns the number of decoded points. Must be called after DecodePoints(). + uint32_t num_decoded_points() const { return num_decoded_points_; } + private: uint32_t GetAxis(uint32_t num_remaining_points, const VectorUint32 &levels, uint32_t last_axis); @@ -146,8 +160,15 @@ template template bool DynamicIntegerPointsKdTreeDecoder::DecodePoints( DecoderBuffer *buffer, OutputIteratorT &&oit) { + return DecodePoints(buffer, oit, std::numeric_limits::max()); +} + +template +template +bool DynamicIntegerPointsKdTreeDecoder::DecodePoints( + DecoderBuffer *buffer, OutputIteratorT &&oit, uint32_t oit_max_points) { OutputIteratorT local = std::forward(oit); - return DecodePoints(buffer, local); + return DecodePoints(buffer, local, oit_max_points); } #endif // DRACO_OLD_GCC @@ -155,6 +176,13 @@ template template bool DynamicIntegerPointsKdTreeDecoder::DecodePoints( DecoderBuffer *buffer, OutputIteratorT &oit) { + return DecodePoints(buffer, oit, std::numeric_limits::max()); +} + +template +template +bool DynamicIntegerPointsKdTreeDecoder::DecodePoints( + DecoderBuffer *buffer, OutputIteratorT &oit, uint32_t oit_max_points) { if (!buffer->Decode(&bit_length_)) { return false; } @@ -167,6 +195,9 @@ bool DynamicIntegerPointsKdTreeDecoder::DecodePoints( if (num_points_ == 0) { return true; } + if (num_points_ > oit_max_points) { + return false; + } num_decoded_points_ = 0; if (!numbers_decoder_.StartDecoding(buffer)) { @@ -227,7 +258,7 @@ bool DynamicIntegerPointsKdTreeDecoder::DecodeInternal( std::stack status_stack; status_stack.push(init_status); - // TODO(hemmer): use preallocated vector instead of stack. + // TODO(b/199760123): Use preallocated vector instead of stack. while (!status_stack.empty()) { const DecodingStatus status = status_stack.top(); status_stack.pop(); @@ -263,7 +294,8 @@ bool DynamicIntegerPointsKdTreeDecoder::DecodeInternal( // Fast decoding of remaining bits if number of points is 1 or 2. if (num_remaining_points <= 2) { - // TODO(hemmer): axes_ not necessary, remove would change bitstream! + // TODO(b/199760123): |axes_| not necessary, remove would change + // bitstream! axes_[0] = axis; for (uint32_t i = 1; i < dimension_; i++) { axes_[i] = DRACO_INCREMENT_MOD(axes_[i - 1], dimension_); @@ -273,8 +305,10 @@ bool DynamicIntegerPointsKdTreeDecoder::DecodeInternal( p_[axes_[j]] = 0; const uint32_t num_remaining_bits = bit_length_ - levels[axes_[j]]; if (num_remaining_bits) { - remaining_bits_decoder_.DecodeLeastSignificantBits32( - num_remaining_bits, &p_[axes_[j]]); + if (!remaining_bits_decoder_.DecodeLeastSignificantBits32( + num_remaining_bits, &p_[axes_[j]])) { + return false; + } } p_[axes_[j]] = old_base[axes_[j]] | p_[axes_[j]]; } @@ -299,7 +333,12 @@ bool DynamicIntegerPointsKdTreeDecoder::DecodeInternal( uint32_t number = 0; DecodeNumber(incoming_bits, &number); - uint32_t first_half = num_remaining_points / 2 - number; + uint32_t first_half = num_remaining_points / 2; + if (first_half < number) { + // Invalid |number|. + return false; + } + first_half -= number; uint32_t second_half = num_remaining_points - first_half; if (first_half != second_half) { diff --git a/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_encoder.h b/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_encoder.h index 14fa32d70..65b3d07a6 100644 --- a/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_encoder.h +++ b/contrib/draco/src/draco/compression/point_cloud/algorithms/dynamic_integer_points_kd_tree_encoder.h @@ -280,7 +280,7 @@ void DynamicIntegerPointsKdTreeEncoder::EncodeInternal( std::stack status_stack; status_stack.push(init_status); - // TODO(hemmer): use preallocated vector instead of stack. + // TODO(b/199760123): Use preallocated vector instead of stack. while (!status_stack.empty()) { Status status = status_stack.top(); status_stack.pop(); @@ -305,7 +305,8 @@ void DynamicIntegerPointsKdTreeEncoder::EncodeInternal( // Fast encoding of remaining bits if number of points is 1 or 2. // Doing this also for 2 gives a slight additional speed up. if (num_remaining_points <= 2) { - // TODO(hemmer): axes_ not necessary, remove would change bitstream! + // TODO(b/199760123): |axes_| not necessary, remove would change + // bitstream! axes_[0] = axis; for (uint32_t i = 1; i < dimension_; i++) { axes_[i] = DRACO_INCREMENT_MOD(axes_[i - 1], dimension_); diff --git a/contrib/draco/src/draco/compression/point_cloud/algorithms/float_points_tree_encoder.h b/contrib/draco/src/draco/compression/point_cloud/algorithms/float_points_tree_encoder.h index 26ba94f1f..44c1b3d3a 100644 --- a/contrib/draco/src/draco/compression/point_cloud/algorithms/float_points_tree_encoder.h +++ b/contrib/draco/src/draco/compression/point_cloud/algorithms/float_points_tree_encoder.h @@ -44,7 +44,7 @@ namespace draco { // there are more leading zeros, which is then compressed better by the // arithmetic encoding. -// TODO(hemmer): Remove class because it duplicates quantization code. +// TODO(b/199760123): Remove class because it duplicates quantization code. class FloatPointsTreeEncoder { public: explicit FloatPointsTreeEncoder(PointCloudCompressionMethod method); @@ -91,7 +91,7 @@ bool FloatPointsTreeEncoder::EncodePointCloud(InputIteratorT points_begin, // Collect necessary data for encoding. num_points_ = std::distance(points_begin, points_end); - // TODO(hemmer): Extend quantization tools to make this more automatic. + // TODO(b/199760123): Extend quantization tools to make this more automatic. // Compute range of points for quantization std::vector qpoints; qpoints.reserve(num_points_); diff --git a/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_decoder.h b/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_decoder.h index 94e523cad..bc31af586 100644 --- a/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_decoder.h +++ b/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_decoder.h @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. // -// TODO(hemmer): Make this a wrapper using DynamicIntegerPointsKdTreeDecoder. +// TODO(b/199760123): Make this a wrapper using +// DynamicIntegerPointsKdTreeDecoder. // // See integer_points_kd_tree_encoder.h for documentation. diff --git a/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_encoder.h b/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_encoder.h index b8811092e..654f14a78 100644 --- a/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_encoder.h +++ b/contrib/draco/src/draco/compression/point_cloud/algorithms/integer_points_kd_tree_encoder.h @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. // -// TODO(hemmer): Make this a wrapper using DynamicIntegerPointsKdTreeEncoder. +// TODO(b/199760123): Make this a wrapper using +// DynamicIntegerPointsKdTreeEncoder. #ifndef DRACO_COMPRESSION_POINT_CLOUD_ALGORITHMS_INTEGER_POINTS_KD_TREE_ENCODER_H_ #define DRACO_COMPRESSION_POINT_CLOUD_ALGORITHMS_INTEGER_POINTS_KD_TREE_ENCODER_H_ diff --git a/contrib/draco/src/draco/compression/point_cloud/algorithms/quantize_points_3.h b/contrib/draco/src/draco/compression/point_cloud/algorithms/quantize_points_3.h index 01943ad9e..8ea0741da 100644 --- a/contrib/draco/src/draco/compression/point_cloud/algorithms/quantize_points_3.h +++ b/contrib/draco/src/draco/compression/point_cloud/algorithms/quantize_points_3.h @@ -22,7 +22,7 @@ namespace draco { -// TODO(hemmer): Make this a stable bounding box. +// TODO(b/199760123): Make this a stable bounding box. struct QuantizationInfo { uint32_t quantization_bits; float range; @@ -41,7 +41,7 @@ OutputIterator QuantizePoints3(const PointIterator &begin, max_range = std::max(std::fabs((*it)[2]), max_range); } - const uint32_t max_quantized_value((1 << info->quantization_bits) - 1); + const uint32_t max_quantized_value((1u << info->quantization_bits) - 1); Quantizer quantize; quantize.Init(max_range, max_quantized_value); info->range = max_range; @@ -66,7 +66,7 @@ void DequantizePoints3(const QPointIterator &begin, const QPointIterator &end, const uint32_t quantization_bits = info.quantization_bits; const float range = info.range; - const uint32_t max_quantized_value((1 << quantization_bits) - 1); + const uint32_t max_quantized_value((1u << quantization_bits) - 1); Dequantizer dequantize; dequantize.Init(range, max_quantized_value); diff --git a/contrib/draco/src/draco/compression/point_cloud/point_cloud_kd_tree_encoding_test.cc b/contrib/draco/src/draco/compression/point_cloud/point_cloud_kd_tree_encoding_test.cc index 2249bb09e..7a7b597f2 100644 --- a/contrib/draco/src/draco/compression/point_cloud/point_cloud_kd_tree_encoding_test.cc +++ b/contrib/draco/src/draco/compression/point_cloud/point_cloud_kd_tree_encoding_test.cc @@ -68,7 +68,7 @@ class PointCloudKdTreeEncodingTest : public ::testing::Test { ++compression_level) { options.SetSpeed(10 - compression_level, 10 - compression_level); encoder.SetPointCloud(pc); - ASSERT_TRUE(encoder.Encode(options, &buffer).ok()); + DRACO_ASSERT_OK(encoder.Encode(options, &buffer)); DecoderBuffer dec_buffer; dec_buffer.Init(buffer.data(), buffer.size()); @@ -76,7 +76,7 @@ class PointCloudKdTreeEncodingTest : public ::testing::Test { std::unique_ptr out_pc(new PointCloud()); DecoderOptions dec_options; - ASSERT_TRUE(decoder.Decode(dec_options, &dec_buffer, out_pc.get()).ok()); + DRACO_ASSERT_OK(decoder.Decode(dec_options, &dec_buffer, out_pc.get())); ComparePointClouds(pc, *out_pc); } diff --git a/contrib/draco/src/draco/core/bounding_box.cc b/contrib/draco/src/draco/core/bounding_box.cc index 8a0709678..8acd6687b 100644 --- a/contrib/draco/src/draco/core/bounding_box.cc +++ b/contrib/draco/src/draco/core/bounding_box.cc @@ -20,11 +20,20 @@ BoundingBox::BoundingBox() : BoundingBox(Vector3f(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()), - Vector3f(-std::numeric_limits::max(), - -std::numeric_limits::max(), - -std::numeric_limits::max())) {} + Vector3f(std::numeric_limits::lowest(), + std::numeric_limits::lowest(), + std::numeric_limits::lowest())) {} BoundingBox::BoundingBox(const Vector3f &min_point, const Vector3f &max_point) : min_point_(min_point), max_point_(max_point) {} +const bool BoundingBox::IsValid() const { + return GetMinPoint()[0] != std::numeric_limits::max() && + GetMinPoint()[1] != std::numeric_limits::max() && + GetMinPoint()[2] != std::numeric_limits::max() && + GetMaxPoint()[0] != std::numeric_limits::lowest() && + GetMaxPoint()[1] != std::numeric_limits::lowest() && + GetMaxPoint()[2] != std::numeric_limits::lowest(); +} + } // namespace draco diff --git a/contrib/draco/src/draco/core/bounding_box.h b/contrib/draco/src/draco/core/bounding_box.h index 31ba2d683..697a73b6f 100644 --- a/contrib/draco/src/draco/core/bounding_box.h +++ b/contrib/draco/src/draco/core/bounding_box.h @@ -38,6 +38,11 @@ class BoundingBox { // Returns the maximum point of the bounding box. inline const Vector3f &GetMaxPoint() const { return max_point_; } + // Checks if the bounding box object was created with the default constructor + // then never updated. Internally, checks if the bounding box minimum and + // maximum points hold the largest positive and smallest negative values. + const bool IsValid() const; + // Conditionally updates the bounding box with a given |new_point|. void Update(const Vector3f &new_point) { for (int i = 0; i < 3; i++) { diff --git a/contrib/draco/src/draco/core/constants.h b/contrib/draco/src/draco/core/constants.h new file mode 100644 index 000000000..3e81992a1 --- /dev/null +++ b/contrib/draco/src/draco/core/constants.h @@ -0,0 +1,6 @@ +#ifndef DRACO_CORE_CONSTANTS_H_ +#define DRACO_CORE_CONSTANTS_H_ + +#define DRACO_PI 3.14159265358979323846 + +#endif // DRACO_CORE_CONSTANTS_H_ diff --git a/contrib/draco/src/draco/core/data_buffer.cc b/contrib/draco/src/draco/core/data_buffer.cc index f0b43d67d..96a378798 100644 --- a/contrib/draco/src/draco/core/data_buffer.cc +++ b/contrib/draco/src/draco/core/data_buffer.cc @@ -52,7 +52,7 @@ void DataBuffer::Resize(int64_t size) { } void DataBuffer::WriteDataToStream(std::ostream &stream) { - if (data_.size() == 0) { + if (data_.empty()) { return; } stream.write(reinterpret_cast(data_.data()), data_.size()); diff --git a/contrib/draco/src/draco/core/data_buffer.h b/contrib/draco/src/draco/core/data_buffer.h index 8ee690540..8eac0f6b4 100644 --- a/contrib/draco/src/draco/core/data_buffer.h +++ b/contrib/draco/src/draco/core/data_buffer.h @@ -67,7 +67,7 @@ class DataBuffer { int64_t update_count() const { return descriptor_.buffer_update_count; } size_t data_size() const { return data_.size(); } const uint8_t *data() const { return data_.data(); } - uint8_t *data() { return &data_[0]; } + uint8_t *data() { return data_.data(); } int64_t buffer_id() const { return descriptor_.buffer_id; } void set_buffer_id(int64_t buffer_id) { descriptor_.buffer_id = buffer_id; } diff --git a/contrib/draco/src/draco/core/decoder_buffer.h b/contrib/draco/src/draco/core/decoder_buffer.h index 0559abbe4..71189b7e7 100644 --- a/contrib/draco/src/draco/core/decoder_buffer.h +++ b/contrib/draco/src/draco/core/decoder_buffer.h @@ -54,12 +54,11 @@ class DecoderBuffer { // Decodes up to 32 bits into out_val. Can be called only in between // StartBitDecoding and EndBitDecoding. Otherwise returns false. - bool DecodeLeastSignificantBits32(int nbits, uint32_t *out_value) { + bool DecodeLeastSignificantBits32(uint32_t nbits, uint32_t *out_value) { if (!bit_decoder_active()) { return false; } - bit_decoder_.GetBits(nbits, out_value); - return true; + return bit_decoder_.GetBits(nbits, out_value); } // Decodes an arbitrary data type. @@ -158,11 +157,12 @@ class DecoderBuffer { inline void ConsumeBits(int k) { bit_offset_ += k; } // Returns |nbits| bits in |x|. - inline bool GetBits(int32_t nbits, uint32_t *x) { - DRACO_DCHECK_GE(nbits, 0); - DRACO_DCHECK_LE(nbits, 32); + inline bool GetBits(uint32_t nbits, uint32_t *x) { + if (nbits > 32) { + return false; + } uint32_t value = 0; - for (int32_t bit = 0; bit < nbits; ++bit) { + for (uint32_t bit = 0; bit < nbits; ++bit) { value |= GetBit() << bit; } *x = value; diff --git a/contrib/draco/src/draco/core/draco_index_type_vector.h b/contrib/draco/src/draco/core/draco_index_type_vector.h index aae1e7aaf..f5256ded9 100644 --- a/contrib/draco/src/draco/core/draco_index_type_vector.h +++ b/contrib/draco/src/draco/core/draco_index_type_vector.h @@ -25,25 +25,32 @@ namespace draco { // A wrapper around the standard std::vector that supports indexing of the // vector entries using the strongly typed indices as defined in -// draco_index_type.h . -// TODO(ostava): Make the interface more complete. It's currently missing -// features such as iterators. -// TODO(vytyaz): Add more unit tests for this class. +// draco_index_type.h. +// TODO(ostava): Make the interface more complete. It's currently missing some +// features. template class IndexTypeVector { public: typedef typename std::vector::const_reference const_reference; typedef typename std::vector::reference reference; + typedef typename std::vector::iterator iterator; + typedef typename std::vector::const_iterator const_iterator; IndexTypeVector() {} explicit IndexTypeVector(size_t size) : vector_(size) {} IndexTypeVector(size_t size, const ValueTypeT &val) : vector_(size, val) {} + iterator begin() { return vector_.begin(); } + const_iterator begin() const { return vector_.begin(); } + iterator end() { return vector_.end(); } + const_iterator end() const { return vector_.end(); } + void clear() { vector_.clear(); } void reserve(size_t size) { vector_.reserve(size); } void resize(size_t size) { vector_.resize(size); } void resize(size_t size, const ValueTypeT &val) { vector_.resize(size, val); } void assign(size_t size, const ValueTypeT &val) { vector_.assign(size, val); } + iterator erase(iterator position) { return vector_.erase(position); } void swap(IndexTypeVector &arg) { vector_.swap(arg.vector_); diff --git a/contrib/draco/src/draco/core/draco_test_utils.cc b/contrib/draco/src/draco/core/draco_test_utils.cc index edca9856d..a71082a86 100644 --- a/contrib/draco/src/draco/core/draco_test_utils.cc +++ b/contrib/draco/src/draco/core/draco_test_utils.cc @@ -16,9 +16,9 @@ #include +#include "draco/core/draco_test_base.h" #include "draco/core/macros.h" #include "draco/io/file_utils.h" -#include "draco_test_base.h" namespace draco { @@ -27,6 +27,8 @@ static constexpr char kTestDataDir[] = DRACO_TEST_DATA_DIR; static constexpr char kTestTempDir[] = DRACO_TEST_TEMP_DIR; } // namespace +std::string GetTestTempDir() { return std::string(kTestDataDir); } + std::string GetTestFileFullPath(const std::string &file_name) { return std::string(kTestDataDir) + std::string("/") + file_name; } @@ -55,11 +57,13 @@ bool CompareGoldenFile(const std::string &golden_file_name, const void *data, size_t remaining_data_size = data_size; int offset = 0; while ((extracted_size = in_file.read(buffer, buffer_size).gcount()) > 0) { - if (remaining_data_size <= 0) + if (remaining_data_size <= 0) { break; // Input and golden sizes are different. + } size_t size_to_check = extracted_size; - if (remaining_data_size < size_to_check) + if (remaining_data_size < size_to_check) { size_to_check = remaining_data_size; + } for (uint32_t i = 0; i < size_to_check; ++i) { if (buffer[i] != data_c8[offset++]) { LOG(INFO) << "Test output differed from golden file at byte " @@ -77,4 +81,20 @@ bool CompareGoldenFile(const std::string &golden_file_name, const void *data, return true; } +#ifdef DRACO_TRANSCODER_SUPPORTED + +template <> +std::unique_ptr ReadGeometryFromTestFile( + const std::string &file_name) { + return ReadMeshFromTestFile(file_name); +} + +template <> +std::unique_ptr ReadGeometryFromTestFile( + const std::string &file_name) { + return ReadSceneFromTestFile(file_name); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + } // namespace draco diff --git a/contrib/draco/src/draco/core/draco_test_utils.h b/contrib/draco/src/draco/core/draco_test_utils.h index fa548f52d..658096fe1 100644 --- a/contrib/draco/src/draco/core/draco_test_utils.h +++ b/contrib/draco/src/draco/core/draco_test_utils.h @@ -15,12 +15,24 @@ #ifndef DRACO_CORE_DRACO_TEST_UTILS_H_ #define DRACO_CORE_DRACO_TEST_UTILS_H_ +#include +#include +#include + #include "draco/core/draco_test_base.h" +#include "draco/draco_features.h" #include "draco/io/mesh_io.h" #include "draco/io/point_cloud_io.h" +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/io/scene_io.h" +#endif + namespace draco { +// Returns test temporary directory. +std::string GetTestTempDir(); + // Returns the full path to a given file system entry, such as test file or test // directory. std::string GetTestFileFullPath(const std::string &entry_name); @@ -65,6 +77,47 @@ inline std::unique_ptr ReadPointCloudFromTestFile( return ReadPointCloudFromFile(path).value(); } +#ifdef DRACO_TRANSCODER_SUPPORTED +inline std::unique_ptr ReadSceneFromTestFile( + const std::string &file_name) { + const std::string path = GetTestFileFullPath(file_name); + return ReadSceneFromFile(path).value(); +} + +// Loads geometry specified by a |file_name| that is going to be automatically +// converted to the correct path available to the testing instance. Supported +// geometry types are Mesh and Scene. +template +std::unique_ptr ReadGeometryFromTestFile(const std::string &file_name); + +#endif // DRACO_TRANSCODER_SUPPORTED + +// Utility class for redirection and capture of stderr/stdout. +class CaptureStream { + public: + explicit CaptureStream(std::ostream &stream) + : old_buffer_(stream.rdbuf(buffer_.rdbuf())), stream_(stream) {} + + ~CaptureStream() { Reset(); } + + std::string GetStringAndRelease() { + Reset(); + return buffer_.str(); + } + + void Reset() { + if (old_buffer_) { + stream_.rdbuf(old_buffer_); + old_buffer_ = nullptr; + } + } + + private: + std::ostringstream buffer_; + std::streambuf *old_buffer_ = nullptr; + std::ostream &stream_; +}; + // Evaluates an expression that returns draco::Status. If the status is not OK, // the macro asserts and logs the error message. #define DRACO_ASSERT_OK(expression) \ diff --git a/contrib/draco/src/draco/core/draco_version.h b/contrib/draco/src/draco/core/draco_version.h index 14a504a50..88856447f 100644 --- a/contrib/draco/src/draco/core/draco_version.h +++ b/contrib/draco/src/draco/core/draco_version.h @@ -18,9 +18,7 @@ namespace draco { // Draco version is comprised of ... -static const char kDracoVersion[] = "1.4.1"; - -const char *Version() { return kDracoVersion; } +static const char kDracoVersion[] = "1.5.6"; } // namespace draco diff --git a/contrib/draco/src/draco/core/macros.h b/contrib/draco/src/draco/core/macros.h index 147bbaafc..a31e7c44b 100644 --- a/contrib/draco/src/draco/core/macros.h +++ b/contrib/draco/src/draco/core/macros.h @@ -15,7 +15,8 @@ #ifndef DRACO_CORE_MACROS_H_ #define DRACO_CORE_MACROS_H_ -#include "assert.h" +#include + #include "draco/draco_features.h" #ifdef ANDROID_LOGGING @@ -37,7 +38,7 @@ namespace draco { #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName &) = delete; \ void operator=(const TypeName &) = delete; -#endif +#endif // DISALLOW_COPY_AND_ASSIGN #ifndef FALLTHROUGH_INTENDED #if defined(__clang__) && defined(__has_warning) @@ -46,7 +47,7 @@ namespace draco { #endif #elif defined(__GNUC__) && __GNUC__ >= 7 #define FALLTHROUGH_INTENDED [[gnu::fallthrough]] -#endif +#endif // FALLTHROUGH_INTENDED // If FALLTHROUGH_INTENDED is still not defined, define it. #ifndef FALLTHROUGH_INTENDED @@ -54,7 +55,7 @@ namespace draco { do { \ } while (0) #endif -#endif +#endif // FALLTHROUGH_INTENDED #ifndef LOG #define LOG(...) std::cout @@ -84,12 +85,16 @@ namespace draco { #define DRACO_DCHECK_LE(a, b) #define DRACO_DCHECK_LT(a, b) #define DRACO_DCHECK_NOTNULL(x) -#endif +#endif // DRACO_DEBUG // Helper macros for concatenating macro values. #define DRACO_MACROS_IMPL_CONCAT_INNER_(x, y) x##y #define DRACO_MACROS_IMPL_CONCAT_(x, y) DRACO_MACROS_IMPL_CONCAT_INNER_(x, y) +#define DRACO_MACROS_IMPL_CONCAT_INNER_3_(x, y, z) x##y##z +#define DRACO_MACROS_IMPL_CONCAT_3_(x, y, z) \ + DRACO_MACROS_IMPL_CONCAT_INNER_3_(x, y, z) + // Expand the n-th argument of the macro. Used to select an argument based on // the number of entries in a variadic macro argument. Example usage: // @@ -100,9 +105,9 @@ namespace draco { // #define VARIADIC_MACRO(...) // DRACO_SELECT_NTH_FROM_3(__VA_ARGS__, FUNC_3, FUNC_2, FUNC_1) __VA_ARGS__ // -#define DRACO_SELECT_NTH_FROM_2(_1, _2, NAME) NAME -#define DRACO_SELECT_NTH_FROM_3(_1, _2, _3, NAME) NAME -#define DRACO_SELECT_NTH_FROM_4(_1, _2, _3, _4, NAME) NAME +#define DRACO_SELECT_NTH_FROM_2(_1, _2, NAME, ...) NAME +#define DRACO_SELECT_NTH_FROM_3(_1, _2, _3, NAME, ...) NAME +#define DRACO_SELECT_NTH_FROM_4(_1, _2, _3, _4, NAME, ...) NAME // Macro that converts the Draco bit-stream into one uint16_t number. // Useful mostly when checking version numbers. diff --git a/contrib/draco/src/draco/core/math_utils.h b/contrib/draco/src/draco/core/math_utils.h index 7f382fa34..d7732e55d 100644 --- a/contrib/draco/src/draco/core/math_utils.h +++ b/contrib/draco/src/draco/core/math_utils.h @@ -19,6 +19,8 @@ #include "draco/core/vector_d.h" +namespace draco { + #define DRACO_INCREMENT_MOD(I, M) (((I) == ((M)-1)) ? 0 : ((I) + 1)) // Returns floor(sqrt(x)) where x is an integer number. The main intend of this @@ -52,4 +54,26 @@ inline uint64_t IntSqrt(uint64_t number) { return square_root; } +// Performs the addition in unsigned type to avoid signed integer overflow. Note +// that the result will be the same (for non-overflowing values). +template < + typename DataTypeT, + typename std::enable_if::value && + std::is_signed::value>::type * = nullptr> +inline DataTypeT AddAsUnsigned(DataTypeT a, DataTypeT b) { + typedef typename std::make_unsigned::type DataTypeUT; + return static_cast(static_cast(a) + + static_cast(b)); +} + +template ::value || + !std::is_signed::value>::type * = + nullptr> +inline DataTypeT AddAsUnsigned(DataTypeT a, DataTypeT b) { + return a + b; +} + +} // namespace draco + #endif // DRACO_CORE_MATH_UTILS_H_ diff --git a/contrib/draco/src/draco/core/math_utils_test.cc b/contrib/draco/src/draco/core/math_utils_test.cc index 8c255d046..460a67444 100644 --- a/contrib/draco/src/draco/core/math_utils_test.cc +++ b/contrib/draco/src/draco/core/math_utils_test.cc @@ -5,7 +5,7 @@ #include "draco/core/draco_test_base.h" -using draco::Vector3f; +namespace draco { TEST(MathUtils, Mod) { EXPECT_EQ(DRACO_INCREMENT_MOD(1, 1 << 1), 0); } @@ -20,3 +20,5 @@ TEST(MathUtils, IntSqrt) { ASSERT_EQ(IntSqrt(number), static_cast(floor(std::sqrt(number)))); } } + +} // namespace draco diff --git a/contrib/draco/src/draco/core/options.cc b/contrib/draco/src/draco/core/options.cc index 9b81db489..ceb87cccc 100644 --- a/contrib/draco/src/draco/core/options.cc +++ b/contrib/draco/src/draco/core/options.cc @@ -15,13 +15,12 @@ #include "draco/core/options.h" #include +#include #include #include namespace draco { -Options::Options() {} - void Options::MergeAndReplace(const Options &other_options) { for (const auto &item : other_options.options_) { options_[item.first] = item.second; diff --git a/contrib/draco/src/draco/core/options.h b/contrib/draco/src/draco/core/options.h index 1bc4dc0fb..3f15d13ba 100644 --- a/contrib/draco/src/draco/core/options.h +++ b/contrib/draco/src/draco/core/options.h @@ -19,6 +19,8 @@ #include #include +#include "draco/draco_features.h" + namespace draco { // Class for storing generic options as a pair in a string map. @@ -27,7 +29,8 @@ namespace draco { // data type. class Options { public: - Options(); + Options() = default; + ~Options() = default; // Merges |other_options| on top of the existing options of this instance // replacing all entries that are present in both options instances. @@ -71,8 +74,6 @@ class Options { private: // All entries are internally stored as strings and converted to the desired // return type based on the used Get* method. - // TODO(ostava): Consider adding type safety mechanism that would prevent - // unsafe operations such as a conversion from vector to int. std::map options_; }; diff --git a/contrib/draco/src/draco/core/status.cc b/contrib/draco/src/draco/core/status.cc new file mode 100644 index 000000000..ecb0b536e --- /dev/null +++ b/contrib/draco/src/draco/core/status.cc @@ -0,0 +1,44 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include "draco/core/status.h" + +#include + +namespace draco { + +std::string Status::code_string() const { + switch (code_) { + case Code::OK: + return "OK"; + case Code::DRACO_ERROR: + return "DRACO_ERROR"; + case Code::IO_ERROR: + return "IO_ERROR"; + case Code::INVALID_PARAMETER: + return "INVALID_PARAMETER"; + case Code::UNSUPPORTED_VERSION: + return "UNSUPPORTED_VERSION"; + case Code::UNKNOWN_VERSION: + return "UNKNOWN_VERSION"; + case Code::UNSUPPORTED_FEATURE: + return "UNSUPPORTED_FEATURE"; + } + return "UNKNOWN_STATUS_VALUE"; +} + +std::string Status::code_and_error_string() const { + return code_string() + ": " + error_msg_string(); +} + +} // namespace draco diff --git a/contrib/draco/src/draco/core/status.h b/contrib/draco/src/draco/core/status.h index 449ad8566..fac96046c 100644 --- a/contrib/draco/src/draco/core/status.h +++ b/contrib/draco/src/draco/core/status.h @@ -15,6 +15,7 @@ #ifndef DRACO_CORE_STATUS_H_ #define DRACO_CORE_STATUS_H_ +#include #include namespace draco { @@ -44,6 +45,8 @@ class Status { Code code() const { return code_; } const std::string &error_msg_string() const { return error_msg_; } const char *error_msg() const { return error_msg_.c_str(); } + std::string code_string() const; + std::string code_and_error_string() const; bool operator==(Code code) const { return code == code_; } bool ok() const { return code_ == OK; } @@ -61,6 +64,9 @@ inline std::ostream &operator<<(std::ostream &os, const Status &status) { } inline Status OkStatus() { return Status(Status::OK); } +inline Status ErrorStatus(const std::string &msg) { + return Status(Status::DRACO_ERROR, msg); +} // Evaluates an expression that returns draco::Status. If the status is not OK, // the macro returns the status object. diff --git a/contrib/draco/src/draco/core/status_test.cc b/contrib/draco/src/draco/core/status_test.cc index c1ad4ab30..dc36496d4 100644 --- a/contrib/draco/src/draco/core/status_test.cc +++ b/contrib/draco/src/draco/core/status_test.cc @@ -29,10 +29,17 @@ TEST_F(StatusTest, TestStatusOutput) { // Tests that the Status can be stored in a provided std::ostream. const draco::Status status(draco::Status::DRACO_ERROR, "Error msg."); ASSERT_EQ(status.code(), draco::Status::DRACO_ERROR); + ASSERT_EQ(status.code_string(), "DRACO_ERROR"); std::stringstream str; str << status; ASSERT_EQ(str.str(), "Error msg."); + + const draco::Status status2 = draco::ErrorStatus("Error msg2."); + ASSERT_EQ(status2.code(), draco::Status::DRACO_ERROR); + ASSERT_EQ(status2.error_msg_string(), "Error msg2."); + ASSERT_EQ(status2.code_string(), "DRACO_ERROR"); + ASSERT_EQ(status2.code_and_error_string(), "DRACO_ERROR: Error msg2."); } } // namespace diff --git a/contrib/draco/src/draco/core/vector_d.h b/contrib/draco/src/draco/core/vector_d.h index a3c46a46a..a0ec2dedf 100644 --- a/contrib/draco/src/draco/core/vector_d.h +++ b/contrib/draco/src/draco/core/vector_d.h @@ -34,7 +34,7 @@ class VectorD { typedef ScalarT Scalar; typedef VectorD Self; - // TODO(hemmer): Deprecate. + // TODO(b/199760123): Deprecate. typedef ScalarT CoefficientType; VectorD() { @@ -45,7 +45,7 @@ class VectorD { // The following constructor does not compile in opt mode, which for now led // to the constructors further down, which is not ideal. - // TODO(hemmer): fix constructor below and remove others. + // TODO(b/199760123): Fix constructor below and remove others. // template // explicit VectorD(Args... args) : v_({args...}) {} @@ -111,7 +111,7 @@ class VectorD { Scalar &operator[](int i) { return v_[i]; } const Scalar &operator[](int i) const { return v_[i]; } - // TODO(hemmer): remove. + // TODO(b/199760123): Remove. // Similar to interface of Eigen library. Scalar &operator()(int i) { return v_[i]; } const Scalar &operator()(int i) const { return v_[i]; } diff --git a/contrib/draco/src/draco/core/vector_d_test.cc b/contrib/draco/src/draco/core/vector_d_test.cc index d66128fb1..21c1ca4c5 100644 --- a/contrib/draco/src/draco/core/vector_d_test.cc +++ b/contrib/draco/src/draco/core/vector_d_test.cc @@ -32,16 +32,6 @@ typedef draco::Vector5ui Vector5ui; typedef draco::VectorD Vector3i; typedef draco::VectorD Vector4i; -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(VectorDTest, TestOperators) { { const Vector3f v; @@ -170,56 +160,6 @@ TEST(VectorTest, TestGetNormalizedWithZeroLengthVector) { ASSERT_EQ(normalized[2], 0); } -TEST(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); -} - TEST(VectorDTest, TestCrossProduct3D) { const Vector3i e1(1, 0, 0); const Vector3i e2(0, 1, 0); diff --git a/contrib/draco/src/draco/io/file_reader_factory.cc b/contrib/draco/src/draco/io/file_reader_factory.cc index ac7b09288..a8f15a11f 100644 --- a/contrib/draco/src/draco/io/file_reader_factory.cc +++ b/contrib/draco/src/draco/io/file_reader_factory.cc @@ -1,5 +1,6 @@ #include "draco/io/file_reader_factory.h" +#include #include namespace draco { @@ -38,7 +39,6 @@ std::unique_ptr FileReaderFactory::OpenReader( } return reader; } - FILEREADER_LOG_ERROR("No file reader able to open input"); return nullptr; } diff --git a/contrib/draco/src/draco/io/file_utils.cc b/contrib/draco/src/draco/io/file_utils.cc index f93cbd899..694d259e8 100644 --- a/contrib/draco/src/draco/io/file_utils.cc +++ b/contrib/draco/src/draco/io/file_utils.cc @@ -14,6 +14,8 @@ // #include "draco/io/file_utils.h" +#include + #include "draco/io/file_reader_factory.h" #include "draco/io/file_reader_interface.h" #include "draco/io/file_writer_factory.h" @@ -30,7 +32,7 @@ void SplitPath(const std::string &full_path, std::string *out_folder_path, std::string ReplaceFileExtension(const std::string &in_file_name, const std::string &new_extension) { - const auto pos = in_file_name.find_last_of("."); + const auto pos = in_file_name.find_last_of('.'); if (pos == std::string::npos) { // No extension found. return in_file_name + "." + new_extension; @@ -46,6 +48,22 @@ std::string LowercaseFileExtension(const std::string &filename) { return parser::ToLower(filename.substr(pos + 1)); } +std::string LowercaseMimeTypeExtension(const std::string &mime_type) { + const size_t pos = mime_type.find_last_of('/'); + if (pos == 0 || pos == std::string::npos || pos == mime_type.length() - 1) { + return ""; + } + return parser::ToLower(mime_type.substr(pos + 1)); +} + +std::string RemoveFileExtension(const std::string &filename) { + const size_t pos = filename.find_last_of('.'); + if (pos == 0 || pos == std::string::npos || pos == filename.length() - 1) { + return filename; + } + return filename.substr(0, pos); +} + std::string GetFullPath(const std::string &input_file_relative_path, const std::string &sibling_file_full_path) { const auto pos = sibling_file_full_path.find_last_of("/\\"); @@ -76,6 +94,23 @@ bool ReadFileToBuffer(const std::string &file_name, return file_reader->ReadFileToBuffer(buffer); } +bool ReadFileToString(const std::string &file_name, std::string *contents) { + if (!contents) { + return false; + } + std::unique_ptr file_reader = + FileReaderFactory::OpenReader(file_name); + if (file_reader == nullptr) { + return false; + } + std::vector buffer; + if (!ReadFileToBuffer(file_name, &buffer)) { + return false; + } + contents->assign(buffer.begin(), buffer.end()); + return true; +} + bool WriteBufferToFile(const char *buffer, size_t buffer_size, const std::string &file_name) { std::unique_ptr file_writer = diff --git a/contrib/draco/src/draco/io/file_utils.h b/contrib/draco/src/draco/io/file_utils.h index 4b734e049..7a5fc475b 100644 --- a/contrib/draco/src/draco/io/file_utils.h +++ b/contrib/draco/src/draco/io/file_utils.h @@ -15,6 +15,7 @@ #ifndef DRACO_IO_FILE_UTILS_H_ #define DRACO_IO_FILE_UTILS_H_ +#include #include #include @@ -37,6 +38,13 @@ std::string ReplaceFileExtension(const std::string &in_file_name, // '.' (e.g. Linux hidden files), the first delimiter is ignored. std::string LowercaseFileExtension(const std::string &filename); +// Returns the mime type extension in lowercase if present, else "". Extension +// is defined as the string after the last '/ character. +std::string LowercaseMimeTypeExtension(const std::string &mime_type); + +// Returns the file name without extension. +std::string RemoveFileExtension(const std::string &filename); + // Given a path of the input file |input_file_relative_path| relative to the // parent directory of |sibling_file_full_path|, this function returns full path // to the input file. If |sibling_file_full_path| has no directory, the relative @@ -46,13 +54,18 @@ std::string LowercaseFileExtension(const std::string &filename); std::string GetFullPath(const std::string &input_file_relative_path, const std::string &sibling_file_full_path); -// Convenience method. Uses draco::FileReaderFactory internally. Reads contents +// Convenience methods. Uses draco::FileReaderFactory internally. Reads contents // of file referenced by |file_name| into |buffer| and returns true upon // success. bool ReadFileToBuffer(const std::string &file_name, std::vector *buffer); bool ReadFileToBuffer(const std::string &file_name, std::vector *buffer); +// Convenience method for reading a file into a std::string. Reads contents +// of file referenced by |file_name| into |contents| and returns true upon +// success. +bool ReadFileToString(const std::string &file_name, std::string *contents); + // Convenience method. Uses draco::FileWriterFactory internally. Writes contents // of |buffer| to file referred to by |file_name|. File is overwritten if it // exists. Returns true after successful write. diff --git a/contrib/draco/src/draco/io/file_utils_test.cc b/contrib/draco/src/draco/io/file_utils_test.cc index 4085ff0cd..b55b1e3be 100644 --- a/contrib/draco/src/draco/io/file_utils_test.cc +++ b/contrib/draco/src/draco/io/file_utils_test.cc @@ -14,6 +14,8 @@ // #include "draco/io/file_utils.h" +#include + #include "draco/core/draco_test_base.h" #include "draco/core/draco_test_utils.h" diff --git a/contrib/draco/src/draco/io/file_writer_factory.cc b/contrib/draco/src/draco/io/file_writer_factory.cc index cb6851602..8ffd5400a 100644 --- a/contrib/draco/src/draco/io/file_writer_factory.cc +++ b/contrib/draco/src/draco/io/file_writer_factory.cc @@ -1,5 +1,6 @@ #include "draco/io/file_writer_factory.h" +#include #include namespace draco { diff --git a/contrib/draco/src/draco/io/file_writer_utils.cc b/contrib/draco/src/draco/io/file_writer_utils.cc index bcadccfc6..3dab80bbf 100644 --- a/contrib/draco/src/draco/io/file_writer_utils.cc +++ b/contrib/draco/src/draco/io/file_writer_utils.cc @@ -7,6 +7,10 @@ #include "draco/draco_features.h" +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "ghc/filesystem.hpp" +#endif // DRACO_TRANSCODER_SUPPORTED + namespace draco { void SplitPathPrivate(const std::string &full_path, @@ -30,8 +34,18 @@ void SplitPathPrivate(const std::string &full_path, } } -bool DirectoryExists(const std::string &path) { +bool DirectoryExists(const std::string &path_arg) { struct stat path_stat; + std::string path = path_arg; + +#if defined(_WIN32) && not defined(__MINGW32__) + // Avoid a silly windows issue: stat() will fail on a drive letter missing the + // trailing slash. + if (path.size() > 0 && path[path.size()] != '\\' && + path[path.size()] != '/') { + path.append("\\"); + } +#endif // Check if |path| exists. if (stat(path.c_str(), &path_stat) != 0) { @@ -50,7 +64,12 @@ bool CheckAndCreatePathForFile(const std::string &filename) { std::string basename; SplitPathPrivate(filename, &path, &basename); +#ifdef DRACO_TRANSCODER_SUPPORTED + const ghc::filesystem::path ghc_path(path); + ghc::filesystem::create_directories(ghc_path); +#endif // DRACO_TRANSCODER_SUPPORTED const bool directory_exists = DirectoryExists(path); + return directory_exists; } diff --git a/contrib/draco/src/draco/io/file_writer_utils_test.cc b/contrib/draco/src/draco/io/file_writer_utils_test.cc new file mode 100644 index 000000000..14a3013e0 --- /dev/null +++ b/contrib/draco/src/draco/io/file_writer_utils_test.cc @@ -0,0 +1,49 @@ +#include "draco/io/file_writer_utils.h" + +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace draco { +namespace { + +TEST(FileWriterUtilsTest, SplitPathPrivateNonWindows) { + const std::string test_path = "/path/to/file"; + std::string directory; + std::string file; + SplitPathPrivate(test_path, &directory, &file); + ASSERT_EQ(directory, "/path/to"); + ASSERT_EQ(file, "file"); +} + +TEST(FileWriterUtilsTest, SplitPathPrivateWindows) { + const std::string test_path = "C:\\path\\to\\file"; + std::string directory; + std::string file; + SplitPathPrivate(test_path, &directory, &file); + ASSERT_EQ(directory, "C:\\path\\to"); + ASSERT_EQ(file, "file"); +} + +TEST(FileWriterUtilsTest, DirectoryExistsTest) { + ASSERT_TRUE(DirectoryExists(GetTestTempDir())); + ASSERT_FALSE(DirectoryExists("fake/test/subdir")); +} + +#ifdef DRACO_TRANSCODER_SUPPORTED +TEST(FileWriterUtilsTest, CheckAndCreatePathForFileTest) { + const std::string fake_file = "fake.file"; + const std::string fake_file_subdir = "a/few/dirs/down"; + const std::string test_temp_dir = GetTestTempDir(); + const std::string fake_file_directory = + test_temp_dir + "/" + fake_file_subdir; + const std::string fake_full_path = + test_temp_dir + "/" + fake_file_subdir + "/" + fake_file; + ASSERT_TRUE(CheckAndCreatePathForFile(fake_full_path)); + ASSERT_TRUE(DirectoryExists(fake_file_directory)); +} +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace +} // namespace draco diff --git a/contrib/draco/src/draco/io/gltf_decoder.cc b/contrib/draco/src/draco/io/gltf_decoder.cc new file mode 100644 index 000000000..521b7524f --- /dev/null +++ b/contrib/draco/src/draco/io/gltf_decoder.cc @@ -0,0 +1,2893 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/gltf_decoder.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include +#include +#include +#include +#include + +#include "draco/core/draco_types.h" +#include "draco/core/hash_utils.h" +#include "draco/core/status.h" +#include "draco/core/status_or.h" +#include "draco/io/tiny_gltf_utils.h" +#include "draco/material/material_library.h" +#include "draco/mesh/mesh.h" +#include "draco/mesh/mesh_features.h" +#include "draco/mesh/triangle_soup_mesh_builder.h" +#include "draco/metadata/property_table.h" +#include "draco/point_cloud/point_cloud_builder.h" +#include "draco/scene/scene_indices.h" +#include "draco/texture/source_image.h" +#include "draco/texture/texture_utils.h" + +namespace draco { + +namespace { +draco::DataType GltfComponentTypeToDracoType(int component_type) { + switch (component_type) { + case TINYGLTF_COMPONENT_TYPE_BYTE: + return DT_INT8; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: + return DT_UINT8; + case TINYGLTF_COMPONENT_TYPE_SHORT: + return DT_INT16; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: + return DT_UINT16; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: + return DT_UINT32; + case TINYGLTF_COMPONENT_TYPE_FLOAT: + return DT_FLOAT32; + } + return DT_INVALID; +} + +GeometryAttribute::Type GltfAttributeToDracoAttribute( + const std::string attribute_name) { + if (attribute_name == "POSITION") { + return GeometryAttribute::POSITION; + } else if (attribute_name == "NORMAL") { + return GeometryAttribute::NORMAL; + } else if (attribute_name == "TEXCOORD_0") { + return GeometryAttribute::TEX_COORD; + } else if (attribute_name == "TEXCOORD_1") { + return GeometryAttribute::TEX_COORD; + } else if (attribute_name == "TANGENT") { + return GeometryAttribute::TANGENT; + } else if (attribute_name == "COLOR_0") { + return GeometryAttribute::COLOR; + } else if (attribute_name == "JOINTS_0") { + return GeometryAttribute::JOINTS; + } else if (attribute_name == "WEIGHTS_0") { + return GeometryAttribute::WEIGHTS; + } else if (attribute_name.rfind("_FEATURE_ID_") == 0) { + // Feature ID attribute like _FEATURE_ID_5 from EXT_mesh_features extension. + return GeometryAttribute::GENERIC; + } + return GeometryAttribute::INVALID; +} + +StatusOr TinyGltfToDracoAxisWrappingMode( + int wrap_mode) { + switch (wrap_mode) { + case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE: + return TextureMap::CLAMP_TO_EDGE; + case TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT: + return TextureMap::MIRRORED_REPEAT; + case TINYGLTF_TEXTURE_WRAP_REPEAT: + return TextureMap::REPEAT; + default: + return Status(Status::UNSUPPORTED_FEATURE, "Unsupported wrapping mode."); + } +} + +StatusOr TinyGltfToDracoFilterType(int filter_type) { + switch (filter_type) { + case -1: + return TextureMap::UNSPECIFIED; + case TINYGLTF_TEXTURE_FILTER_NEAREST: + return TextureMap::NEAREST; + case TINYGLTF_TEXTURE_FILTER_LINEAR: + return TextureMap::LINEAR; + case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST: + return TextureMap::NEAREST_MIPMAP_NEAREST; + case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST: + return TextureMap::LINEAR_MIPMAP_NEAREST; + case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR: + return TextureMap::NEAREST_MIPMAP_LINEAR; + case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR: + return TextureMap::LINEAR_MIPMAP_LINEAR; + default: + return Status(Status::DRACO_ERROR, "Unsupported texture filter type."); + } +} + +StatusOr> CopyDataAsUint32( + const tinygltf::Model &model, const tinygltf::Accessor &accessor) { + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_BYTE) { + return Status(Status::DRACO_ERROR, "Byte cannot be converted to Uint32."); + } + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_SHORT) { + return Status(Status::DRACO_ERROR, "Short cannot be converted to Uint32."); + } + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_INT) { + return Status(Status::DRACO_ERROR, "Int cannot be converted to Uint32."); + } + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { + return Status(Status::DRACO_ERROR, "Float cannot be converted to Uint32."); + } + if (accessor.componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) { + return Status(Status::DRACO_ERROR, "Double cannot be converted to Uint32."); + } + if (accessor.bufferView < 0) { + return Status(Status::DRACO_ERROR, + "Error CopyDataAsUint32() bufferView < 0."); + } + + const tinygltf::BufferView &buffer_view = + model.bufferViews[accessor.bufferView]; + if (buffer_view.buffer < 0) { + return Status(Status::DRACO_ERROR, "Error CopyDataAsUint32() buffer < 0."); + } + + const tinygltf::Buffer &buffer = model.buffers[buffer_view.buffer]; + + const uint8_t *const data_start = + buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset; + const int byte_stride = accessor.ByteStride(buffer_view); + const int component_size = + tinygltf::GetComponentSizeInBytes(accessor.componentType); + const int num_components = + TinyGltfUtils::GetNumComponentsForType(accessor.type); + const int num_elements = accessor.count * num_components; + + std::vector output; + output.resize(num_elements); + + int out_index = 0; + const uint8_t *data = data_start; + for (int i = 0; i < accessor.count; ++i) { + for (int c = 0; c < num_components; ++c) { + uint32_t value = 0; + memcpy(&value, data + (c * component_size), component_size); + output[out_index++] = value; + } + + data += byte_stride; + } + + return output; +} + +// Specialization for arithmetic types. +template < + typename TypeT, + typename std::enable_if::value>::type * = nullptr> +StatusOr> CopyDataAs(const tinygltf::Model &model, + const tinygltf::Accessor &accessor) { + if (std::is_same::value) { + if (TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE != accessor.componentType) { + return ErrorStatus("Accessor data cannot be converted to Uint8."); + } + } else if (std::is_same::value) { + if (TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE != accessor.componentType && + TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT != accessor.componentType) { + return ErrorStatus("Accessor data cannot be converted to Uint16."); + } + } else if (std::is_same::value) { + if (TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE != accessor.componentType && + TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT != accessor.componentType && + TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT != accessor.componentType) { + return ErrorStatus("Accessor data cannot be converted to Uint32."); + } + } else if (std::is_same::value) { + if (TINYGLTF_COMPONENT_TYPE_FLOAT != accessor.componentType) { + return ErrorStatus("Accessor data cannot be converted to Float."); + } + } + if (accessor.bufferView < 0) { + return Status(Status::DRACO_ERROR, "Error CopyDataAs() bufferView < 0."); + } + + const tinygltf::BufferView &buffer_view = + model.bufferViews[accessor.bufferView]; + if (buffer_view.buffer < 0) { + return Status(Status::DRACO_ERROR, "Error CopyDataAs() buffer < 0."); + } + + const tinygltf::Buffer &buffer = model.buffers[buffer_view.buffer]; + + const uint8_t *const data_start = + buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset; + const int byte_stride = accessor.ByteStride(buffer_view); + const int component_size = + tinygltf::GetComponentSizeInBytes(accessor.componentType); + + std::vector output; + output.resize(accessor.count); + + const int num_components = + TinyGltfUtils::GetNumComponentsForType(accessor.type); + int out_index = 0; + const uint8_t *data = data_start; + for (int i = 0; i < accessor.count; ++i) { + for (int c = 0; c < num_components; ++c) { + TypeT value = 0; + memcpy(&value, data + (c * component_size), component_size); + output[out_index++] = value; + } + data += byte_stride; + } + return output; +} + +// Specialization for remaining types is used for draco::VectorD. +template ::value>::type * = + nullptr> +StatusOr> CopyDataAs(const tinygltf::Model &model, + const tinygltf::Accessor &accessor) { + const int num_components = + TinyGltfUtils::GetNumComponentsForType(accessor.type); + if (num_components != TypeT::dimension) { + return Status(Status::DRACO_ERROR, + "Dimension does not equal num components."); + } + if (accessor.bufferView < 0) { + return Status(Status::DRACO_ERROR, "Error CopyDataAs() bufferView < 0."); + } + + const tinygltf::BufferView &buffer_view = + model.bufferViews[accessor.bufferView]; + if (buffer_view.buffer < 0) { + return Status(Status::DRACO_ERROR, "Error CopyDataAs() buffer < 0."); + } + + const tinygltf::Buffer &buffer = model.buffers[buffer_view.buffer]; + + const uint8_t *const data_start = + buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset; + const int byte_stride = accessor.ByteStride(buffer_view); + const int component_size = + tinygltf::GetComponentSizeInBytes(accessor.componentType); + + std::vector output; + output.resize(accessor.count); + + const uint8_t *data = data_start; + for (int i = 0; i < accessor.count; ++i) { + TypeT values; + for (int c = 0; c < num_components; ++c) { + memcpy(&values[c], data + (c * component_size), component_size); + } + output[i] = values; + data += byte_stride; + } + return output; +} + +// Copies the data referenced from |buffer_view_id| into |data|. Currently only +// supports a byte stride of 0. I.e. tightly packed. +Status CopyDataFromBufferView(const tinygltf::Model &model, int buffer_view_id, + std::vector *data) { + if (buffer_view_id < 0) { + return ErrorStatus("Error CopyDataFromBufferView() bufferView < 0."); + } + const tinygltf::BufferView &buffer_view = model.bufferViews[buffer_view_id]; + if (buffer_view.buffer < 0) { + return ErrorStatus("Error CopyDataFromBufferView() buffer < 0."); + } + if (buffer_view.byteStride != 0) { + return Status(Status::DRACO_ERROR, "Error buffer view byteStride != 0."); + } + + const tinygltf::Buffer &buffer = model.buffers[buffer_view.buffer]; + const uint8_t *const data_start = buffer.data.data() + buffer_view.byteOffset; + + data->resize(buffer_view.byteLength); + memcpy(&(*data)[0], data_start, buffer_view.byteLength); + return OkStatus(); +} + +// Returns a SourceImage created from |image|. +StatusOr> GetSourceImage( + const tinygltf::Model &model, const tinygltf::Image &image, + const Texture &texture) { + std::unique_ptr source_image(new SourceImage()); + // If the image is in an external file then the buffer view is < 0. + if (image.bufferView >= 0) { + DRACO_RETURN_IF_ERROR(CopyDataFromBufferView( + model, image.bufferView, &source_image->MutableEncodedData())); + } + source_image->set_filename(image.uri); + source_image->set_mime_type(image.mimeType); + + return source_image; +} + +std::unique_ptr GetNodeTrsMatrix(const tinygltf::Node &node) { + std::unique_ptr trsm(new TrsMatrix()); + if (node.matrix.size() == 16) { + Eigen::Matrix4d transformation; + // clang-format off + // |node.matrix| is in the column-major order. + transformation << + node.matrix[0], node.matrix[4], node.matrix[8], node.matrix[12], + node.matrix[1], node.matrix[5], node.matrix[9], node.matrix[13], + node.matrix[2], node.matrix[6], node.matrix[10], node.matrix[14], + node.matrix[3], node.matrix[7], node.matrix[11], node.matrix[15]; + // clang-format on + if (transformation != Eigen::Matrix4d::Identity()) { + trsm->SetMatrix(transformation); + } + } + + if (node.translation.size() == 3) { + const Eigen::Vector3d default_translation(0.0, 0.0, 0.0); + const Eigen::Vector3d node_translation( + node.translation[0], node.translation[1], node.translation[2]); + if (node_translation != default_translation) { + trsm->SetTranslation(node_translation); + } + } + if (node.scale.size() == 3) { + const Eigen::Vector3d default_scale(1.0, 1.0, 1.0); + const Eigen::Vector3d node_scale(node.scale[0], node.scale[1], + node.scale[2]); + if (node_scale != default_scale) { + trsm->SetScale(node_scale); + } + } + if (node.rotation.size() == 4) { + // Eigen quaternion is defined in (w, x, y, z) vs glTF that uses + // (x, y, z, w). + const Eigen::Quaterniond default_rotation(0.0, 0.0, 0.0, 1.0); + const Eigen::Quaterniond node_rotation(node.rotation[3], node.rotation[0], + node.rotation[1], node.rotation[2]); + if (node_rotation != default_rotation) { + trsm->SetRotation(node_rotation); + } + } + + return trsm; +} + +Eigen::Matrix4d UpdateMatrixForNormals( + const Eigen::Matrix4d &transform_matrix) { + Eigen::Matrix3d mat3x3; + // clang-format off + mat3x3 << + transform_matrix(0, 0), transform_matrix(0, 1), transform_matrix(0, 2), + transform_matrix(1, 0), transform_matrix(1, 1), transform_matrix(1, 2), + transform_matrix(2, 0), transform_matrix(2, 1), transform_matrix(2, 2); + // clang-format on + + mat3x3 = mat3x3.inverse().transpose(); + Eigen::Matrix4d mat4x4; + // clang-format off + mat4x4 << mat3x3(0, 0), mat3x3(0, 1), mat3x3(0, 2), 0.0, + mat3x3(1, 0), mat3x3(1, 1), mat3x3(1, 2), 0.0, + mat3x3(2, 0), mat3x3(2, 1), mat3x3(2, 2), 0.0, + 0.0, 0.0, 0.0, 1.0; + // clang-format on + return mat4x4; +} + +float Determinant(const Eigen::Matrix4d &transform_matrix) { + Eigen::Matrix3d mat3x3; + // clang-format off + mat3x3 << + transform_matrix(0, 0), transform_matrix(0, 1), transform_matrix(0, 2), + transform_matrix(1, 0), transform_matrix(1, 1), transform_matrix(1, 2), + transform_matrix(2, 0), transform_matrix(2, 1), transform_matrix(2, 2); + // clang-format on + return mat3x3.determinant(); +} + +bool FileExists(const std::string &filepath, void * /*user_data*/) { + return GetFileSize(filepath) != 0; +} + +bool ReadWholeFile(std::vector *out, std::string *err, + const std::string &filepath, void *user_data) { + if (!ReadFileToBuffer(filepath, out)) { + if (err) { + *err = "Unable to read: " + filepath; + } + return false; + } + if (user_data) { + auto *files_vector = + reinterpret_cast *>(user_data); + files_vector->push_back(filepath); + } + return true; +} + +bool WriteWholeFile(std::string * /*err*/, const std::string &filepath, + const std::vector &contents, + void * /*user_data*/) { + return WriteBufferToFile(contents.data(), contents.size(), filepath); +} + +} // namespace + +GltfDecoder::GltfDecoder() + : next_face_id_(0), + next_point_id_(0), + total_face_indices_count_(0), + total_point_indices_count_(0), + material_att_id_(-1) {} + +StatusOr> GltfDecoder::DecodeFromFile( + const std::string &file_name) { + return DecodeFromFile(file_name, nullptr); +} + +StatusOr> GltfDecoder::DecodeFromFile( + const std::string &file_name, std::vector *mesh_files) { + DRACO_RETURN_IF_ERROR(LoadFile(file_name, mesh_files)); + return BuildMesh(); +} + +StatusOr> GltfDecoder::DecodeFromBuffer( + DecoderBuffer *buffer) { + DRACO_RETURN_IF_ERROR(LoadBuffer(*buffer)); + return BuildMesh(); +} + +StatusOr> GltfDecoder::DecodeFromFileToScene( + const std::string &file_name) { + return DecodeFromFileToScene(file_name, nullptr); +} + +StatusOr> GltfDecoder::DecodeFromFileToScene( + const std::string &file_name, std::vector *scene_files) { + DRACO_RETURN_IF_ERROR(LoadFile(file_name, scene_files)); + scene_ = std::unique_ptr(new Scene()); + DRACO_RETURN_IF_ERROR(DecodeGltfToScene()); + return std::move(scene_); +} + +StatusOr> GltfDecoder::DecodeFromBufferToScene( + DecoderBuffer *buffer) { + DRACO_RETURN_IF_ERROR(LoadBuffer(*buffer)); + scene_ = std::unique_ptr(new Scene()); + DRACO_RETURN_IF_ERROR(DecodeGltfToScene()); + return std::move(scene_); +} + +Status GltfDecoder::LoadFile(const std::string &file_name, + std::vector *input_files) { + const std::string extension = LowercaseFileExtension(file_name); + tinygltf::TinyGLTF loader; + std::string err; + std::string warn; + + const tinygltf::FsCallbacks fs_callbacks = { + &FileExists, + // TinyGLTF's ExpandFilePath does not do filesystem i/o, so it's safe to + // use in all environments. + &tinygltf::ExpandFilePath, &ReadWholeFile, &WriteWholeFile, + reinterpret_cast(input_files)}; + + loader.SetFsCallbacks(fs_callbacks); + + if (extension == "glb") { + if (!loader.LoadBinaryFromFile(&gltf_model_, &err, &warn, file_name)) { + return Status(Status::DRACO_ERROR, + "TinyGLTF failed to load glb file: " + err); + } + } else if (extension == "gltf") { + if (!loader.LoadASCIIFromFile(&gltf_model_, &err, &warn, file_name)) { + return Status(Status::DRACO_ERROR, + "TinyGLTF failed to load glTF file: " + err); + } + } else { + return Status(Status::DRACO_ERROR, "Unknown input file extension."); + } + DRACO_RETURN_IF_ERROR(CheckUnsupportedFeatures()); + input_file_name_ = file_name; + return OkStatus(); +} + +Status GltfDecoder::LoadBuffer(const DecoderBuffer &buffer) { + tinygltf::TinyGLTF loader; + std::string err; + std::string warn; + + if (!loader.LoadBinaryFromMemory( + &gltf_model_, &err, &warn, + reinterpret_cast(buffer.data_head()), + buffer.remaining_size())) { + return Status(Status::DRACO_ERROR, + "TinyGLTF failed to load glb buffer: " + err); + } + DRACO_RETURN_IF_ERROR(CheckUnsupportedFeatures()); + input_file_name_.clear(); + return OkStatus(); +} + +StatusOr> GltfDecoder::BuildMesh() { + DRACO_RETURN_IF_ERROR(GatherAttributeAndMaterialStats()); + if (total_face_indices_count_ > 0 && total_point_indices_count_ > 0) { + return ErrorStatus( + "Decoding to mesh can't handle triangle and point primitives at the " + "same time."); + } + if (total_face_indices_count_ > 0) { + mb_.Start(total_face_indices_count_ / 3); + DRACO_RETURN_IF_ERROR(AddAttributesToDracoMesh(&mb_)); + } else { + pb_.Start(total_point_indices_count_); + DRACO_RETURN_IF_ERROR(AddAttributesToDracoMesh(&pb_)); + } + + for (const tinygltf::Scene &scene : gltf_model_.scenes) { + for (int i = 0; i < scene.nodes.size(); ++i) { + const Eigen::Matrix4d parent_matrix = Eigen::Matrix4d::Identity(); + DRACO_RETURN_IF_ERROR(DecodeNode(scene.nodes[i], parent_matrix)); + } + } + DRACO_ASSIGN_OR_RETURN( + std::unique_ptr mesh, + BuildMeshFromBuilder(total_face_indices_count_ > 0, &mb_, &pb_)); + + DRACO_RETURN_IF_ERROR(CopyTextures(mesh.get())); + SetAttributePropertiesOnDracoMesh(mesh.get()); + DRACO_RETURN_IF_ERROR(AddMaterialsToDracoMesh(mesh.get())); + DRACO_RETURN_IF_ERROR(AddMeshFeaturesToDracoMesh(mesh.get())); + DRACO_RETURN_IF_ERROR(AddStructuralMetadataToGeometry(mesh.get())); + MoveNonMaterialTextures(mesh.get()); + return mesh; +} + +Status GltfDecoder::AddMeshFeaturesToDracoMesh(Mesh *mesh) { + for (const tinygltf::Scene &scene : gltf_model_.scenes) { + for (int i = 0; i < scene.nodes.size(); ++i) { + DRACO_RETURN_IF_ERROR(AddMeshFeaturesToDracoMesh(scene.nodes[i], mesh)); + } + } + return OkStatus(); +} + +Status GltfDecoder::AddMeshFeaturesToDracoMesh(int node_index, Mesh *mesh) { + const tinygltf::Node &node = gltf_model_.nodes[node_index]; + if (node.mesh >= 0) { + const tinygltf::Mesh &gltf_mesh = gltf_model_.meshes[node.mesh]; + for (const auto &primitive : gltf_mesh.primitives) { + // Decode mesh feature ID sets if present in this primitive. + DRACO_RETURN_IF_ERROR(DecodeMeshFeatures( + primitive, &mesh->GetMaterialLibrary().MutableTextureLibrary(), + mesh)); + } + } + for (int i = 0; i < node.children.size(); ++i) { + DRACO_RETURN_IF_ERROR(AddMeshFeaturesToDracoMesh(node.children[i], mesh)); + } + return OkStatus(); +} + +Status GltfDecoder::CheckUnsupportedFeatures() { + // Check for morph targets. + for (const auto &mesh : gltf_model_.meshes) { + for (const auto &primitive : mesh.primitives) { + if (!primitive.targets.empty()) { + return Status(Status::UNSUPPORTED_FEATURE, + "Morph targets are unsupported."); + } + } + } + + // Check for sparse accessors. + for (const auto &accessor : gltf_model_.accessors) { + if (accessor.sparse.isSparse) { + return Status(Status::UNSUPPORTED_FEATURE, + "Sparse accessors are unsupported."); + } + } + + // Check for extensions. + for (const auto &extension : gltf_model_.extensionsRequired) { + if (extension != "KHR_materials_unlit" && + extension != "KHR_texture_transform" && + extension != "KHR_draco_mesh_compression") { + return Status(Status::UNSUPPORTED_FEATURE, + extension + " is unsupported."); + } + } + return OkStatus(); +} + +Status GltfDecoder::DecodeNode(int node_index, + const Eigen::Matrix4d &parent_matrix) { + const tinygltf::Node &node = gltf_model_.nodes[node_index]; + const std::unique_ptr trsm = GetNodeTrsMatrix(node); + const Eigen::Matrix4d node_matrix = + parent_matrix * trsm->ComputeTransformationMatrix(); + + if (node.mesh >= 0) { + const tinygltf::Mesh &mesh = gltf_model_.meshes[node.mesh]; + for (const auto &primitive : mesh.primitives) { + DRACO_RETURN_IF_ERROR(DecodePrimitive(primitive, node_matrix)); + } + } + for (int i = 0; i < node.children.size(); ++i) { + DRACO_RETURN_IF_ERROR(DecodeNode(node.children[i], node_matrix)); + } + return OkStatus(); +} + +StatusOr GltfDecoder::DecodePrimitiveAttributeCount( + const tinygltf::Primitive &primitive) const { + // Use the first primitive attribute as all attributes have the same entry + // count according to glTF 2.0 spec. + if (primitive.attributes.empty()) { + return Status(Status::DRACO_ERROR, "Primitive has no attributes."); + } + const tinygltf::Accessor &accessor = + gltf_model_.accessors[primitive.attributes.begin()->second]; + return accessor.count; +} + +StatusOr GltfDecoder::DecodePrimitiveIndicesCount( + const tinygltf::Primitive &primitive) const { + if (primitive.indices < 0) { + // Primitive has implicit indices [0, 1, 2, 3, ...]. Determine indices count + // based on entry count of a primitive attribute. + return DecodePrimitiveAttributeCount(primitive); + } + const tinygltf::Accessor &indices = gltf_model_.accessors[primitive.indices]; + return indices.count; +} + +StatusOr> GltfDecoder::DecodePrimitiveIndices( + const tinygltf::Primitive &primitive) const { + std::vector indices_data; + if (primitive.indices < 0) { + // Primitive has implicit indices [0, 1, 2, 3, ...]. Create indices based on + // entry count of a primitive attribute. + DRACO_ASSIGN_OR_RETURN(const int num_vertices, + DecodePrimitiveAttributeCount(primitive)); + indices_data.reserve(num_vertices); + for (int i = 0; i < num_vertices; i++) { + indices_data.push_back(i); + } + } else { + // Get indices from the primitive's indices property. + const tinygltf::Accessor &indices = + gltf_model_.accessors[primitive.indices]; + if (indices.count <= 0) { + return Status(Status::DRACO_ERROR, "Could not convert indices."); + } + DRACO_ASSIGN_OR_RETURN(indices_data, + CopyDataAsUint32(gltf_model_, indices)); + } + return indices_data; +} + +Status GltfDecoder::DecodePrimitive(const tinygltf::Primitive &primitive, + const Eigen::Matrix4d &transform_matrix) { + if (primitive.mode != TINYGLTF_MODE_TRIANGLES && + primitive.mode != TINYGLTF_MODE_POINTS) { + return Status(Status::DRACO_ERROR, + "Primitive does not contain triangles or points."); + } + + // Store the transformation scale of this primitive loading as draco::Mesh. + if (scene_ == nullptr) { + // TODO(vytyaz): Do something for non-uniform scaling. + const float scale = transform_matrix.col(0).norm(); + gltf_primitive_material_to_scales_[primitive.material].push_back(scale); + } + + // Handle indices first. + DRACO_ASSIGN_OR_RETURN(const std::vector indices_data, + DecodePrimitiveIndices(primitive)); + const int number_of_faces = indices_data.size() / 3; + const int number_of_points = indices_data.size(); + + for (const auto &attribute : primitive.attributes) { + const tinygltf::Accessor &accessor = + gltf_model_.accessors[attribute.second]; + + const int att_id = + attribute_name_to_draco_mesh_attribute_id_[attribute.first]; + if (att_id == -1) { + continue; + } + + if (primitive.mode == TINYGLTF_MODE_TRIANGLES) { + DRACO_RETURN_IF_ERROR(AddAttributeValuesToBuilder( + attribute.first, accessor, indices_data, att_id, number_of_faces, + transform_matrix, &mb_)); + } else { + DRACO_RETURN_IF_ERROR(AddAttributeValuesToBuilder( + attribute.first, accessor, indices_data, att_id, number_of_points, + transform_matrix, &pb_)); + } + } + + // Add the material data only if there is more than one material. + if (gltf_primitive_material_to_draco_material_.size() > 1) { + const int material_index = primitive.material; + const auto it = + gltf_primitive_material_to_draco_material_.find(material_index); + if (it != gltf_primitive_material_to_draco_material_.end()) { + if (primitive.mode == TINYGLTF_MODE_TRIANGLES) { + DRACO_RETURN_IF_ERROR( + AddMaterialDataToBuilder(it->second, number_of_faces, &mb_)); + } else { + DRACO_RETURN_IF_ERROR( + AddMaterialDataToBuilder(it->second, number_of_points, &pb_)); + } + } + } + + next_face_id_ += number_of_faces; + next_point_id_ += number_of_points; + return OkStatus(); +} + +Status GltfDecoder::NodeGatherAttributeAndMaterialStats( + const tinygltf::Node &node) { + if (node.mesh >= 0) { + const tinygltf::Mesh &mesh = gltf_model_.meshes[node.mesh]; + for (const auto &primitive : mesh.primitives) { + DRACO_RETURN_IF_ERROR(AccumulatePrimitiveStats(primitive)); + + const auto it = + gltf_primitive_material_to_draco_material_.find(primitive.material); + if (it == gltf_primitive_material_to_draco_material_.end()) { + gltf_primitive_material_to_draco_material_[primitive.material] = + gltf_primitive_material_to_draco_material_.size(); + } + } + } + for (int i = 0; i < node.children.size(); ++i) { + const tinygltf::Node &child = gltf_model_.nodes[node.children[i]]; + DRACO_RETURN_IF_ERROR(NodeGatherAttributeAndMaterialStats(child)); + } + + return OkStatus(); +} + +Status GltfDecoder::GatherAttributeAndMaterialStats() { + for (const auto &scene : gltf_model_.scenes) { + for (int i = 0; i < scene.nodes.size(); ++i) { + const tinygltf::Node &node = gltf_model_.nodes[scene.nodes[i]]; + DRACO_RETURN_IF_ERROR(NodeGatherAttributeAndMaterialStats(node)); + } + } + return OkStatus(); +} + +void GltfDecoder::SumAttributeStats(const std::string &attribute_name, + int count) { + // We know that there must be a valid entry for |attribute_name| at this time. + mesh_attribute_data_[attribute_name].total_attribute_counts += count; +} + +Status GltfDecoder::CheckTypes(const std::string &attribute_name, + int component_type, int type, bool normalized) { + auto it_mad = mesh_attribute_data_.find(attribute_name); + + if (it_mad == mesh_attribute_data_.end()) { + MeshAttributeData mad; + mad.component_type = component_type; + mad.attribute_type = type; + mad.normalized = normalized; + mesh_attribute_data_[attribute_name] = mad; + return OkStatus(); + } + if (it_mad->second.component_type != component_type) { + return Status( + Status::DRACO_ERROR, + attribute_name + " attribute component type does not match previous."); + } + if (it_mad->second.attribute_type != type) { + return Status(Status::DRACO_ERROR, + attribute_name + " attribute type does not match previous."); + } + if (it_mad->second.normalized != normalized) { + return Status( + Status::DRACO_ERROR, + attribute_name + + " attribute normalized property does not match previous."); + } + + return OkStatus(); +} + +Status GltfDecoder::AccumulatePrimitiveStats( + const tinygltf::Primitive &primitive) { + DRACO_ASSIGN_OR_RETURN(const int indices_count, + DecodePrimitiveIndicesCount(primitive)); + if (primitive.mode == TINYGLTF_MODE_TRIANGLES) { + total_face_indices_count_ += indices_count; + } else if (primitive.mode == TINYGLTF_MODE_POINTS) { + total_point_indices_count_ += indices_count; + } else { + return ErrorStatus("Unsupported primitive indices mode."); + } + + for (const auto &attribute : primitive.attributes) { + if (attribute.second >= gltf_model_.accessors.size()) { + return ErrorStatus("Invalid accessor."); + } + const tinygltf::Accessor &accessor = + gltf_model_.accessors[attribute.second]; + + DRACO_RETURN_IF_ERROR(CheckTypes(attribute.first, accessor.componentType, + accessor.type, accessor.normalized)); + SumAttributeStats(attribute.first, accessor.count); + } + return OkStatus(); +} + +template +Status GltfDecoder::AddAttributesToDracoMesh(BuilderT *builder) { + for (const auto &attribute : mesh_attribute_data_) { + const GeometryAttribute::Type draco_att_type = + GltfAttributeToDracoAttribute(attribute.first); + if (draco_att_type == GeometryAttribute::INVALID) { + // Map an invalid attribute to attribute id -1 that will be ignored and + // not included in the Draco mesh. + attribute_name_to_draco_mesh_attribute_id_[attribute.first] = -1; + continue; + } + DRACO_ASSIGN_OR_RETURN( + const int att_id, + AddAttribute(draco_att_type, attribute.second.component_type, + attribute.second.attribute_type, builder)); + attribute_name_to_draco_mesh_attribute_id_[attribute.first] = att_id; + } + + // Add the material attribute. + if (gltf_model_.materials.size() > 1) { + draco::DataType component_type = DT_UINT32; + if (gltf_model_.materials.size() < 256) { + component_type = DT_UINT8; + } else if (gltf_model_.materials.size() < (1 << 16)) { + component_type = DT_UINT16; + } + material_att_id_ = + builder->AddAttribute(GeometryAttribute::MATERIAL, 1, component_type); + } + + return OkStatus(); +} + +template +Status GltfDecoder::AddAttributeValuesToBuilder( + const std::string &attribute_name, const tinygltf::Accessor &accessor, + const std::vector &indices_data, int att_id, + int number_of_elements, const Eigen::Matrix4d &transform_matrix, + BuilderT *builder) { + const bool reverse_winding = Determinant(transform_matrix) < 0; + if (attribute_name == "TEXCOORD_0" || attribute_name == "TEXCOORD_1") { + DRACO_RETURN_IF_ERROR(AddTexCoordToBuilder(accessor, indices_data, att_id, + number_of_elements, + reverse_winding, builder)); + } else if (attribute_name == "TANGENT") { + const Eigen::Matrix4d matrix = UpdateMatrixForNormals(transform_matrix); + DRACO_RETURN_IF_ERROR(AddTangentToBuilder(accessor, indices_data, att_id, + number_of_elements, matrix, + reverse_winding, builder)); + } else if (attribute_name == "POSITION" || attribute_name == "NORMAL") { + const Eigen::Matrix4d matrix = + (attribute_name == "NORMAL") ? UpdateMatrixForNormals(transform_matrix) + : transform_matrix; + const bool normalize = (attribute_name == "NORMAL"); + DRACO_RETURN_IF_ERROR(AddTransformedDataToBuilder( + accessor, indices_data, att_id, number_of_elements, matrix, normalize, + reverse_winding, builder)); + } else if (attribute_name.rfind("_FEATURE_ID_") == 0) { + DRACO_RETURN_IF_ERROR(AddFeatureIdToBuilder( + accessor, indices_data, att_id, number_of_elements, reverse_winding, + attribute_name, builder)); + } else { + DRACO_RETURN_IF_ERROR(AddAttributeDataByTypes(accessor, indices_data, + att_id, number_of_elements, + reverse_winding, builder)); + } + return OkStatus(); +} + +template +Status GltfDecoder::AddTangentToBuilder( + const tinygltf::Accessor &accessor, + const std::vector &indices_data, int att_id, + int number_of_elements, const Eigen::Matrix4d &transform_matrix, + bool reverse_winding, BuilderT *builder) { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + TinyGltfUtils::CopyDataAsFloat(gltf_model_, accessor)); + + for (int v = 0; v < data.size(); ++v) { + Eigen::Vector4d vec4(data[v][0], data[v][1], data[v][2], 1); + vec4 = transform_matrix * vec4; + + // Normalize the data. + Eigen::Vector3d vec3(vec4[0], vec4[1], vec4[2]); + vec3 = vec3.normalized(); + for (int i = 0; i < 3; ++i) { + vec4[i] = vec3[i]; + } + + // Add back the original w component. + vec4[3] = data[v][3]; + for (int i = 0; i < 4; ++i) { + data[v][i] = vec4[i]; + } + } + + SetValuesForBuilder(indices_data, att_id, number_of_elements, data, + reverse_winding, builder); + return OkStatus(); +} + +template +Status GltfDecoder::AddTexCoordToBuilder( + const tinygltf::Accessor &accessor, + const std::vector &indices_data, int att_id, + int number_of_elements, bool reverse_winding, BuilderT *builder) { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + TinyGltfUtils::CopyDataAsFloat(gltf_model_, accessor)); + + // glTF stores texture coordinates flipped on the horizontal axis compared to + // how Draco stores texture coordinates. + for (auto &uv : data) { + uv[1] = 1.0 - uv[1]; + } + + SetValuesForBuilder(indices_data, att_id, number_of_elements, data, + reverse_winding, builder); + return OkStatus(); +} + +template +Status GltfDecoder::AddFeatureIdToBuilder( + const tinygltf::Accessor &accessor, + const std::vector &indices_data, int att_id, + int number_of_elements, bool reverse_winding, + const std::string &attribute_name, BuilderT *builder) { + // Check that the feature ID attribute has correct type. + const int num_components = + TinyGltfUtils::GetNumComponentsForType(accessor.type); + if (num_components != 1) { + return ErrorStatus("Invalid feature ID attribute type."); + } + const draco::DataType draco_component_type = + GltfComponentTypeToDracoType(accessor.componentType); + if (draco_component_type != DT_UINT8 && draco_component_type != DT_UINT16 && + draco_component_type != DT_FLOAT32) { + return ErrorStatus("Invalid feature ID attribute component type."); + } + + // Set feature ID attribute values to mesh faces. + DRACO_RETURN_IF_ERROR(AddAttributeDataByTypes(accessor, indices_data, att_id, + number_of_elements, + reverse_winding, builder)); + + // Store feature ID attribute name with index like _FEATURE_ID_5 in Draco + // attribute metadata. + std::unique_ptr metadata(new draco::AttributeMetadata()); + metadata->AddEntryString("attribute_name", attribute_name); + builder->AddAttributeMetadata(att_id, std::move(metadata)); + return OkStatus(); +} + +template +Status GltfDecoder::AddTransformedDataToBuilder( + const tinygltf::Accessor &accessor, + const std::vector &indices_data, int att_id, + int number_of_elements, const Eigen::Matrix4d &transform_matrix, + bool normalize, bool reverse_winding, BuilderT *builder) { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + TinyGltfUtils::CopyDataAsFloat(gltf_model_, accessor)); + + for (int v = 0; v < data.size(); ++v) { + Eigen::Vector4d vec4(data[v][0], data[v][1], data[v][2], 1); + vec4 = transform_matrix * vec4; + Eigen::Vector3d vec3(vec4[0], vec4[1], vec4[2]); + if (normalize) { + vec3 = vec3.normalized(); + } + for (int i = 0; i < 3; ++i) { + data[v][i] = vec3[i]; + } + } + + SetValuesForBuilder(indices_data, att_id, number_of_elements, data, + reverse_winding, builder); + return OkStatus(); +} + +template +void GltfDecoder::SetValuesForBuilder(const std::vector &indices_data, + int att_id, int number_of_elements, + const std::vector &data, + bool reverse_winding, + TriangleSoupMeshBuilder *builder) { + SetValuesPerFace(indices_data, att_id, number_of_elements, data, + reverse_winding, builder); +} + +template +void GltfDecoder::SetValuesForBuilder(const std::vector &indices_data, + int att_id, int number_of_elements, + const std::vector &data, + bool reverse_winding, + PointCloudBuilder *builder) { + for (int i = 0; i < number_of_elements; ++i) { + const uint32_t v_id = indices_data[i]; + const PointIndex pi(v_id + next_point_id_); + builder->SetAttributeValueForPoint(att_id, pi, + GetDataContentAddress(data[v_id])); + } +} + +template +void GltfDecoder::SetValuesPerFace(const std::vector &indices_data, + int att_id, int number_of_faces, + const std::vector &data, + bool reverse_winding, + TriangleSoupMeshBuilder *mb) { + for (int f = 0; f < number_of_faces; ++f) { + const int base_corner = f * 3; + const uint32_t v_id = indices_data[base_corner]; + const int next_offset = reverse_winding ? 2 : 1; + const int prev_offset = reverse_winding ? 1 : 2; + const uint32_t v_next_id = indices_data[base_corner + next_offset]; + const uint32_t v_prev_id = indices_data[base_corner + prev_offset]; + + const FaceIndex face_index(f + next_face_id_); + mb->SetAttributeValuesForFace(att_id, face_index, + GetDataContentAddress(data[v_id]), + GetDataContentAddress(data[v_next_id]), + GetDataContentAddress(data[v_prev_id])); + } +} + +// Get the address of data content for arithmetic types |T|. +template +const void *GetDataContentAddressImpl(const T &data, + std::true_type /* is_arithmetic */) { + return &data; +} + +// Get the address of data content for vector types |T|. +template +const void *GetDataContentAddressImpl(const T &data, + std::false_type /* is_arithmetic */) { + return data.data(); +} + +template +const void *GltfDecoder::GetDataContentAddress(const T &data) const { + return GetDataContentAddressImpl(data, std::is_arithmetic()); +} + +template +Status GltfDecoder::AddAttributeDataByTypes( + const tinygltf::Accessor &accessor, + const std::vector &indices_data, int att_id, + int number_of_elements, bool reverse_winding, BuilderT *builder) { + typedef VectorD Vector2u8i; + typedef VectorD Vector3u8i; + typedef VectorD Vector4u8i; + typedef VectorD Vector2u16i; + typedef VectorD Vector3u16i; + typedef VectorD Vector4u16i; + switch (accessor.type) { + case TINYGLTF_TYPE_SCALAR: + switch (accessor.componentType) { + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, number_of_elements, + data, reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_FLOAT: { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, number_of_elements, + data, reverse_winding, builder); + } break; + default: + return ErrorStatus("Add attribute data, unknown component type."); + } + break; + case TINYGLTF_TYPE_VEC2: + switch (accessor.componentType) { + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_FLOAT: { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + TinyGltfUtils::CopyDataAsFloat(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + default: + return Status(Status::DRACO_ERROR, + "Add attribute data, unknown component type."); + } + break; + case TINYGLTF_TYPE_VEC3: + switch (accessor.componentType) { + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_FLOAT: { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + TinyGltfUtils::CopyDataAsFloat(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + default: + return Status(Status::DRACO_ERROR, + "Add attribute data, unknown component type."); + } + break; + case TINYGLTF_TYPE_VEC4: + switch (accessor.componentType) { + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + CopyDataAs(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + case TINYGLTF_COMPONENT_TYPE_FLOAT: { + DRACO_ASSIGN_OR_RETURN( + std::vector data, + TinyGltfUtils::CopyDataAsFloat(gltf_model_, accessor)); + SetValuesForBuilder(indices_data, att_id, + number_of_elements, data, + reverse_winding, builder); + } break; + default: + return Status(Status::DRACO_ERROR, + "Add attribute data, unknown component type."); + } + break; + default: + return Status(Status::DRACO_ERROR, "Add attribute data, unknown type."); + } + return OkStatus(); +} + +template +Status GltfDecoder::CopyTextures(T *owner) { + for (int i = 0; i < gltf_model_.images.size(); ++i) { + const tinygltf::Image &image = gltf_model_.images[i]; + if (image.width == -1 || image.height == -1 || image.component == -1) { + // TinyGLTF does not return an error when it cannot find an image. It will + // add an image with negative values. + return Status(Status::DRACO_ERROR, "Error loading image."); + } + + std::unique_ptr draco_texture(new Texture()); + + // Update mapping between glTF images and textures in the texture library. + gltf_image_to_draco_texture_[i] = draco_texture.get(); + + DRACO_ASSIGN_OR_RETURN(std::unique_ptr source_image, + GetSourceImage(gltf_model_, image, *draco_texture)); + if (source_image->encoded_data().empty() && + !source_image->filename().empty()) { + // Update filename of source image to be relative of the glTF file. + std::string dirname; + std::string basename; + SplitPath(input_file_name_, &dirname, &basename); + source_image->set_filename(dirname + "/" + source_image->filename()); + } + draco_texture->set_source_image(*source_image); + + owner->GetMaterialLibrary().MutableTextureLibrary().PushTexture( + std::move(draco_texture)); + } + return OkStatus(); +} + +void GltfDecoder::SetAttributePropertiesOnDracoMesh(Mesh *mesh) { + for (const auto &mad : mesh_attribute_data_) { + const int att_id = attribute_name_to_draco_mesh_attribute_id_[mad.first]; + if (att_id == -1) { + continue; + } + if (mad.second.normalized) { + mesh->attribute(att_id)->set_normalized(true); + } + } +} + +Status GltfDecoder::AddMaterialsToDracoMesh(Mesh *mesh) { + bool is_normal_map_used = false; + + int default_material_index = -1; + const auto it = gltf_primitive_material_to_draco_material_.find(-1); + if (it != gltf_primitive_material_to_draco_material_.end()) { + default_material_index = it->second; + } + + int output_material_index = 0; + for (int input_material_index = 0; + input_material_index < gltf_model_.materials.size(); + ++input_material_index) { + if (default_material_index == input_material_index) { + // Insert a default material here for primitives that did not have a + // material index. + mesh->GetMaterialLibrary().MutableMaterial(output_material_index++); + } + + Material *const output_material = + mesh->GetMaterialLibrary().MutableMaterial(output_material_index++); + DRACO_RETURN_IF_ERROR( + AddGltfMaterial(input_material_index, output_material)); + if (output_material->GetTextureMapByType( + TextureMap::NORMAL_TANGENT_SPACE)) { + is_normal_map_used = true; + } + } + + return OkStatus(); +} + +template +Status GltfDecoder::AddMaterialDataToBuilder(int material_value, + int number_of_elements, + BuilderT *builder) { + if (gltf_primitive_material_to_draco_material_.size() < 256) { + const uint8_t typed_material_value = material_value; + DRACO_RETURN_IF_ERROR(AddMaterialDataToBuilderInternal( + typed_material_value, number_of_elements, builder)); + } else if (gltf_primitive_material_to_draco_material_.size() < (1 << 16)) { + const uint16_t typed_material_value = material_value; + DRACO_RETURN_IF_ERROR(AddMaterialDataToBuilderInternal( + typed_material_value, number_of_elements, builder)); + } else { + const uint32_t typed_material_value = material_value; + DRACO_RETURN_IF_ERROR(AddMaterialDataToBuilderInternal( + typed_material_value, number_of_elements, builder)); + } + return OkStatus(); +} + +template +Status GltfDecoder::AddMaterialDataToBuilderInternal( + T material_value, int number_of_faces, TriangleSoupMeshBuilder *builder) { + for (int f = 0; f < number_of_faces; ++f) { + const FaceIndex face_index(f + next_face_id_); + builder->SetPerFaceAttributeValueForFace(material_att_id_, face_index, + &material_value); + } + return OkStatus(); +} + +template +Status GltfDecoder::AddMaterialDataToBuilderInternal( + T material_value, int number_of_points, PointCloudBuilder *builder) { + for (int pi = 0; pi < number_of_points; ++pi) { + const PointIndex point_index(pi + next_point_id_); + builder->SetAttributeValueForPoint(material_att_id_, point_index, + &material_value); + } + return OkStatus(); +} + +Status GltfDecoder::CheckAndAddTextureToDracoMaterial( + int texture_index, int tex_coord_attribute_index, + const tinygltf::ExtensionMap &tex_info_ext, Material *material, + TextureMap::Type type) { + if (texture_index < 0) { + return OkStatus(); + } + + const tinygltf::Texture &input_texture = gltf_model_.textures[texture_index]; + int source_index = input_texture.source; + + const auto texture_it = gltf_image_to_draco_texture_.find(source_index); + if (texture_it != gltf_image_to_draco_texture_.end()) { + Texture *const texture = texture_it->second; + // Default GLTF 2.0 sampler uses REPEAT mode along both S and T directions. + TextureMap::WrappingMode wrapping_mode(TextureMap::REPEAT); + TextureMap::FilterType min_filter = TextureMap::UNSPECIFIED; + TextureMap::FilterType mag_filter = TextureMap::UNSPECIFIED; + + if (input_texture.sampler >= 0) { + const tinygltf::Sampler &sampler = + gltf_model_.samplers[input_texture.sampler]; + DRACO_ASSIGN_OR_RETURN(wrapping_mode.s, + TinyGltfToDracoAxisWrappingMode(sampler.wrapS)); + DRACO_ASSIGN_OR_RETURN(wrapping_mode.t, + TinyGltfToDracoAxisWrappingMode(sampler.wrapT)); + DRACO_ASSIGN_OR_RETURN(min_filter, + TinyGltfToDracoFilterType(sampler.minFilter)); + DRACO_ASSIGN_OR_RETURN(mag_filter, + TinyGltfToDracoFilterType(sampler.magFilter)); + } + if (tex_coord_attribute_index < 0 || tex_coord_attribute_index > 1) { + return Status(Status::DRACO_ERROR, "Incompatible tex coord index."); + } + TextureTransform transform; + DRACO_ASSIGN_OR_RETURN(const bool has_transform, + CheckKhrTextureTransform(tex_info_ext, &transform)); + if (has_transform) { + DRACO_RETURN_IF_ERROR(material->SetTextureMap( + texture, type, wrapping_mode, min_filter, mag_filter, transform, + tex_coord_attribute_index)); + } else { + DRACO_RETURN_IF_ERROR( + material->SetTextureMap(texture, type, wrapping_mode, min_filter, + mag_filter, tex_coord_attribute_index)); + } + } + return OkStatus(); +} + +Status GltfDecoder::DecodeGltfToScene() { + DRACO_RETURN_IF_ERROR(GatherAttributeAndMaterialStats()); + DRACO_RETURN_IF_ERROR(AddLightsToScene()); + DRACO_RETURN_IF_ERROR(AddMaterialsVariantsNamesToScene()); + DRACO_RETURN_IF_ERROR(AddStructuralMetadataToGeometry(scene_.get())); + DRACO_RETURN_IF_ERROR(CopyTextures(scene_.get())); + for (const tinygltf::Scene &scene : gltf_model_.scenes) { + for (int i = 0; i < scene.nodes.size(); ++i) { + DRACO_RETURN_IF_ERROR( + DecodeNodeForScene(scene.nodes[i], kInvalidSceneNodeIndex)); + scene_->AddRootNodeIndex(gltf_node_to_scenenode_index_[scene.nodes[i]]); + } + } + + DRACO_RETURN_IF_ERROR(AddAnimationsToScene()); + DRACO_RETURN_IF_ERROR(AddMaterialsToScene()); + DRACO_RETURN_IF_ERROR(AddSkinsToScene()); + MoveNonMaterialTextures(scene_.get()); + + return OkStatus(); +} + +Status GltfDecoder::AddLightsToScene() { + // Add all lights to Draco scene. + for (const auto &light : gltf_model_.lights) { + // Add a new light to the scene. + const LightIndex light_index = scene_->AddLight(); + Light *scene_light = scene_->GetLight(light_index); + + // Decode light type. + const std::map types = { + {"directional", Light::DIRECTIONAL}, + {"point", Light::POINT}, + {"spot", Light::SPOT}}; + if (types.count(light.type) == 0) { + return ErrorStatus("Light type is invalid."); + } + scene_light->SetType(types.at(light.type)); + + // Decode spot light properties. + if (scene_light->GetType() == Light::SPOT) { + scene_light->SetInnerConeAngle(light.spot.innerConeAngle); + scene_light->SetOuterConeAngle(light.spot.outerConeAngle); + } + + // Decode other light properties. + scene_light->SetName(light.name); + if (!light.color.empty()) { // Empty means that color is not specified. + if (light.color.size() != 3) { + return ErrorStatus("Light color is malformed."); + } + scene_light->SetColor( + Vector3f(light.color[0], light.color[1], light.color[2])); + } + scene_light->SetIntensity(light.intensity); + if (light.range != 0.0) { // Zero means that range is not specified. + if (light.range < 0.0) { + return ErrorStatus("Light range must be positive."); + } + scene_light->SetRange(light.range); + } + } + return OkStatus(); +} + +Status GltfDecoder::AddMaterialsVariantsNamesToScene() { + // Check whether the scene has materials variants. + const auto &e = gltf_model_.extensions.find("KHR_materials_variants"); + if (e == gltf_model_.extensions.end()) { + // The scene has no materials variants. + return OkStatus(); + } + + // Decode all materials variants names into Draco scene from JSON like this: + // "KHR_materials_variants": { + // "variants": [ + // {"name": "Loki" }, + // {"name": "Odin" }, + // ] + // } + const tinygltf::Value::Object &o = e->second.Get(); + const auto &variants = o.find("variants"); + if (variants == o.end()) { + return ErrorStatus("Materials variants extension with names is malformed."); + } + const tinygltf::Value &variants_array = variants->second; + if (!variants_array.IsArray()) { + return ErrorStatus("Materials variants names array is malformed."); + } + for (int i = 0; i < variants_array.Size(); i++) { + const auto &variant_object = variants_array.Get(i); + if (!variant_object.IsObject() || !variant_object.Has("name")) { + return ErrorStatus("Materials variants name is missing."); + } + const auto &name_string = variant_object.Get("name"); + if (!name_string.IsString()) { + return ErrorStatus("Materials variant name is malformed."); + } + const std::string &name = name_string.Get(); + scene_->GetMaterialLibrary().AddMaterialsVariant(name); + } + return OkStatus(); +} + +template +Status GltfDecoder::AddStructuralMetadataToGeometry(GeometryT *geometry) { + // Check whether the glTF model has structural metadata. + const auto &e = gltf_model_.extensions.find("EXT_structural_metadata"); + if (e == gltf_model_.extensions.end()) { + // The glTF model has no structural metadata. + return OkStatus(); + } + const tinygltf::Value::Object &o = e->second.Get(); + + // Decode property table schema. + { + const auto &value = o.find("schema"); + if (value == o.end()) { + return ErrorStatus("Structural metadata extension has no schema."); + } + const tinygltf::Value &object = value->second; + if (!object.IsObject()) { + return ErrorStatus("Structural metadata extension schema is malformed."); + } + + // Decodes tinygltf::Value into PropertyTable::Schema::Object. + struct SchemaParser { + static Status Parse(const tinygltf::Value &value, + PropertyTable::Schema::Object *object) { + switch (value.Type()) { + case tinygltf::OBJECT_TYPE: { + for (auto &it : value.Get()) { + object->SetObjects().emplace_back(it.first); + DRACO_RETURN_IF_ERROR( + Parse(it.second, &object->SetObjects().back())); + } + } break; + case tinygltf::ARRAY_TYPE: { + for (int i = 0; i < value.ArrayLen(); ++i) { + object->SetArray().emplace_back(); + DRACO_RETURN_IF_ERROR( + Parse(value.Get(i), &object->SetArray().back())); + } + } break; + case tinygltf::STRING_TYPE: + object->SetString(value.Get()); + break; + case tinygltf::INT_TYPE: + object->SetInteger(value.Get()); + break; + case tinygltf::BOOL_TYPE: + object->SetBoolean(value.Get()); + break; + case tinygltf::REAL_TYPE: + case tinygltf::BINARY_TYPE: + case tinygltf::NULL_TYPE: + default: + // Not used in the schema JSON. + return ErrorStatus("Unsupported JSON type in schema."); + } + return OkStatus(); + } + }; + + // Parse property table schema and set it to |geometry|. + PropertyTable::Schema schema; + DRACO_RETURN_IF_ERROR(SchemaParser::Parse(object, &schema.json)); + geometry->GetStructuralMetadata().SetPropertyTableSchema(schema); + } + + // Decode property tables. + { + const auto &tables = o.find("propertyTables"); + if (tables == o.end()) { + return ErrorStatus( + "Structural metadata extension has no property tables."); + } + const tinygltf::Value &tables_array = tables->second; + if (!tables_array.IsArray()) { + return ErrorStatus("Property tables array is malformed."); + } + + // Loop over all property tables. + for (int i = 0; i < tables_array.Size(); i++) { + // Create a property table and populate it below. + std::unique_ptr property_table(new PropertyTable()); + + const auto &object = tables_array.Get(i); + if (!object.IsObject()) { + return ErrorStatus("Property table is malformed."); + } + const auto o = object.Get(); + + // The "class" property is required. + bool success; + std::string str_value; + DRACO_ASSIGN_OR_RETURN(success, DecodeString("class", o, &str_value)); + if (success) { + property_table->SetClass(str_value); + } else { + return ErrorStatus("Property class is malformed."); + } + + // The "count" property is required. + int int_value; + DRACO_ASSIGN_OR_RETURN(success, DecodeInt("count", o, &int_value)); + if (success) { + property_table->SetCount(int_value); + } else { + return ErrorStatus("Property count is malformed."); + } + + // The "name" property is optional. + DRACO_ASSIGN_OR_RETURN(success, DecodeString("name", o, &str_value)); + if (success) { + property_table->SetName(str_value); + } + + // Decode property table properties (columns). + { + constexpr char kName[] = "properties"; + if (!object.Has(kName)) { + return ErrorStatus("Property table is malformed."); + } + const tinygltf::Value &value = object.Get(kName); + if (!value.IsObject()) { + return ErrorStatus( + "Property table properties property is malformed."); + } + + // Loop over property table properties. + for (const auto &key : value.Keys()) { + // Create a property table property and populate it below. + std::unique_ptr property( + new PropertyTable::Property()); + + const auto &property_object = value.Get(key); + if (!property_object.IsObject()) { + return ErrorStatus("Property entry is malformed."); + } + property->SetName(key); + const auto o = property_object.Get(); + + // The "values" property is required. + DRACO_ASSIGN_OR_RETURN( + success, + DecodePropertyTableData("values", o, &property->GetData())); + if (!success) { + return ErrorStatus("Property values property is malformed."); + } + + // All other properties are not required. + DRACO_ASSIGN_OR_RETURN( + success, DecodeString("stringOffsetType", o, &str_value)); + if (success) { + property->GetStringOffsets().type = str_value; + } + DRACO_ASSIGN_OR_RETURN( + success, DecodeString("arrayOffsetType", o, &str_value)); + if (success) { + property->GetArrayOffsets().type = str_value; + } + DRACO_ASSIGN_OR_RETURN( + success, + DecodePropertyTableData("arrayOffsets", o, + &property->GetArrayOffsets().data)); + DRACO_ASSIGN_OR_RETURN( + success, + DecodePropertyTableData("stringOffsets", o, + &property->GetStringOffsets().data)); + + // Add property to the property table. + property_table->AddProperty(std::move(property)); + } + } + + // Add property table to structural metadata. + geometry->GetStructuralMetadata().AddPropertyTable( + std::move(property_table)); + } + } + return OkStatus(); +} + +Status GltfDecoder::AddAnimationsToScene() { + for (const auto &animation : gltf_model_.animations) { + const AnimationIndex animation_index = scene_->AddAnimation(); + Animation *const encoder_animation = scene_->GetAnimation(animation_index); + encoder_animation->SetName(animation.name); + + for (const tinygltf::AnimationChannel &channel : animation.channels) { + const auto it = gltf_node_to_scenenode_index_.find(channel.target_node); + if (it == gltf_node_to_scenenode_index_.end()) { + return Status(Status::DRACO_ERROR, "Could not find Node in the scene."); + } + DRACO_RETURN_IF_ERROR(TinyGltfUtils::AddChannelToAnimation( + gltf_model_, animation, channel, it->second.value(), + encoder_animation)); + } + } + return OkStatus(); +} + +Status GltfDecoder::DecodeNodeForScene(int node_index, + SceneNodeIndex parent_index) { + SceneNodeIndex scene_node_index = kInvalidSceneNodeIndex; + SceneNode *scene_node = nullptr; + bool is_new_node; + if (gltf_scene_graph_mode_ == GltfSceneGraphMode::DAG && + gltf_node_to_scenenode_index_.find(node_index) != + gltf_node_to_scenenode_index_.end()) { + // Node has been decoded already. + scene_node_index = gltf_node_to_scenenode_index_[node_index]; + scene_node = scene_->GetNode(scene_node_index); + is_new_node = false; + } else { + scene_node_index = scene_->AddNode(); + // Update mapping between glTF Nodes and indices in the scene. + gltf_node_to_scenenode_index_[node_index] = scene_node_index; + + scene_node = scene_->GetNode(scene_node_index); + is_new_node = true; + } + + if (parent_index != kInvalidSceneNodeIndex) { + scene_node->AddParentIndex(parent_index); + SceneNode *const parent_node = scene_->GetNode(parent_index); + parent_node->AddChildIndex(scene_node_index); + } + + if (!is_new_node) { + return OkStatus(); + } + const tinygltf::Node &node = gltf_model_.nodes[node_index]; + if (!node.name.empty()) { + scene_node->SetName(node.name); + } + std::unique_ptr trsm = GetNodeTrsMatrix(node); + scene_node->SetTrsMatrix(*trsm); + if (node.skin >= 0) { + // Save the index to the source skins in the node. This will be updated + // later when the skins are processed. + scene_node->SetSkinIndex(SkinIndex(node.skin)); + } + if (node.mesh >= 0) { + // Check if we have already parsed this glTF Mesh. + const auto it = gltf_mesh_to_scene_mesh_group_.find(node.mesh); + if (it != gltf_mesh_to_scene_mesh_group_.end()) { + // We already processed this glTF mesh. + scene_node->SetMeshGroupIndex(it->second); + } else { + const MeshGroupIndex scene_mesh_group_index = scene_->AddMeshGroup(); + MeshGroup *const scene_mesh = + scene_->GetMeshGroup(scene_mesh_group_index); + + const tinygltf::Mesh &mesh = gltf_model_.meshes[node.mesh]; + if (!mesh.name.empty()) { + scene_mesh->SetName(mesh.name); + } + for (const auto &primitive : mesh.primitives) { + DRACO_RETURN_IF_ERROR(DecodePrimitiveForScene(primitive, scene_mesh)); + } + scene_node->SetMeshGroupIndex(scene_mesh_group_index); + gltf_mesh_to_scene_mesh_group_[node.mesh] = scene_mesh_group_index; + } + } + + // Decode light index. + const auto &e = node.extensions.find("KHR_lights_punctual"); + if (e != node.extensions.end()) { + const tinygltf::Value::Object &o = e->second.Get(); + const auto &light = o.find("light"); + if (light != o.end()) { + const tinygltf::Value &value = light->second; + if (!value.IsInt()) { + return ErrorStatus("Node light index is malformed."); + } + const int light_index = value.Get(); + if (light_index < 0 || light_index >= scene_->NumLights()) { + return ErrorStatus("Node light index is out of bounds."); + } + scene_node->SetLightIndex(LightIndex(light_index)); + } + } + + for (int i = 0; i < node.children.size(); ++i) { + DRACO_RETURN_IF_ERROR( + DecodeNodeForScene(node.children[i], scene_node_index)); + } + return OkStatus(); +} + +Status GltfDecoder::DecodePrimitiveForScene( + const tinygltf::Primitive &primitive, MeshGroup *mesh_group) { + if (primitive.mode != TINYGLTF_MODE_TRIANGLES && + primitive.mode != TINYGLTF_MODE_POINTS) { + return Status(Status::DRACO_ERROR, + "Primitive does not contain triangles or points."); + } + + // Decode materials variants mappings if present in this primitive. + std::vector mappings; + const auto &e = primitive.extensions.find("KHR_materials_variants"); + if (e != primitive.extensions.end()) { + DRACO_RETURN_IF_ERROR(DecodeMaterialsVariantsMappings( + e->second.Get(), &mappings)); + } + + const PrimitiveSignature signature(primitive); + const auto existing_mesh_index = + gltf_primitive_to_draco_mesh_index_.find(signature); + if (existing_mesh_index != gltf_primitive_to_draco_mesh_index_.end()) { + mesh_group->AddMeshInstance( + {existing_mesh_index->second, primitive.material, mappings}); + return OkStatus(); + } + + // Handle indices first. + DRACO_ASSIGN_OR_RETURN(const std::vector indices_data, + DecodePrimitiveIndices(primitive)); + const int number_of_faces = indices_data.size() / 3; + const int number_of_points = indices_data.size(); + + // Note that glTF mesh |primitive| has no name; no name is set to Draco mesh. + TriangleSoupMeshBuilder mb; + PointCloudBuilder pb; + if (primitive.mode == TINYGLTF_MODE_TRIANGLES) { + mb.Start(number_of_faces); + } else { + pb.Start(number_of_points); + } + + std::set normalized_attributes; + for (const auto &attribute : primitive.attributes) { + if (attribute.second >= gltf_model_.accessors.size()) { + return ErrorStatus("Invalid accessor."); + } + const tinygltf::Accessor &accessor = + gltf_model_.accessors[attribute.second]; + const int component_type = accessor.componentType; + const int type = accessor.type; + const bool normalized = accessor.normalized; + int att_id = -1; + if (primitive.mode == TINYGLTF_MODE_TRIANGLES) { + DRACO_ASSIGN_OR_RETURN( + att_id, AddAttribute(attribute.first, component_type, type, &mb)); + } else { + DRACO_ASSIGN_OR_RETURN( + att_id, AddAttribute(attribute.first, component_type, type, &pb)); + } + if (att_id == -1) { + continue; + } + if (normalized) { + normalized_attributes.insert(att_id); + } + + if (primitive.mode == TINYGLTF_MODE_TRIANGLES) { + DRACO_RETURN_IF_ERROR(AddAttributeValuesToBuilder( + attribute.first, accessor, indices_data, att_id, number_of_faces, + Eigen::Matrix4d::Identity(), &mb)); + } else { + DRACO_RETURN_IF_ERROR(AddAttributeValuesToBuilder( + attribute.first, accessor, indices_data, att_id, number_of_points, + Eigen::Matrix4d::Identity(), &pb)); + } + } + + int material_index = primitive.material; + + DRACO_ASSIGN_OR_RETURN( + std::unique_ptr mesh, + BuildMeshFromBuilder(primitive.mode == TINYGLTF_MODE_TRIANGLES, &mb, + &pb)); + + // Set all normalized flags for appropriate attributes. + for (const int32_t att_id : normalized_attributes) { + mesh->attribute(att_id)->set_normalized(true); + } + // Decode mesh feature ID sets if present in this primitive. + DRACO_RETURN_IF_ERROR(DecodeMeshFeatures( + primitive, &scene_->GetMaterialLibrary().MutableTextureLibrary(), + mesh.get())); + + const MeshIndex mesh_index = scene_->AddMesh(std::move(mesh)); + if (mesh_index == kInvalidMeshIndex) { + return Status(Status::DRACO_ERROR, "Could not add Draco mesh to scene."); + } + mesh_group->AddMeshInstance({mesh_index, material_index, mappings}); + + gltf_primitive_to_draco_mesh_index_[signature] = mesh_index; + return OkStatus(); +} + +Status GltfDecoder::DecodeMaterialsVariantsMappings( + const tinygltf::Value::Object &extension, + std::vector *mappings) { + // Decode all materials variants mappings from JSON like this: + // "KHR_materials_variants" : { + // "mappings": [ + // { + // "material": 2, + // "variants": [0, 2, 4] + // }, + // { + // "material": 3, + // "variants": [1, 3] + // } + // ] + // } + const auto &mappings_object = extension.find("mappings"); + if (mappings_object == extension.end()) { + return ErrorStatus("Materials variants extension is malformed."); + } + const tinygltf::Value &mappings_array = mappings_object->second; + if (!mappings_array.IsArray()) { + return ErrorStatus("Materials variants mappings array is malformed."); + } + for (int i = 0; i < mappings_array.Size(); i++) { + const auto &mapping_object = mappings_array.Get(i); + if (!mapping_object.IsObject() || !mapping_object.Has("material") || + !mapping_object.Has("variants")) { + return ErrorStatus("Materials variants mapping is malformed."); + } + const tinygltf::Value &material_int = mapping_object.Get("material"); + if (!material_int.IsInt()) { + return ErrorStatus("Materials variant mapping material is malformed."); + } + const int material = material_int.Get(); + const tinygltf::Value &variants_array = mapping_object.Get("variants"); + if (!variants_array.IsArray()) { + return ErrorStatus("Materials variant mapping variants is malformed."); + } + std::vector variants; + for (int j = 0; j < variants_array.Size(); j++) { + const tinygltf::Value &variant_int = variants_array.Get(j); + if (!variant_int.IsInt()) { + return ErrorStatus("Materials variants mapping variant is malformed."); + } + variants.push_back(variant_int.Get()); + } + mappings->push_back({material, variants}); + } + return OkStatus(); +} + +Status GltfDecoder::DecodeMeshFeatures(const tinygltf::Primitive &primitive, + TextureLibrary *texture_library, + Mesh *mesh) { + const auto &e = primitive.extensions.find("EXT_mesh_features"); + if (e == primitive.extensions.end()) { + return OkStatus(); + } + std::vector> mesh_features; + DRACO_RETURN_IF_ERROR( + DecodeMeshFeatures(e->second.Get(), + texture_library, &mesh_features)); + for (int i = 0; i < mesh_features.size(); i++) { + const MeshFeaturesIndex mfi = + mesh->AddMeshFeatures(std::move(mesh_features[i])); + if (scene_ == nullptr) { + // If we are decoding to a mesh, we need to restrict the mesh features to + // the primitive's material. + // TODO(ostava): This will not work properly when two primitives share the + // same material but have different mesh features. We will need to + // duplicate the materials in this case. + const auto mat_it = + gltf_primitive_material_to_draco_material_.find(primitive.material); + if (mat_it != gltf_primitive_material_to_draco_material_.end()) { + mesh->AddMeshFeaturesMaterialMask(mfi, mat_it->second); + } + } + } + return OkStatus(); +} + +Status GltfDecoder::DecodeMeshFeatures( + const tinygltf::Value::Object &extension, TextureLibrary *texture_library, + std::vector> *mesh_features) { + // Decode all mesh feature ID sets from JSON like this: + // "EXT_mesh_features": { + // "featureIds": [ + // { + // "label": "water", + // "featureCount": 2, + // "propertyTable": 0, + // "attribute": 0 + // }, + // { + // "featureCount": 12, + // "nullFeatureId": 100, + // "texture" : { + // "index": 0, + // "texCoord": 0, + // "channels": [0, 1, 2, 3] + // } + // } + // ] + // } + const auto &object = extension.find("featureIds"); + if (object == extension.end()) { + return ErrorStatus("Mesh features extension is malformed."); + } + const tinygltf::Value &array = object->second; + if (!array.IsArray()) { + return ErrorStatus("Mesh features array is malformed."); + } + for (int i = 0; i < array.Size(); i++) { + // Create a new feature ID set object and populate it below. + mesh_features->push_back(std::unique_ptr(new MeshFeatures())); + MeshFeatures &features = *mesh_features->back(); + + const auto &object = array.Get(i); + if (!object.IsObject()) { + return ErrorStatus("Mesh features array entry is malformed."); + } + + // The "featureCount" property is required. + { + constexpr char kName[] = "featureCount"; + if (!object.Has(kName)) { + return ErrorStatus("Mesh features is malformed."); + } + const tinygltf::Value &value = object.Get(kName); + if (!value.IsInt()) { + return ErrorStatus("Feature count property is malformed."); + } + features.SetFeatureCount(value.Get()); + } + + // All other properties are optional. + { + constexpr char kName[] = "nullFeatureId"; + if (object.Has(kName)) { + const tinygltf::Value &value = object.Get(kName); + if (!value.IsInt()) { + return ErrorStatus("Null feature ID property is malformed."); + } + features.SetNullFeatureId(value.Get()); + } + } + { + constexpr char kName[] = "label"; + if (object.Has(kName)) { + const tinygltf::Value &value = object.Get(kName); + if (!value.IsString()) { + return ErrorStatus("Label property is malformed."); + } + features.SetLabel(value.Get()); + } + } + { + constexpr char kName[] = "attribute"; + if (object.Has(kName)) { + const tinygltf::Value &value = object.Get(kName); + if (!value.IsInt()) { + return ErrorStatus("Attribute property is malformed."); + } + features.SetAttributeIndex(value.Get()); + } + } + { + constexpr char kName[] = "texture"; + if (object.Has(kName)) { + const tinygltf::Value &value = object.Get(kName); + if (!value.IsObject()) { + return ErrorStatus("Texture property is malformed."); + } + + // Decode texture contining mesh feature IDs into the |features| object + // via a temporary |material| object. + Material material(texture_library); + const auto &container_object = object.Get(); + DRACO_RETURN_IF_ERROR(DecodeTexture(kName, TextureMap::GENERIC, + container_object, &material)); + features.SetTextureMap( + *material.GetTextureMapByType(TextureMap::GENERIC)); + + // Decode array of texture channel indices. + std::vector channels; + { + constexpr char kName[] = "channels"; + if (value.Has(kName)) { + const tinygltf::Value &array = value.Get(kName); + if (!array.IsArray()) { + return ErrorStatus("Channels property is malformed."); + } + for (int i = 0; i < array.Size(); i++) { + const tinygltf::Value &value = array.Get(i); + if (!value.IsNumber()) { + return Status(Status::DRACO_ERROR, + "Channels value is malformed."); + } + channels.push_back(value.Get()); + } + } else { + channels = {0}; + } + } + features.SetTextureChannels(channels); + } + } + { + constexpr char kName[] = "propertyTable"; + if (object.Has(kName)) { + const tinygltf::Value &value = object.Get(kName); + if (!value.IsInt()) { + return ErrorStatus("Property table property is malformed."); + } + features.SetPropertyTableIndex(value.Get()); + } + } + } + return OkStatus(); +} + +template +StatusOr GltfDecoder::AddAttribute(const std::string &attribute_name, + int component_type, int type, + BuilderT *builder) { + const GeometryAttribute::Type draco_att_type = + GltfAttributeToDracoAttribute(attribute_name); + if (draco_att_type == GeometryAttribute::INVALID) { + // Return attribute id -1 that will be ignored and not included in the mesh. + return -1; + } + DRACO_ASSIGN_OR_RETURN( + const int att_id, + AddAttribute(draco_att_type, component_type, type, builder)); + return att_id; +} + +template +StatusOr GltfDecoder::AddAttribute(GeometryAttribute::Type attribute_type, + int component_type, int type, + BuilderT *builder) { + const int num_components = TinyGltfUtils::GetNumComponentsForType(type); + if (num_components == 0) { + return Status(Status::DRACO_ERROR, + "Could not add attribute with 0 components."); + } + + const draco::DataType draco_component_type = + GltfComponentTypeToDracoType(component_type); + if (draco_component_type == DT_INVALID) { + return Status(Status::DRACO_ERROR, + "Could not add attribute with invalid type."); + } + const int att_id = builder->AddAttribute(attribute_type, num_components, + draco_component_type); + if (att_id < 0) { + return Status(Status::DRACO_ERROR, "Could not add attribute."); + } + return att_id; +} + +StatusOr GltfDecoder::CheckKhrTextureTransform( + const tinygltf::ExtensionMap &extension, TextureTransform *transform) { + bool transform_set = false; + + const auto &e = extension.find("KHR_texture_transform"); + if (e == extension.end()) { + return false; + } + const tinygltf::Value::Object &o = e->second.Get(); + const auto &scale = o.find("scale"); + if (scale != o.end()) { + const tinygltf::Value &array = scale->second; + if (!array.IsArray() || array.Size() != 2) { + return Status(Status::DRACO_ERROR, + "KhrTextureTransform scale is malformed."); + } + std::array scale; + for (int i = 0; i < array.Size(); i++) { + const tinygltf::Value &value = array.Get(i); + if (!value.IsNumber()) { + return Status(Status::DRACO_ERROR, + "KhrTextureTransform scale is malformed."); + } + scale[i] = value.Get(); + transform_set = true; + } + transform->set_scale(scale); + } + const auto &rotation = o.find("rotation"); + if (rotation != o.end()) { + const tinygltf::Value &value = rotation->second; + if (!value.IsNumber()) { + return Status(Status::DRACO_ERROR, + "KhrTextureTransform rotation is malformed."); + } + transform->set_rotation(value.Get()); + transform_set = true; + } + const auto &offset = o.find("offset"); + if (offset != o.end()) { + const tinygltf::Value &array = offset->second; + if (!array.IsArray() || array.Size() != 2) { + return Status(Status::DRACO_ERROR, + "KhrTextureTransform offset is malformed."); + } + std::array offset; + for (int i = 0; i < array.Size(); i++) { + const tinygltf::Value &value = array.Get(i); + if (!value.IsNumber()) { + return Status(Status::DRACO_ERROR, + "KhrTextureTransform offset is malformed."); + } + offset[i] = value.Get(); + transform_set = true; + } + transform->set_offset(offset); + } + const auto &tex_coord = o.find("texCoord"); + if (tex_coord != o.end()) { + const tinygltf::Value &value = tex_coord->second; + if (!value.IsInt()) { + return Status(Status::DRACO_ERROR, + "KhrTextureTransform texCoord is malformed."); + } + transform->set_tex_coord(value.Get()); + transform_set = true; + } + return transform_set; +} + +Status GltfDecoder::AddGltfMaterial(int input_material_index, + Material *output_material) { + const tinygltf::Material &input_material = + gltf_model_.materials[input_material_index]; + + output_material->SetName(input_material.name); + output_material->SetTransparencyMode( + TinyGltfUtils::TextToMaterialMode(input_material.alphaMode)); + output_material->SetAlphaCutoff(input_material.alphaCutoff); + if (input_material.emissiveFactor.size() == 3) { + output_material->SetEmissiveFactor(Vector3f( + input_material.emissiveFactor[0], input_material.emissiveFactor[1], + input_material.emissiveFactor[2])); + } + const tinygltf::PbrMetallicRoughness &pbr = + input_material.pbrMetallicRoughness; + + if (pbr.baseColorFactor.size() == 4) { + output_material->SetColorFactor( + Vector4f(pbr.baseColorFactor[0], pbr.baseColorFactor[1], + pbr.baseColorFactor[2], pbr.baseColorFactor[3])); + } + output_material->SetMetallicFactor(pbr.metallicFactor); + output_material->SetRoughnessFactor(pbr.roughnessFactor); + output_material->SetDoubleSided(input_material.doubleSided); + + DRACO_RETURN_IF_ERROR(CheckAndAddTextureToDracoMaterial( + pbr.baseColorTexture.index, pbr.baseColorTexture.texCoord, + pbr.baseColorTexture.extensions, output_material, TextureMap::COLOR)); + DRACO_RETURN_IF_ERROR(CheckAndAddTextureToDracoMaterial( + pbr.metallicRoughnessTexture.index, pbr.metallicRoughnessTexture.texCoord, + pbr.metallicRoughnessTexture.extensions, output_material, + TextureMap::METALLIC_ROUGHNESS)); + + DRACO_RETURN_IF_ERROR(CheckAndAddTextureToDracoMaterial( + input_material.normalTexture.index, input_material.normalTexture.texCoord, + input_material.normalTexture.extensions, output_material, + TextureMap::NORMAL_TANGENT_SPACE)); + if (input_material.normalTexture.scale != 1.0) { + output_material->SetNormalTextureScale(input_material.normalTexture.scale); + } + DRACO_RETURN_IF_ERROR(CheckAndAddTextureToDracoMaterial( + input_material.occlusionTexture.index, + input_material.occlusionTexture.texCoord, + input_material.occlusionTexture.extensions, output_material, + TextureMap::AMBIENT_OCCLUSION)); + DRACO_RETURN_IF_ERROR(CheckAndAddTextureToDracoMaterial( + input_material.emissiveTexture.index, + input_material.emissiveTexture.texCoord, + input_material.emissiveTexture.extensions, output_material, + TextureMap::EMISSIVE)); + + // Decode material extensions. + DecodeMaterialUnlitExtension(input_material, output_material); + DRACO_RETURN_IF_ERROR( + DecodeMaterialSheenExtension(input_material, output_material)); + DRACO_RETURN_IF_ERROR( + DecodeMaterialTransmissionExtension(input_material, output_material)); + DRACO_RETURN_IF_ERROR( + DecodeMaterialClearcoatExtension(input_material, output_material)); + DRACO_RETURN_IF_ERROR(DecodeMaterialVolumeExtension( + input_material, input_material_index, output_material)); + DRACO_RETURN_IF_ERROR( + DecodeMaterialIorExtension(input_material, output_material)); + DRACO_RETURN_IF_ERROR( + DecodeMaterialSpecularExtension(input_material, output_material)); + + return OkStatus(); +} + +void GltfDecoder::DecodeMaterialUnlitExtension( + const tinygltf::Material &input_material, Material *output_material) { + // Do nothing if extension is absent. + const auto &extension_it = + input_material.extensions.find("KHR_materials_unlit"); + if (extension_it == input_material.extensions.end()) { + return; + } + + // Set the unlit property in Draco material. + output_material->SetUnlit(true); +} + +Status GltfDecoder::DecodeMaterialSheenExtension( + const tinygltf::Material &input_material, Material *output_material) { + // Do nothing if extension is absent. + const auto &extension_it = + input_material.extensions.find("KHR_materials_sheen"); + if (extension_it == input_material.extensions.end()) { + return OkStatus(); + } + + output_material->SetHasSheen(true); + const tinygltf::Value::Object &extension_object = + extension_it->second.Get(); + + // Decode sheen color factor. + Vector3f vector; + bool success; + DRACO_ASSIGN_OR_RETURN( + success, DecodeVector3f("sheenColorFactor", extension_object, &vector)); + if (success) { + output_material->SetSheenColorFactor(vector); + } + + // Decode sheen roughness factor. + float value; + DRACO_ASSIGN_OR_RETURN( + success, DecodeFloat("sheenRoughnessFactor", extension_object, &value)); + if (success) { + output_material->SetSheenRoughnessFactor(value); + } + + // Decode sheen color texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("sheenColorTexture", + TextureMap::SHEEN_COLOR, extension_object, + output_material)); + + // Decode sheen roughness texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("sheenRoughnessTexture", + TextureMap::SHEEN_ROUGHNESS, + extension_object, output_material)); + + return OkStatus(); +} + +Status GltfDecoder::DecodeMaterialTransmissionExtension( + const tinygltf::Material &input_material, Material *output_material) { + // Do nothing if extension is absent. + const auto &extension_it = + input_material.extensions.find("KHR_materials_transmission"); + if (extension_it == input_material.extensions.end()) { + return OkStatus(); + } + + output_material->SetHasTransmission(true); + const tinygltf::Value::Object &extension_object = + extension_it->second.Get(); + + // Decode transmission factor. + float value; + DRACO_ASSIGN_OR_RETURN( + const bool success, + DecodeFloat("transmissionFactor", extension_object, &value)); + if (success) { + output_material->SetTransmissionFactor(value); + } + + // Decode transmission texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("transmissionTexture", + TextureMap::TRANSMISSION, + extension_object, output_material)); + + return OkStatus(); +} + +Status GltfDecoder::DecodeMaterialClearcoatExtension( + const tinygltf::Material &input_material, Material *output_material) { + // Do nothing if extension is absent. + const auto &extension_it = + input_material.extensions.find("KHR_materials_clearcoat"); + if (extension_it == input_material.extensions.end()) { + return OkStatus(); + } + + output_material->SetHasClearcoat(true); + const tinygltf::Value::Object &extension_object = + extension_it->second.Get(); + + // Decode clearcoat factor. + float value; + bool success; + DRACO_ASSIGN_OR_RETURN( + success, DecodeFloat("clearcoatFactor", extension_object, &value)); + if (success) { + output_material->SetClearcoatFactor(value); + } + + // Decode clearcoat roughness factor. + DRACO_ASSIGN_OR_RETURN(success, DecodeFloat("clearcoatRoughnessFactor", + extension_object, &value)); + if (success) { + output_material->SetClearcoatRoughnessFactor(value); + } + + // Decode clearcoat texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("clearcoatTexture", TextureMap::CLEARCOAT, + extension_object, output_material)); + + // Decode clearcoat roughness texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("clearcoatRoughnessTexture", + TextureMap::CLEARCOAT_ROUGHNESS, + extension_object, output_material)); + + // Decode clearcoat normal texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("clearcoatNormalTexture", + TextureMap::CLEARCOAT_NORMAL, + extension_object, output_material)); + + return OkStatus(); +} + +Status GltfDecoder::DecodeMaterialVolumeExtension( + const tinygltf::Material &input_material, int input_material_index, + Material *output_material) { + // Do nothing if extension is absent. + const auto &extension_it = + input_material.extensions.find("KHR_materials_volume"); + if (extension_it == input_material.extensions.end()) { + return OkStatus(); + } + + output_material->SetHasVolume(true); + const tinygltf::Value::Object &extension_object = + extension_it->second.Get(); + + // Decode thickness factor. + float value; + bool success; + DRACO_ASSIGN_OR_RETURN( + success, DecodeFloat("thicknessFactor", extension_object, &value)); + if (success) { + // Volume thickness factor is given in the coordinate space of the model. + // When the model is loaded as draco::Mesh, the scene graph transformations + // are applied to position attribute. Since this effectively scales the + // model coordinate space, the volume thickness factor also must be scaled. + // No scaling is done when the model is loaded as draco::Scene. + float scale = 1.0f; + if (scene_ == nullptr) { + if (gltf_primitive_material_to_scales_.count(input_material_index) == 1) { + const std::vector &scales = + gltf_primitive_material_to_scales_[input_material_index]; + + // It is only possible to scale the volume thickness factor if all + // primitives using this material have the same transformation scale. + // An alternative would be to create a separate meterial for each scale. + scale = scales[0]; + for (int i = 1; i < scales.size(); i++) { + // Note that close-enough scales could also be permitted. + if (scales[i] != scale) { + return ErrorStatus("Cannot represent volume thickness in a mesh."); + } + } + } + } + output_material->SetThicknessFactor(scale * value); + } + + // Decode attenuation distance. + DRACO_ASSIGN_OR_RETURN( + success, DecodeFloat("attenuationDistance", extension_object, &value)); + if (success) { + output_material->SetAttenuationDistance(value); + } + + // Decode attenuation color. + Vector3f vector; + DRACO_ASSIGN_OR_RETURN( + success, DecodeVector3f("attenuationColor", extension_object, &vector)); + if (success) { + output_material->SetAttenuationColor(vector); + } + + // Decode thickness texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("thicknessTexture", TextureMap::THICKNESS, + extension_object, output_material)); + + return OkStatus(); +} + +Status GltfDecoder::DecodeMaterialIorExtension( + const tinygltf::Material &input_material, Material *output_material) { + // Do nothing if extension is absent. + const auto &extension_it = + input_material.extensions.find("KHR_materials_ior"); + if (extension_it == input_material.extensions.end()) { + return OkStatus(); + } + + output_material->SetHasIor(true); + const tinygltf::Value::Object &extension_object = + extension_it->second.Get(); + + // Decode index of refraction. + float value; + DRACO_ASSIGN_OR_RETURN(const bool success, + DecodeFloat("ior", extension_object, &value)); + if (success) { + output_material->SetIor(value); + } + + return OkStatus(); +} + +Status GltfDecoder::DecodeMaterialSpecularExtension( + const tinygltf::Material &input_material, Material *output_material) { + // Do nothing if extension is absent. + const auto &extension_it = + input_material.extensions.find("KHR_materials_specular"); + if (extension_it == input_material.extensions.end()) { + return OkStatus(); + } + + output_material->SetHasSpecular(true); + const tinygltf::Value::Object &extension_object = + extension_it->second.Get(); + + // Decode specular factor. + float value; + bool success; + DRACO_ASSIGN_OR_RETURN( + success, DecodeFloat("specularFactor", extension_object, &value)); + if (success) { + output_material->SetSpecularFactor(value); + } + + // Decode specular color factor. + Vector3f vector; + DRACO_ASSIGN_OR_RETURN(success, DecodeVector3f("specularColorFactor", + extension_object, &vector)); + if (success) { + output_material->SetSpecularColorFactor(vector); + } + + // Decode speclar texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("specularTexture", TextureMap::SPECULAR, + extension_object, output_material)); + + // Decode specular color texture. + DRACO_RETURN_IF_ERROR(DecodeTexture("specularColorTexture", + TextureMap::SPECULAR_COLOR, + extension_object, output_material)); + + return OkStatus(); +} + +StatusOr GltfDecoder::DecodeFloat(const std::string &name, + const tinygltf::Value::Object &object, + float *value) { + const auto &it = object.find(name); + if (it == object.end()) { + return false; + } + const tinygltf::Value &number = it->second; + if (!number.IsNumber()) { + return Status(Status::DRACO_ERROR, "Invalid " + name + "."); + } + *value = number.Get(); + return true; +} + +StatusOr GltfDecoder::DecodeInt(const std::string &name, + const tinygltf::Value::Object &object, + int *value) { + const auto &it = object.find(name); + if (it == object.end()) { + return false; + } + const tinygltf::Value &number = it->second; + if (!number.IsNumber()) { + return ErrorStatus("Invalid " + name + "."); + } + *value = number.Get(); + return true; +} + +StatusOr GltfDecoder::DecodeString(const std::string &name, + const tinygltf::Value::Object &object, + std::string *value) { + const auto &it = object.find(name); + if (it == object.end()) { + return false; + } + const tinygltf::Value &string = it->second; + if (!string.IsString()) { + return ErrorStatus("Invalid " + name + "."); + } + *value = string.Get(); + return true; +} + +StatusOr GltfDecoder::DecodePropertyTableData( + const std::string &name, const tinygltf::Value::Object &object, + PropertyTable::Property::Data *data) { + int buffer_view_index; + DRACO_ASSIGN_OR_RETURN(const bool success, + DecodeInt(name, object, &buffer_view_index)); + if (!success) { + return false; + } + DRACO_RETURN_IF_ERROR( + CopyDataFromBufferView(gltf_model_, buffer_view_index, &data->data)); + data->target = gltf_model_.bufferViews[buffer_view_index].target; + return true; +} + +StatusOr GltfDecoder::DecodeVector3f( + const std::string &name, const tinygltf::Value::Object &object, + Vector3f *value) { + const auto &it = object.find(name); + if (it == object.end()) { + return false; + } + const tinygltf::Value &array = it->second; + if (!array.IsArray() || array.Size() != 3) { + return Status(Status::DRACO_ERROR, "Invalid " + name + "."); + } + for (int i = 0; i < array.Size(); i++) { + const tinygltf::Value &array_entry = array.Get(i); + if (!array_entry.IsNumber()) { + return Status(Status::DRACO_ERROR, "Invalid " + name + "."); + } + (*value)[i] = array_entry.Get(); + } + return true; +} + +Status GltfDecoder::DecodeTexture(const std::string &name, + TextureMap::Type type, + const tinygltf::Value::Object &object, + Material *material) { + tinygltf::TextureInfo info; + DRACO_RETURN_IF_ERROR(ParseTextureInfo(name, object, &info)); + DRACO_RETURN_IF_ERROR(CheckAndAddTextureToDracoMaterial( + info.index, info.texCoord, info.extensions, material, type)); + return OkStatus(); +} + +Status GltfDecoder::ParseTextureInfo( + const std::string &texture_name, + const tinygltf::Value::Object &container_object, + tinygltf::TextureInfo *texture_info) { + // Note that tinygltf only parses material textures and not material extension + // textures. This method mimics the behavior of tinygltf's private function + // ParseTextureInfo() in order for Draco to decode extension textures. + + // Do nothing if texture with such name is absent. + const auto &texture_object_it = container_object.find(texture_name); + if (texture_object_it == container_object.end()) { + return OkStatus(); + } + + const tinygltf::Value::Object &texture_object = + texture_object_it->second.Get(); + + // Decode texture index. + const auto &index_it = texture_object.find("index"); + if (index_it != texture_object.end()) { + const tinygltf::Value &value = index_it->second; + if (!value.IsNumber()) { + return Status(Status::DRACO_ERROR, "Invalid texture index."); + } + texture_info->index = value.Get(); + } + + // Decode texture coordinate index. + const auto &tex_coord_it = texture_object.find("texCoord"); + if (tex_coord_it != texture_object.end()) { + const tinygltf::Value &value = tex_coord_it->second; + if (!value.IsInt()) { + return Status(Status::DRACO_ERROR, "Invalid texture texCoord."); + } + texture_info->texCoord = value.Get(); + } + + // Decode texture extensions. + const auto &extensions_it = texture_object.find("extensions"); + if (extensions_it != texture_object.end()) { + const tinygltf::Value &extensions = extensions_it->second; + if (!extensions.IsObject()) { + return Status(Status::DRACO_ERROR, "Invalid extension."); + } + for (const std::string &key : extensions.Keys()) { + texture_info->extensions[key] = extensions.Get(key); + } + } + + // Decode texture extras. + const auto &extras_it = texture_object.find("extras"); + if (extras_it != texture_object.end()) { + texture_info->extras = extras_it->second; + } + + return OkStatus(); +} + +Status GltfDecoder::AddMaterialsToScene() { + for (int input_material_index = 0; + input_material_index < gltf_model_.materials.size(); + ++input_material_index) { + Material *const output_material = + scene_->GetMaterialLibrary().MutableMaterial(input_material_index); + DRACO_RETURN_IF_ERROR( + AddGltfMaterial(input_material_index, output_material)); + } + + // Check if we need to add a default material for primitives without an + // assigned material. + const int default_material_index = + scene_->GetMaterialLibrary().NumMaterials(); + bool default_material_needed = false; + for (MeshGroupIndex mgi(0); mgi < scene_->NumMeshGroups(); ++mgi) { + MeshGroup *const mg = scene_->GetMeshGroup(mgi); + for (int mi = 0; mi < mg->NumMeshInstances(); ++mi) { + MeshGroup::MeshInstance &mesh_instance = mg->GetMeshInstance(mi); + if (mesh_instance.material_index == -1) { + mesh_instance.material_index = default_material_index; + default_material_needed = true; + } + } + } + if (default_material_needed) { + // Create an empty default material (our defaults correspond to glTF + // defaults). + scene_->GetMaterialLibrary().MutableMaterial(default_material_index); + } + + std::unordered_set meshes_that_need_tangents; + // Check if we need to generate tangent space for any of the loaded meshes. + for (MeshGroupIndex mgi(0); mgi < scene_->NumMeshGroups(); ++mgi) { + const MeshGroup *const mg = scene_->GetMeshGroup(mgi); + for (int mi = 0; mi < mg->NumMeshInstances(); ++mi) { + const MeshGroup::MeshInstance &mesh_instance = mg->GetMeshInstance(mi); + const auto tangent_map = + scene_->GetMaterialLibrary() + .GetMaterial(mesh_instance.material_index) + ->GetTextureMapByType(TextureMap::NORMAL_TANGENT_SPACE); + if (tangent_map != nullptr) { + Mesh &mesh = scene_->GetMesh(mesh_instance.mesh_index); + if (mesh.GetNamedAttribute(GeometryAttribute::TANGENT) == nullptr) { + meshes_that_need_tangents.insert(&mesh); + } + } + } + } + + return OkStatus(); +} + +Status GltfDecoder::AddSkinsToScene() { + for (int source_skin_index = 0; source_skin_index < gltf_model_.skins.size(); + ++source_skin_index) { + const tinygltf::Skin &skin = gltf_model_.skins[source_skin_index]; + const SkinIndex skin_index = scene_->AddSkin(); + Skin *const new_skin = scene_->GetSkin(skin_index); + + // The skin index was set previously while processing the nodes. + if (skin_index.value() != source_skin_index) { + return Status(Status::DRACO_ERROR, "Skin indices are mismatched."); + } + + if (skin.inverseBindMatrices >= 0) { + const tinygltf::Accessor &accessor = + gltf_model_.accessors[skin.inverseBindMatrices]; + DRACO_RETURN_IF_ERROR(TinyGltfUtils::AddAccessorToAnimationData( + gltf_model_, accessor, &new_skin->GetInverseBindMatrices())); + } + + if (skin.skeleton >= 0) { + const auto it = gltf_node_to_scenenode_index_.find(skin.skeleton); + if (it == gltf_node_to_scenenode_index_.end()) { + // TODO(b/200317162): If skeleton is not found set the default. + return Status(Status::DRACO_ERROR, + "Could not find skeleton in the skin."); + } + new_skin->SetJointRoot(it->second); + } + + for (int joint : skin.joints) { + const auto it = gltf_node_to_scenenode_index_.find(joint); + if (it == gltf_node_to_scenenode_index_.end()) { + // TODO(b/200317162): If skeleton is not found set the default. + return Status(Status::DRACO_ERROR, + "Could not find skeleton in the skin."); + } + new_skin->AddJoint(it->second); + } + } + return OkStatus(); +} + +void GltfDecoder::MoveNonMaterialTextures(Mesh *mesh) { + std::unordered_set non_material_textures; + for (MeshFeaturesIndex i(0); i < mesh->NumMeshFeatures(); i++) { + Texture *const texture = mesh->GetMeshFeatures(i).GetTextureMap().texture(); + if (texture != nullptr) { + non_material_textures.insert(texture); + } + } + MoveNonMaterialTextures(non_material_textures, + &mesh->GetMaterialLibrary().MutableTextureLibrary(), + &mesh->GetNonMaterialTextureLibrary()); +} + +void GltfDecoder::MoveNonMaterialTextures(Scene *scene) { + std::unordered_set non_material_textures; + for (MeshIndex i(0); i < scene->NumMeshes(); i++) { + for (MeshFeaturesIndex j(0); j < scene->GetMesh(i).NumMeshFeatures(); j++) { + Texture *const texture = + scene->GetMesh(i).GetMeshFeatures(j).GetTextureMap().texture(); + if (texture != nullptr) { + non_material_textures.insert(texture); + } + } + } + MoveNonMaterialTextures(non_material_textures, + &scene->GetMaterialLibrary().MutableTextureLibrary(), + &scene->GetNonMaterialTextureLibrary()); +} + +void GltfDecoder::MoveNonMaterialTextures( + const std::unordered_set &non_material_textures, + TextureLibrary *material_tl, TextureLibrary *non_material_tl) { + // TODO(vytyaz): Consider textures that are both material and non-material. + for (int i = 0; i < material_tl->NumTextures(); i++) { + // Move non-material texture from material to non-material texture library. + if (non_material_textures.count(material_tl->GetTexture(i)) == 1) { + non_material_tl->PushTexture(material_tl->RemoveTexture(i--)); + } + } +} + +bool GltfDecoder::PrimitiveSignature::operator==( + const PrimitiveSignature &signature) const { + return primitive.indices == signature.primitive.indices && + primitive.attributes == signature.primitive.attributes && + primitive.extras == signature.primitive.extras && + primitive.extensions == signature.primitive.extensions && + primitive.mode == signature.primitive.mode && + primitive.targets == signature.primitive.targets; +} + +size_t GltfDecoder::PrimitiveSignature::Hash::operator()( + const PrimitiveSignature &signature) const { + size_t hash = 79; // Magic number. + hash = HashCombine(signature.primitive.attributes.size(), hash); + for (auto it = signature.primitive.attributes.begin(); + it != signature.primitive.attributes.end(); ++it) { + hash = HashCombine(it->first, hash); + hash = HashCombine(it->second, hash); + } + hash = HashCombine(signature.primitive.indices, hash); + hash = HashCombine(signature.primitive.mode, hash); + return hash; +} + +StatusOr> GltfDecoder::BuildMeshFromBuilder( + bool use_mesh_builder, TriangleSoupMeshBuilder *mb, PointCloudBuilder *pb) { + std::unique_ptr mesh; + if (use_mesh_builder) { + mesh = mb->Finalize(); + } else { + std::unique_ptr pc = pb->Finalize(true); + if (pc) { + mesh.reset(new Mesh()); + PointCloud *mesh_pc = mesh.get(); + mesh_pc->Copy(*pc); + } + } + if (!mesh) { + return ErrorStatus("Failed to build Draco mesh from glTF data."); + } + return mesh; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/io/gltf_decoder.h b/contrib/draco/src/draco/io/gltf_decoder.h new file mode 100644 index 000000000..2ae12106e --- /dev/null +++ b/contrib/draco/src/draco/io/gltf_decoder.h @@ -0,0 +1,524 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_GLTF_DECODER_H_ +#define DRACO_IO_GLTF_DECODER_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include +#include +#include +#include + +#include "draco/core/decoder_buffer.h" +#include "draco/core/status.h" +#include "draco/core/status_or.h" +#include "draco/io/tiny_gltf_utils.h" +#include "draco/mesh/mesh.h" +#include "draco/mesh/triangle_soup_mesh_builder.h" +#include "draco/point_cloud/point_cloud_builder.h" +#include "draco/scene/scene.h" + +namespace draco { + +// Decodes a glTF file and returns a draco::Mesh. All of the |mesh|'s attributes +// will be merged into one draco::Mesh +class GltfDecoder { + public: + GltfDecoder(); + + // Decodes a glTF file stored in the input |file_name| or |buffer| to a Mesh. + // The second form returns a vector of files used as input to the mesh during + // the decoding process. Returns nullptr when decode fails. + StatusOr> DecodeFromFile(const std::string &file_name); + StatusOr> DecodeFromFile( + const std::string &file_name, std::vector *mesh_files); + StatusOr> DecodeFromBuffer(DecoderBuffer *buffer); + + // Decodes a glTF file stored in the input |file_name| or |buffer| to a Scene. + // The second form returns a vector of files used as input to the scene during + // the decoding process. Returns nullptr if the decode fails. + StatusOr> DecodeFromFileToScene( + const std::string &file_name); + StatusOr> DecodeFromFileToScene( + const std::string &file_name, std::vector *scene_files); + StatusOr> DecodeFromBufferToScene( + DecoderBuffer *buffer); + + // Scene graph can be loaded either as a tree or a general directed acyclic + // graph (DAG) that allows multiple parent nodes. By default. we decode the + // scene graph as a tree. If the tree mode is selected and the input contains + // nodes with multiple parents, these nodes are duplicated to form a tree. + // TODO(ostava): Add support for DAG mode to other parts of the Draco + // library. + enum class GltfSceneGraphMode { TREE, DAG }; + void SetSceneGraphMode(GltfSceneGraphMode mode) { + gltf_scene_graph_mode_ = mode; + } + + private: + // Loads |file_name| into |gltf_model_|. Fills |input_files| with paths to all + // input files when non-null. + Status LoadFile(const std::string &file_name, + std::vector *input_files); + + // Loads |gltf_model_| from |buffer| in GLB format. + Status LoadBuffer(const DecoderBuffer &buffer); + + // Builds mesh from |gltf_model_|. + StatusOr> BuildMesh(); + + // Checks |gltf_model_| for unsupported features. If |gltf_model_| contains + // unsupported features then the function will return with a status code of + // UNSUPPORTED_FEATURE. + Status CheckUnsupportedFeatures(); + + // Decodes a glTF Node as well as any child Nodes. If |node| contains a mesh + // it will process all of the mesh's primitives. + Status DecodeNode(int node_index, const Eigen::Matrix4d &parent_matrix); + + // Decodes the number of entries in the first attribute of a given glTF + // |primitive|. Note that all attributes have the same entry count according + // to glTF 2.0 spec. + StatusOr DecodePrimitiveAttributeCount( + const tinygltf::Primitive &primitive) const; + + // Decodes the number of indices in a given glTF |primitive|. If primitive's + // indices property is not defined, the index count is implied from the entry + // count of a primitive attribute. + StatusOr DecodePrimitiveIndicesCount( + const tinygltf::Primitive &primitive) const; + + // Decodes indices property of a given glTF |primitive|. If primitive's + // indices property is not defined, the indices are generated based on entry + // count of a primitive attribute. + StatusOr> DecodePrimitiveIndices( + const tinygltf::Primitive &primitive) const; + + // Decodes a glTF Primitive. All of the |primitive|'s attributes will be + // merged into the draco::Mesh output if they are of the same type that + // already has been decoded. + Status DecodePrimitive(const tinygltf::Primitive &primitive, + const Eigen::Matrix4d &transform_matrix); + + // Sums the number of elements per attribute for |node|'s mesh and any of + // |node|'s children. Fills out the material index map. + Status NodeGatherAttributeAndMaterialStats(const tinygltf::Node &node); + + // Sums the number of elements per attribute for all of the meshes and + // primitives. + Status GatherAttributeAndMaterialStats(); + + // Sums the attribute counts into total_attribute_counts_. + void SumAttributeStats(const std::string &attribute_name, int count); + + // Checks that all the same glTF attribute types in different meshes and + // primitives contain the same characteristics. + Status CheckTypes(const std::string &attribute_name, int component_type, + int type, bool normalized); + + // Accumulates the number of elements per attribute for |primitive|. + Status AccumulatePrimitiveStats(const tinygltf::Primitive &primitive); + + // Adds all of the attributes from the glTF file to a Draco mesh. + // GatherAttributeAndMaterialStats() must be called before this function. The + // GeometryAttribute::MATERIAL attribute will be created only if the glTF file + // contains more than one material. + template + Status AddAttributesToDracoMesh(BuilderT *builder); + + // Copies attribute data from |accessor| and adds it to a Draco mesh using the + // geometry builder |builder|. + template + Status AddAttributeValuesToBuilder(const std::string &attribute_name, + const tinygltf::Accessor &accessor, + const std::vector &indices_data, + int att_id, int number_of_elements, + const Eigen::Matrix4d &transform_matrix, + BuilderT *builder); + + // Copies the tangent attribute data from |accessor| and adds it to a Draco + // mesh. This function will transform all of the data by |transform_matrix| + // and then normalize before adding the data to the Draco mesh. + // |indices_data| is the indices data from the glTF file. |att_id| is the + // attribute id of the tangent attribute in the Draco mesh. + // |number_of_elements| is the number of faces or points this function will + // process. |reverse_winding| if set will change the orientation of the data. + template + Status AddTangentToBuilder(const tinygltf::Accessor &accessor, + const std::vector &indices_data, + int att_id, int number_of_elements, + const Eigen::Matrix4d &transform_matrix, + bool reverse_winding, BuilderT *builder); + + // Copies the texture coordinate attribute data from |accessor| and adds it to + // a Draco mesh. This function will flip the data on the horizontal axis as + // Draco meshes store the texture coordinates differently than glTF. + // |indices_data| is the indices data from the glTF file. |att_id| is the + // attribute id of the texture coordinate attribute in the Draco mesh. + // |number_of_elements| is the number of faces or points this function will + // process. |reverse_winding| if set will change the orientation of the data. + template + Status AddTexCoordToBuilder(const tinygltf::Accessor &accessor, + const std::vector &indices_data, + int att_id, int number_of_elements, + bool reverse_winding, BuilderT *builder); + + // Copies the mesh feature ID attribute data from |accessor| and adds it to a + // Draco mesh. |indices_data| is the indices data from the glTF file. |att_id| + // is the attribute ID of the mesh feature ID attribute in the Draco mesh. + // |number_of_elements| is the number of faces or points this function will + // process. |reverse_winding| if set will change the orientation of the data. + template + Status AddFeatureIdToBuilder(const tinygltf::Accessor &accessor, + const std::vector &indices_data, + int att_id, int number_of_elements, + bool reverse_winding, + const std::string &attribute_name, + BuilderT *builder); + + // Copies the attribute data from |accessor| and adds it to a Draco mesh. + // This function will transform all of the data by |transform_matrix| before + // adding the data to the Draco mesh. |indices_data| is the indices data + // from the glTF file. |att_id| is the attribute id of the attribute in the + // Draco mesh. |number_of_elements| is the number of faces or points this + // function will process. |normalize| if set will normalize all of the vector + // data after transformation. |reverse_winding| if set will change the + // orientation of the data. + template + Status AddTransformedDataToBuilder(const tinygltf::Accessor &accessor, + const std::vector &indices_data, + int att_id, int number_of_elements, + const Eigen::Matrix4d &transform_matrix, + bool normalize, bool reverse_winding, + BuilderT *builder); + + // Sets values in |data| into the builder |builder| for |att_id|. + template + void SetValuesForBuilder(const std::vector &indices_data, + int att_id, int number_of_elements, + const std::vector &data, bool reverse_winding, + TriangleSoupMeshBuilder *builder); + template + void SetValuesForBuilder(const std::vector &indices_data, + int att_id, int number_of_elements, + const std::vector &data, bool reverse_winding, + PointCloudBuilder *builder); + + // Sets values in |data| into the mesh builder |mb| for |att_id|. + // |reverse_winding| if set will change the orientation of the data. + template + void SetValuesPerFace(const std::vector &indices_data, int att_id, + int number_of_faces, const std::vector &data, + bool reverse_winding, TriangleSoupMeshBuilder *mb); + + // Returns an address pointing to the content stored in |data|. This is used + // when passing values to mesh / point cloud builder when the input type can + // be either a VectorD or an arithmetic type. + template + const void *GetDataContentAddress(const T &data) const; + + // Adds the attribute data in |accessor| to |mb| for unique attribute + // |att_id|. |indices_data| is the mesh's indices data. |reverse_winding| if + // set will change the orientation of the data. + template + Status AddAttributeDataByTypes(const tinygltf::Accessor &accessor, + const std::vector &indices_data, + int att_id, int number_of_elements, + bool reverse_winding, BuilderT *builder); + + // Adds the textures to |owner|. + template + Status CopyTextures(T *owner); + + // Sets extra attribute properties on a constructed draco mesh. + void SetAttributePropertiesOnDracoMesh(Mesh *mesh); + + // Adds the materials to |mesh|. + Status AddMaterialsToDracoMesh(Mesh *mesh); + + // Adds the material data for the GeometryAttribute::MATERIAL attribute to the + // Draco mesh. + template + Status AddMaterialDataToBuilder(int material_value, int number_of_elements, + BuilderT *builder); + template + Status AddMaterialDataToBuilderInternal(T material_value, int number_of_faces, + TriangleSoupMeshBuilder *builder); + template + Status AddMaterialDataToBuilderInternal(T material_value, + int number_of_points, + PointCloudBuilder *builder); + + // Checks if the glTF file contains a texture. If there is a texture, this + // function will read the texture data and add it to the Draco |material|. If + // there is no texture, this function will return OkStatus(). |texture_info| + // is the data structure containing information about the texture in the glTF + // file. |type| is the type of texture defined by Draco. This is not the same + // as the texture coordinate attribute id. + Status CheckAndAddTextureToDracoMaterial( + int texture_index, int tex_coord_attribute_index, + const tinygltf::ExtensionMap &tex_info_ext, Material *material, + TextureMap::Type type); + + // Decode glTF file to scene. + Status DecodeGltfToScene(); + + // Decode glTF lights into a scene. + Status AddLightsToScene(); + + // Decodes glTF materials variants names into a scene. + Status AddMaterialsVariantsNamesToScene(); + + // Decode glTF animations into a scene. All of the glTF nodes must be decoded + // to the scene before this function is called. + Status AddAnimationsToScene(); + + // Decode glTF node into a Draco scene. |parent_index| is the index of the + // parent node. If |node| is a root node set |parent_index| to + // |kInvalidSceneNodeIndex|. All glTF lights must be decoded to the scene + // before this function is called. + Status DecodeNodeForScene(int node_index, SceneNodeIndex parent_index); + + // Decode glTF primitive into a Draco scene. + Status DecodePrimitiveForScene(const tinygltf::Primitive &primitive, + MeshGroup *mesh_group); + + // Decodes glTF materials variants from |extension| and adds it into materials + // variants |mappings|. Before calling this function, all materials variants + // names must be decoded by calling AddMaterialsVariantsNamesToScene(). + Status DecodeMaterialsVariantsMappings( + const tinygltf::Value::Object &extension, + std::vector *mappings); + + // Decodes glTF mesh feature ID sets from all glTF primitives and adds them to + // |mesh|. + Status AddMeshFeaturesToDracoMesh(Mesh *mesh); + + // Decodes glTF mesh feature ID sets from glTF primitive in glTF node at + // |node_index| and adds them to |mesh|. + Status AddMeshFeaturesToDracoMesh(int node_index, Mesh *mesh); + + // Decodes glTF structural metadata from glTF model and adds it to |geometry|. + template + Status AddStructuralMetadataToGeometry(GeometryT *geometry); + + // Decodes glTF mesh feature ID sets from |primitive| and adds them to |mesh|. + Status DecodeMeshFeatures(const tinygltf::Primitive &primitive, + TextureLibrary *texture_library, Mesh *mesh); + + // Decodes glTF mesh feature ID sets from |extension| and adds them to the + // |mesh_features| vector. + Status DecodeMeshFeatures( + const tinygltf::Value::Object &extension, TextureLibrary *texture_library, + std::vector> *mesh_features); + + // Adds an attribute of type |attribute_name| to |builder|. Returns the + // attribute id. + template + StatusOr AddAttribute(const std::string &attribute_name, + int component_type, int type, BuilderT *builder); + + // Adds an attribute of |attribute_type| to |builder|. Returns the attribute + // id. + template + StatusOr AddAttribute(GeometryAttribute::Type attribute_type, + int component_type, int type, BuilderT *builder); + + // Returns true if the KHR_texture_transform extension is set in |extension|. + // If the KHR_texture_transform extension is set then the values are returned + // in |transform|. + StatusOr CheckKhrTextureTransform( + const tinygltf::ExtensionMap &extension, TextureTransform *transform); + + // Adds glTF material |input_material_index| to |output_material|. + Status AddGltfMaterial(int input_material_index, Material *output_material); + + // Adds unlit property from glTF |input_material| to |output_material|. + void DecodeMaterialUnlitExtension(const tinygltf::Material &input_material, + Material *output_material); + + // Adds sheen properties from glTF |input_material| to |output_material|. + Status DecodeMaterialSheenExtension(const tinygltf::Material &input_material, + Material *output_material); + + // Adds transmission from glTF |input_material| to |output_material|. + Status DecodeMaterialTransmissionExtension( + const tinygltf::Material &input_material, Material *output_material); + + // Adds clearcoat properties from glTF |input_material| to |output_material|. + Status DecodeMaterialClearcoatExtension( + const tinygltf::Material &input_material, Material *output_material); + + // Adds volume properties from glTF |input_material| to |output_material|. + Status DecodeMaterialVolumeExtension(const tinygltf::Material &input_material, + int input_material_index, + Material *output_material); + + // Adds ior properties from glTF |input_material| to |output_material|. + Status DecodeMaterialIorExtension(const tinygltf::Material &input_material, + Material *output_material); + + // Adds specular properties from glTF |input_material| to |output_material|. + Status DecodeMaterialSpecularExtension( + const tinygltf::Material &input_material, Material *output_material); + + // Decodes a float value with |name| from |object| to |value| and returns true + // if a well-formed value with such |name| is present. + static StatusOr DecodeFloat(const std::string &name, + const tinygltf::Value::Object &object, + float *value); + + // Decodes an integer value with |name| from |object| to |value| and returns + // true if a well-formed value with such |name| is present. + static StatusOr DecodeInt(const std::string &name, + const tinygltf::Value::Object &object, + int *value); + + // Decodes a string value with |name| from |object| to |value| and returns + // true if a well-formed value with such |name| is present. + static StatusOr DecodeString(const std::string &name, + const tinygltf::Value::Object &object, + std::string *value); + + // Decodes data and data target from buffer view index with |name| in |object| + // to |data| and returns true if a well-formed data is present. + StatusOr DecodePropertyTableData(const std::string &name, + const tinygltf::Value::Object &object, + PropertyTable::Property::Data *data); + + // Decodes a 3D vector with |name| from |object| to |value| and returns true + // if a well-formed vector with such |name| is present. + static StatusOr DecodeVector3f(const std::string &name, + const tinygltf::Value::Object &object, + Vector3f *value); + + // Decodes a texture with |name| from |object| and adds it to |material| as a + // texture map of |type|. + Status DecodeTexture(const std::string &name, TextureMap::Type type, + const tinygltf::Value::Object &object, + Material *material); + + // Reads texture with |texture_name| from |container_object| into + // |texture_info|. + static Status ParseTextureInfo( + const std::string &texture_name, + const tinygltf::Value::Object &container_object, + tinygltf::TextureInfo *texture_info); + + // Adds the materials to the scene. + Status AddMaterialsToScene(); + + // Adds the skins to the scene. + Status AddSkinsToScene(); + + // All material and non-material textures (e.g., from EXT_mesh_features) are + // initially loaded into a texture library inside the the material library. + // These methods move |non_material_textures| from material texture library + // |material_tl| to non-material texture library |non_material_tl|. + static void MoveNonMaterialTextures(Mesh *mesh); + static void MoveNonMaterialTextures(Scene *scene); + static void MoveNonMaterialTextures( + const std::unordered_set &non_material_textures, + TextureLibrary *material_tl, TextureLibrary *non_material_tl); + + // Builds and returns a mesh constructed from either mesh builder |mb| or + // point cloud builder |pb|. Mesh builder is used if |use_mesh_builder| is set + // to true. + static StatusOr> BuildMeshFromBuilder( + bool use_mesh_builder, TriangleSoupMeshBuilder *mb, + PointCloudBuilder *pb); + + // Map of glTF Mesh to Draco scene mesh group. + std::map gltf_mesh_to_scene_mesh_group_; + + // Data structure that stores the glTF data. + tinygltf::Model gltf_model_; + + // Path to the glTF file. + std::string input_file_name_; + + // Class used to build the Draco mesh. + TriangleSoupMeshBuilder mb_; + PointCloudBuilder pb_; + + // Next face index used when adding attribute data to the Draco mesh. + int next_face_id_; + + // Next point index used when adding attribute data to the point cloud. + int next_point_id_; + + // Total number of indices from all the meshes and primitives. + int total_face_indices_count_; + int total_point_indices_count_; + + // This is the id of the GeometryAttribute::MATERIAL attribute added to the + // Draco mesh. + int material_att_id_; + + // Data used when decoding the entire glTF asset into a single draco::Mesh. + // The struct tracks the total number of elements across all matching + // attributes and it ensures all matching attributes are compatible. + struct MeshAttributeData { + int component_type = 0; + int attribute_type = 0; + bool normalized = false; + int total_attribute_counts = 0; + }; + + // Map of glTF attribute name to attribute component type. + std::map mesh_attribute_data_; + + // Map of glTF attribute name to Draco mesh attribute id. + std::map attribute_name_to_draco_mesh_attribute_id_; + + // Map of glTF material to Draco material index. + std::map gltf_primitive_material_to_draco_material_; + + // Map of glTF material index to transformation scales of primitives. + std::map> gltf_primitive_material_to_scales_; + + // Map of glTF image to Draco textures. + std::map gltf_image_to_draco_texture_; + + std::unique_ptr scene_; + + // Map of glTF Node to local store order. + std::map gltf_node_to_scenenode_index_; + + // Selected mode of the decoded scene graph. + GltfSceneGraphMode gltf_scene_graph_mode_ = GltfSceneGraphMode::TREE; + + // Functionality for deduping primitives on decode. + struct PrimitiveSignature { + const tinygltf::Primitive &primitive; + explicit PrimitiveSignature(const tinygltf::Primitive &primitive) + : primitive(primitive) {} + bool operator==(const PrimitiveSignature &signature) const; + struct Hash { + size_t operator()(const PrimitiveSignature &signature) const; + }; + }; + std::unordered_map + gltf_primitive_to_draco_mesh_index_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_GLTF_DECODER_H_ diff --git a/contrib/draco/src/draco/io/gltf_decoder_test.cc b/contrib/draco/src/draco/io/gltf_decoder_test.cc new file mode 100644 index 000000000..fade3ee26 --- /dev/null +++ b/contrib/draco/src/draco/io/gltf_decoder_test.cc @@ -0,0 +1,1402 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/gltf_decoder.h" + +#include +#include +#include +#include +#include +#include + +#include "draco/material/material_library.h" +#include "draco/scene/mesh_group.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/constants.h" +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/core/draco_types.h" +#include "draco/io/gltf_test_helper.h" +#include "draco/io/texture_io.h" +#include "draco/mesh/mesh_are_equivalent.h" +#include "draco/mesh/mesh_utils.h" +#include "draco/scene/scene_indices.h" +#include "draco/scene/scene_utils.h" +#include "draco/texture/texture_utils.h" + +namespace draco { + +namespace { +std::unique_ptr DecodeGltfFile(const std::string &file_name) { + const std::string path = GetTestFileFullPath(file_name); + GltfDecoder decoder; + + auto maybe_geometry = decoder.DecodeFromFile(path); + if (!maybe_geometry.ok()) { + return nullptr; + } + std::unique_ptr geometry = std::move(maybe_geometry).value(); + return geometry; +} + +std::unique_ptr DecodeGltfFileToScene(const std::string &file_name) { + const std::string path = GetTestFileFullPath(file_name); + GltfDecoder decoder; + + auto maybe_scene = decoder.DecodeFromFileToScene(path); + if (!maybe_scene.ok()) { + return nullptr; + } + std::unique_ptr scene = std::move(maybe_scene).value(); + return scene; +} + +void CompareVectorArray(const std::array &a, + const std::array &b) { + for (int v = 0; v < 3; ++v) { + for (int c = 0; c < 3; ++c) { + EXPECT_FLOAT_EQ(a[v][c], b[v][c]) << "v:" << v << " c:" << c; + } + } +} +} // namespace + +// Tests multiple textures. +TEST(GltfDecoderTest, SphereGltf) { + const std::string file_name = "sphere.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->num_attributes(), 4) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 231) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 224) << "Unexpected number of faces."; + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 2); +} + +TEST(GltfDecoderTest, TriangleGltf) { + const std::string file_name = "one_face_123.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->num_attributes(), 1) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 3) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 1) << "Unexpected number of faces."; + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 0); + + const auto *const pos_attribute = + mesh->GetNamedAttribute(GeometryAttribute::POSITION); + EXPECT_NE(pos_attribute, nullptr); + const auto &face = mesh->face(FaceIndex(0)); + std::array pos; + for (int c = 0; c < 3; ++c) { + pos_attribute->GetMappedValue(face[c], &pos[c][0]); + } + + // Test position values match. + std::array pos_test; + pos_test[0] = Vector3f(1, 0.0999713, 0); + pos_test[1] = Vector3f(2.00006104, 0.01, 0); + pos_test[2] = Vector3f(3, 0.10998169, 0); + CompareVectorArray(pos, pos_test); +} + +TEST(GltfDecoderTest, MirroredTriangleGltf) { + const std::string file_name = "one_face_123_mirror.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->num_attributes(), 1) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 3) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 1) << "Unexpected number of faces."; + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 0); + + const auto *const pos_attribute = + mesh->GetNamedAttribute(GeometryAttribute::POSITION); + EXPECT_NE(pos_attribute, nullptr); + const auto &face = mesh->face(FaceIndex(0)); + std::array pos; + for (int c = 0; c < 3; ++c) { + pos_attribute->GetMappedValue(face[c], &pos[c][0]); + } + + // Test position values match. + std::array pos_test; + pos_test[0] = Vector3f(-1, -0.0999713, 0); + pos_test[1] = Vector3f(-3, -0.10998169, 0); + pos_test[2] = Vector3f(-2.00006104, -0.01, 0); + CompareVectorArray(pos, pos_test); +} + +TEST(GltfDecoderTest, TranslateTriangleGltf) { + const std::string file_name = "one_face_123_translated.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->num_attributes(), 1) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 3) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 1) << "Unexpected number of faces."; + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 0); + + const auto *const pos_attribute = + mesh->GetNamedAttribute(GeometryAttribute::POSITION); + EXPECT_NE(pos_attribute, nullptr); + const auto &face = mesh->face(FaceIndex(0)); + std::array pos; + for (int c = 0; c < 3; ++c) { + pos_attribute->GetMappedValue(face[c], &pos[c][0]); + } + + // Test position values match. The glTF file contains a matrix in the main + // node. The matrix defines a translation of (-1.5, 5.0, 2.3). + std::array pos_test; + pos_test[0] = Vector3f(1, 0.0999713, 0); + pos_test[1] = Vector3f(2.00006104, 0.01, 0); + pos_test[2] = Vector3f(3, 0.10998169, 0); + const Vector3f translate(-1.5, 5.0, 2.3); + for (int v = 0; v < 3; ++v) { + pos_test[v] = pos_test[v] + translate; + } + CompareVectorArray(pos, pos_test); +} + +// Tests multiple materials. +TEST(GltfDecoderTest, MilkTruckGltf) { + const std::string file_name = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->num_attributes(), 4) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 3564) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 3624) << "Unexpected number of faces."; + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 4); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(1)->NumTextureMaps(), 0); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(2)->NumTextureMaps(), 0); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(3)->NumTextureMaps(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->GetName(), "truck"); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(1)->GetName(), "glass"); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(2)->GetName(), + "window_trim"); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(3)->GetName(), "wheels"); +} + +TEST(GltfDecoderTest, SceneMilkTruckGltf) { + const std::string file_name = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + + ASSERT_EQ(scene->NumMeshes(), 4); + ASSERT_EQ(scene->NumMeshGroups(), 2); + ASSERT_EQ(scene->NumNodes(), 5); + ASSERT_EQ(scene->NumRootNodes(), 1); + ASSERT_EQ(scene->NumLights(), 0); + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterials(), 4); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(1)->NumTextureMaps(), 0); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(2)->NumTextureMaps(), 0); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(3)->NumTextureMaps(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->GetName(), "truck"); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(1)->GetName(), "glass"); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(2)->GetName(), + "window_trim"); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(3)->GetName(), "wheels"); + ASSERT_EQ(scene->NumAnimations(), 1); + ASSERT_EQ(scene->NumSkins(), 0); + for (AnimationIndex i(0); i < scene->NumAnimations(); ++i) { + const Animation *const animation = scene->GetAnimation(i); + ASSERT_NE(animation, nullptr); + ASSERT_EQ(animation->NumSamplers(), 2); + ASSERT_EQ(animation->NumChannels(), 2); + } + + ASSERT_EQ(scene->GetMeshGroup(MeshGroupIndex(0))->GetName(), + "Cesium_Milk_Truck"); + ASSERT_EQ(scene->GetMeshGroup(MeshGroupIndex(1))->GetName(), "Wheels"); + + // Check all of the meshes do not have any materials. + for (MeshIndex i(0); i < scene->NumMeshes(); ++i) { + const Mesh &mesh = scene->GetMesh(i); + ASSERT_EQ(mesh.GetMaterialLibrary().NumMaterials(), 0); + } +} + +TEST(GltfDecoderTest, AnimatedBonesGltf) { + const std::string file_name = "CesiumMan/glTF/CesiumMan.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 1); + const MeshGroup &mesh_group = *scene->GetMeshGroup(MeshGroupIndex(0)); + ASSERT_EQ(mesh_group.NumMeshInstances(), 1); + ASSERT_EQ(mesh_group.GetMeshInstance(0).material_index, 0); + ASSERT_EQ(scene->NumNodes(), 22); + ASSERT_EQ(scene->NumRootNodes(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(scene->NumAnimations(), 1); + ASSERT_EQ(scene->NumSkins(), 1); + for (AnimationIndex i(0); i < scene->NumAnimations(); ++i) { + const Animation *const animation = scene->GetAnimation(i); + ASSERT_NE(animation, nullptr); + ASSERT_EQ(animation->NumSamplers(), 57); + ASSERT_EQ(animation->NumChannels(), 57); + } + + // Check all of the meshes do not have any materials. + for (MeshIndex i(0); i < scene->NumMeshes(); ++i) { + const Mesh &mesh = scene->GetMesh(i); + ASSERT_EQ(mesh.GetMaterialLibrary().NumMaterials(), 0); + } +} + +TEST(GltfDecoderTest, AnimatedBonesGlb) { + const std::string file_name = "CesiumMan/glTF_Binary/CesiumMan.glb"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 1); + const MeshGroup &mesh_group = *scene->GetMeshGroup(MeshGroupIndex(0)); + ASSERT_EQ(mesh_group.NumMeshInstances(), 1); + ASSERT_EQ(mesh_group.GetMeshInstance(0).material_index, 0); + ASSERT_EQ(scene->NumNodes(), 22); + ASSERT_EQ(scene->NumRootNodes(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(scene->NumAnimations(), 1); + ASSERT_EQ(scene->NumSkins(), 1); + for (AnimationIndex i(0); i < scene->NumAnimations(); ++i) { + const Animation *const animation = scene->GetAnimation(i); + ASSERT_NE(animation, nullptr); + ASSERT_EQ(animation->NumSamplers(), 57); + ASSERT_EQ(animation->NumChannels(), 57); + } + + // Check all of the meshes do not have any materials. + for (MeshIndex i(0); i < scene->NumMeshes(); ++i) { + const Mesh &mesh = scene->GetMesh(i); + ASSERT_EQ(mesh.GetMaterialLibrary().NumMaterials(), 0); + } +} + +// Tests multiple primitives with the same material index. +TEST(GltfDecoderTest, LanternGltf) { + const std::string file_name = "Lantern/glTF/Lantern.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + + EXPECT_EQ(mesh->num_attributes(), 4) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 4145) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 5394) << "Unexpected number of faces."; + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 4); +} + +// Tests COLOR_0 input attribute. +TEST(GltfDecoderTest, ColorAttributeGltf) { + const std::string file_name = "test_pos_color.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->num_attributes(), 2) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 114) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 224) << "Unexpected number of faces."; + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 0); + ASSERT_NE(mesh->GetNamedAttribute(GeometryAttribute::COLOR), nullptr); + ASSERT_EQ(mesh->GetNamedAttribute(GeometryAttribute::COLOR)->data_type(), + draco::DT_UINT8); + // Ensure the normalized property for the color attribute is set properly. + ASSERT_TRUE(mesh->GetNamedAttribute(GeometryAttribute::COLOR)->normalized()); +} + +// Tests COLOR_0 input attribute when the asset is loaded into a scene. +TEST(GltfDecoderTest, ColorAttributeGltfScene) { + const std::string file_name = "test_pos_color.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_EQ(scene->NumMeshes(), 1); + const Mesh &mesh = scene->GetMesh(MeshIndex(0)); + ASSERT_NE(mesh.GetNamedAttribute(GeometryAttribute::COLOR), nullptr); + ASSERT_EQ(mesh.GetNamedAttribute(GeometryAttribute::COLOR)->data_type(), + draco::DT_UINT8); + // Ensure the normalized property for the color attribute is set properly. + ASSERT_TRUE(mesh.GetNamedAttribute(GeometryAttribute::COLOR)->normalized()); +} + +// Tests a mesh with two sets of texture coordinates. +TEST(GltfDecoderTest, TwoTexCoordAttributesGltf) { + const std::string file_name = "sphere_two_tex_coords.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->NumNamedAttributes(GeometryAttribute::TEX_COORD), 2); +} + +// Tests an input with a valid tangent attribute does not auto generate the +// tangent attribute. +TEST(GltfDecoderTest, TestSceneWithTangents) { + const std::string file_name = "Lantern/glTF/Lantern.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + // Ensure no mesh has auto-generated tangents (and that some meshes have the + // tangent attribute). + int num_tangent_attributes = 0; + for (MeshIndex mi(0); mi < scene->NumMeshes(); ++mi) { + if (scene->GetMesh(mi).GetNamedAttribute(GeometryAttribute::TANGENT) != + nullptr) { + num_tangent_attributes++; + ASSERT_FALSE(MeshUtils::HasAutoGeneratedTangents(scene->GetMesh(mi))); + } + } + ASSERT_GT(num_tangent_attributes, 0); +} + +// Tests an input file where multiple textures share the same image asset. +TEST(GltfDecoderTest, SharedImages) { + const std::string file_name = "SphereAllSame/sphere_texture_all.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 5); + ASSERT_EQ(mesh->GetMaterialLibrary().GetTextureLibrary().NumTextures(), 4); +} + +TEST(GltfDecoderTest, TextureNamesAreNotEmpty) { + const std::string file_name = "SphereAllSame/sphere_texture_all.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 5); + ASSERT_EQ(mesh->GetMaterialLibrary().GetTextureLibrary().NumTextures(), 4); + const std::vector textures = { + mesh->GetMaterialLibrary().GetTextureLibrary().GetTexture(0), + mesh->GetMaterialLibrary().GetTextureLibrary().GetTexture(1), + mesh->GetMaterialLibrary().GetTextureLibrary().GetTexture(2), + mesh->GetMaterialLibrary().GetTextureLibrary().GetTexture(3)}; + EXPECT_EQ(TextureUtils::GetTargetStem(*textures[0]), "256x256_all_orange"); + EXPECT_EQ(TextureUtils::GetTargetStem(*textures[1]), "256x256_all_blue"); + EXPECT_EQ(TextureUtils::GetTargetStem(*textures[2]), "256x256_all_red"); + EXPECT_EQ(TextureUtils::GetTargetStem(*textures[3]), "256x256_all_green"); + EXPECT_EQ(TextureUtils::GetTargetFormat(*textures[0]), ImageFormat::PNG); + EXPECT_EQ(TextureUtils::GetTargetFormat(*textures[1]), ImageFormat::PNG); + EXPECT_EQ(TextureUtils::GetTargetFormat(*textures[2]), ImageFormat::PNG); + EXPECT_EQ(TextureUtils::GetTargetFormat(*textures[3]), ImageFormat::PNG); +} + +TEST(GltfDecoderTest, TestTexCoord1) { + const std::string file_name = "MultiUVTest/glTF/MultiUVTest.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 2); + ASSERT_EQ(mesh->GetMaterialLibrary().GetTextureLibrary().NumTextures(), 2); + const std::vector textures = { + mesh->GetMaterialLibrary().GetTextureLibrary().GetTexture(0), + mesh->GetMaterialLibrary().GetTextureLibrary().GetTexture(1)}; + EXPECT_EQ(TextureUtils::GetTargetStem(*textures[0]), "uv0"); + EXPECT_EQ(TextureUtils::GetTargetStem(*textures[1]), "uv1"); + EXPECT_EQ(TextureUtils::GetTargetFormat(*textures[0]), ImageFormat::PNG); + EXPECT_EQ(TextureUtils::GetTargetFormat(*textures[1]), ImageFormat::PNG); + ASSERT_EQ(mesh->NumNamedAttributes(GeometryAttribute::TEX_COORD), 2); + ASSERT_EQ(mesh->NumNamedAttributes(GeometryAttribute::POSITION), 1); + ASSERT_EQ(mesh->NumNamedAttributes(GeometryAttribute::NORMAL), 1); + ASSERT_EQ(mesh->NumNamedAttributes(GeometryAttribute::TANGENT), 1); +} + +TEST(GltfDecoderTest, SimpleScene) { + const std::string file_name = "Box/glTF/Box.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 1); + const MeshGroup &mesh_group = *scene->GetMeshGroup(MeshGroupIndex(0)); + ASSERT_EQ(mesh_group.NumMeshInstances(), 1); + ASSERT_EQ(mesh_group.GetMeshInstance(0).material_index, 0); + ASSERT_EQ(scene->NumNodes(), 2); + ASSERT_EQ(scene->NumRootNodes(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 0); + ASSERT_EQ(scene->NumSkins(), 0); + ASSERT_EQ(scene->NumAnimations(), 0); + + // Check all of the meshes do not have any materials. + for (MeshIndex i(0); i < scene->NumMeshes(); ++i) { + const Mesh &mesh = scene->GetMesh(i); + ASSERT_EQ(mesh.GetMaterialLibrary().NumMaterials(), 0); + } + + // Check names of nodes are empty. + EXPECT_TRUE(scene->GetNode(SceneNodeIndex(0))->GetName().empty()); + EXPECT_TRUE(scene->GetNode(SceneNodeIndex(1))->GetName().empty()); +} + +TEST(GltfDecoderTest, LanternScene) { + const std::string file_name = "Lantern/glTF/Lantern.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + + EXPECT_EQ(scene->NumMeshes(), 3); + EXPECT_EQ(scene->NumMeshGroups(), 3); + EXPECT_EQ(scene->NumNodes(), 4); + EXPECT_EQ(scene->NumRootNodes(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 4); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->GetDoubleSided(), + false); + EXPECT_EQ(scene->NumSkins(), 0); + EXPECT_EQ(scene->NumAnimations(), 0); + + // Check names of nodes have been populated. + EXPECT_EQ(scene->GetNode(SceneNodeIndex(0))->GetName(), "Lantern"); + EXPECT_EQ(scene->GetNode(SceneNodeIndex(1))->GetName(), "LanternPole_Body"); + EXPECT_EQ(scene->GetNode(SceneNodeIndex(2))->GetName(), "LanternPole_Chain"); + EXPECT_EQ(scene->GetNode(SceneNodeIndex(3))->GetName(), + "LanternPole_Lantern"); +} + +TEST(GltfDecoderTest, SimpleTriangleMesh) { + const std::string file_name = "Triangle/glTF/Triangle.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + + EXPECT_EQ(mesh->num_attributes(), 1) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 3) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 1) << "Unexpected number of faces."; + EXPECT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 0); +} + +TEST(GltfDecoderTest, SimpleTriangleScene) { + const std::string file_name = "Triangle/glTF/Triangle.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + + EXPECT_EQ(scene->NumMeshes(), 1); + EXPECT_EQ(scene->NumMeshGroups(), 1); + const MeshGroup &mesh_group = *scene->GetMeshGroup(MeshGroupIndex(0)); + ASSERT_EQ(mesh_group.NumMeshInstances(), 1); + ASSERT_EQ(mesh_group.GetMeshInstance(0).material_index, 0); + EXPECT_EQ(scene->NumNodes(), 1); + EXPECT_EQ(scene->NumRootNodes(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(scene->NumSkins(), 0); + EXPECT_EQ(scene->NumAnimations(), 0); +} + +TEST(GltfDecoderTest, ThreeMeshesOneNoMaterialScene) { + const std::string file_name = + "three_meshes_two_materials_one_no_material/" + "three_meshes_two_materials_one_no_material.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + + EXPECT_EQ(scene->NumMeshes(), 3); + EXPECT_EQ(scene->NumMeshGroups(), 3); + EXPECT_EQ(scene->NumNodes(), 4); + EXPECT_EQ(scene->NumRootNodes(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 3); + EXPECT_EQ(scene->NumSkins(), 0); + EXPECT_EQ(scene->NumAnimations(), 0); +} + +TEST(GltfDecoderTest, ThreeMeshesOneNoMaterialMesh) { + const std::string file_name = + "three_meshes_two_materials_one_no_material/" + "three_meshes_two_materials_one_no_material.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + + EXPECT_EQ(mesh->num_attributes(), 4) << "Unexpected number of attributes."; + EXPECT_EQ(mesh->num_points(), 72) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 36) << "Unexpected number of faces."; + EXPECT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 3); +} + +TEST(GltfDecoderTest, DoubleSidedMaterial) { + const std::string file_name = "TwoSidedPlane/glTF/TwoSidedPlane.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->GetDoubleSided(), true); + + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->GetDoubleSided(), true); +} + +TEST(GltfDecoderTest, VertexColorTest) { + const std::string file_name = "VertexColorTest/glTF/VertexColorTest.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + EXPECT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 2); + EXPECT_EQ(mesh->NumNamedAttributes(GeometryAttribute::COLOR), 1); + + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 2); + EXPECT_EQ(scene->NumMeshes(), 2); + const Mesh &second_mesh = scene->GetMesh(MeshIndex(1)); + EXPECT_EQ(second_mesh.NumNamedAttributes(GeometryAttribute::COLOR), 1); +} + +TEST(GltfDecoderTest, MorphTargets) { + const std::string filename = + "KhronosSampleModels/AnimatedMorphCube/glTF/AnimatedMorphCube.gltf"; + const std::string path = GetTestFileFullPath(filename); + GltfDecoder decoder; + const auto maybe_scene = decoder.DecodeFromFileToScene(path); + EXPECT_FALSE(maybe_scene.ok()); + EXPECT_EQ(maybe_scene.status().code(), Status::Code::UNSUPPORTED_FEATURE); +} + +TEST(GltfDecoderTest, SparseAccessors) { + const std::string filename = + "KhronosSampleModels/SimpleSparseAccessor/glTF/SimpleSparseAccessor.gltf"; + const std::string path = GetTestFileFullPath(filename); + GltfDecoder decoder; + const auto maybe_scene = decoder.DecodeFromFileToScene(path); + EXPECT_FALSE(maybe_scene.ok()); + EXPECT_EQ(maybe_scene.status().code(), Status::Code::UNSUPPORTED_FEATURE); +} + +TEST(GltfDecoderTest, PbrSpecularGlossinessExtension) { + const std::string filename = + "KhronosSampleModels/SpecGlossVsMetalRough/glTF/" + "SpecGlossVsMetalRough.gltf"; + const std::string path = GetTestFileFullPath(filename); + GltfDecoder decoder; + const auto maybe_scene = decoder.DecodeFromFileToScene(path); + EXPECT_FALSE(maybe_scene.ok()); + EXPECT_EQ(maybe_scene.status().code(), Status::Code::UNSUPPORTED_FEATURE); +} + +TEST(GltfDecoderTest, DifferentWrappingModes) { + const std::string filename = + "KhronosSampleModels/TextureSettingsTest/glTF/TextureSettingsTest.gltf"; + const std::string path = GetTestFileFullPath(filename); + GltfDecoder decoder; + const auto maybe_scene = decoder.DecodeFromFileToScene(path); + EXPECT_TRUE(maybe_scene.ok()); + const draco::Scene &scene = *maybe_scene.value(); + ASSERT_EQ(scene.GetMaterialLibrary().GetTextureLibrary().NumTextures(), 3); + ASSERT_EQ(scene.GetMaterialLibrary().NumMaterials(), 10); + const draco::Material &material = *scene.GetMaterialLibrary().GetMaterial(0); + ASSERT_EQ(material.NumTextureMaps(), 1); + ASSERT_EQ(material.GetTextureMapByIndex(0)->wrapping_mode().s, + draco::TextureMap::REPEAT); + ASSERT_EQ(material.GetTextureMapByIndex(0)->wrapping_mode().t, + draco::TextureMap::MIRRORED_REPEAT); +} + +TEST(GltfDecoderTest, KhrMaterialsUnlitExtension) { + const std::string no_unlit_filename = "Box/glTF/Box.gltf"; + const std::unique_ptr scene_no_unlit( + DecodeGltfFileToScene(no_unlit_filename)); + EXPECT_EQ(scene_no_unlit->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(scene_no_unlit->GetMaterialLibrary().GetMaterial(0)->GetUnlit(), + false); + + const std::string filename = + "KhronosSampleModels/UnlitTest/glTF/UnlitTest.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(filename)); + EXPECT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 2); + EXPECT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->GetUnlit(), true); + EXPECT_EQ(mesh->GetMaterialLibrary().GetMaterial(1)->GetUnlit(), true); + + const std::unique_ptr scene(DecodeGltfFileToScene(filename)); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 2); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->GetUnlit(), true); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(1)->GetUnlit(), true); +} + +TEST(GltfDecoderTest, KhrMaterialsSheenExtension) { + // Check that a model with no sheen is loaded with no sheen. + { + const std::unique_ptr scene( + DecodeGltfFileToScene("Box/glTF/Box.gltf")); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + + // Check that material has no sheen. + const Material &material = *scene->GetMaterialLibrary().GetMaterial(0); + EXPECT_FALSE(material.HasSheen()); + + // Check that sheen color and roughness factors have default values. + EXPECT_EQ(material.GetSheenColorFactor(), Vector3f(0.f, 0.f, 0.f)); + EXPECT_EQ(material.GetSheenRoughnessFactor(), 0.f); + + // Check that sheen textures are absent. + EXPECT_EQ(material.GetTextureMapByType(TextureMap::SHEEN_COLOR), nullptr); + EXPECT_EQ(material.GetTextureMapByType(TextureMap::SHEEN_ROUGHNESS), + nullptr); + } + + // Check that a model with sheen is loaded as a mesh with sheen. + { + // Load model as a mesh. + const std::unique_ptr mesh( + DecodeGltfFile("KhronosSampleModels/SheenCloth/glTF/SheenCloth.gltf")); + EXPECT_NE(mesh, nullptr); + const Material &material = *mesh->GetMaterialLibrary().GetMaterial(0); + + // Check that material has sheen. + EXPECT_TRUE(material.HasSheen()); + + // Check that sheen color and roughness factors are present. + EXPECT_EQ(material.GetSheenColorFactor(), Vector3f(1.f, 1.f, 1.f)); + EXPECT_EQ(material.GetSheenRoughnessFactor(), 1.f); + + // Check that sheen color and roughness textures are present. + EXPECT_NE(material.GetTextureMapByType(TextureMap::SHEEN_COLOR), nullptr); + EXPECT_NE(material.GetTextureMapByType(TextureMap::SHEEN_ROUGHNESS), + nullptr); + + // Check that sheen color and roughness textures are shared. + EXPECT_EQ( + material.GetTextureMapByType(TextureMap::SHEEN_COLOR)->texture(), + material.GetTextureMapByType(TextureMap::SHEEN_ROUGHNESS)->texture()); + } + + // Check that a model with sheen is loaded as a scene with sheen. + { + // Load model as a scene. + const std::unique_ptr scene(DecodeGltfFileToScene( + "KhronosSampleModels/SheenCloth/glTF/SheenCloth.gltf")); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + const Material &material = *scene->GetMaterialLibrary().GetMaterial(0); + + // Check that material has sheen. + EXPECT_TRUE(material.HasSheen()); + + // Check that sheen color and roughness factors are present. + EXPECT_EQ(material.GetSheenColorFactor(), Vector3f(1.f, 1.f, 1.f)); + EXPECT_EQ(material.GetSheenRoughnessFactor(), 1.f); + + // Check that sheen color and roughness textures are present. + EXPECT_NE(material.GetTextureMapByType(TextureMap::SHEEN_COLOR), nullptr); + EXPECT_NE(material.GetTextureMapByType(TextureMap::SHEEN_ROUGHNESS), + nullptr); + + // Check that sheen color and roughness textures are shared. + EXPECT_EQ( + material.GetTextureMapByType(TextureMap::SHEEN_COLOR)->texture(), + material.GetTextureMapByType(TextureMap::SHEEN_ROUGHNESS)->texture()); + } +} + +TEST(GltfDecoderTest, PbrNextExtensions) { + // Check that a model with no material extensions is loaded correctly. + { + const std::unique_ptr scene( + DecodeGltfFileToScene("Box/glTF/Box.gltf")); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + const Material &m = *scene->GetMaterialLibrary().GetMaterial(0); + + // Check that material has no extensions. + EXPECT_FALSE(m.HasSheen()); + EXPECT_FALSE(m.HasTransmission()); + EXPECT_FALSE(m.HasClearcoat()); + EXPECT_FALSE(m.HasVolume()); + EXPECT_FALSE(m.HasIor()); + EXPECT_FALSE(m.HasSpecular()); + } + + // Check that a model with material extensions is loaded correctly. + { + const std::unique_ptr mesh( + DecodeGltfFile("pbr_next/sphere/glTF/sphere.gltf")); + EXPECT_NE(mesh, nullptr); + const Material &m = *mesh->GetMaterialLibrary().GetMaterial(0); + + // Check that material has extensions. + EXPECT_TRUE(m.HasSheen()); + EXPECT_TRUE(m.HasTransmission()); + EXPECT_TRUE(m.HasClearcoat()); + EXPECT_TRUE(m.HasVolume()); + EXPECT_TRUE(m.HasIor()); + EXPECT_TRUE(m.HasSpecular()); + + // Check that material has correct extension properties. + EXPECT_EQ(m.GetSheenColorFactor(), Vector3f(1.0f, 0.329f, 0.1f)); + EXPECT_EQ(m.GetSheenRoughnessFactor(), 0.8f); + EXPECT_EQ(m.GetTransmissionFactor(), 0.75f); + EXPECT_EQ(m.GetClearcoatFactor(), 0.95f); + EXPECT_EQ(m.GetClearcoatRoughnessFactor(), 0.03f); + EXPECT_EQ(m.GetAttenuationColor(), Vector3f(0.921f, 0.640f, 0.064f)); + EXPECT_EQ(m.GetAttenuationDistance(), 0.155f); + EXPECT_EQ(m.GetThicknessFactor(), 2.27f); + EXPECT_EQ(m.GetIor(), 1.55f); + EXPECT_EQ(m.GetSpecularFactor(), 0.3f); + EXPECT_EQ(m.GetSpecularColorFactor(), Vector3f(0.212f, 0.521f, 0.051f)); + + // Check that material has all extension textures. + EXPECT_NE(m.GetTextureMapByType(TextureMap::SHEEN_COLOR), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::SHEEN_ROUGHNESS), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::TRANSMISSION), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::CLEARCOAT), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::CLEARCOAT_ROUGHNESS), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::CLEARCOAT_NORMAL), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::THICKNESS), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::SPECULAR), nullptr); + EXPECT_NE(m.GetTextureMapByType(TextureMap::SPECULAR_COLOR), nullptr); + } +} + +TEST(GltfDecoderTest, TextureTransformTest) { + const std::string filename = + "KhronosSampleModels/TextureTransformTest/glTF/TextureTransformTest.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(filename)); + EXPECT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 9); + for (int i = 0; i < 6; ++i) { + EXPECT_FALSE(TextureTransform::IsDefault(mesh->GetMaterialLibrary() + .GetMaterial(i) + ->GetTextureMapByIndex(0) + ->texture_transform())); + } + for (int i = 6; i < 9; ++i) { + EXPECT_TRUE(TextureTransform::IsDefault(mesh->GetMaterialLibrary() + .GetMaterial(i) + ->GetTextureMapByIndex(0) + ->texture_transform())); + } + + const std::unique_ptr scene(DecodeGltfFileToScene(filename)); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 9); + for (int i = 0; i < 6; ++i) { + EXPECT_FALSE(TextureTransform::IsDefault(scene->GetMaterialLibrary() + .GetMaterial(i) + ->GetTextureMapByIndex(0) + ->texture_transform())); + } + for (int i = 6; i < 9; ++i) { + EXPECT_TRUE(TextureTransform::IsDefault(scene->GetMaterialLibrary() + .GetMaterial(i) + ->GetTextureMapByIndex(0) + ->texture_transform())); + } +} + +TEST(GltfDecoderTest, GlbTextureSource) { + const std::string file_name = "KhronosSampleModels/Duck/glTF_Binary/Duck.glb"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + EXPECT_EQ(scene->NumMeshes(), 1); + EXPECT_EQ(scene->NumMeshGroups(), 1); + const MeshGroup &mesh_group = *scene->GetMeshGroup(MeshGroupIndex(0)); + ASSERT_EQ(mesh_group.NumMeshInstances(), 1); + ASSERT_EQ(mesh_group.GetMeshInstance(0).material_index, 0); + EXPECT_EQ(scene->NumNodes(), 3); + EXPECT_EQ(scene->NumRootNodes(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 1); + EXPECT_EQ(scene->NumAnimations(), 0); + EXPECT_EQ(scene->NumSkins(), 0); + EXPECT_EQ(scene->GetMaterialLibrary().GetTextureLibrary().NumTextures(), 1); + const Texture *const texture = + scene->GetMaterialLibrary().GetTextureLibrary().GetTexture(0); + ASSERT_NE(texture, nullptr); + const SourceImage &source_image = texture->source_image(); + EXPECT_EQ(source_image.encoded_data().size(), 16302); + EXPECT_EQ(source_image.filename(), ""); + EXPECT_EQ(source_image.mime_type(), "image/png"); +} + +TEST(GltfDecoderTest, GltfTextureSource) { + const std::string file_name = "KhronosSampleModels/Duck/glTF/Duck.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + EXPECT_EQ(scene->NumMeshes(), 1); + EXPECT_EQ(scene->NumMeshGroups(), 1); + const MeshGroup &mesh_group = *scene->GetMeshGroup(MeshGroupIndex(0)); + ASSERT_EQ(mesh_group.NumMeshInstances(), 1); + ASSERT_EQ(mesh_group.GetMeshInstance(0).material_index, 0); + EXPECT_EQ(scene->NumNodes(), 3); + EXPECT_EQ(scene->NumRootNodes(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 1); + EXPECT_EQ(scene->NumAnimations(), 0); + EXPECT_EQ(scene->NumSkins(), 0); + EXPECT_EQ(scene->GetMaterialLibrary().GetTextureLibrary().NumTextures(), 1); + const Texture *const texture = + scene->GetMaterialLibrary().GetTextureLibrary().GetTexture(0); + ASSERT_NE(texture, nullptr); + const SourceImage &source_image = texture->source_image(); + EXPECT_EQ(source_image.encoded_data().size(), 0); + EXPECT_FALSE(source_image.filename().empty()); + EXPECT_EQ(source_image.mime_type(), ""); +} + +TEST(GltfDecoderTest, GltfDecodeWithDraco) { + // Tests that we can decode a glTF containing Draco compressed geometry. + const std::string file_name = "Box/glTF_Binary/Box.glb"; + const std::string file_name_with_draco = "Box/glTF_Binary/Box_Draco.glb"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + const std::unique_ptr scene_draco( + DecodeGltfFileToScene(file_name_with_draco)); + ASSERT_NE(scene, nullptr); + ASSERT_NE(scene_draco, nullptr); + EXPECT_EQ(scene->NumMeshes(), scene_draco->NumMeshes()); + EXPECT_EQ(scene->NumMeshGroups(), scene_draco->NumMeshGroups()); + EXPECT_EQ(scene->NumNodes(), scene_draco->NumNodes()); + EXPECT_EQ(scene->NumRootNodes(), scene_draco->NumRootNodes()); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), + scene_draco->GetMaterialLibrary().NumMaterials()); + EXPECT_EQ(scene->NumAnimations(), scene_draco->NumAnimations()); + EXPECT_EQ(scene->NumSkins(), scene_draco->NumSkins()); + + EXPECT_EQ(scene->NumMeshes(), 1); + EXPECT_EQ(scene->GetMesh(draco::MeshIndex(0)).num_faces(), + scene_draco->GetMesh(draco::MeshIndex(0)).num_faces()); +} + +TEST(GltfDecoderTest, TestAnimationNames) { + const std::string file_name = "InterpolationTest/glTF/InterpolationTest.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + EXPECT_EQ(scene->NumAnimations(), 9); + + const std::vector animation_names{ + "Step Scale", "Linear Scale", + "CubicSpline Scale", "Step Rotation", + "CubicSpline Rotation", "Linear Rotation", + "Step Translation", "CubicSpline Translation", + "Linear Translation"}; + for (int i = 0; i < scene->NumAnimations(); ++i) { + const Animation *const anim = scene->GetAnimation(AnimationIndex(i)); + ASSERT_NE(anim, nullptr); + ASSERT_EQ(anim->GetName(), animation_names[i]); + } +} + +TEST(GltfDecoderTest, DuplicatePrimitives) { + const std::string file_name = "DuplicateMeshes/duplicate_meshes.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + // There should be only one unique base mesh in the scene and four mesh + // groups (instances). + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 4); + + // There should be two materials used by the instances. + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterials(), 2); +} + +TEST(GltfDecoderTest, SimpleSkin) { + // This is a simple skin example from glTF tutorial. + const std::string file_name = "simple_skin.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + // Check scene size. + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 1); + ASSERT_EQ(scene->GetMeshGroup(draco::MeshGroupIndex(0))->NumMeshInstances(), + 1); + ASSERT_EQ(scene->NumNodes(), 3); + ASSERT_EQ(scene->NumRootNodes(), 1); + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(scene->NumAnimations(), 1); + ASSERT_EQ(scene->NumSkins(), 1); + + // Check animation size. + const Animation *const animation = scene->GetAnimation(AnimationIndex(0)); + ASSERT_NE(animation, nullptr); + ASSERT_EQ(animation->NumSamplers(), 1); + ASSERT_EQ(animation->NumChannels(), 1); + ASSERT_EQ(animation->NumNodeAnimationData(), 2); + + // Check animation sampler. + const AnimationSampler *const sampler = animation->GetSampler(0); + ASSERT_NE(sampler, nullptr); + ASSERT_EQ(sampler->input_index, 0); + ASSERT_EQ(sampler->interpolation_type, + AnimationSampler::SamplerInterpolation::LINEAR); + ASSERT_EQ(sampler->output_index, 1); + + // Check animation channel. + const AnimationChannel *const channel = animation->GetChannel(0); + ASSERT_NE(channel, nullptr); + ASSERT_EQ(channel->sampler_index, 0); + ASSERT_EQ(channel->target_index, 2); + ASSERT_EQ(channel->transformation_type, + AnimationChannel::ChannelTransformation::ROTATION); + + // Check the first node animation data. + { + const NodeAnimationData *const node_animation = + animation->GetNodeAnimationData(0); + ASSERT_EQ(node_animation->ComponentSize(), 4); + ASSERT_EQ(node_animation->NumComponents(), 1); + ASSERT_EQ(node_animation->count(), 12); + ASSERT_EQ(node_animation->type(), NodeAnimationData::Type::SCALAR); + ASSERT_FALSE(node_animation->normalized()); + const std::vector &node_animation_data = *node_animation->GetData(); + const std::vector expected_node_animation_data{ + 0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.5f, 5.0f, 5.5f}; + ASSERT_EQ(node_animation_data, expected_node_animation_data); + } + + // Check the second node animation data. + { + const NodeAnimationData *const node_animation = + animation->GetNodeAnimationData(1); + ASSERT_EQ(node_animation->ComponentSize(), 4); + ASSERT_EQ(node_animation->NumComponents(), 4); + ASSERT_EQ(node_animation->count(), 12); + ASSERT_EQ(node_animation->type(), NodeAnimationData::Type::VEC4); + ASSERT_FALSE(node_animation->normalized()); + const std::vector &node_animation_data = *node_animation->GetData(); + std::cout << std::endl; + // clang-format off + const std::vector expected_node_animation_data{ + 0.000f, 0.000f, 0.000f, 1.000f, + 0.000f, 0.000f, 0.383f, 0.924f, + 0.000f, 0.000f, 0.707f, 0.707f, + 0.000f, 0.000f, 0.707f, 0.707f, + 0.000f, 0.000f, 0.383f, 0.924f, + 0.000f, 0.000f, 0.000f, 1.000f, + 0.000f, 0.000f, 0.000f, 1.000f, + 0.000f, 0.000f, -0.383f, 0.924f, + 0.000f, 0.000f, -0.707f, 0.707f, + 0.000f, 0.000f, -0.707f, 0.707f, + 0.000f, 0.000f, -0.383f, 0.924f, + 0.000f, 0.000f, 0.000f, 1.000f}; + // clang-format on + ASSERT_EQ(node_animation_data, expected_node_animation_data); + } + + // Check skin. + const Skin *const skin = scene->GetSkin(SkinIndex(0)); + ASSERT_NE(skin, nullptr); + ASSERT_EQ(skin->NumJoints(), 2); + ASSERT_EQ(skin->GetJointRoot(), kInvalidSceneNodeIndex); + ASSERT_EQ(skin->GetJoint(0), SceneNodeIndex(1)); + ASSERT_EQ(skin->GetJoint(1), SceneNodeIndex(2)); + + // Check inverse bind matrices. + const NodeAnimationData &bind_matrices = skin->GetInverseBindMatrices(); + ASSERT_EQ(bind_matrices.type(), NodeAnimationData::Type::MAT4); + ASSERT_EQ(bind_matrices.count(), 2); + ASSERT_EQ(bind_matrices.normalized(), false); + ASSERT_NE(bind_matrices.GetData(), nullptr); + const std::vector &bind_matrices_data = *bind_matrices.GetData(); + // clang-format off + const std::vector expected_bind_matrices_data{ + // First matrix. + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + -0.5f, -1.0f, 0.0f, 1.0f, + // Second matrix. + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + -0.5f, -1.0f, 0.0f, 1.0f}; + // clang-format on + ASSERT_EQ(bind_matrices_data, expected_bind_matrices_data); + + // Check mesh size. + const Mesh &mesh = scene->GetMesh(MeshIndex(0)); + ASSERT_EQ(mesh.num_faces(), 8); + ASSERT_EQ(mesh.num_points(), 10); + ASSERT_EQ(mesh.num_attributes(), 3); + + // Check vertex joint indices. + const PointAttribute *const joints_att = + mesh.GetNamedAttribute(GeometryAttribute::JOINTS); + ASSERT_NE(joints_att, nullptr); + ASSERT_EQ(joints_att->data_type(), DT_UINT16); + ASSERT_EQ(joints_att->num_components(), 4); + ASSERT_EQ(joints_att->size(), 1); + // clang-format off + const std::array expected_joints = { + // Each vertex is associated with four joints. + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0, + 0, 1, 0, 0 }; + // clang-format on + std::array joints; + for (draco::PointIndex pi(0); pi < mesh.num_points(); ++pi) { + joints_att->GetMappedValue(pi, &joints[4 * pi.value()]); + } + ASSERT_EQ(joints, expected_joints); + + // Check vertex joint weights. + const PointAttribute *const weights_att = + mesh.GetNamedAttribute(GeometryAttribute::WEIGHTS); + ASSERT_NE(weights_att, nullptr); + ASSERT_EQ(weights_att->data_type(), DT_FLOAT32); + ASSERT_EQ(weights_att->num_components(), 4); + ASSERT_EQ(weights_att->size(), 5); + // clang-format off + const std::array expected_weights = { + // Each vertex has four joint weights. + 1.00f, 0.00f, 0.00f, 0.00f, + 1.00f, 0.00f, 0.00f, 0.00f, + 0.75f, 0.25f, 0.00f, 0.00f, + 0.75f, 0.25f, 0.00f, 0.00f, + 0.50f, 0.50f, 0.00f, 0.00f, + 0.50f, 0.50f, 0.00f, 0.00f, + 0.25f, 0.75f, 0.00f, 0.00f, + 0.25f, 0.75f, 0.00f, 0.00f, + 0.00f, 1.00f, 0.00f, 0.00f, + 0.00f, 1.00f, 0.00f, 0.00f }; + // clang-format on + std::array weights; + for (draco::PointIndex pi(0); pi < mesh.num_points(); ++pi) { + weights_att->GetMappedValue(pi, &weights[4 * pi.value()]); + } + ASSERT_EQ(weights, expected_weights); +} + +TEST(GltfDecoderTest, DecodeMeshWithImplicitPrimitiveIndices) { + // Check that glTF primitives with implicit indices can be loaded as a mesh. + const std::string file_name = "Fox/glTF/Fox.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->num_faces(), 576); +} + +TEST(GltfDecoderTest, DecodeSceneWithImplicitPrimitiveIndices) { + // Check that glTF primitives with implicit indices can be loaded as a scene. + const std::string file_name = "Fox/glTF/Fox.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->GetMesh(MeshIndex(0)).num_faces(), 576); +} + +TEST(GltfDecoderTest, DecodeFromBufferToMesh) { + // Checks that a mesh can be decoded from buffer in GLB format. + // Read GLB file contents into a buffer. + const std::string file_name = "KhronosSampleModels/Duck/glTF_Binary/Duck.glb"; + const std::string file_path = GetTestFileFullPath(file_name); + std::vector file_data; + ASSERT_TRUE(ReadFileToBuffer(file_path, &file_data)); + DecoderBuffer buffer; + buffer.Init(file_data.data(), file_data.size()); + + // Decode mesh from buffer. + GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto mesh, decoder.DecodeFromBuffer(&buffer)); + ASSERT_NE(mesh, nullptr); + + // Decode mesh from GLB file. + const std::unique_ptr expected_mesh(DecodeGltfFile(file_name)); + ASSERT_NE(expected_mesh, nullptr); + + // Check that meshes decoded from the buffer and from GLB file are equivalent. + MeshAreEquivalent eq; + ASSERT_TRUE(eq(*mesh, *expected_mesh)); +} + +TEST(GltfDecoderTest, DecodeGraph) { + // Checks that we can decode a scene with a general graph structure where a + // node has multiple parents. + // The input model has one root node, 4 children nodes that all point to a + // single node that contains the cube mesh. + const std::string file_name = "CubeScaledInstances/glTF/cube_att.gltf"; + const std::string file_path = GetTestFileFullPath(file_name); + + // First decode the scene into a tree-graph. + draco::GltfDecoder dec_tree; + DRACO_ASSIGN_OR_ASSERT(auto scene_tree, + dec_tree.DecodeFromFileToScene(file_path)); + // We expect to have 9 nodes with 4 mesh instances. The leaf node with the + // cube is duplicated 4 times, once for each instance. + ASSERT_EQ(scene_tree->NumNodes(), 9); + auto instances_tree = draco::SceneUtils::ComputeAllInstances(*scene_tree); + ASSERT_EQ(instances_tree.size(), 4); + + // Decode the scene into a scene-graph. + draco::GltfDecoder dec_graph; + dec_graph.SetSceneGraphMode(draco::GltfDecoder::GltfSceneGraphMode::DAG); + DRACO_ASSIGN_OR_ASSERT(auto scene_graph, + dec_graph.DecodeFromFileToScene(file_path)); + + // We expect to have 6 nodes with 4 mesh instances. The leaf node is shared + // for all mesh instances. + ASSERT_EQ(scene_graph->NumNodes(), 6); + auto instances_graph = draco::SceneUtils::ComputeAllInstances(*scene_graph); + ASSERT_EQ(instances_graph.size(), 4); + + // Check that all instances share the same scene node. + for (draco::MeshInstanceIndex mii(1); mii < 4; ++mii) { + ASSERT_EQ(instances_graph[mii - 1].scene_node_index, + instances_graph[mii].scene_node_index); + } +} + +TEST(GltfDecoderTest, CorrectVolumeThicknessFactor) { + // Checks that when a model is decoded as draco::Mesh the PBR material volume + // thickness factor is corrected according to geometry transformation scale in + // the scene graph. + constexpr float kDragonScale = 0.25f; + constexpr float kDragonVolumeThickness = 2.27f; + + // Read model as draco::Scene and check dragon mesh transformation scale and + // its PBR material volume thickness factor. + const std::unique_ptr scene = draco::ReadSceneFromTestFile( + "KhronosSampleModels/DragonAttenuation/glTF/DragonAttenuation.gltf"); + ASSERT_NE(scene, nullptr); + auto instances = draco::SceneUtils::ComputeAllInstances(*scene); + ASSERT_EQ(instances.size(), 2); + ASSERT_EQ(instances[MeshInstanceIndex(0)].transform.col(0).norm(), + kDragonScale); + ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(1)->GetThicknessFactor(), + kDragonVolumeThickness); + + // Read model as draco::Mesh and check corrected volume thickness factor. + const std::unique_ptr mesh = draco::ReadMeshFromTestFile( + "KhronosSampleModels/DragonAttenuation/glTF/DragonAttenuation.gltf"); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(1)->GetThicknessFactor(), + kDragonScale * kDragonVolumeThickness); +} + +TEST(GltfDecoderTest, DecodeLightsIntoMesh) { + // Checks that a model with lights can be decoded into draco::Mesh with the + // lights discarded. + const std::string file_name = "sphere_lights.gltf"; + const std::unique_ptr mesh(DecodeGltfFile(file_name)); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->num_faces(), 224); +} + +TEST(GltfDecoderTest, DecodeLightsIntoScene) { + // Checks that a model with lights can be decoded into draco::Scene. + const std::string file_name = "sphere_lights.gltf"; + const std::unique_ptr scene(DecodeGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumLights(), 4); + + // Check spot light with all properties specified. + Light &light = *scene->GetLight(LightIndex(0)); + ASSERT_EQ(light.GetName(), "Blue Lightsaber"); + ASSERT_EQ(light.GetColor(), draco::Vector3f(0.72f, 0.71f, 1.00f)); + ASSERT_EQ(light.GetIntensity(), 3.0); + ASSERT_EQ(light.GetType(), draco::Light::SPOT); + ASSERT_EQ(light.GetRange(), 100); + ASSERT_EQ(light.GetInnerConeAngle(), 0.2); + ASSERT_EQ(light.GetOuterConeAngle(), 0.8); + + // Check point light with all properties specified. + light = *scene->GetLight(LightIndex(1)); + ASSERT_EQ(light.GetName(), "The Star of Earendil"); + ASSERT_EQ(light.GetColor(), draco::Vector3f(0.90f, 0.97f, 1.0f)); + ASSERT_EQ(light.GetIntensity(), 5.0); + ASSERT_EQ(light.GetType(), draco::Light::POINT); + ASSERT_EQ(light.GetRange(), 1000); + ASSERT_EQ(light.GetInnerConeAngle(), 0.0); + ASSERT_NEAR(light.GetOuterConeAngle(), DRACO_PI / 4.0f, 1e-8); + + // Check directional light with some properties specified. + light = *scene->GetLight(LightIndex(2)); + ASSERT_EQ(light.GetName(), "Arc Reactor"); + ASSERT_EQ(light.GetColor(), draco::Vector3f(0.9f, 0.9, 0.9f)); + ASSERT_EQ(light.GetIntensity(), 1.0); + ASSERT_EQ(light.GetType(), draco::Light::DIRECTIONAL); + ASSERT_EQ(light.GetRange(), 200.0); + + // Check spot light with no properties specified. + light = *scene->GetLight(LightIndex(3)); + ASSERT_EQ(light.GetName(), ""); + ASSERT_EQ(light.GetColor(), draco::Vector3f(1.0f, 1.0f, 1.0f)); + ASSERT_EQ(light.GetIntensity(), 1.0); + ASSERT_EQ(light.GetType(), draco::Light::SPOT); + ASSERT_EQ(light.GetRange(), std::numeric_limits::max()); + ASSERT_EQ(light.GetInnerConeAngle(), 0.0); + ASSERT_NEAR(light.GetOuterConeAngle(), DRACO_PI / 4.0f, 1e-8); + + // Check that lights are referenced by the scene nodes. + ASSERT_EQ(scene->GetNode(SceneNodeIndex(0))->GetLightIndex(), + kInvalidLightIndex); + ASSERT_EQ(scene->GetNode(SceneNodeIndex(1))->GetLightIndex(), LightIndex(0)); + ASSERT_EQ(scene->GetNode(SceneNodeIndex(2))->GetLightIndex(), LightIndex(2)); + ASSERT_EQ(scene->GetNode(SceneNodeIndex(3))->GetLightIndex(), LightIndex(3)); + ASSERT_EQ(scene->GetNode(SceneNodeIndex(4))->GetLightIndex(), LightIndex(1)); +} + +TEST(GltfDecoderTest, MaterialsVariants) { + // Checks that a model with KHR_materials_variants extension can be decoded. + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto scene, + decoder.DecodeFromFileToScene(GetTestFileFullPath( + "KhronosSampleModels/DragonAttenuation/glTF/" + "DragonAttenuation.gltf"))); + ASSERT_NE(scene, nullptr); + const draco::MaterialLibrary &library = scene->GetMaterialLibrary(); + ASSERT_EQ(library.NumMaterialsVariants(), 2); + ASSERT_EQ(library.GetMaterialsVariantName(0), "Attenuation"); + ASSERT_EQ(library.GetMaterialsVariantName(1), "Surface Color"); + + // Check that the cloth mesh has no material variants. + const draco::MeshGroup &cloth_group = + *scene->GetMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(cloth_group.GetName(), "Cloth Backdrop"); + ASSERT_EQ(cloth_group.NumMeshInstances(), 1); + const auto &cloth_mappings = + cloth_group.GetMeshInstance(0).materials_variants_mappings; + ASSERT_EQ(cloth_mappings.size(), 0); + + // Check that the dragon has correct materials variants. + const draco::MeshGroup &dragon_group = + *scene->GetMeshGroup(draco::MeshGroupIndex(1)); + ASSERT_EQ(dragon_group.GetName(), "Dragon"); + ASSERT_EQ(dragon_group.NumMeshInstances(), 1); + const auto &dragon_mappings = + dragon_group.GetMeshInstance(0).materials_variants_mappings; + ASSERT_EQ(dragon_mappings.size(), 2); + ASSERT_EQ(dragon_mappings[0].material, 1); + ASSERT_EQ(dragon_mappings[1].material, 2); + ASSERT_EQ(dragon_mappings[0].variants.size(), 1); + ASSERT_EQ(dragon_mappings[1].variants.size(), 1); + ASSERT_EQ(dragon_mappings[0].variants[0], 0); + ASSERT_EQ(dragon_mappings[1].variants[0], 1); +} + +TEST(GltfDecoderTest, DecodeMeshWithMeshFeaturesWithStructuralMetadata) { + // Checks decoding of a simple glTF with mesh features and structural metadata + // property table as draco::Mesh. + constexpr bool kDracoCompressionEnabled = false; + const auto path = GetTestFileFullPath("BoxMeta/glTF/BoxMeta.gltf"); + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto mesh, decoder.DecodeFromFile(path)); + ASSERT_NE(mesh, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*mesh, kDracoCompressionEnabled); + GltfTestHelper::CheckBoxMetaStructuralMetadata(*mesh); +} + +TEST(GltfDecoderTest, DecodeMeshWithMeshFeaturesWithDracoCompression) { + // Checks decoding of a simple glTF with mesh features compressed with Draco + // as draco::Mesh. + constexpr bool kDracoCompressionEnabled = true; + const auto path = GetTestFileFullPath("BoxMetaDraco/glTF/BoxMetaDraco.gltf"); + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto mesh, decoder.DecodeFromFile(path)); + ASSERT_NE(mesh, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*mesh, kDracoCompressionEnabled); +} + +TEST(GltfDecoderTest, DecodeSceneWithMeshFeaturesWithStructuralMetadata) { + // Checks decoding of a simple glTF with mesh features and structural metadata + // property table as draco::Scene. + constexpr bool kHasDracoCompression = false; + const auto path = GetTestFileFullPath("BoxMeta/glTF/BoxMeta.gltf"); + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto scene, decoder.DecodeFromFileToScene(path)); + ASSERT_NE(scene, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*scene, kHasDracoCompression); + GltfTestHelper::CheckBoxMetaStructuralMetadata(*scene); +} + +TEST(GltfDecoderTest, DecodeSceneWithMeshFeaturesWithDracoCompression) { + // Checks decoding of a simple glTF with mesh features compressed with Draco + // as draco::Scene. + constexpr bool kHasDracoCompression = true; + const auto path = GetTestFileFullPath("BoxMetaDraco/glTF/BoxMetaDraco.gltf"); + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto scene, decoder.DecodeFromFileToScene(path)); + ASSERT_NE(scene, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*scene, kHasDracoCompression); +} + +TEST(GltfDecoderTest, DecodePointCloudToMesh) { + // Checks decoding of a simple glTF with point primitives (no meshes). + const auto path = GetTestFileFullPath( + "SphereTwoMaterials/sphere_two_materials_point_cloud.gltf"); + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto mesh, decoder.DecodeFromFile(path)); + ASSERT_NE(mesh, nullptr); + + // Check the point cloud has expected number of points and attributes. + ASSERT_EQ(mesh->num_faces(), 0); + ASSERT_EQ(mesh->num_points(), 462); + + ASSERT_EQ(mesh->NumNamedAttributes(draco::GeometryAttribute::NORMAL), 1); + ASSERT_EQ(mesh->NumNamedAttributes(draco::GeometryAttribute::TEX_COORD), 1); + ASSERT_EQ(mesh->NumNamedAttributes(draco::GeometryAttribute::TANGENT), 1); + ASSERT_EQ(mesh->NumNamedAttributes(draco::GeometryAttribute::MATERIAL), 1); + + // Check the point cloud has two materials. + ASSERT_EQ(mesh->GetNamedAttribute(draco::GeometryAttribute::MATERIAL)->size(), + 2); +} + +TEST(GltfDecoderTest, DecodeMeshAndPointCloudToMesh) { + // Checks decoding of a simple glTF with a mesh and point primitives into + // draco::Mesh. This should fail (draco::Mesh can't support mixed primitives). + const auto path = GetTestFileFullPath( + "SphereTwoMaterials/sphere_two_materials_mesh_and_point_cloud.gltf"); + draco::GltfDecoder decoder; + ASSERT_FALSE(decoder.DecodeFromFile(path).ok()); +} + +TEST(GltfDecoderTest, DecodePointCloudToScene) { + // Checks decoding of a simple glTF with point primitives (no meshes) into + // draco::Scene. + const auto path = GetTestFileFullPath( + "SphereTwoMaterials/sphere_two_materials_point_cloud.gltf"); + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto scene, decoder.DecodeFromFileToScene(path)); + ASSERT_NE(scene, nullptr); + + ASSERT_EQ(scene->NumMeshes(), 2); + + // Check that each point cloud has expected number of points and attributes. + for (draco::MeshIndex mi(0); mi < scene->NumMeshes(); ++mi) { + const auto &mesh = scene->GetMesh(mi); + ASSERT_EQ(mesh.num_faces(), 0); + ASSERT_EQ(mesh.num_points(), 231); + + ASSERT_EQ(mesh.NumNamedAttributes(draco::GeometryAttribute::NORMAL), 1); + ASSERT_EQ(mesh.NumNamedAttributes(draco::GeometryAttribute::TEX_COORD), 1); + ASSERT_EQ(mesh.NumNamedAttributes(draco::GeometryAttribute::TANGENT), 1); + ASSERT_EQ(mesh.NumNamedAttributes(draco::GeometryAttribute::MATERIAL), 0); + } + + // Check the materials are properly assigned to each point cloud. + const auto instances = draco::SceneUtils::ComputeAllInstances(*scene); + ASSERT_EQ(instances.size(), 2); + ASSERT_EQ(draco::SceneUtils::GetMeshInstanceMaterialIndex( + *scene, instances[draco::MeshInstanceIndex(0)]), + 0); + ASSERT_EQ(draco::SceneUtils::GetMeshInstanceMaterialIndex( + *scene, instances[draco::MeshInstanceIndex(1)]), + 1); +} + +TEST(GltfDecoderTest, DecodeMeshAndPointCloudToScene) { + // Checks decoding of a simple glTF with a mesh and point primitives into + // draco::Scene. + const auto path = GetTestFileFullPath( + "SphereTwoMaterials/sphere_two_materials_mesh_and_point_cloud.gltf"); + draco::GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto scene, decoder.DecodeFromFileToScene(path)); + ASSERT_NE(scene, nullptr); + + ASSERT_EQ(scene->NumMeshes(), 2); + + // First mesh should be a real mesh while the other one should be a point + // cloud (no faces). Otherwise, they should have the same properties. + for (draco::MeshIndex mi(0); mi < scene->NumMeshes(); ++mi) { + const auto &mesh = scene->GetMesh(mi); + ASSERT_EQ(mesh.num_faces(), mi.value() == 0 ? 224 : 0); + ASSERT_EQ(mesh.num_points(), 231); + + ASSERT_EQ(mesh.NumNamedAttributes(draco::GeometryAttribute::NORMAL), 1); + ASSERT_EQ(mesh.NumNamedAttributes(draco::GeometryAttribute::TEX_COORD), 1); + ASSERT_EQ(mesh.NumNamedAttributes(draco::GeometryAttribute::TANGENT), 1); + } +} + +TEST(GltfDecoderTest, TestLoadUnsupportedTexCoordAttributes) { + // Checks that unsupported attributes (TEXCOORD_2 ... TEXCOORD_7) are ignored + // without causing the decoder to fail. + auto scene = draco::ReadSceneFromTestFile("UnusedTexCoords/TexCoord2.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->GetMesh(draco::MeshIndex(0)) + .NumNamedAttributes(draco::GeometryAttribute::TEX_COORD), + 2); +} + +} // namespace draco +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/io/gltf_encoder.cc b/contrib/draco/src/draco/io/gltf_encoder.cc new file mode 100644 index 000000000..0509b588f --- /dev/null +++ b/contrib/draco/src/draco/io/gltf_encoder.cc @@ -0,0 +1,3662 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/gltf_encoder.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "draco/attributes/geometry_attribute.h" +#include "draco/attributes/point_attribute.h" +#include "draco/compression/draco_compression_options.h" +#include "draco/compression/expert_encode.h" +#include "draco/core/draco_types.h" +#include "draco/core/vector_d.h" +#include "draco/io/file_utils.h" +#include "draco/io/file_writer_utils.h" +#include "draco/io/gltf_utils.h" +#include "draco/io/texture_io.h" +#include "draco/mesh/mesh_features.h" +#include "draco/mesh/mesh_splitter.h" +#include "draco/mesh/mesh_utils.h" +#include "draco/scene/instance_array.h" +#include "draco/scene/scene_indices.h" +#include "draco/scene/scene_utils.h" +#include "draco/texture/texture_utils.h" + +namespace draco { + +// Values are specfified from glTF 2.0 sampler spec. See here for more +// information: +// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#sampler +int TextureFilterTypeToGltfValue(TextureMap::FilterType filter_type) { + switch (filter_type) { + case TextureMap::NEAREST: + return 9728; + case TextureMap::LINEAR: + return 9729; + case TextureMap::NEAREST_MIPMAP_NEAREST: + return 9984; + case TextureMap::LINEAR_MIPMAP_NEAREST: + return 9985; + case TextureMap::NEAREST_MIPMAP_LINEAR: + return 9986; + case TextureMap::LINEAR_MIPMAP_LINEAR: + return 9987; + default: + return -1; + } +} + +// Values are specfified from glTF 2.0 sampler spec. See here for more +// information: +// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#sampler +int TextureAxisWrappingModeToGltfValue(TextureMap::AxisWrappingMode mode) { + switch (mode) { + case TextureMap::CLAMP_TO_EDGE: + return 33071; + case TextureMap::MIRRORED_REPEAT: + return 33648; + case TextureMap::REPEAT: + return 10497; + default: + return -1; + } +} + +// Checks |att| metadata entry in |mesh| with key "attribute_name" and returns +// entry value if it begins with "_FEATURE_ID_", or an empty string otherwise. +std::string GetFeatureIdAttributeName(const PointAttribute &att, + const Mesh &mesh) { + const auto *const metadata = + mesh.GetAttributeMetadataByAttributeId(att.unique_id()); + if (metadata) { + std::string attribute_name; + if (metadata->GetEntryString("attribute_name", &attribute_name)) { + constexpr char kPrefix[] = "_FEATURE_ID_"; + if (attribute_name.rfind(kPrefix) == 0) { + return attribute_name; + } + } + } + return std::string(); +} + +// Struct to hold glTF Scene data. +struct GltfScene { + std::vector node_indices; +}; + +// Struct to hold glTF Node data. +struct GltfNode { + GltfNode() + : mesh_index(-1), + skin_index(-1), + light_index(-1), + instance_array_index(-1), + root_node(false) {} + + std::string name; + std::vector childern_indices; + int mesh_index; + int skin_index; + int light_index; + int instance_array_index; + bool root_node; + TrsMatrix trs_matrix; +}; + +// Struct to hold image data. +struct GltfImage { + std::string image_name; + const Texture *texture; + std::unique_ptr owned_texture; + int num_components = 0; + int buffer_view = -1; + std::string mime_type; +}; + +// Struct to hold texture filtering options. The members are based on glTF 2.0 +// samplers. For more information see: +// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#samplers +struct TextureSampler { + TextureSampler(TextureMap::FilterType min, TextureMap::FilterType mag, + TextureMap::WrappingMode mode) + : min_filter(min), mag_filter(mag), wrapping_mode(mode) {} + + bool operator==(const TextureSampler &other) const { + if (min_filter != other.min_filter) { + return false; + } + if (mag_filter != other.mag_filter) { + return false; + } + return wrapping_mode.s == other.wrapping_mode.s && + wrapping_mode.t == other.wrapping_mode.t; + } + + TextureMap::FilterType min_filter = TextureMap::UNSPECIFIED; + TextureMap::FilterType mag_filter = TextureMap::UNSPECIFIED; + TextureMap::WrappingMode wrapping_mode = {TextureMap::CLAMP_TO_EDGE, + TextureMap::CLAMP_TO_EDGE}; +}; + +// Struct to hold texture data. Multiple textures can reference the same image. +struct GltfTexture { + GltfTexture(int image, int sampler) + : image_index(image), sampler_index(sampler) {} + bool operator==(const GltfTexture &other) const { + return image_index == other.image_index && + sampler_index == other.sampler_index; + } + int image_index; + int sampler_index; +}; + +// Struct to hold glTF Accessor data. +struct GltfAccessor { + GltfAccessor() + : buffer_view_index(-1), + byte_stride(0), + component_type(-1), + normalized(false) {} + + int buffer_view_index; + int byte_stride; + int component_type; + int64_t count; + std::vector max; + std::vector min; + std::string type; + bool normalized; +}; + +// Struct to hold glTF BufferView data. Currently there is only one Buffer, so +// there is no need to store a buffer index. +struct GltfBufferView { + int64_t buffer_byte_offset = -1; + int64_t byte_length = 0; + int target = 0; +}; + +// Struct to hold information about a Draco compressed mesh. +struct GltfDracoCompressedMesh { + int buffer_view_index = -1; + std::map attributes; +}; + +// Struct to hold glTF Primitive data. +struct GltfPrimitive { + GltfPrimitive() : indices(-1), mode(4), material(0) {} + + int indices; + int mode; + int material; + std::vector material_variants_mappings; + std::vector mesh_features; + std::map attributes; + GltfDracoCompressedMesh compressed_mesh_info; +}; + +struct GltfMesh { + std::string name; + std::vector primitives; +}; + +// Class to hold and output glTF data. +class GltfAsset { + public: + // glTF value types and values. + enum ComponentType { + BYTE = 5120, + UNSIGNED_BYTE = 5121, + SHORT = 5122, + UNSIGNED_SHORT = 5123, + UNSIGNED_INT = 5125, + FLOAT = 5126 + }; + // Return the size of the component based on |max_value|. + static int UnsignedIntComponentSize(unsigned int max_value); + + // Return component type based on |max_value|. + static ComponentType UnsignedIntComponentType(unsigned int max_value); + + GltfAsset(); + + std::string generator() const { return generator_; } + std::string version() const { return version_; } + std::string buffer_name() const { return buffer_name_; } + void buffer_name(const std::string &name) { buffer_name_ = name; } + const EncoderBuffer *Buffer() const { return &buffer_; } + + // Convert a Draco Mesh to glTF data. + bool AddDracoMesh(const Mesh &mesh); + + // Convert a Draco Scene to glTF data. + Status AddScene(const Scene &scene); + + // Copy the glTF data to |buf_out|. + Status Output(EncoderBuffer *buf_out); + + // Return the output image referenced by |index|. + const GltfImage *GetImage(int index) const; + + // Return the number of images added to the GltfAsset. + int NumImages() const { return images_.size(); } + + const std::string &image_name(int i) const { return images_[i].image_name; } + + void set_add_images_to_buffer(bool flag) { add_images_to_buffer_ = flag; } + bool add_images_to_buffer() const { return add_images_to_buffer_; } + void set_output_type(GltfEncoder::OutputType type) { output_type_ = type; } + GltfEncoder::OutputType output_type() const { return output_type_; } + void set_json_output_mode(JsonWriter::Mode mode) { gltf_json_.SetMode(mode); } + + private: + // Pad |buffer_| to 4 byte boundary. + bool PadBuffer(); + + // Returns the index of the scene that was added. -1 on error. + int AddScene(); + + // Add a glTF attribute index to |draco_extension|. + void AddAttributeToDracoExtension( + const Mesh &mesh, GeometryAttribute::Type type, int index, + const std::string &name, GltfDracoCompressedMesh *compressed_mesh_info); + + // Compresses |mesh| using Draco. On success returns the buffer_view in + // |primitive| and number of encoded points and faces. + Status CompressMeshWithDraco(const Mesh &mesh, + const Eigen::Matrix4d &transform, + GltfPrimitive *primitive, + int64_t *num_encoded_points, + int64_t *num_encoded_faces); + + // Adds a Draco mesh associated with a material id and material variants. + bool AddDracoMesh(const Mesh &mesh, int material_id, + const std::vector + &material_variants_mappings, + const Eigen::Matrix4d &transform); + + // Add the Draco mesh indices to the glTF data. |num_encoded_faces| is the + // number of faces encoded in |mesh|, which can be different than + // mesh.numfaces(). Returns the index of the accessor that was added. -1 on + // error. + int AddDracoIndices(const Mesh &mesh, int64_t num_encoded_faces); + + // Add the Draco mesh positions attribute to the glTF data. + // |num_encoded_points| is the number of points encoded in |mesh|, which can + // be different than mesh.num_points(). Returns the index of the accessor that + // was added. -1 on error. + int AddDracoPositions(const Mesh &mesh, int num_encoded_points); + + // Add the Draco mesh normals attribute to the glTF data. |num_encoded_points| + // is the number of points encoded in |mesh|, which can be different than + // mesh.num_points(). Returns the index of + // the accessor that was added. -1 on error. + int AddDracoNormals(const Mesh &mesh, int num_encoded_points); + + // Add the Draco mesh vertex color attribute to the glTF data. + // |num_encoded_points| is the number of points encoded in |mesh|, which can + // be different than mesh.num_points(). Returns the index of the accessor that + // was added. -1 on error. + int AddDracoColors(const Mesh &mesh, int num_encoded_points); + + // Add the Draco mesh texture attribute to the glTF data. |tex_coord_index| is + // the index into the texture coordinates added to |mesh|. + // |num_encoded_points| is the number of points encoded in |mesh|, which can + // be different than mesh.num_points(). Returns the index of the accessor that + // was added. -1 on error. + int AddDracoTexture(const Mesh &mesh, int tex_coord_index, + int num_encoded_points); + + // Add the Draco mesh tangent attribute to the glTF data. The Draco mesh + // tangents only contains the x, y, and z components and glTF needs the + // x, y, z, and w components for glTF mesh tangents. Note this is not true + // for tangents of glTF morph targets. This function will add the w component + // to the glTF tangents. |num_encoded_points| is the + // number of points encoded in |mesh|, which can be different than + // mesh.num_points(). Returns the index of the accessor that was added. + // -1 on error. + // Note: Tangents are not added if the attribute contains "auto_generated" + // metadata. See go/tangents_and_draco_simplifier for more details. + int AddDracoTangents(const Mesh &mesh, int num_encoded_points); + + int AddDracoJoints(const Mesh &mesh, int num_encoded_points); + int AddDracoWeights(const Mesh &mesh, int num_encoded_points); + std::vector> AddDracoGenerics( + const Mesh &mesh, int num_encoded_points); + + // Iterate through the materials that are associated with |mesh| and add them + // to the asset. Returns true if |mesh| does not contain any materials or all + // the materials are supported. Returns false if |mesh| contains materials + // that are not supported. + bool AddMaterials(const Mesh &mesh); + + // Checks whether a given Draco |attribute| has data of expected |data_type| + // and whether the data has one of expected |num_components|. Returns true + // when the |attribute| meets expectations, false otherwise. + static bool CheckDracoAttribute(const PointAttribute *attribute, + const std::set &data_types, + const std::set &num_components); + + // Returns the name of |texture|. If |texture|'s name is empty then it will + // generate a name using |texture_index| and |suffix|. If it cannot generate a + // name then it will return an empty string. + std::string GetTextureName(const Texture &texture, int texture_index, + const std::string &suffix) const; + + // Adds a new glTF image to the asset and returns its index. |owned_texture| + // is an optional argument that can be used when the added image is not + // contained in the encoded MaterialLibrary (e.g. for images that are locally + // modified before they are encoded to disk). The image file name is generated + // by combining |image_stem| and image mime type contained in the |texture|. + StatusOr AddImage(const std::string &image_stem, const Texture *texture, + int num_components); + StatusOr AddImage(const std::string &image_stem, const Texture *texture, + std::unique_ptr owned_texture, + int num_components); + + // Saves an image with a given |image_index| into a buffer. + Status SaveImageToBuffer(int image_index); + + // Adds |sampler| to vector of samplers and returns the index. If |sampler| is + // equal to default values then |sampler| is not added to the vector and + // returns -1. + StatusOr AddTextureSampler(const TextureSampler &sampler); + + // Adds a Draco SceneNode, referenced by |scene_node_index|, to the glTF data. + Status AddSceneNode(const Scene &scene, SceneNodeIndex scene_node_index); + + // Iterate through the materials that are associated with |scene| and add them + // to the asset. Returns true if |scene| does not contain any materials or all + // the materials are supported. Returns false if |scene| contains materials + // that are not supported. + bool AddMaterials(const Scene &scene); + + // Iterate through the animations that are associated with |scene| and add + // them to the asset. Returns OkStatus() if |scene| does not contain any + // animations. + Status AddAnimations(const Scene &scene); + + // Converts the data associated with |node_animation_data| and adds that to + // the encoder as an accessor. + StatusOr AddNodeAnimationData( + const NodeAnimationData &node_animation_data); + + // Iterate through the skins that are associated with |scene| and add + // them to the asset. Returns OkStatus() if |scene| does not contain any + // skins. + Status AddSkins(const Scene &scene); + + // Iterate through the lights that are associated with |scene| and add them to + // the asset. Returns OkStatus() if |scene| does not contain any lights. + Status AddLights(const Scene &scene); + + // Iterate through materials variants names that are associated with |scene| + // and add them to the asset. Returns OkStatus() if |scene| does not contain + // any materials variants. + Status AddMaterialsVariantsNames(const Scene &scene); + + // Iterate through the mesh group instance arrays that are associated with + // |scene| and add them to the asset. Returns OkStatus() if |scene| does not + // contain any mesh group instance arrays. + Status AddInstanceArrays(const Scene &scene); + + // Adds structural metadata from |geometry| to the asset, if any. + template + void AddStructuralMetadata(const GeometryT &geometry); + + // Adds float |data| representing |num_components|-length vectors to the + // encoder as accessor and return the new accessor index. + StatusOr AddData(const std::vector &data, int num_components); + + // Adds property table |data| as buffer view and returns buffer view index. + StatusOr AddBufferView(const PropertyTable::Property::Data &data); + + bool EncodeAssetProperty(EncoderBuffer *buf_out); + bool EncodeScenesProperty(EncoderBuffer *buf_out); + bool EncodeInitialSceneProperty(EncoderBuffer *buf_out); + bool EncodeNodesProperty(EncoderBuffer *buf_out); + Status EncodeMeshesProperty(EncoderBuffer *buf_out); + Status EncodePrimitiveExtensionsProperty(const GltfPrimitive &primitive, + EncoderBuffer *buf_out); + Status EncodeMaterials(EncoderBuffer *buf_out); + + // Encodes a color material. |red|, |green|, |blue|, |alpha|, and + // |metallic_factor| are values in the range of 0.0 - 1.0. + void EncodeColorMaterial(float red, float green, float blue, float alpha, + float metallic_factor); + Status EncodeDefaultMaterial(EncoderBuffer *buf_out); + + // Encodes a texture map. |object_name| is the name of the texture map. + // |image_index| is the index into the texture image array. |tex_coord_index| + // is the index into the texture coordinates. |texture_map| is a reference to + // the texture map that is going to be encoded. + Status EncodeTextureMap(const std::string &object_name, int image_index, + int tex_coord_index, const Material &material, + const TextureMap &texture_map); + + // Encodes a texture map similar to the method above. When the |object_name| + // is "texture" and |channels| is not empty, then the |channels| is encoded + // into the "channels" property as required by the "texture" object of the + // EXT_mesh_features extension. + Status EncodeTextureMap(const std::string &object_name, int image_index, + int tex_coord_index, const Material &material, + const TextureMap &texture_map, + const std::vector &channels); + Status EncodeMaterialsProperty(EncoderBuffer *buf_out); + + void EncodeMaterialUnlitExtension(const Material &material); + Status EncodeMaterialSheenExtension(const Material &material, + const Material &defaults, + int material_index); + Status EncodeMaterialTransmissionExtension(const Material &material, + const Material &defaults, + int material_index); + Status EncodeMaterialClearcoatExtension(const Material &material, + const Material &defaults, + int material_index); + Status EncodeMaterialVolumeExtension(const Material &material, + const Material &defaults, + int material_index); + Status EncodeMaterialIorExtension(const Material &material, + const Material &defaults); + Status EncodeMaterialSpecularExtension(const Material &material, + const Material &defaults, + int material_index); + Status EncodeTexture(const std::string &name, const std::string &stem_suffix, + TextureMap::Type type, int num_components, + const Material &material, int material_index); + Status EncodeAnimationsProperty(EncoderBuffer *buf_out); + Status EncodeSkinsProperty(EncoderBuffer *buf_out); + Status EncodeTopLevelExtensionsProperty(EncoderBuffer *buf_out); + Status EncodeLightsProperty(EncoderBuffer *buf_out); + Status EncodeMaterialsVariantsNamesProperty(EncoderBuffer *buf_out); + Status EncodeStructuralMetadataProperty(EncoderBuffer *buf_out); + bool EncodeAccessorsProperty(EncoderBuffer *buf_out); + bool EncodeBufferViewsProperty(EncoderBuffer *buf_out); + bool EncodeBuffersProperty(EncoderBuffer *buf_out); + Status EncodeExtensionsProperties(EncoderBuffer *buf_out); + + // Encodes a draco::VectorNX as a glTF array. + template + void EncodeVectorArray(const std::string &array_name, T vec) { + gltf_json_.BeginArray(array_name); + for (int i = 0; i < T::dimension; ++i) { + gltf_json_.OutputValue(vec[i]); + } + gltf_json_.EndArray(); + } + + // Add a mesh Draco attribute |att| that is comprised of floats to the glTF + // data. Returns the index accessor added to the glTF data. Returns -1 on + // error. + template + int AddAttribute(const PointAttribute &att, int num_points, + int num_encoded_points, bool compress) { + const int num_components = att.num_components(); + switch (num_components) { + case 1: + return AddAttribute<1, att_data_t>(att, num_points, num_encoded_points, + "SCALAR", compress); + break; + case 2: + return AddAttribute<2, att_data_t>(att, num_points, num_encoded_points, + "VEC2", compress); + break; + case 3: + return AddAttribute<3, att_data_t>(att, num_points, num_encoded_points, + "VEC3", compress); + break; + case 4: + return AddAttribute<4, att_data_t>(att, num_points, num_encoded_points, + "VEC4", compress); + break; + default: + break; + } + return -1; + } + + // Template method only has specialized implementations for known glTF types. + template + ComponentType GetComponentType() const = delete; + + template + int AddAttribute(const PointAttribute &att, int num_points, + int num_encoded_points, const std::string &type, + bool compress); + + std::string generator_; + std::string version_; + std::vector scenes_; + + // Initial scene to load. + int scene_index_; + + std::vector nodes_; + std::vector accessors_; + std::vector buffer_views_; + std::vector meshes_; + + // Data structure to copy the input meshes materials. + MaterialLibrary material_library_; + + std::vector images_; + std::vector textures_; + + std::unordered_map texture_to_image_index_map_; + + std::string buffer_name_; + EncoderBuffer buffer_; + JsonWriter gltf_json_; + + // Keeps track if the glTF mesh has been added. + std::map mesh_group_index_to_gltf_mesh_; + std::map> mesh_index_to_gltf_mesh_primitive_; + IndexTypeVector base_mesh_transforms_; + + struct EncoderAnimation { + std::string name; + std::vector> samplers; + std::vector> channels; + }; + std::vector> animations_; + + struct EncoderSkin { + EncoderSkin() : inverse_bind_matrices_index(-1), skeleton_index(-1) {} + int inverse_bind_matrices_index; + std::vector joints; + int skeleton_index; + }; + + // Instance array is represented by its attribute accessors. + struct EncoderInstanceArray { + EncoderInstanceArray() : translation(-1), rotation(-1), scale(-1) {} + int translation; + int rotation; + int scale; + }; + + std::vector> skins_; + std::vector> lights_; + std::vector materials_variants_names_; + std::vector instance_arrays_; + PropertyTable::Schema property_table_schema_; + std::vector property_tables_; + + // Indicates whether Draco compression is used for any of the asset meshes. + bool draco_compression_used_; + + // Indicates whether mesh features are used. + bool mesh_features_used_; + + // Counter for naming mesh feature textures. + int mesh_features_texture_index_; + + // If set GltfAsset will add the images to |buffer_| instead of writing the + // images to separate files. + bool add_images_to_buffer_; + + // Used to hold the extensions used and required by the glTF asset. + std::set extensions_used_; + std::set extensions_required_; + + std::vector texture_samplers_; + + GltfEncoder::OutputType output_type_; + + // Temporary storage for meshes created during the runtime of the GltfEncoder. + // We need to store them here to ensure their content doesn't get deleted + // before it is used by the encoder. + std::vector> local_meshes_; +}; + +int GltfAsset::UnsignedIntComponentSize(unsigned int max_value) { + // According to GLTF 2.0 spec, 0xff (and 0xffff respectively) are reserved for + // the primitive restart symbol. + if (max_value < 0xff) { + return 1; + } else if (max_value < 0xffff) { + return 2; + } + return 4; +} + +GltfAsset::ComponentType GltfAsset::UnsignedIntComponentType( + unsigned int max_value) { + // According to GLTF 2.0 spec, 0xff (and 0xffff respectively) are reserved for + // the primitive restart symbol. + if (max_value < 0xff) { + return UNSIGNED_BYTE; + } else if (max_value < 0xffff) { + return UNSIGNED_SHORT; + } + return UNSIGNED_INT; +} + +GltfAsset::GltfAsset() + : generator_("draco_decoder"), + version_("2.0"), + scene_index_(-1), + buffer_name_("buffer0.bin"), + draco_compression_used_(false), + mesh_features_used_(false), + mesh_features_texture_index_(0), + add_images_to_buffer_(false), + output_type_(GltfEncoder::COMPACT) {} + +bool GltfAsset::AddDracoMesh(const Mesh &mesh) { + const int scene_index = AddScene(); + if (scene_index < 0) { + return false; + } + if (!AddMaterials(mesh)) { + return false; + } + + GltfMesh gltf_mesh; + meshes_.push_back(gltf_mesh); + + AddStructuralMetadata(mesh); + + const int32_t material_att_id = + mesh.GetNamedAttributeId(GeometryAttribute::MATERIAL); + if (material_att_id == -1) { + if (!AddDracoMesh(mesh, 0, {}, Eigen::Matrix4d::Identity())) { + return false; + } + } else { + const auto mat_att = mesh.GetNamedAttribute(GeometryAttribute::MATERIAL); + + // Split mesh using the material attribute. + MeshSplitter splitter; + auto split_maybe = splitter.SplitMesh(mesh, material_att_id); + if (!split_maybe.ok()) { + return false; + } + auto split_meshes = std::move(split_maybe).value(); + for (int i = 0; i < split_meshes.size(); ++i) { + if (split_meshes[i] == nullptr) { + continue; // Empty mesh. Ignore. + } + uint32_t mat_index = 0; + mat_att->GetValue(AttributeValueIndex(i), &mat_index); + + // Copy over mesh features for a given material index. + Mesh::CopyMeshFeaturesForMaterial(mesh, split_meshes[i].get(), mat_index); + + // Move the split mesh to a temporary storage of the GltfAsset. This will + // ensure the mesh will stay alive as long the asset needs it. We have to + // do this because the split mesh may contain mesh features data that are + // used later in the encoding process. + local_meshes_.push_back(std::move(split_meshes[i])); + + // The material index in the glTF file corresponds to the index of the + // split mesh. + if (!AddDracoMesh(*(local_meshes_.back().get()), mat_index, {}, + Eigen::Matrix4d::Identity())) { + return false; + } + } + } + + // Currently output only one mesh. + GltfNode mesh_node; + mesh_node.mesh_index = 0; + nodes_.push_back(mesh_node); + nodes_.back().root_node = true; + return true; +} + +int GltfAsset::AddScene() { + GltfScene scene; + scenes_.push_back(scene); + const int scene_index = static_cast(scenes_.size()) - 1; + + if (scene_index_ == -1) { + scene_index_ = scene_index; + } + return scene_index; +} + +Status GltfAsset::Output(EncoderBuffer *buf_out) { + gltf_json_.BeginObject(); + if (!EncodeAssetProperty(buf_out)) { + return Status(Status::DRACO_ERROR, "Failed encoding asset."); + } + if (!EncodeScenesProperty(buf_out)) { + return Status(Status::DRACO_ERROR, "Failed encoding scenes."); + } + if (!EncodeInitialSceneProperty(buf_out)) { + return Status(Status::DRACO_ERROR, "Failed encoding initial scene."); + } + if (!EncodeNodesProperty(buf_out)) { + return Status(Status::DRACO_ERROR, "Failed encoding nodes."); + } + DRACO_RETURN_IF_ERROR(EncodeMeshesProperty(buf_out)); + DRACO_RETURN_IF_ERROR(EncodeMaterials(buf_out)); + if (!EncodeAccessorsProperty(buf_out)) { + return Status(Status::DRACO_ERROR, "Failed encoding accessors."); + } + DRACO_RETURN_IF_ERROR(EncodeAnimationsProperty(buf_out)); + DRACO_RETURN_IF_ERROR(EncodeSkinsProperty(buf_out)); + DRACO_RETURN_IF_ERROR(EncodeTopLevelExtensionsProperty(buf_out)); + if (!EncodeBufferViewsProperty(buf_out)) { + return Status(Status::DRACO_ERROR, "Failed encoding buffer views."); + } + if (!EncodeBuffersProperty(buf_out)) { + return Status(Status::DRACO_ERROR, "Failed encoding buffers."); + } + DRACO_RETURN_IF_ERROR(EncodeExtensionsProperties(buf_out)); + gltf_json_.EndObject(); + + const std::string asset_str = gltf_json_.MoveData(); + if (!buf_out->Encode(asset_str.data(), asset_str.length())) { + return Status(Status::DRACO_ERROR, "Failed encoding json data."); + } + if (!buf_out->Encode("\n", 1)) { + return Status(Status::DRACO_ERROR, "Failed encoding json data."); + } + return OkStatus(); +} + +const GltfImage *GltfAsset::GetImage(int index) const { + if (index < 0 || index >= images_.size()) { + return nullptr; + } + return &images_[index]; +} + +bool GltfAsset::PadBuffer() { + if (buffer_.size() % 4 != 0) { + const int pad_bytes = 4 - buffer_.size() % 4; + const int pad_data = 0; + if (!buffer_.Encode(&pad_data, pad_bytes)) { + return false; + } + } + return true; +} + +void GltfAsset::AddAttributeToDracoExtension( + const Mesh &mesh, GeometryAttribute::Type type, int index, + const std::string &name, GltfDracoCompressedMesh *compressed_mesh_info) { + if (mesh.IsCompressionEnabled()) { + const PointAttribute *const att = mesh.GetNamedAttribute(type, index); + if (att) { + compressed_mesh_info->attributes.insert( + std::pair(name, att->unique_id())); + } + } +} + +Status GltfAsset::CompressMeshWithDraco(const Mesh &mesh, + const Eigen::Matrix4d &transform, + GltfPrimitive *primitive, + int64_t *num_encoded_points, + int64_t *num_encoded_faces) { + // Check that geometry comression options are valid. + DracoCompressionOptions compression_options = mesh.GetCompressionOptions(); + DRACO_RETURN_IF_ERROR(compression_options.Check()); + + // Make a copy of the mesh. It will be modified and compressed. + std::unique_ptr mesh_copy(new Mesh()); + mesh_copy->Copy(mesh); + + // Delete auto-generated tangents. + if (MeshUtils::HasAutoGeneratedTangents(*mesh_copy)) { + for (int i = 0; i < mesh_copy->num_attributes(); ++i) { + PointAttribute *const att = mesh_copy->attribute(i); + if (att->attribute_type() == GeometryAttribute::TANGENT) { + while (mesh_copy->GetNamedAttribute(GeometryAttribute::TANGENT)) { + mesh_copy->DeleteAttribute( + mesh_copy->GetNamedAttributeId(GeometryAttribute::TANGENT)); + } + break; + } + } + } + + // Create Draco encoder. + EncoderBuffer buffer; + ExpertEncoder encoder(*mesh_copy); + encoder.SetTrackEncodedProperties(true); + + // Convert compression level to speed (that 0 = slowest, 10 = fastest). + const int speed = 10 - compression_options.compression_level; + encoder.SetSpeedOptions(speed, speed); + + // Configure attribute quantization. + for (int i = 0; i < mesh_copy->num_attributes(); ++i) { + const PointAttribute *const att = mesh_copy->attribute(i); + if (att->attribute_type() == GeometryAttribute::POSITION && + !compression_options.quantization_position + .AreQuantizationBitsDefined()) { + // Desired spacing in the "global" coordinate system. + const float global_spacing = + compression_options.quantization_position.spacing(); + + // Note: Ideally we would transform the whole mesh before encoding and + // apply the original global spacing on the transformed mesh. But neither + // KHR_draco_mesh_compression, nor Draco bitstream support post-decoding + // transformations so we have to modify the grid settings here. + + // Transform this spacing to the local coordinate system of the base mesh. + // We will get the largest scale factor from the transformation matrix and + // use it to adjust the grid spacing. + const Vector3f scale_vec(transform.col(0).norm(), transform.col(1).norm(), + transform.col(2).norm()); + + const float max_scale = scale_vec.MaxCoeff(); + + // Spacing is inverse to the scale. The larger the scale, the smaller the + // spacing must be. + const float local_spacing = global_spacing / max_scale; + + // Update the compression options of the processed mesh. + compression_options.quantization_position.SetGrid(local_spacing); + } else { + int num_quantization_bits = -1; + switch (att->attribute_type()) { + case GeometryAttribute::POSITION: + num_quantization_bits = + compression_options.quantization_position.quantization_bits(); + break; + case GeometryAttribute::NORMAL: + num_quantization_bits = compression_options.quantization_bits_normal; + break; + case GeometryAttribute::TEX_COORD: + num_quantization_bits = + compression_options.quantization_bits_tex_coord; + break; + case GeometryAttribute::TANGENT: + num_quantization_bits = compression_options.quantization_bits_tangent; + break; + case GeometryAttribute::WEIGHTS: + num_quantization_bits = compression_options.quantization_bits_weight; + break; + case GeometryAttribute::GENERIC: + if (GetFeatureIdAttributeName(*att, *mesh_copy).empty()) { + num_quantization_bits = + compression_options.quantization_bits_generic; + } else { + // Quantization is explicitly disabled for feature ID attributes. + encoder.SetAttributeQuantization(i, -1); + } + break; + default: + break; + } + if (num_quantization_bits > 0) { + encoder.SetAttributeQuantization(i, num_quantization_bits); + } + } + } + + // Flip UV values as required by glTF Draco and non-Draco files. + for (int i = 0; i < mesh_copy->num_attributes(); ++i) { + PointAttribute *const att = mesh_copy->attribute(i); + if (att->attribute_type() == GeometryAttribute::TEX_COORD) { + if (!MeshUtils::FlipTextureUvValues(false, true, att)) { + return Status(Status::DRACO_ERROR, "Could not flip texture UV values."); + } + } + } + + // Change tangents, joints, and weights attribute types to generic. The + // original mesh's attribute type is unchanged and the mapping of the glTF + // attribute type to Draco compressed attribute id is written to the output + // glTF file. + for (int i = 0; i < mesh_copy->num_attributes(); ++i) { + PointAttribute *const att = mesh_copy->attribute(i); + if (att->attribute_type() == GeometryAttribute::TANGENT || + att->attribute_type() == GeometryAttribute::JOINTS || + att->attribute_type() == GeometryAttribute::WEIGHTS) { + att->set_attribute_type(GeometryAttribute::GENERIC); + } + } + + // |compression_options| may have been modified and we need to update them + // before we start the encoding. + mesh_copy->SetCompressionOptions(compression_options); + DRACO_RETURN_IF_ERROR(encoder.EncodeToBuffer(&buffer)); + *num_encoded_points = encoder.num_encoded_points(); + *num_encoded_faces = encoder.num_encoded_faces(); + const size_t buffer_start_offset = buffer_.size(); + if (!buffer_.Encode(buffer.data(), buffer.size())) { + return Status(Status::DRACO_ERROR, "Could not copy Draco compressed data."); + } + if (!PadBuffer()) { + return Status(Status::DRACO_ERROR, "Could not pad glTF buffer."); + } + + GltfBufferView buffer_view; + buffer_view.buffer_byte_offset = buffer_start_offset; + buffer_view.byte_length = buffer_.size() - buffer_start_offset; + buffer_views_.push_back(buffer_view); + primitive->compressed_mesh_info.buffer_view_index = + static_cast(buffer_views_.size() - 1); + return OkStatus(); +} + +bool CheckAndGetTexCoordAttributeOrder(const Mesh &mesh, + std::vector *tex_coord_order) { + // We will only consider at most two texture coordinate attributes. + *tex_coord_order = {0, 1}; + const int num_attributes = + std::min(mesh.NumNamedAttributes(GeometryAttribute::TEX_COORD), 2); + + // Collect texture coordinate attribute names from metadata. + std::vector names(num_attributes, ""); + for (int i = 0; i < num_attributes; i++) { + const auto metadata = mesh.GetAttributeMetadataByAttributeId( + mesh.GetNamedAttributeId(GeometryAttribute::TEX_COORD, i)); + std::string attribute_name; + if (metadata != nullptr) { + metadata->GetEntryString("attribute_name", &attribute_name); + names[i] = attribute_name; + } + } + + // Attribute names may be absent. + if (num_attributes == 0 || + std::all_of(names.begin(), names.end(), + [](const std::string &name) { return name.empty(); })) { + return true; + } + + // Attribute names must be unique. + const std::unordered_set unique_names(names.begin(), + names.end()); + if (unique_names.size() != num_attributes) { + return false; + } + + // Attribute names must be valid. + if (std::any_of(names.begin(), names.end(), [](const std::string &name) { + return name != "TEXCOORD_0" && name != "TEXCOORD_1"; + })) { + return false; + } + + // Populate texture coordinate order index based on attribute names. + if (names[0] == "TEXCOORD_1") { + *tex_coord_order = {1, 0}; + } + return true; +} + +bool GltfAsset::AddDracoMesh( + const Mesh &mesh, int material_id, + const std::vector + &material_variants_mappings, + const Eigen::Matrix4d &transform) { + GltfPrimitive primitive; + int64_t num_encoded_points = mesh.num_points(); + int64_t num_encoded_faces = mesh.num_faces(); + if (num_encoded_faces > 0 && mesh.IsCompressionEnabled()) { + const Status status = CompressMeshWithDraco( + mesh, transform, &primitive, &num_encoded_points, &num_encoded_faces); + if (!status.ok()) { + return false; + } + draco_compression_used_ = true; + } + int indices_index = -1; + if (num_encoded_faces > 0) { + indices_index = AddDracoIndices(mesh, num_encoded_faces); + if (indices_index < 0) { + return false; + } + } + const int position_index = AddDracoPositions(mesh, num_encoded_points); + if (position_index < 0) { + return false; + } + // Check texture coordinate attributes and get the desired encoding order. + std::vector tex_coord_order; + if (!CheckAndGetTexCoordAttributeOrder(mesh, &tex_coord_order)) { + return false; + } + const int normals_accessor_index = AddDracoNormals(mesh, num_encoded_points); + const int colors_accessor_index = AddDracoColors(mesh, num_encoded_points); + const int texture0_accessor_index = + AddDracoTexture(mesh, tex_coord_order[0], num_encoded_points); + const int texture1_accessor_index = + AddDracoTexture(mesh, tex_coord_order[1], num_encoded_points); + const int tangent_accessor_index = AddDracoTangents(mesh, num_encoded_points); + const int joints_accessor_index = AddDracoJoints(mesh, num_encoded_points); + const int weights_accessor_index = AddDracoWeights(mesh, num_encoded_points); + const std::vector> generics_accessors = + AddDracoGenerics(mesh, num_encoded_points); + + if (num_encoded_faces == 0) { + primitive.mode = 0; // POINTS mode. + } + primitive.material = material_id; + primitive.material_variants_mappings = material_variants_mappings; + primitive.mesh_features.reserve(mesh.NumMeshFeatures()); + for (MeshFeaturesIndex i(0); i < mesh.NumMeshFeatures(); ++i) { + primitive.mesh_features.push_back(&mesh.GetMeshFeatures(i)); + } + primitive.indices = indices_index; + primitive.attributes.insert( + std::pair("POSITION", position_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::POSITION, 0, "POSITION", + &primitive.compressed_mesh_info); + if (normals_accessor_index > 0) { + primitive.attributes.insert( + std::pair("NORMAL", normals_accessor_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::NORMAL, 0, "NORMAL", + &primitive.compressed_mesh_info); + } + if (colors_accessor_index > 0) { + primitive.attributes.insert( + std::pair("COLOR_0", colors_accessor_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::COLOR, 0, "COLOR_0", + &primitive.compressed_mesh_info); + } + if (texture0_accessor_index > 0) { + primitive.attributes.insert( + std::pair("TEXCOORD_0", texture0_accessor_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::TEX_COORD, 0, + "TEXCOORD_0", &primitive.compressed_mesh_info); + } + if (texture1_accessor_index > 0) { + primitive.attributes.insert( + std::pair("TEXCOORD_1", texture1_accessor_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::TEX_COORD, 1, + "TEXCOORD_1", &primitive.compressed_mesh_info); + } + if (tangent_accessor_index > 0) { + primitive.attributes.insert( + std::pair("TANGENT", tangent_accessor_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::TANGENT, 0, "TANGENT", + &primitive.compressed_mesh_info); + } + if (joints_accessor_index > 0) { + primitive.attributes.insert( + std::pair("JOINTS_0", joints_accessor_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::JOINTS, 0, "JOINTS_0", + &primitive.compressed_mesh_info); + } + if (weights_accessor_index > 0) { + primitive.attributes.insert( + std::pair("WEIGHTS_0", weights_accessor_index)); + AddAttributeToDracoExtension(mesh, GeometryAttribute::WEIGHTS, 0, + "WEIGHTS_0", &primitive.compressed_mesh_info); + } + for (int att_index = 0; att_index < generics_accessors.size(); ++att_index) { + const std::string &attribute_name = generics_accessors[att_index].first; + if (!attribute_name.empty()) { + primitive.attributes.insert(generics_accessors[att_index]); + AddAttributeToDracoExtension(mesh, GeometryAttribute::GENERIC, att_index, + attribute_name, + &primitive.compressed_mesh_info); + } + } + + meshes_.back().primitives.push_back(primitive); + return true; +} + +int GltfAsset::AddDracoIndices(const Mesh &mesh, int64_t num_encoded_faces) { + // Get the min and max value for the indices. + uint32_t min_index = 0xffffffff; + uint32_t max_index = 0; + for (FaceIndex i(0); i < mesh.num_faces(); ++i) { + const auto &f = mesh.face(i); + + for (int j = 0; j < 3; ++j) { + if (f[j] < min_index) { + min_index = f[j].value(); + } + if (f[j] > max_index) { + max_index = f[j].value(); + } + } + } + + const int component_size = GltfAsset::UnsignedIntComponentSize(max_index); + + GltfAccessor accessor; + if (!mesh.IsCompressionEnabled()) { + const size_t buffer_start_offset = buffer_.size(); + for (FaceIndex i(0); i < mesh.num_faces(); ++i) { + const auto &f = mesh.face(i); + for (int j = 0; j < 3; ++j) { + int index = f[j].value(); + if (!buffer_.Encode(&index, component_size)) { + return -1; + } + } + } + + if (!PadBuffer()) { + return -1; + } + + GltfBufferView buffer_view; + buffer_view.buffer_byte_offset = buffer_start_offset; + buffer_view.byte_length = buffer_.size() - buffer_start_offset; + buffer_views_.push_back(buffer_view); + accessor.buffer_view_index = static_cast(buffer_views_.size() - 1); + } + + accessor.component_type = UnsignedIntComponentType(max_index); + accessor.count = num_encoded_faces * 3; + if (output_type_ == GltfEncoder::VERBOSE) { + accessor.max.push_back(GltfValue(max_index)); + accessor.min.push_back(GltfValue(min_index)); + } + accessor.type = "SCALAR"; + accessors_.push_back(accessor); + return static_cast(accessors_.size() - 1); +} + +int GltfAsset::AddDracoPositions(const Mesh &mesh, int num_encoded_points) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::POSITION); + if (!CheckDracoAttribute(att, {DT_FLOAT32}, {3})) { + return -1; + } + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); +} + +int GltfAsset::AddDracoNormals(const Mesh &mesh, int num_encoded_points) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::NORMAL); + if (!CheckDracoAttribute(att, {DT_FLOAT32}, {3})) { + return -1; + } + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); +} + +int GltfAsset::AddDracoColors(const Mesh &mesh, int num_encoded_points) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::COLOR); + // TODO(b/200302561): Add support for DT_UINT16 with COLOR. + if (!CheckDracoAttribute(att, {DT_UINT8, DT_FLOAT32}, {3, 4})) { + return -1; + } + if (att->data_type() == DT_FLOAT32) { + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); + } + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); +} + +int GltfAsset::AddDracoTexture(const Mesh &mesh, int tex_coord_index, + int num_encoded_points) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::TEX_COORD, tex_coord_index); + // TODO(b/200303080): Add support for DT_UINT8 and DT_UINT16 with TEX_COORD. + if (!CheckDracoAttribute(att, {DT_FLOAT32}, {2})) { + return -1; + } + + // glTF stores texture coordinates flipped on the horizontal axis compared to + // how Draco stores texture coordinates. + GeometryAttribute ga; + ga.Init(GeometryAttribute::TEX_COORD, nullptr, 2, att->data_type(), false, + DataTypeLength(att->data_type()) * 2, 0); + PointAttribute ta(ga); + ta.SetIdentityMapping(); + ta.Reset(mesh.num_points()); + + std::array value; + for (PointIndex v(0); v < mesh.num_points(); ++v) { + if (!att->GetValue(att->mapped_index(v), &value)) { + return -1; + } + + // Draco texture v component needs to be flipped. + Vector2f texture_coord(value[0], 1.0 - value[1]); + ta.SetAttributeValue(AttributeValueIndex(v.value()), texture_coord.data()); + } + return AddAttribute(ta, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); +} + +int GltfAsset::AddDracoTangents(const Mesh &mesh, int num_encoded_points) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::TANGENT); + if (!CheckDracoAttribute(att, {DT_FLOAT32}, {3, 4})) { + return -1; + } + if (MeshUtils::HasAutoGeneratedTangents(mesh)) { + // Ignore auto-generated tangents. See go/tangents_and_draco_simplifier. + return -1; + } + + if (att->num_components() == 4) { + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); + } + + // glTF mesh needs the w component. + GeometryAttribute ga; + ga.Init(GeometryAttribute::TANGENT, nullptr, 4, DT_FLOAT32, false, + DataTypeLength(DT_FLOAT32) * 4, 0); + PointAttribute ta(ga); + ta.SetIdentityMapping(); + ta.Reset(mesh.num_points()); + + std::array value; + for (PointIndex v(0); v < mesh.num_points(); ++v) { + if (!att->GetValue(att->mapped_index(v), &value)) { + return -1; + } + + // Draco tangent w component is always 1.0. + Vector4f tangent(value[0], value[1], value[2], 1.0); + ta.SetAttributeValue(AttributeValueIndex(v.value()), tangent.data()); + } + return AddAttribute(ta, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); +} + +int GltfAsset::AddDracoJoints(const Mesh &mesh, int num_encoded_points) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::JOINTS); + if (!CheckDracoAttribute(att, {DT_UINT8, DT_UINT16}, {4})) { + return -1; + } + if (att->data_type() == DT_UINT16) { + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); + } + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); +} + +int GltfAsset::AddDracoWeights(const Mesh &mesh, int num_encoded_points) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::WEIGHTS); + // TODO(b/200303026): Add support for DT_UINT8 and DT_UINT16 with WEIGHTS. + if (!CheckDracoAttribute(att, {DT_FLOAT32}, {4})) { + return -1; + } + return AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); +} + +// Adds generic attributes that have metadata describing the attribute name. +// This allows for export of application-specific attributes and feature ID +// attributes defined in glTF extension EXT_mesh_features. Returns a vector of +// attribute-name, accessor pairs for each valid attribute. The length of the +// vector is equal to the number of generic attributes. Vector entries +// corresponding to unsupported attributes (e.g., with no metadata) contain +// empty attribute names. +std::vector> GltfAsset::AddDracoGenerics( + const Mesh &mesh, int num_encoded_points) { + const int num_attributes = + mesh.NumNamedAttributes(GeometryAttribute::GENERIC); + std::vector> attrs(num_attributes); + for (int i = 0; i < num_attributes; ++i) { + const PointAttribute *const att = + mesh.GetNamedAttribute(GeometryAttribute::GENERIC, i); + auto const *metadata = + mesh.GetAttributeMetadataByAttributeId(att->unique_id()); + if (metadata) { + std::string attr_name; + if (metadata->GetEntryString(GltfEncoder::kDracoMetadataGltfAttributeName, + &attr_name)) { + if (att->data_type() == DT_FLOAT32) { + int accessor = + AddAttribute(*att, mesh.num_points(), num_encoded_points, + mesh.IsCompressionEnabled()); + attrs[i] = {attr_name, accessor}; + } + } else { + // Try to find feature ID attribute name like "_FEATURE_ID_5" then check + // that the attribute stores scalar values of complient data types as + // defined by the EXT_mesh_features glTF extension. + attr_name = GetFeatureIdAttributeName(*att, mesh); + if (!attr_name.empty() && att->num_components() == 1) { + int accessor = -1; + switch (att->data_type()) { + case DT_UINT8: + accessor = AddAttribute(*att, mesh.num_points(), + num_encoded_points, + mesh.IsCompressionEnabled()); + break; + case DT_UINT16: + accessor = AddAttribute(*att, mesh.num_points(), + num_encoded_points, + mesh.IsCompressionEnabled()); + break; + case DT_FLOAT32: + accessor = AddAttribute(*att, mesh.num_points(), + num_encoded_points, + mesh.IsCompressionEnabled()); + break; + default: + continue; + } + attrs[i] = {attr_name, accessor}; + } + } + } + } + return attrs; +} + +bool GltfAsset::AddMaterials(const Mesh &mesh) { + if (mesh.GetMaterialLibrary().NumMaterials() == 0) { + return true; + } + material_library_.Copy(mesh.GetMaterialLibrary()); + return true; +} + +bool GltfAsset::CheckDracoAttribute(const PointAttribute *attribute, + const std::set &data_types, + const std::set &num_components) { + // Attribute must be valid. + if (attribute == nullptr || attribute->size() == 0) { + return false; + } + + // Attribute must have an expected data type. + if (data_types.find(attribute->data_type()) == data_types.end()) { + return false; + } + + // Attribute must have an expected number of components. + if (num_components.find(attribute->num_components()) == + num_components.end()) { + return false; + } + + return true; +} + +StatusOr GltfAsset::AddImage(const std::string &image_stem, + const Texture *texture, int num_components) { + return AddImage(image_stem, texture, nullptr, num_components); +} + +StatusOr GltfAsset::AddImage(const std::string &image_stem, + const Texture *texture, + std::unique_ptr owned_texture, + int num_components) { + const auto it = texture_to_image_index_map_.find(texture); + if (it != texture_to_image_index_map_.end()) { + // We already have an image for the given |texture|. Update its number of + // components if needed. + GltfImage &image = images_[it->second]; + if (image.num_components < num_components) { + image.num_components = num_components; + } + return it->second; + } + std::string extension = TextureUtils::GetTargetExtension(*texture); + if (extension.empty()) { + // Try to get extension from the source file name. + extension = LowercaseFileExtension(texture->source_image().filename()); + } + GltfImage image; + image.image_name = image_stem + "." + extension; + image.texture = texture; + image.owned_texture = std::move(owned_texture); + image.num_components = num_components; + + // Always maintain the mime_type. Used elsewhere to determine image type. + if (extension == "jpg") { + image.mime_type = "image/jpeg"; + } else { + image.mime_type = "image/" + extension; + } + + // For KTX2 with Basis compression, state that its extension is required. + if (extension == "ktx2") { + extensions_used_.insert("KHR_texture_basisu"); + extensions_required_.insert("KHR_texture_basisu"); + } + + // If this is webp, state that its extension is required. + if (extension == "webp") { + extensions_used_.insert("EXT_texture_webp"); + extensions_required_.insert("EXT_texture_webp"); + } + + images_.push_back(std::move(image)); + texture_to_image_index_map_[texture] = images_.size() - 1; + return images_.size() - 1; +} + +Status GltfAsset::SaveImageToBuffer(int image_index) { + GltfImage &image = images_[image_index]; + const Texture *const texture = image.texture; + const int num_components = image.num_components; + std::vector buffer; + DRACO_RETURN_IF_ERROR(WriteTextureToBuffer(*texture, &buffer)); + + // Add the image data to the buffer. + const size_t buffer_start_offset = buffer_.size(); + buffer_.Encode(buffer.data(), buffer.size()); + if (!PadBuffer()) { + return Status(Status::DRACO_ERROR, + "Could not pad buffer in SaveImageToBuffer."); + } + + // Add a buffer view pointing to the image data in the buffer. + GltfBufferView buffer_view; + buffer_view.buffer_byte_offset = buffer_start_offset; + buffer_view.byte_length = buffer_.size() - buffer_start_offset; + buffer_views_.push_back(buffer_view); + + image.buffer_view = buffer_views_.size() - 1; + return OkStatus(); +} + +// TODO(vytyaz): The return type could be int. +StatusOr GltfAsset::AddTextureSampler(const TextureSampler &sampler) { + // If sampler is equal to defaults do not add to vector and return -1. + if (sampler.min_filter == TextureMap::UNSPECIFIED && + sampler.mag_filter == TextureMap::UNSPECIFIED && + sampler.wrapping_mode.s == TextureMap::REPEAT && + sampler.wrapping_mode.t == TextureMap::REPEAT) { + return -1; + } + + const auto &it = + std::find(texture_samplers_.begin(), texture_samplers_.end(), sampler); + if (it != texture_samplers_.end()) { + const int index = std::distance(texture_samplers_.begin(), it); + return index; + } + + texture_samplers_.push_back(sampler); + return texture_samplers_.size() - 1; +} + +Status GltfAsset::AddScene(const Scene &scene) { + const int scene_index = AddScene(); + if (scene_index < 0) { + return Status(Status::DRACO_ERROR, "Error creating a new scene."); + } + if (!AddMaterials(scene)) { + return Status(Status::DRACO_ERROR, "Error adding materials to the scene."); + } + // Initialize base mesh transforms that may be needed when the base meshes are + // compressed with Draco. + base_mesh_transforms_ = SceneUtils::FindLargestBaseMeshTransforms(scene); + for (SceneNodeIndex i(0); i < scene.NumNodes(); ++i) { + DRACO_RETURN_IF_ERROR(AddSceneNode(scene, i)); + } + // There is 1:1 mapping between draco::Scene node indices and |nodes_|. + for (int i = 0; i < scene.NumRootNodes(); ++i) { + nodes_[scene.GetRootNodeIndex(i).value()].root_node = true; + } + DRACO_RETURN_IF_ERROR(AddAnimations(scene)); + DRACO_RETURN_IF_ERROR(AddSkins(scene)); + DRACO_RETURN_IF_ERROR(AddLights(scene)); + DRACO_RETURN_IF_ERROR(AddMaterialsVariantsNames(scene)); + DRACO_RETURN_IF_ERROR(AddInstanceArrays(scene)); + AddStructuralMetadata(scene); + return OkStatus(); +} + +Status GltfAsset::AddSceneNode(const Scene &scene, + SceneNodeIndex scene_node_index) { + const SceneNode *const scene_node = scene.GetNode(scene_node_index); + if (scene_node == nullptr) { + return Status(Status::DRACO_ERROR, "Could not find node in scene."); + } + + GltfNode node; + node.name = scene_node->GetName(); + node.trs_matrix.Copy(scene_node->GetTrsMatrix()); + + for (int i = 0; i < scene_node->NumChildren(); ++i) { + node.childern_indices.push_back(scene_node->Child(i).value()); + } + + const MeshGroupIndex mesh_group_index = scene_node->GetMeshGroupIndex(); + if (mesh_group_index != kInvalidMeshGroupIndex) { + const auto it = mesh_group_index_to_gltf_mesh_.find(mesh_group_index); + if (it == mesh_group_index_to_gltf_mesh_.end()) { + GltfMesh gltf_mesh; + const MeshGroup *const mesh_group = scene.GetMeshGroup(mesh_group_index); + if (!mesh_group->GetName().empty()) { + gltf_mesh.name = mesh_group->GetName(); + } + meshes_.push_back(gltf_mesh); + + for (int i = 0; i < mesh_group->NumMeshInstances(); ++i) { + const MeshGroup::MeshInstance &instance = + mesh_group->GetMeshInstance(i); + const auto mi_it = + mesh_index_to_gltf_mesh_primitive_.find(instance.mesh_index); + if (mi_it == mesh_index_to_gltf_mesh_primitive_.end()) { + // We have not added the mesh to the scene yet. + const Mesh &mesh = scene.GetMesh(instance.mesh_index); + if (!AddDracoMesh(mesh, instance.material_index, + instance.materials_variants_mappings, + base_mesh_transforms_[instance.mesh_index])) { + return Status(Status::DRACO_ERROR, "Adding a Draco mesh failed."); + } + const int gltf_mesh_index = meshes_.size() - 1; + const int gltf_primitive_index = meshes_.back().primitives.size() - 1; + mesh_index_to_gltf_mesh_primitive_[instance.mesh_index] = + std::make_pair(gltf_mesh_index, gltf_primitive_index); + } else { + // The mesh was already added to the scene. This is a copy instance + // that may have a different material. + const int gltf_mesh_index = mi_it->second.first; + const int gltf_primitive_index = mi_it->second.second; + GltfPrimitive primitive = + meshes_[gltf_mesh_index].primitives[gltf_primitive_index]; + primitive.material = instance.material_index; + primitive.material_variants_mappings = + instance.materials_variants_mappings; + const Mesh &mesh = scene.GetMesh(instance.mesh_index); + primitive.mesh_features.clear(); + primitive.mesh_features.reserve(mesh.NumMeshFeatures()); + for (MeshFeaturesIndex j(0); j < mesh.NumMeshFeatures(); ++j) { + primitive.mesh_features.push_back(&mesh.GetMeshFeatures(j)); + } + meshes_.back().primitives.push_back(primitive); + } + } + mesh_group_index_to_gltf_mesh_[mesh_group_index] = meshes_.size() - 1; + } + node.mesh_index = mesh_group_index_to_gltf_mesh_[mesh_group_index]; + } + node.skin_index = scene_node->GetSkinIndex().value(); + node.light_index = scene_node->GetLightIndex().value(); + node.instance_array_index = scene_node->GetInstanceArrayIndex().value(); + + nodes_.push_back(node); + return OkStatus(); +} + +bool GltfAsset::AddMaterials(const Scene &scene) { + if (scene.GetMaterialLibrary().NumMaterials() == 0) { + return true; + } + material_library_.Copy(scene.GetMaterialLibrary()); + return true; +} + +Status GltfAsset::AddAnimations(const Scene &scene) { + if (scene.NumAnimations() == 0) { + return OkStatus(); + } + // Mapping of the node animation data to the output accessors. The first part + // of the key is the animation index and the second part of the key is the + // node animation data index. + std::map, int> node_animation_data_to_accessor; + + // Mapping of the node animation data to the output accessors. + std::unordered_map + data_to_index_map; + + // First add all the accessors and create a mapping from animation accessors + // to accessors owned by the encoder. + for (AnimationIndex i(0); i < scene.NumAnimations(); ++i) { + const Animation *const animation = scene.GetAnimation(i); + + for (int j = 0; j < animation->NumNodeAnimationData(); ++j) { + const NodeAnimationData *const node_animation_data = + animation->GetNodeAnimationData(j); + + int index = -1; + + NodeAnimationDataHash nadh(node_animation_data); + if (data_to_index_map.find(nadh) == data_to_index_map.end()) { + // The current data is new, add it to the encoder. + DRACO_ASSIGN_OR_RETURN( + index, AddNodeAnimationData(*nadh.GetNodeAnimationData())); + data_to_index_map[nadh] = index; + } else { + index = data_to_index_map[nadh]; + } + + const auto key = std::make_pair(i.value(), j); + node_animation_data_to_accessor[key] = index; + } + } + + // Add all the samplers and channels. + for (AnimationIndex i(0); i < scene.NumAnimations(); ++i) { + const Animation *const animation = scene.GetAnimation(i); + std::unique_ptr new_animation(new EncoderAnimation); + new_animation->name = animation->GetName(); + + for (int j = 0; j < animation->NumSamplers(); ++j) { + const AnimationSampler *const sampler = animation->GetSampler(j); + const auto input_key = std::make_pair(i.value(), sampler->input_index); + const auto input_it = node_animation_data_to_accessor.find(input_key); + if (input_it == node_animation_data_to_accessor.end()) { + return Status(Status::DRACO_ERROR, + "Could not find animation accessor input index."); + } + const auto output_key = std::make_pair(i.value(), sampler->output_index); + const auto output_it = node_animation_data_to_accessor.find(output_key); + if (output_it == node_animation_data_to_accessor.end()) { + return Status(Status::DRACO_ERROR, + "Could not find animation accessor output index."); + } + + std::unique_ptr new_sampler(new AnimationSampler()); + new_sampler->input_index = input_it->second; + new_sampler->output_index = output_it->second; + + if (output_type_ == GltfEncoder::COMPACT) { + // Remove min/max from output accessor. + accessors_[new_sampler->output_index].min.clear(); + accessors_[new_sampler->output_index].max.clear(); + } + + new_sampler->interpolation_type = sampler->interpolation_type; + + new_animation->samplers.push_back(std::move(new_sampler)); + } + + for (int j = 0; j < animation->NumChannels(); ++j) { + const AnimationChannel *const channel = animation->GetChannel(j); + std::unique_ptr new_channel(new AnimationChannel()); + new_channel->Copy(*channel); + new_animation->channels.push_back(std::move(new_channel)); + } + + animations_.push_back(std::move(new_animation)); + } + return OkStatus(); +} + +StatusOr GltfAsset::AddNodeAnimationData( + const NodeAnimationData &node_animation_data) { + const size_t buffer_start_offset = buffer_.size(); + + const int component_size = node_animation_data.ComponentSize(); + const int num_components = node_animation_data.NumComponents(); + const std::vector *data = node_animation_data.GetData(); + + std::vector min_values; + min_values.resize(num_components); + for (int j = 0; j < num_components; ++j) { + min_values[j] = (*data)[j]; + } + std::vector max_values = min_values; + + for (int i = 0; i < node_animation_data.count(); ++i) { + for (int j = 0; j < num_components; ++j) { + const float value = (*data)[(i * num_components) + j]; + if (value < min_values[j]) { + min_values[j] = value; + } + if (value > max_values[j]) { + max_values[j] = value; + } + + buffer_.Encode(&value, component_size); + } + } + + if (!PadBuffer()) { + return Status(Status::DRACO_ERROR, + "AddNodeAnimationData: PadBuffer returned DRACO_ERROR."); + } + + GltfBufferView buffer_view; + buffer_view.buffer_byte_offset = buffer_start_offset; + buffer_view.byte_length = buffer_.size() - buffer_start_offset; + buffer_views_.push_back(buffer_view); + + GltfAccessor accessor; + accessor.buffer_view_index = static_cast(buffer_views_.size() - 1); + accessor.component_type = ComponentType::FLOAT; + accessor.count = node_animation_data.count(); + for (int j = 0; j < num_components; ++j) { + accessor.max.push_back(GltfValue(max_values[j])); + accessor.min.push_back(GltfValue(min_values[j])); + } + accessor.type = node_animation_data.TypeAsString(); + accessor.normalized = node_animation_data.normalized(); + accessors_.push_back(accessor); + return static_cast(accessors_.size() - 1); +} + +Status GltfAsset::AddSkins(const Scene &scene) { + if (scene.NumSkins() == 0) { + return OkStatus(); + } + + for (SkinIndex i(0); i < scene.NumSkins(); ++i) { + const Skin *const skin = scene.GetSkin(i); + DRACO_ASSIGN_OR_RETURN( + const int output_accessor_index, + AddNodeAnimationData(skin->GetInverseBindMatrices())); + + std::unique_ptr encoder_skin(new EncoderSkin); + encoder_skin->inverse_bind_matrices_index = output_accessor_index; + encoder_skin->joints.reserve(skin->NumJoints()); + for (int j = 0; j < skin->NumJoints(); j++) { + encoder_skin->joints.push_back(skin->GetJoint(j).value()); + } + encoder_skin->skeleton_index = skin->GetJointRoot().value(); + skins_.push_back(std::move(encoder_skin)); + } + return OkStatus(); +} + +Status GltfAsset::AddLights(const Scene &scene) { + if (scene.NumLights() == 0) { + return OkStatus(); + } + + for (LightIndex i(0); i < scene.NumLights(); ++i) { + std::unique_ptr light = std::unique_ptr(new Light()); + light->Copy(*scene.GetLight(i)); + lights_.push_back(std::move(light)); + } + return OkStatus(); +} + +Status GltfAsset::AddMaterialsVariantsNames(const Scene &scene) { + const MaterialLibrary &library = scene.GetMaterialLibrary(); + for (int i = 0; i < library.NumMaterialsVariants(); ++i) { + materials_variants_names_.push_back(library.GetMaterialsVariantName(i)); + } + return OkStatus(); +} + +Status GltfAsset::AddInstanceArrays(const Scene &scene) { + if (scene.NumInstanceArrays() == 0) { + return OkStatus(); + } + + // Add each of the instance arrays. + std::vector t_data; + std::vector r_data; + std::vector s_data; + for (InstanceArrayIndex i(0); i < scene.NumInstanceArrays(); ++i) { + // Find which of the optional TRS components are set. + // TODO(vytyaz): Treat default TRS component vectors as absent. + const InstanceArray &array = *scene.GetInstanceArray(i); + bool is_t_set = false; + bool is_r_set = false; + bool is_s_set = false; + for (int i = 0; i < array.NumInstances(); i++) { + const InstanceArray::Instance &instance = array.GetInstance(i); + if (instance.trs.TranslationSet()) { + is_t_set = true; + } + if (instance.trs.RotationSet()) { + is_r_set = true; + } + if (instance.trs.ScaleSet()) { + is_s_set = true; + } + } + + // Create contiguous data vectors for individual TRS components. + t_data.clear(); + r_data.clear(); + s_data.clear(); + if (is_t_set) { + t_data.reserve(array.NumInstances() * 3); + } + if (is_r_set) { + r_data.reserve(array.NumInstances() * 4); + } + if (is_s_set) { + s_data.reserve(array.NumInstances() * 3); + } + + // Add TRS vectors of each instance to corresponding data vectors. + for (int i = 0; i < array.NumInstances(); i++) { + const InstanceArray::Instance &instance = array.GetInstance(i); + if (is_t_set) { + DRACO_ASSIGN_OR_RETURN(const auto &t_vector, + instance.trs.Translation()); + t_data.push_back(t_vector.x()); + t_data.push_back(t_vector.y()); + t_data.push_back(t_vector.z()); + } + if (is_r_set) { + DRACO_ASSIGN_OR_RETURN(const auto &r_vector, instance.trs.Rotation()); + r_data.push_back(r_vector.x()); + r_data.push_back(r_vector.y()); + r_data.push_back(r_vector.z()); + r_data.push_back(r_vector.w()); + } + if (is_s_set) { + DRACO_ASSIGN_OR_RETURN(const auto &s_vector, instance.trs.Scale()); + s_data.push_back(s_vector.x()); + s_data.push_back(s_vector.y()); + s_data.push_back(s_vector.z()); + } + } + + // Add TRS vectors to attribute buffers and collect their accessor indices. + EncoderInstanceArray accessors; + if (is_t_set) { + DRACO_ASSIGN_OR_RETURN(accessors.translation, AddData(t_data, 3)); + } + if (is_r_set) { + DRACO_ASSIGN_OR_RETURN(accessors.rotation, AddData(r_data, 4)); + } + if (is_s_set) { + DRACO_ASSIGN_OR_RETURN(accessors.scale, AddData(s_data, 3)); + } + + // Store accessors for later to encode as EXT_mesh_gpu_instancing extension. + instance_arrays_.push_back(accessors); + } + return OkStatus(); +} + +template +void GltfAsset::AddStructuralMetadata(const GeometryT &geometry) { + const StructuralMetadata &structural_metadata = + geometry.GetStructuralMetadata(); + if (!structural_metadata.GetPropertyTableSchema().Empty()) { + property_table_schema_ = structural_metadata.GetPropertyTableSchema(); + for (int i = 0; i < structural_metadata.NumPropertyTables(); ++i) { + property_tables_.push_back(&structural_metadata.GetPropertyTable(i)); + } + } +} + +StatusOr GltfAsset::AddData(const std::vector &data, + int num_components) { + std::string type; + switch (num_components) { + case 3: + type = "VEC3"; + break; + case 4: + type = "VEC4"; + break; + default: + return ErrorStatus("Unsupported number of components."); + } + + const size_t buffer_start_offset = buffer_.size(); + + std::vector min_values(num_components); + for (int j = 0; j < num_components; ++j) { + min_values[j] = data[j]; + } + std::vector max_values = min_values; + + const int count = data.size() / num_components; + for (int i = 0; i < count; ++i) { + for (int j = 0; j < num_components; ++j) { + const float value = data[(i * num_components) + j]; + if (value < min_values[j]) { + min_values[j] = value; + } + if (value > max_values[j]) { + max_values[j] = value; + } + buffer_.Encode(&value, sizeof(float)); + } + } + + if (!PadBuffer()) { + return ErrorStatus("AddArray: PadBuffer returned DRACO_ERROR."); + } + + GltfBufferView buffer_view; + buffer_view.buffer_byte_offset = buffer_start_offset; + buffer_view.byte_length = buffer_.size() - buffer_start_offset; + buffer_views_.push_back(buffer_view); + + GltfAccessor accessor; + accessor.buffer_view_index = static_cast(buffer_views_.size() - 1); + accessor.component_type = ComponentType::FLOAT; + accessor.count = count; + for (int j = 0; j < num_components; ++j) { + accessor.max.push_back(GltfValue(max_values[j])); + accessor.min.push_back(GltfValue(min_values[j])); + } + accessor.type = type; + accessor.normalized = false; + accessors_.push_back(accessor); + return static_cast(accessors_.size() - 1); +} + +StatusOr GltfAsset::AddBufferView( + const PropertyTable::Property::Data &data) { + const size_t buffer_start_offset = buffer_.size(); + buffer_.Encode(data.data.data(), data.data.size()); + if (!PadBuffer()) { + return ErrorStatus("AddBufferView: PadBuffer returned DRACO_ERROR."); + } + GltfBufferView buffer_view; + buffer_view.buffer_byte_offset = buffer_start_offset; + buffer_view.byte_length = buffer_.size() - buffer_start_offset; + buffer_view.target = data.target; + buffer_views_.push_back(buffer_view); + return static_cast(buffer_views_.size() - 1); +} + +bool GltfAsset::EncodeAssetProperty(EncoderBuffer *buf_out) { + gltf_json_.BeginObject("asset"); + gltf_json_.OutputValue("version", version_); + gltf_json_.OutputValue("generator", generator_); + gltf_json_.EndObject(); + + const std::string asset_str = gltf_json_.MoveData(); + return buf_out->Encode(asset_str.data(), asset_str.length()); +} + +bool GltfAsset::EncodeScenesProperty(EncoderBuffer *buf_out) { + // We currently only support one scene. + gltf_json_.BeginArray("scenes"); + gltf_json_.BeginObject(); + gltf_json_.BeginArray("nodes"); + + for (int i = 0; i < nodes_.size(); ++i) { + if (nodes_[i].root_node) { + gltf_json_.OutputValue(i); + } + } + gltf_json_.EndArray(); + gltf_json_.EndObject(); + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + return buf_out->Encode(asset_str.data(), asset_str.length()); +} + +bool GltfAsset::EncodeInitialSceneProperty(EncoderBuffer *buf_out) { + gltf_json_.OutputValue("scene", scene_index_); + const std::string asset_str = gltf_json_.MoveData(); + return buf_out->Encode(asset_str.data(), asset_str.length()); +} + +bool GltfAsset::EncodeNodesProperty(EncoderBuffer *buf_out) { + gltf_json_.BeginArray("nodes"); + + for (int i = 0; i < nodes_.size(); ++i) { + gltf_json_.BeginObject(); + if (!nodes_[i].name.empty()) { + gltf_json_.OutputValue("name", nodes_[i].name); + } + if (nodes_[i].mesh_index >= 0) { + gltf_json_.OutputValue("mesh", nodes_[i].mesh_index); + } + if (nodes_[i].skin_index >= 0) { + gltf_json_.OutputValue("skin", nodes_[i].skin_index); + } + if (nodes_[i].instance_array_index >= 0 || nodes_[i].light_index >= 0) { + gltf_json_.BeginObject("extensions"); + if (nodes_[i].instance_array_index >= 0) { + gltf_json_.BeginObject("EXT_mesh_gpu_instancing"); + gltf_json_.BeginObject("attributes"); + const int index = nodes_[i].instance_array_index; + const EncoderInstanceArray &accessors = instance_arrays_[index]; + if (accessors.translation != -1) { + gltf_json_.OutputValue("TRANSLATION", accessors.translation); + } + if (accessors.rotation != -1) { + gltf_json_.OutputValue("ROTATION", accessors.rotation); + } + if (accessors.scale != -1) { + gltf_json_.OutputValue("SCALE", accessors.scale); + } + gltf_json_.EndObject(); + gltf_json_.EndObject(); + } + if (nodes_[i].light_index >= 0) { + gltf_json_.BeginObject("KHR_lights_punctual"); + gltf_json_.OutputValue("light", nodes_[i].light_index); + gltf_json_.EndObject(); + } + gltf_json_.EndObject(); + } + + if (!nodes_[i].childern_indices.empty()) { + gltf_json_.BeginArray("children"); + for (int j = 0; j < nodes_[i].childern_indices.size(); ++j) { + gltf_json_.OutputValue(nodes_[i].childern_indices[j]); + } + gltf_json_.EndArray(); + } + + if (!nodes_[i].trs_matrix.IsMatrixIdentity()) { + const auto maybe_transformation = nodes_[i].trs_matrix.Matrix(); + const auto transformation = maybe_transformation.ValueOrDie(); + + if (nodes_[i].trs_matrix.IsMatrixTranslationOnly()) { + gltf_json_.BeginArray("translation"); + for (int j = 0; j < 3; ++j) { + gltf_json_.OutputValue(transformation(j, 3)); + } + gltf_json_.EndArray(); + } else { + gltf_json_.BeginArray("matrix"); + for (int j = 0; j < 4; ++j) { + for (int k = 0; k < 4; ++k) { + gltf_json_.OutputValue(transformation(k, j)); + } + } + gltf_json_.EndArray(); + } + } else { + if (nodes_[i].trs_matrix.TranslationSet()) { + const auto maybe_translation = nodes_[i].trs_matrix.Translation(); + const auto translation = maybe_translation.ValueOrDie(); + gltf_json_.BeginArray("translation"); + for (int j = 0; j < 3; ++j) { + gltf_json_.OutputValue(translation[j]); + } + gltf_json_.EndArray(); + } + if (nodes_[i].trs_matrix.RotationSet()) { + const auto maybe_rotation = nodes_[i].trs_matrix.Rotation(); + const auto rotation = maybe_rotation.ValueOrDie(); + gltf_json_.BeginArray("rotation"); + for (int j = 0; j < 4; ++j) { + // Note: coeffs() returns quaternion values as (x, y, z, w) which is + // the expected format of glTF. + gltf_json_.OutputValue(rotation.coeffs()[j]); + } + gltf_json_.EndArray(); + } + if (nodes_[i].trs_matrix.ScaleSet()) { + const auto maybe_scale = nodes_[i].trs_matrix.Scale(); + const auto scale = maybe_scale.ValueOrDie(); + gltf_json_.BeginArray("scale"); + for (int j = 0; j < 3; ++j) { + gltf_json_.OutputValue(scale[j]); + } + gltf_json_.EndArray(); + } + } + + gltf_json_.EndObject(); + } + + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + return buf_out->Encode(asset_str.data(), asset_str.length()); +} + +Status GltfAsset::EncodeMeshesProperty(EncoderBuffer *buf_out) { + mesh_features_texture_index_ = 0; + gltf_json_.BeginArray("meshes"); + + for (int i = 0; i < meshes_.size(); ++i) { + gltf_json_.BeginObject(); + + if (!meshes_[i].name.empty()) { + gltf_json_.OutputValue("name", meshes_[i].name); + } + + if (!meshes_[i].primitives.empty()) { + gltf_json_.BeginArray("primitives"); + + for (int j = 0; j < meshes_[i].primitives.size(); ++j) { + const GltfPrimitive &primitive = meshes_[i].primitives[j]; + gltf_json_.BeginObject(); + + gltf_json_.BeginObject("attributes"); + for (auto const &it : primitive.attributes) { + gltf_json_.OutputValue(it.first, it.second); + } + gltf_json_.EndObject(); + + if (primitive.indices >= 0) { + gltf_json_.OutputValue("indices", primitive.indices); + } + gltf_json_.OutputValue("mode", primitive.mode); + if (primitive.material >= 0) { + gltf_json_.OutputValue("material", primitive.material); + } + DRACO_RETURN_IF_ERROR( + EncodePrimitiveExtensionsProperty(primitive, buf_out)); + gltf_json_.EndObject(); + } + + gltf_json_.EndArray(); + } + + gltf_json_.EndObject(); + } + + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + if (!buf_out->Encode(asset_str.data(), asset_str.length())) { + return ErrorStatus("Failed encoding meshes."); + } + return OkStatus(); +} + +Status GltfAsset::EncodePrimitiveExtensionsProperty( + const GltfPrimitive &primitive, EncoderBuffer *buf_out) { + // Return if the primitive has no extensions to encode. + const bool has_draco_mesh_compression = + primitive.compressed_mesh_info.buffer_view_index >= 0; + const bool has_materials_variants = + !primitive.material_variants_mappings.empty(); + const bool has_mesh_features = !primitive.mesh_features.empty(); + if (!has_draco_mesh_compression && !has_materials_variants && + !has_mesh_features) { + return OkStatus(); + } + + // Encode primitive extensions. + gltf_json_.BeginObject("extensions"); + if (has_draco_mesh_compression) { + gltf_json_.BeginObject("KHR_draco_mesh_compression"); + gltf_json_.OutputValue("bufferView", + primitive.compressed_mesh_info.buffer_view_index); + gltf_json_.BeginObject("attributes"); + for (auto const &it : primitive.compressed_mesh_info.attributes) { + gltf_json_.OutputValue(it.first, it.second); + } + gltf_json_.EndObject(); // attributes entry. + gltf_json_.EndObject(); // KHR_draco_mesh_compression entry. + } + if (has_materials_variants) { + gltf_json_.BeginObject("KHR_materials_variants"); + gltf_json_.BeginArray("mappings"); + for (const auto &mapping : primitive.material_variants_mappings) { + gltf_json_.BeginObject(); + gltf_json_.OutputValue("material", mapping.material); + gltf_json_.BeginArray("variants"); + for (const int variant : mapping.variants) { + gltf_json_.OutputValue(variant); + } + gltf_json_.EndArray(); // variants array. + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); // mappings array. + gltf_json_.EndObject(); // KHR_materials_variants entry. + } + if (has_mesh_features) { + gltf_json_.BeginObject("EXT_mesh_features"); + gltf_json_.BeginArray("featureIds"); + for (int i = 0; i < primitive.mesh_features.size(); i++) { + const auto &features = primitive.mesh_features[i]; + gltf_json_.BeginObject(); + if (!features->GetLabel().empty()) { + gltf_json_.OutputValue("label", features->GetLabel()); + } + gltf_json_.OutputValue("featureCount", features->GetFeatureCount()); + if (features->GetAttributeIndex() != -1) { + gltf_json_.OutputValue("attribute", features->GetAttributeIndex()); + } + if (features->GetPropertyTableIndex() != -1) { + gltf_json_.OutputValue("propertyTable", + features->GetPropertyTableIndex()); + } + if (features->GetTextureMap().tex_coord_index() != -1) { + const TextureMap &texture_map = features->GetTextureMap(); + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *texture_map.texture(), mesh_features_texture_index_++, + "_MeshFeatures"); + + // Save image as RGBA if the A channel is used to store feature ID. + const auto &channels = features->GetTextureChannels(); + const int num_channels = + std::count(channels.begin(), channels.end(), 3) == 1 ? 4 : 3; + DRACO_ASSIGN_OR_RETURN( + const int image_index, + AddImage(texture_stem, texture_map.texture(), num_channels)); + const int tex_coord_index = texture_map.tex_coord_index(); + Material dummy_material; + DRACO_RETURN_IF_ERROR(EncodeTextureMap("texture", image_index, + tex_coord_index, dummy_material, + texture_map, channels)); + } + if (features->GetNullFeatureId() != -1) { + gltf_json_.OutputValue("nullFeatureId", features->GetNullFeatureId()); + } + gltf_json_.EndObject(); + mesh_features_used_ = true; + } + gltf_json_.EndArray(); // featureIds array. + gltf_json_.EndObject(); // EXT_mesh_features entry. + } + gltf_json_.EndObject(); // extensions entry. + return OkStatus(); +} + +Status GltfAsset::EncodeMaterials(EncoderBuffer *buf_out) { + // Check if we have textures to write. + if (material_library_.NumMaterials() == 0) { + return EncodeDefaultMaterial(buf_out); + } + return EncodeMaterialsProperty(buf_out); +} + +void GltfAsset::EncodeColorMaterial(float red, float green, float blue, + float alpha, float metallic_factor) { + gltf_json_.BeginObject("pbrMetallicRoughness"); + + gltf_json_.BeginArray("baseColorFactor"); + gltf_json_.OutputValue(red); + gltf_json_.OutputValue(green); + gltf_json_.OutputValue(blue); + gltf_json_.OutputValue(alpha); + gltf_json_.EndArray(); + gltf_json_.OutputValue("metallicFactor", metallic_factor); + + gltf_json_.EndObject(); // pbrMetallicRoughness +} + +Status GltfAsset::EncodeDefaultMaterial(EncoderBuffer *buf_out) { + gltf_json_.BeginArray("materials"); + gltf_json_.BeginObject(); + EncodeColorMaterial(0.75, 0.75, 0.75, 1.0, 0.0); + gltf_json_.EndObject(); + gltf_json_.EndArray(); // materials + + const std::string asset_str = gltf_json_.MoveData(); + if (!buf_out->Encode(asset_str.data(), asset_str.length())) { + return Status(Status::DRACO_ERROR, "Error encoding default material."); + } + return OkStatus(); +} + +Status GltfAsset::EncodeTextureMap(const std::string &object_name, + int image_index, int tex_coord_index, + const Material &material, + const TextureMap &texture_map) { + return EncodeTextureMap(object_name, image_index, tex_coord_index, material, + texture_map, {}); +} + +Status GltfAsset::EncodeTextureMap(const std::string &object_name, + int image_index, int tex_coord_index, + const Material &material, + const TextureMap &texture_map, + const std::vector &channels) { + // Create a new texture sampler (or reuse an existing one if possible). + const TextureSampler sampler(texture_map.min_filter(), + texture_map.mag_filter(), + texture_map.wrapping_mode()); + DRACO_ASSIGN_OR_RETURN(const int sampler_index, AddTextureSampler(sampler)); + + // Check if we can reuse an existing texture object. + const GltfTexture texture(image_index, sampler_index); + const auto texture_it = + std::find(textures_.begin(), textures_.end(), texture); + int texture_index; + if (texture_it == textures_.end()) { + // Create a new texture object for this texture map. + texture_index = textures_.size(); + textures_.push_back(GltfTexture(image_index, sampler_index)); + } else { + // Reuse an existing texture object. + texture_index = std::distance(textures_.begin(), texture_it); + } + + gltf_json_.BeginObject(object_name); + gltf_json_.OutputValue("index", texture_index); + gltf_json_.OutputValue("texCoord", tex_coord_index); + if (object_name == "normalTexture") { + const float scale = material.GetNormalTextureScale(); + if (scale != 1.0f) { + gltf_json_.OutputValue("scale", scale); + } + } + + // The "texture" object of the EXT_mesh_features extension has a custom + // property "channels" that is encoded here. + if (object_name == "texture" && !channels.empty()) { + gltf_json_.BeginArray("channels"); + for (const int channel : channels) { + gltf_json_.OutputValue(channel); + } + gltf_json_.EndArray(); // channels array. + } + + // Check if |texture_map| is using the KHR_texture_transform extension. + if (!TextureTransform::IsDefault(texture_map.texture_transform())) { + gltf_json_.BeginObject("extensions"); + gltf_json_.BeginObject("KHR_texture_transform"); + if (texture_map.texture_transform().IsOffsetSet()) { + const std::array &offset = + texture_map.texture_transform().offset(); + gltf_json_.BeginArray("offset"); + gltf_json_.OutputValue(offset[0]); + gltf_json_.OutputValue(offset[1]); + gltf_json_.EndArray(); + } + if (texture_map.texture_transform().IsRotationSet()) { + gltf_json_.OutputValue("rotation", + texture_map.texture_transform().rotation()); + } + if (texture_map.texture_transform().IsScaleSet()) { + const std::array &scale = + texture_map.texture_transform().scale(); + gltf_json_.BeginArray("scale"); + gltf_json_.OutputValue(scale[0]); + gltf_json_.OutputValue(scale[1]); + gltf_json_.EndArray(); + } + // TODO(fgalligan): The spec says the extension is not required if the + // pre-transform and the post-transform tex coords are the same. But I'm not + // sure why. I have filed a bug asking for clarification. + // https://github.com/KhronosGroup/glTF/issues/1724 + if (texture_map.texture_transform().IsTexCoordSet()) { + gltf_json_.OutputValue("texCoord", + texture_map.texture_transform().tex_coord()); + } else { + extensions_required_.insert("KHR_texture_transform"); + } + gltf_json_.EndObject(); + gltf_json_.EndObject(); + + extensions_used_.insert("KHR_texture_transform"); + } + gltf_json_.EndObject(); + return OkStatus(); +} + +Status GltfAsset::EncodeMaterialsProperty(EncoderBuffer *buf_out) { + gltf_json_.BeginArray("materials"); + for (int i = 0; i < material_library_.NumMaterials(); ++i) { + const Material *const material = material_library_.GetMaterial(i); + if (!material) { + return Status(Status::DRACO_ERROR, "Error getting material."); + } + + const TextureMap *const color = + material->GetTextureMapByType(TextureMap::COLOR); + const TextureMap *const metallic = + material->GetTextureMapByType(TextureMap::METALLIC_ROUGHNESS); + const TextureMap *const normal = + material->GetTextureMapByType(TextureMap::NORMAL_TANGENT_SPACE); + const TextureMap *const occlusion = + material->GetTextureMapByType(TextureMap::AMBIENT_OCCLUSION); + const TextureMap *const emissive = + material->GetTextureMapByType(TextureMap::EMISSIVE); + + // Check if material is unlit and does not have a fallback. + if (material->GetUnlit() && + (!color || metallic || normal || occlusion || emissive || + material->GetMetallicFactor() != 0.0 || + material->GetRoughnessFactor() <= 0.5 || + material->GetEmissiveFactor() != Vector3f(0.0, 0.0, 0.0))) { + // If we find one material that is unlit and does not contain a fallback + // we must set "KHR_materials_unlit" in extensions reqruied for the entire + // glTF file. + extensions_required_.insert("KHR_materials_unlit"); + } + + int occlusion_metallic_roughness_image_index = -1; + + gltf_json_.BeginObject(); // material object. + + gltf_json_.BeginObject("pbrMetallicRoughness"); + if (color) { + const bool rgba = true; // Unused for now. + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *color->texture(), i, "_BaseColor"); + DRACO_ASSIGN_OR_RETURN( + const int color_image_index, + AddImage(texture_stem, color->texture(), rgba ? 4 : 3)); + DRACO_RETURN_IF_ERROR( + EncodeTextureMap("baseColorTexture", color_image_index, + color->tex_coord_index(), *material, *color)); + } + // Try to combine metallic and occlusion only if they have the same tex + // coord index. + // TODO(b/145991271): Check out if we need to check texture indices. + if (metallic && occlusion && + metallic->tex_coord_index() == occlusion->tex_coord_index()) { + if (metallic->texture() == occlusion->texture()) { + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *metallic->texture(), i, "_OcclusionMetallicRoughness"); + // Metallic and occlusion textures are already combined. + DRACO_ASSIGN_OR_RETURN(occlusion_metallic_roughness_image_index, + AddImage(texture_stem, metallic->texture(), 3)); + } + if (occlusion_metallic_roughness_image_index != -1) + DRACO_RETURN_IF_ERROR(EncodeTextureMap( + "metallicRoughnessTexture", + occlusion_metallic_roughness_image_index, + metallic->tex_coord_index(), *material, *metallic)); + } + + if (metallic && occlusion_metallic_roughness_image_index == -1) { + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *metallic->texture(), i, "_MetallicRoughness"); + DRACO_ASSIGN_OR_RETURN(const int metallic_roughness_image_index, + AddImage(texture_stem, metallic->texture(), 3)); + DRACO_RETURN_IF_ERROR(EncodeTextureMap( + "metallicRoughnessTexture", metallic_roughness_image_index, + metallic->tex_coord_index(), *material, *metallic)); + } + + EncodeVectorArray("baseColorFactor", material->GetColorFactor()); + gltf_json_.OutputValue("metallicFactor", material->GetMetallicFactor()); + gltf_json_.OutputValue("roughnessFactor", material->GetRoughnessFactor()); + gltf_json_.EndObject(); // pbrMetallicRoughness + + if (normal) { + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *normal->texture(), i, "_Normal"); + DRACO_ASSIGN_OR_RETURN(const int normal_image_index, + AddImage(texture_stem, normal->texture(), 3)); + DRACO_RETURN_IF_ERROR( + EncodeTextureMap("normalTexture", normal_image_index, + normal->tex_coord_index(), *material, *normal)); + } + + if (occlusion_metallic_roughness_image_index != -1) { + DRACO_RETURN_IF_ERROR(EncodeTextureMap( + "occlusionTexture", occlusion_metallic_roughness_image_index, + metallic->tex_coord_index(), *material, *metallic)); + } else if (occlusion) { + // Store occlusion texture in a grayscale format, unless it is used by + // metallic-roughness map of some other matierial. It is possible that + // this material uses occlusion (R channel) and some other material uses + // metallic-roughness (GB channels) from this texture. + const int num_components = TextureUtils::ComputeRequiredNumChannels( + *occlusion->texture(), material_library_); + const std::string suffix = + (num_components == 1) ? "_Occlusion" : "_OcclusionMetallicRoughness"; + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *occlusion->texture(), i, suffix); + DRACO_ASSIGN_OR_RETURN( + const int occlusion_image_index, + AddImage(texture_stem, occlusion->texture(), num_components)); + DRACO_RETURN_IF_ERROR(EncodeTextureMap( + "occlusionTexture", occlusion_image_index, + occlusion->tex_coord_index(), *material, *occlusion)); + } + + if (emissive) { + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *emissive->texture(), i, "_Emissive"); + DRACO_ASSIGN_OR_RETURN(const int emissive_image_index, + AddImage(texture_stem, emissive->texture(), 3)); + DRACO_RETURN_IF_ERROR( + EncodeTextureMap("emissiveTexture", emissive_image_index, + emissive->tex_coord_index(), *material, *emissive)); + } + + EncodeVectorArray("emissiveFactor", + material->GetEmissiveFactor()); + + switch (material->GetTransparencyMode()) { + case Material::TransparencyMode::TRANSPARENCY_MASK: + gltf_json_.OutputValue("alphaMode", "MASK"); + gltf_json_.OutputValue("alphaCutoff", material->GetAlphaCutoff()); + break; + case Material::TransparencyMode::TRANSPARENCY_BLEND: + gltf_json_.OutputValue("alphaMode", "BLEND"); + break; + default: + gltf_json_.OutputValue("alphaMode", "OPAQUE"); + break; + } + if (!material->GetName().empty()) { + gltf_json_.OutputValue("name", material->GetName()); + } + + // Output doubleSided if different than the default. + if (material->GetDoubleSided()) { + gltf_json_.OutputValue("doubleSided", material->GetDoubleSided()); + } + + // Encode material extensions if any. + if (material->GetUnlit() || material->HasSheen() || + material->HasTransmission() || material->HasClearcoat() || + material->HasVolume() || material->HasIor() || + material->HasSpecular()) { + gltf_json_.BeginObject("extensions"); + + // Encode individual material extensions. + if (material->GetUnlit()) { + EncodeMaterialUnlitExtension(*material); + } else { + // PBR extensions can only be added to non-unlit materials. + Material defaults; + if (material->HasSheen()) { + DRACO_RETURN_IF_ERROR( + EncodeMaterialSheenExtension(*material, defaults, i)); + } + if (material->HasTransmission()) { + DRACO_RETURN_IF_ERROR( + EncodeMaterialTransmissionExtension(*material, defaults, i)); + } + if (material->HasClearcoat()) { + DRACO_RETURN_IF_ERROR( + EncodeMaterialClearcoatExtension(*material, defaults, i)); + } + if (material->HasVolume()) { + DRACO_RETURN_IF_ERROR( + EncodeMaterialVolumeExtension(*material, defaults, i)); + } + if (material->HasIor()) { + DRACO_RETURN_IF_ERROR( + EncodeMaterialIorExtension(*material, defaults)); + } + if (material->HasSpecular()) { + DRACO_RETURN_IF_ERROR( + EncodeMaterialSpecularExtension(*material, defaults, i)); + } + } + + gltf_json_.EndObject(); // extensions object. + } + + gltf_json_.EndObject(); // material object. + } + + gltf_json_.EndArray(); // materials array. + + if (!textures_.empty()) { + gltf_json_.BeginArray("textures"); + for (int i = 0; i < textures_.size(); ++i) { + const int image_index = textures_[i].image_index; + gltf_json_.BeginObject(); + if (images_[image_index].mime_type == "image/webp") { + gltf_json_.BeginObject("extensions"); + gltf_json_.BeginObject("EXT_texture_webp"); + gltf_json_.OutputValue("source", image_index); + gltf_json_.EndObject(); + gltf_json_.EndObject(); + } else if (images_[image_index].mime_type == "image/ktx2") { + gltf_json_.BeginObject("extensions"); + gltf_json_.BeginObject("KHR_texture_basisu"); + gltf_json_.OutputValue("source", image_index); + gltf_json_.EndObject(); + gltf_json_.EndObject(); + } else { + gltf_json_.OutputValue("source", image_index); + } + if (textures_[i].sampler_index >= 0) { + gltf_json_.OutputValue("sampler", textures_[i].sampler_index); + } + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); + } + + if (!texture_samplers_.empty()) { + gltf_json_.BeginArray("samplers"); + for (int i = 0; i < texture_samplers_.size(); ++i) { + gltf_json_.BeginObject(); + + const int mode_s = TextureAxisWrappingModeToGltfValue( + texture_samplers_[i].wrapping_mode.s); + const int mode_t = TextureAxisWrappingModeToGltfValue( + texture_samplers_[i].wrapping_mode.t); + gltf_json_.OutputValue("wrapS", mode_s); + gltf_json_.OutputValue("wrapT", mode_t); + + if (texture_samplers_[i].min_filter != TextureMap::UNSPECIFIED) { + gltf_json_.OutputValue( + "minFilter", + TextureFilterTypeToGltfValue(texture_samplers_[i].min_filter)); + } + if (texture_samplers_[i].mag_filter != TextureMap::UNSPECIFIED) { + gltf_json_.OutputValue( + "magFilter", + TextureFilterTypeToGltfValue(texture_samplers_[i].mag_filter)); + } + + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); + } + + if (!images_.empty()) { + gltf_json_.BeginArray("images"); + for (int i = 0; i < images_.size(); ++i) { + if (add_images_to_buffer_) { + DRACO_RETURN_IF_ERROR(SaveImageToBuffer(i)); + } + gltf_json_.BeginObject(); + if (images_[i].buffer_view >= 0) { + gltf_json_.OutputValue("bufferView", images_[i].buffer_view); + gltf_json_.OutputValue("mimeType", images_[i].mime_type); + } else { + gltf_json_.OutputValue("uri", images_[i].image_name); + } + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); + } + + const std::string asset_str = gltf_json_.MoveData(); + if (!buf_out->Encode(asset_str.data(), asset_str.length())) { + return Status(Status::DRACO_ERROR, "Error encoding materials."); + } + return OkStatus(); +} + +void GltfAsset::EncodeMaterialUnlitExtension(const Material &material) { + extensions_used_.insert("KHR_materials_unlit"); + gltf_json_.BeginObject("KHR_materials_unlit"); + gltf_json_.EndObject(); +} + +Status GltfAsset::EncodeMaterialSheenExtension(const Material &material, + const Material &defaults, + int material_index) { + extensions_used_.insert("KHR_materials_sheen"); + gltf_json_.BeginObject("KHR_materials_sheen"); + + // Add sheen color factor, unless it is the default. + if (material.GetSheenColorFactor() != defaults.GetSheenColorFactor()) { + EncodeVectorArray("sheenColorFactor", + material.GetSheenColorFactor()); + } + + // Add sheen roughness factor, unless it is the default. + if (material.GetSheenRoughnessFactor() != + defaults.GetSheenRoughnessFactor()) { + gltf_json_.OutputValue("sheenRoughnessFactor", + material.GetSheenRoughnessFactor()); + } + + // Add sheen color texture (RGB channels) if present. + // TODO(vytyaz): Combine sheen color and roughness images if possible. + DRACO_RETURN_IF_ERROR(EncodeTexture("sheenColorTexture", "_SheenColor", + TextureMap::SHEEN_COLOR, -1, material, + material_index)); + + // Add sheen roughness texture (A channel) if present. + DRACO_RETURN_IF_ERROR( + EncodeTexture("sheenRoughnessTexture", "_SheenRoughness", + TextureMap::SHEEN_ROUGHNESS, 4, material, material_index)); + + gltf_json_.EndObject(); // KHR_materials_sheen object. + + return OkStatus(); +} + +Status GltfAsset::EncodeMaterialTransmissionExtension(const Material &material, + const Material &defaults, + int material_index) { + extensions_used_.insert("KHR_materials_transmission"); + gltf_json_.BeginObject("KHR_materials_transmission"); + + // Add transmission factor, unless it is the default. + if (material.GetTransmissionFactor() != defaults.GetTransmissionFactor()) { + gltf_json_.OutputValue("transmissionFactor", + material.GetTransmissionFactor()); + } + + // Add transmission texture (R channel) if present. + // TODO(vytyaz): Store texture in a grayscale format if possible. + DRACO_RETURN_IF_ERROR(EncodeTexture("transmissionTexture", "_Transmission", + TextureMap::TRANSMISSION, 3, material, + material_index)); + + gltf_json_.EndObject(); // KHR_materials_transmission object. + + return OkStatus(); +} + +Status GltfAsset::EncodeMaterialClearcoatExtension(const Material &material, + const Material &defaults, + int material_index) { + extensions_used_.insert("KHR_materials_clearcoat"); + gltf_json_.BeginObject("KHR_materials_clearcoat"); + + // Add clearcoat factor, unless it is the default. + if (material.GetClearcoatFactor() != defaults.GetClearcoatFactor()) { + gltf_json_.OutputValue("clearcoatFactor", material.GetClearcoatFactor()); + } + + // Add clearcoat roughness factor, unless it is the default. + if (material.GetClearcoatRoughnessFactor() != + defaults.GetClearcoatRoughnessFactor()) { + gltf_json_.OutputValue("clearcoatRoughnessFactor", + material.GetClearcoatRoughnessFactor()); + } + + // Add clearcoat texture (R channel) if present. + // TODO(vytyaz): Combine clearcoat and clearcoat roughness images if possible. + // TODO(vytyaz): Store texture in a grayscale format if possible. + DRACO_RETURN_IF_ERROR(EncodeTexture("clearcoatTexture", "_Clearcoat", + TextureMap::CLEARCOAT, 3, material, + material_index)); + + // Add clearcoat roughness texture (G channel) if present. + DRACO_RETURN_IF_ERROR(EncodeTexture( + "clearcoatRoughnessTexture", "_ClearcoatRoughness", + TextureMap::CLEARCOAT_ROUGHNESS, 3, material, material_index)); + + // Add clearcoat normal texture (RGB channels) if present. + DRACO_RETURN_IF_ERROR( + EncodeTexture("clearcoatNormalTexture", "_ClearcoatNormal", + TextureMap::CLEARCOAT_NORMAL, 3, material, material_index)); + + gltf_json_.EndObject(); // KHR_materials_clearcoat object. + + return OkStatus(); +} + +Status GltfAsset::EncodeMaterialVolumeExtension(const Material &material, + const Material &defaults, + int material_index) { + extensions_used_.insert("KHR_materials_volume"); + gltf_json_.BeginObject("KHR_materials_volume"); + + // Add thickness factor, unless it is the default. + if (material.GetThicknessFactor() != defaults.GetThicknessFactor()) { + gltf_json_.OutputValue("thicknessFactor", material.GetThicknessFactor()); + } + + // Add attenuation distance, unless it is the default. + if (material.GetAttenuationDistance() != defaults.GetAttenuationDistance()) { + gltf_json_.OutputValue("attenuationDistance", + material.GetAttenuationDistance()); + } + + // Add attenuation color, unless it is the default. + if (material.GetAttenuationColor() != defaults.GetAttenuationColor()) { + EncodeVectorArray("attenuationColor", + material.GetAttenuationColor()); + } + + // Add thickness texture (G channel) if present. + DRACO_RETURN_IF_ERROR(EncodeTexture("thicknessTexture", "_Thickness", + TextureMap::THICKNESS, 3, material, + material_index)); + + gltf_json_.EndObject(); // KHR_materials_volume object. + + return OkStatus(); +} + +Status GltfAsset::EncodeMaterialIorExtension(const Material &material, + const Material &defaults) { + extensions_used_.insert("KHR_materials_ior"); + gltf_json_.BeginObject("KHR_materials_ior"); + + // Add ior, unless it is the default. + if (material.GetIor() != defaults.GetIor()) { + gltf_json_.OutputValue("ior", material.GetIor()); + } + + gltf_json_.EndObject(); // KHR_materials_ior object. + + return OkStatus(); +} + +Status GltfAsset::EncodeMaterialSpecularExtension(const Material &material, + const Material &defaults, + int material_index) { + extensions_used_.insert("KHR_materials_specular"); + gltf_json_.BeginObject("KHR_materials_specular"); + + // Add specular factor, unless it is the default. + if (material.GetSpecularFactor() != defaults.GetSpecularFactor()) { + gltf_json_.OutputValue("specularFactor", material.GetSpecularFactor()); + } + + // Add specular color factor, unless it is the default. + if (material.GetSpecularColorFactor() != defaults.GetSpecularColorFactor()) { + EncodeVectorArray("specularColorFactor", + material.GetSpecularColorFactor()); + } + + // Add specular texture (A channel) if present. + // TODO(vytyaz): Combine specular and specular color images if possible. + DRACO_RETURN_IF_ERROR(EncodeTexture("specularTexture", "_Specular", + TextureMap::SPECULAR, 4, material, + material_index)); + + // Add specular color texture (RGB channels) if present. + DRACO_RETURN_IF_ERROR(EncodeTexture("specularColorTexture", "_SpecularColor", + TextureMap::SPECULAR_COLOR, -1, material, + material_index)); + + gltf_json_.EndObject(); // KHR_materials_specular object. + + return OkStatus(); +} + +Status GltfAsset::EncodeTexture(const std::string &name, + const std::string &stem_suffix, + TextureMap::Type type, int num_components, + const Material &material, int material_index) { + const TextureMap *const texture_map = material.GetTextureMapByType(type); + if (texture_map) { + if (num_components == -1) { + const bool rgba = true; // Unused for now. + num_components = rgba ? 4 : 3; + } + const std::string texture_stem = TextureUtils::GetOrGenerateTargetStem( + *texture_map->texture(), material_index, stem_suffix); + DRACO_ASSIGN_OR_RETURN( + const int image_index, + AddImage(texture_stem, texture_map->texture(), num_components)); + DRACO_RETURN_IF_ERROR(EncodeTextureMap(name, image_index, + texture_map->tex_coord_index(), + material, *texture_map)); + } + return OkStatus(); +} + +Status GltfAsset::EncodeAnimationsProperty(EncoderBuffer *buf_out) { + if (animations_.empty()) { + return OkStatus(); + } + + gltf_json_.BeginArray("animations"); + for (int i = 0; i < animations_.size(); ++i) { + gltf_json_.BeginObject(); + + if (!animations_[i]->name.empty()) { + gltf_json_.OutputValue("name", animations_[i]->name); + } + + gltf_json_.BeginArray("samplers"); + for (int j = 0; j < animations_[i]->samplers.size(); ++j) { + gltf_json_.BeginObject(); + gltf_json_.OutputValue("input", animations_[i]->samplers[j]->input_index); + gltf_json_.OutputValue( + "interpolation", + AnimationSampler::InterpolationToString( + animations_[i]->samplers[j]->interpolation_type)); + gltf_json_.OutputValue("output", + animations_[i]->samplers[j]->output_index); + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); + + gltf_json_.BeginArray("channels"); + for (int j = 0; j < animations_[i]->channels.size(); ++j) { + gltf_json_.BeginObject(); + gltf_json_.OutputValue("sampler", + animations_[i]->channels[j]->sampler_index); + + gltf_json_.BeginObject("target"); + gltf_json_.OutputValue("node", animations_[i]->channels[j]->target_index); + gltf_json_.OutputValue( + "path", AnimationChannel::TransformationToString( + animations_[i]->channels[j]->transformation_type)); + gltf_json_.EndObject(); + + gltf_json_.EndObject(); // Channel entry. + } + gltf_json_.EndArray(); + + gltf_json_.EndObject(); // Animmation entry. + } + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + if (!buf_out->Encode(asset_str.data(), asset_str.length())) { + return Status(Status::DRACO_ERROR, "Could not encode animations."); + } + return OkStatus(); +} + +Status GltfAsset::EncodeSkinsProperty(EncoderBuffer *buf_out) { + if (skins_.empty()) { + return OkStatus(); + } + + gltf_json_.BeginArray("skins"); + for (int i = 0; i < skins_.size(); ++i) { + gltf_json_.BeginObject(); + + if (skins_[i]->inverse_bind_matrices_index >= 0) { + gltf_json_.OutputValue("inverseBindMatrices", + skins_[i]->inverse_bind_matrices_index); + } + if (skins_[i]->skeleton_index >= 0) { + gltf_json_.OutputValue("skeleton", skins_[i]->skeleton_index); + } + + if (!skins_[i]->joints.empty()) { + gltf_json_.BeginArray("joints"); + for (int j = 0; j < skins_[i]->joints.size(); ++j) { + gltf_json_.OutputValue(skins_[i]->joints[j]); + } + gltf_json_.EndArray(); + } + gltf_json_.EndObject(); // Skin entry. + } + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + if (!buf_out->Encode(asset_str.data(), asset_str.length())) { + return Status(Status::DRACO_ERROR, "Could not encode animations."); + } + return OkStatus(); +} + +Status GltfAsset::EncodeTopLevelExtensionsProperty(EncoderBuffer *buf_out) { + // Return if there are no top-level asset extensions to encode. + if (lights_.empty() && materials_variants_names_.empty() && + property_tables_.empty()) { + return OkStatus(); + } + + // Encode top-level extensions. + gltf_json_.BeginObject("extensions"); + DRACO_RETURN_IF_ERROR(EncodeLightsProperty(buf_out)); + DRACO_RETURN_IF_ERROR(EncodeMaterialsVariantsNamesProperty(buf_out)); + DRACO_RETURN_IF_ERROR(EncodeStructuralMetadataProperty(buf_out)); + gltf_json_.EndObject(); // extensions entry. + return OkStatus(); +} + +Status GltfAsset::EncodeLightsProperty(EncoderBuffer *buf_out) { + if (lights_.empty()) { + return OkStatus(); + } + + gltf_json_.BeginObject("KHR_lights_punctual"); + gltf_json_.BeginArray("lights"); + const Light defaults; + for (const auto &light : lights_) { + gltf_json_.BeginObject(); + if (light->GetName() != defaults.GetName()) { + gltf_json_.OutputValue("name", light->GetName()); + } + if (light->GetColor() != defaults.GetColor()) { + gltf_json_.BeginArray("color"); + gltf_json_.OutputValue(light->GetColor()[0]); + gltf_json_.OutputValue(light->GetColor()[1]); + gltf_json_.OutputValue(light->GetColor()[2]); + gltf_json_.EndArray(); + } + if (light->GetIntensity() != defaults.GetIntensity()) { + gltf_json_.OutputValue("intensity", light->GetIntensity()); + } + switch (light->GetType()) { + case Light::DIRECTIONAL: + gltf_json_.OutputValue("type", "directional"); + break; + case Light::POINT: + gltf_json_.OutputValue("type", "point"); + break; + case Light::SPOT: + gltf_json_.OutputValue("type", "spot"); + break; + } + if (light->GetRange() != defaults.GetRange()) { + gltf_json_.OutputValue("range", light->GetRange()); + } + if (light->GetType() == Light::SPOT) { + gltf_json_.BeginObject("spot"); + if (light->GetInnerConeAngle() != defaults.GetInnerConeAngle()) { + gltf_json_.OutputValue("innerConeAngle", light->GetInnerConeAngle()); + } + if (light->GetOuterConeAngle() != defaults.GetOuterConeAngle()) { + gltf_json_.OutputValue("outerConeAngle", light->GetOuterConeAngle()); + } + gltf_json_.EndObject(); + } + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); + gltf_json_.EndObject(); // KHR_lights_punctual entry. + return OkStatus(); +} + +Status GltfAsset::EncodeMaterialsVariantsNamesProperty(EncoderBuffer *buf_out) { + if (materials_variants_names_.empty()) { + return OkStatus(); + } + + gltf_json_.BeginObject("KHR_materials_variants"); + gltf_json_.BeginArray("variants"); + for (const std::string &name : materials_variants_names_) { + gltf_json_.BeginObject(); + gltf_json_.OutputValue("name", name); + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); + gltf_json_.EndObject(); // KHR_materials_variants entry. + return OkStatus(); +} + +Status GltfAsset::EncodeStructuralMetadataProperty(EncoderBuffer *buf_out) { + if (property_table_schema_.Empty()) { + return OkStatus(); + } + + gltf_json_.BeginObject("EXT_structural_metadata"); + + // Encodes property table schema. + struct SchemaWriter { + static void Write(const PropertyTable::Schema::Object &object, + JsonWriter *json_writer) { + switch (object.GetType()) { + case PropertyTable::Schema::Object::OBJECT: + json_writer->BeginObject(object.GetName()); + for (const PropertyTable::Schema::Object &obj : object.GetObjects()) { + Write(obj, json_writer); + } + json_writer->EndObject(); + break; + case PropertyTable::Schema::Object::ARRAY: + json_writer->BeginArray(object.GetName()); + for (const PropertyTable::Schema::Object &obj : object.GetArray()) { + Write(obj, json_writer); + } + json_writer->EndArray(); + break; + case PropertyTable::Schema::Object::STRING: + json_writer->OutputValue(object.GetName(), object.GetString()); + break; + case PropertyTable::Schema::Object::INTEGER: + json_writer->OutputValue(object.GetName(), object.GetInteger()); + break; + case PropertyTable::Schema::Object::BOOLEAN: + json_writer->OutputValue(object.GetName(), object.GetBoolean()); + break; + } + } + }; + + // Encode property table schema. + SchemaWriter::Write(property_table_schema_.json, &gltf_json_); + + // Encode all property tables. + gltf_json_.BeginArray("propertyTables"); + for (const PropertyTable *const table : property_tables_) { + gltf_json_.BeginObject(); + if (!table->GetName().empty()) { + gltf_json_.OutputValue("name", table->GetName()); + } + if (!table->GetClass().empty()) { + gltf_json_.OutputValue("class", table->GetClass()); + } + gltf_json_.OutputValue("count", table->GetCount()); + + // Encoder all property table properties. + gltf_json_.BeginObject("properties"); + for (int i = 0; i < table->NumProperties(); ++i) { + const PropertyTable::Property &property = table->GetProperty(i); + gltf_json_.BeginObject(property.GetName()); + + // Encode property values. + DRACO_ASSIGN_OR_RETURN(const int buffer_view_index, + AddBufferView(property.GetData())); + gltf_json_.OutputValue("values", buffer_view_index); + + // Encode offsets for variable-length arrays. + if (!property.GetArrayOffsets().data.data.empty()) { + if (!property.GetArrayOffsets().type.empty()) { + gltf_json_.OutputValue("arrayOffsetType", + property.GetArrayOffsets().type); + } + DRACO_ASSIGN_OR_RETURN(const int buffer_view_index, + AddBufferView(property.GetArrayOffsets().data)); + gltf_json_.OutputValue("arrayOffsets", buffer_view_index); + } + + // Encode offsets for strings. + if (!property.GetStringOffsets().data.data.empty()) { + if (!property.GetStringOffsets().type.empty()) { + gltf_json_.OutputValue("stringOffsetType", + property.GetStringOffsets().type); + } + DRACO_ASSIGN_OR_RETURN(const int buffer_view_index, + AddBufferView(property.GetStringOffsets().data)); + gltf_json_.OutputValue("stringOffsets", buffer_view_index); + } + gltf_json_.EndObject(); // Named property entry. + } + gltf_json_.EndObject(); // properties entry. + gltf_json_.EndObject(); + } + gltf_json_.EndArray(); // propertyTables entry. + gltf_json_.EndObject(); // EXT_structural_metadata entry. + return OkStatus(); +} + +bool GltfAsset::EncodeAccessorsProperty(EncoderBuffer *buf_out) { + gltf_json_.BeginArray("accessors"); + + for (int i = 0; i < accessors_.size(); ++i) { + gltf_json_.BeginObject(); + + if (accessors_[i].buffer_view_index >= 0) { + gltf_json_.OutputValue("bufferView", accessors_[i].buffer_view_index); + if (output_type_ == GltfEncoder::VERBOSE) { + gltf_json_.OutputValue("byteOffset", 0); + } + } + gltf_json_.OutputValue("componentType", accessors_[i].component_type); + gltf_json_.OutputValue("count", accessors_[i].count); + if (accessors_[i].normalized) { + gltf_json_.OutputValue("normalized", accessors_[i].normalized); + } + + if (!accessors_[i].max.empty()) { + gltf_json_.BeginArray("max"); + for (int j = 0; j < accessors_[i].max.size(); ++j) { + gltf_json_.OutputValue(accessors_[i].max[j]); + } + gltf_json_.EndArray(); + } + + if (!accessors_[i].min.empty()) { + gltf_json_.BeginArray("min"); + for (int j = 0; j < accessors_[i].min.size(); ++j) { + gltf_json_.OutputValue(accessors_[i].min[j]); + } + gltf_json_.EndArray(); + } + + gltf_json_.OutputValue("type", accessors_[i].type); + + gltf_json_.EndObject(); + } + + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + return buf_out->Encode(asset_str.data(), asset_str.length()); +} + +bool GltfAsset::EncodeBufferViewsProperty(EncoderBuffer *buf_out) { + // We currently only support one buffer. + gltf_json_.BeginArray("bufferViews"); + + for (int i = 0; i < buffer_views_.size(); ++i) { + gltf_json_.BeginObject(); + gltf_json_.OutputValue("buffer", 0); + gltf_json_.OutputValue("byteOffset", buffer_views_[i].buffer_byte_offset); + gltf_json_.OutputValue("byteLength", buffer_views_[i].byte_length); + if (buffer_views_[i].target != 0) { + gltf_json_.OutputValue("target", buffer_views_[i].target); + } + gltf_json_.EndObject(); + } + + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + return buf_out->Encode(asset_str.data(), asset_str.length()); +} + +bool GltfAsset::EncodeBuffersProperty(EncoderBuffer *buf_out) { + if (buffer_.size() == 0) { + return true; + } + // We currently only support one buffer. + gltf_json_.BeginArray("buffers"); + gltf_json_.BeginObject(); + gltf_json_.OutputValue("byteLength", buffer_.size()); + if (!buffer_name_.empty()) { + gltf_json_.OutputValue("uri", buffer_name_); + } + gltf_json_.EndObject(); + gltf_json_.EndArray(); + + const std::string asset_str = gltf_json_.MoveData(); + return buf_out->Encode(asset_str.data(), asset_str.length()); +} + +Status GltfAsset::EncodeExtensionsProperties(EncoderBuffer *buf_out) { + if (draco_compression_used_) { + const std::string draco_tag = "KHR_draco_mesh_compression"; + extensions_used_.insert(draco_tag); + extensions_required_.insert(draco_tag); + } + if (!lights_.empty()) { + extensions_used_.insert("KHR_lights_punctual"); + } + if (!materials_variants_names_.empty()) { + extensions_used_.insert("KHR_materials_variants"); + } + if (!instance_arrays_.empty()) { + extensions_used_.insert("EXT_mesh_gpu_instancing"); + extensions_required_.insert("EXT_mesh_gpu_instancing"); + } + if (mesh_features_used_) { + extensions_used_.insert("EXT_mesh_features"); + } + if (!property_table_schema_.Empty()) { + extensions_used_.insert("EXT_structural_metadata"); + } + + if (!extensions_required_.empty()) { + gltf_json_.BeginArray("extensionsRequired"); + for (const auto &extension : extensions_required_) { + gltf_json_.OutputValue(extension); + } + gltf_json_.EndArray(); + } + if (!extensions_used_.empty()) { + gltf_json_.BeginArray("extensionsUsed"); + for (const auto &extension : extensions_used_) { + gltf_json_.OutputValue(extension); + } + gltf_json_.EndArray(); + } + + const std::string asset_str = gltf_json_.MoveData(); + if (!asset_str.empty()) { + if (!buf_out->Encode(asset_str.data(), asset_str.length())) { + return Status(Status::DRACO_ERROR, "Could not encode extensions."); + } + } + return OkStatus(); +} + +template <> +GltfAsset::ComponentType GltfAsset::GetComponentType() const { + return BYTE; +} + +template <> +GltfAsset::ComponentType GltfAsset::GetComponentType() const { + return UNSIGNED_BYTE; +} + +template <> +GltfAsset::ComponentType GltfAsset::GetComponentType() const { + return SHORT; +} + +template <> +GltfAsset::ComponentType GltfAsset::GetComponentType() const { + return UNSIGNED_SHORT; +} + +template <> +GltfAsset::ComponentType GltfAsset::GetComponentType() const { + return UNSIGNED_INT; +} + +template <> +GltfAsset::ComponentType GltfAsset::GetComponentType() const { + return FLOAT; +} + +template +int GltfAsset::AddAttribute(const PointAttribute &att, int num_points, + int num_encoded_points, const std::string &type, + bool compress) { + if (att.size() == 0) { + return -1; // Attribute size must be greater than 0. + } + + std::array value; + std::array min_values; + std::array max_values; + + // Set min and max values. + if (!att.ConvertValue(AttributeValueIndex(0), + &min_values[0])) { + return -1; + } + max_values = min_values; + + if (output_type_ == GltfEncoder::VERBOSE || + att.attribute_type() == GeometryAttribute::POSITION) { + for (AttributeValueIndex i(1); i < static_cast(att.size()); ++i) { + if (!att.ConvertValue(i, &value[0])) { + return -1; + } + for (int j = 0; j < att_components_t; ++j) { + if (value[j] < min_values[j]) { + min_values[j] = value[j]; + } + if (value[j] > max_values[j]) { + max_values[j] = value[j]; + } + } + } + } + + const int kComponentSize = sizeof(att_data_t); + + GltfAccessor accessor; + if (!compress) { + const size_t buffer_start_offset = buffer_.size(); + for (PointIndex v(0); v < num_points; ++v) { + if (!att.ConvertValue(att.mapped_index(v), + &value[0])) { + return -1; + } + + for (int j = 0; j < att_components_t; ++j) { + buffer_.Encode(&value[j], kComponentSize); + } + } + + if (!PadBuffer()) { + return -1; + } + + GltfBufferView buffer_view; + buffer_view.buffer_byte_offset = buffer_start_offset; + buffer_view.byte_length = buffer_.size() - buffer_start_offset; + buffer_views_.push_back(buffer_view); + accessor.buffer_view_index = static_cast(buffer_views_.size() - 1); + } + + accessor.component_type = GetComponentType(); + accessor.count = num_encoded_points; + if (output_type_ == GltfEncoder::VERBOSE || + att.attribute_type() == GeometryAttribute::POSITION) { + for (int j = 0; j < att_components_t; ++j) { + accessor.max.push_back(GltfValue(max_values[j])); + accessor.min.push_back(GltfValue(min_values[j])); + } + } + accessor.type = type; + accessor.normalized = att.attribute_type() == GeometryAttribute::COLOR && + att.data_type() != DT_FLOAT32; + accessors_.push_back(accessor); + return static_cast(accessors_.size() - 1); +} + +const char GltfEncoder::kDracoMetadataGltfAttributeName[] = + "//GLTF/ApplicationSpecificAttributeName"; + +GltfEncoder::GltfEncoder() : out_buffer_(nullptr), output_type_(COMPACT) {} + +template +bool GltfEncoder::EncodeToFile(const T &geometry, const std::string &file_name, + const std::string &base_dir) { + const std::string buffer_name = base_dir + "/buffer0.bin"; + return EncodeFile(geometry, file_name, buffer_name, base_dir).ok(); +} + +template +Status GltfEncoder::EncodeFile(const T &geometry, const std::string &filename) { + if (filename.empty()) { + return Status(Status::DRACO_ERROR, "Output parameter is empty."); + } + + std::string dir_path; + std::string basename; + draco::SplitPath(filename, &dir_path, &basename); + const std::string bin_basename = ReplaceFileExtension(basename, "bin"); + const std::string bin_filename = dir_path + "/" + bin_basename; + return EncodeFile(geometry, filename, bin_filename, dir_path); +} + +template +Status GltfEncoder::EncodeFile(const T &geometry, const std::string &filename, + const std::string &bin_filename) { + if (filename.empty()) { + return Status(Status::DRACO_ERROR, "Output parameter is empty."); + } + + std::string dir_path; + std::string basename; + draco::SplitPath(filename, &dir_path, &basename); + return EncodeFile(geometry, filename, bin_filename, dir_path); +} + +template +Status GltfEncoder::EncodeFile(const T &geometry, const std::string &filename, + const std::string &bin_filename, + const std::string &resource_dir) { + if (filename.empty() || bin_filename.empty() || resource_dir.empty()) { + return Status(Status::DRACO_ERROR, "Output parameter is empty."); + } + const std::string extension = LowercaseFileExtension(filename); + if (extension != "gltf" && extension != "glb") { + return Status(Status::DRACO_ERROR, + "gltf_encoder only supports .gltf or .glb output."); + } + + GltfAsset gltf_asset; + gltf_asset.set_output_type(output_type_); + + if (extension == "gltf") { + std::string bin_path; + std::string bin_basename; + draco::SplitPath(bin_filename, &bin_path, &bin_basename); + gltf_asset.buffer_name(bin_basename); + } else { + gltf_asset.buffer_name(""); + gltf_asset.set_add_images_to_buffer(true); + } + + // Encode the geometry into a buffer. + EncoderBuffer buffer; + DRACO_RETURN_IF_ERROR(EncodeToBuffer(geometry, &gltf_asset, &buffer)); + if (extension == "glb") { + return WriteGlbFile(gltf_asset, buffer, filename); + } + return WriteGltfFiles(gltf_asset, buffer, filename, bin_filename, + resource_dir); +} + +template +Status GltfEncoder::EncodeToBuffer(const T &geometry, + EncoderBuffer *out_buffer) { + GltfAsset gltf_asset; + gltf_asset.set_output_type(output_type_); + gltf_asset.buffer_name(""); + gltf_asset.set_add_images_to_buffer(true); + + // Encode the geometry into a buffer. + EncoderBuffer buffer; + DRACO_RETURN_IF_ERROR(EncodeToBuffer(geometry, &gltf_asset, &buffer)); + + // Define a function for concatenating GLB file chunks into a single buffer. + const auto encode_chunk_to_buffer = + [&out_buffer](const EncoderBuffer &chunk) -> Status { + if (!out_buffer->Encode(chunk.data(), chunk.size())) { + return Status(Status::DRACO_ERROR, "Error writing to buffer."); + } + return OkStatus(); + }; + + // Create GLB file chunks and concatenate them to a single buffer. + return ProcessGlbFileChunks(gltf_asset, buffer, encode_chunk_to_buffer); +} + +// Explicit instantiation for Mesh and Scene. +template bool GltfEncoder::EncodeToFile(const Mesh &geometry, + const std::string &file_name, + const std::string &base_dir); +template bool GltfEncoder::EncodeToFile(const Scene &geometry, + const std::string &file_name, + const std::string &base_dir); +template Status GltfEncoder::EncodeFile(const Mesh &geometry, + const std::string &filename); +template Status GltfEncoder::EncodeFile(const Scene &geometry, + const std::string &filename); +template Status GltfEncoder::EncodeFile(const Mesh &geometry, + const std::string &filename, + const std::string &bin_filename); +template Status GltfEncoder::EncodeFile(const Scene &geometry, + const std::string &filename, + const std::string &bin_filename); +template Status GltfEncoder::EncodeFile(const Mesh &geometry, + const std::string &filename, + const std::string &bin_filename, + const std::string &resource_dir); +template Status GltfEncoder::EncodeFile(const Scene &geometry, + const std::string &filename, + const std::string &bin_filename, + const std::string &resource_dir); +template Status GltfEncoder::EncodeToBuffer(const Mesh &geometry, + EncoderBuffer *out_buffer); +template Status GltfEncoder::EncodeToBuffer(const Scene &geometry, + EncoderBuffer *out_buffer); + +Status GltfEncoder::EncodeToBuffer(const Mesh &mesh, GltfAsset *gltf_asset, + EncoderBuffer *out_buffer) { + out_buffer_ = out_buffer; + SetJsonWriterMode(gltf_asset); + if (!gltf_asset->AddDracoMesh(mesh)) { + return Status(Status::DRACO_ERROR, "Error adding Draco mesh."); + } + return gltf_asset->Output(out_buffer); +} + +Status GltfEncoder::EncodeToBuffer(const Scene &scene, GltfAsset *gltf_asset, + EncoderBuffer *out_buffer) { + out_buffer_ = out_buffer; + SetJsonWriterMode(gltf_asset); + DRACO_RETURN_IF_ERROR(gltf_asset->AddScene(scene)); + return gltf_asset->Output(out_buffer); +} + +void GltfEncoder::SetJsonWriterMode(class GltfAsset *gltf_asset) { + if (gltf_asset->output_type() == COMPACT && + gltf_asset->add_images_to_buffer()) { + gltf_asset->set_json_output_mode(JsonWriter::COMPACT); + } else { + gltf_asset->set_json_output_mode(JsonWriter::READABLE); + } +} + +Status GltfEncoder::WriteGltfFiles(const GltfAsset &gltf_asset, + const EncoderBuffer &buffer, + const std::string &filename, + const std::string &bin_filename, + const std::string &resource_dir) { + std::unique_ptr file = + FileWriterFactory::OpenWriter(filename); + if (!file) { + return Status(Status::DRACO_ERROR, "Output glTF file could not be opened."); + } + std::unique_ptr bin_file = + FileWriterFactory::OpenWriter(bin_filename); + if (!bin_file) { + return Status(Status::DRACO_ERROR, + "Output glTF bin file could not be opened."); + } + + // Write the glTF data into the file. + if (!file->Write(buffer.data(), buffer.size())) { + return Status(Status::DRACO_ERROR, "Error writing to glTF file."); + } + + // Write the glTF buffer into the file. + if (!bin_file->Write(gltf_asset.Buffer()->data(), + gltf_asset.Buffer()->size())) { + return Status(Status::DRACO_ERROR, "Error writing to glTF bin file."); + } + + for (int i = 0; i < gltf_asset.NumImages(); ++i) { + const std::string name = resource_dir + "/" + gltf_asset.image_name(i); + const GltfImage *const image = gltf_asset.GetImage(i); + if (!image) { + return Status(Status::DRACO_ERROR, "Error getting glTF image."); + } + DRACO_RETURN_IF_ERROR(WriteTextureToFile(name, *image->texture)); + } + return OkStatus(); +} + +Status GltfEncoder::WriteGlbFile(const GltfAsset &gltf_asset, + const EncoderBuffer &json_data, + const std::string &filename) { + std::unique_ptr file = + FileWriterFactory::OpenWriter(filename); + if (!file) { + return Status(Status::DRACO_ERROR, "Output glb file could not be opened."); + } + + // Define a function for writing GLB file chunks to |file|. + const auto write_chunk_to_file = + [&file](const EncoderBuffer &chunk) -> Status { + if (!file->Write(chunk.data(), chunk.size())) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + return OkStatus(); + }; + + // Create GLB file chunks and write them to file. + return ProcessGlbFileChunks(gltf_asset, json_data, write_chunk_to_file); +} + +Status GltfEncoder::ProcessGlbFileChunks( + const class GltfAsset &gltf_asset, const EncoderBuffer &json_data, + const std::function &process_chunk) const { + // The json data must be padded so the next chunk starts on a 4-byte boundary. + const uint32_t json_pad_length = + (json_data.size() % 4) ? 4 - json_data.size() % 4 : 0; + const uint32_t json_length = json_data.size() + json_pad_length; + const uint32_t total_length = + 12 + 8 + json_length + 8 + gltf_asset.Buffer()->size(); + + EncoderBuffer header; + // Write the glb file header. + const uint32_t gltf_version = 2; + if (!header.Encode("glTF", 4)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + if (!header.Encode(gltf_version)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + if (!header.Encode(total_length)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + + // Write the JSON chunk. + const uint32_t json_chunk_type = 0x4E4F534A; + if (!header.Encode(json_length)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + if (!header.Encode(json_chunk_type)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + DRACO_RETURN_IF_ERROR(process_chunk(header)); + DRACO_RETURN_IF_ERROR(process_chunk(json_data)); + + // Pad the data if needed. + header.Clear(); + if (json_pad_length > 0) { + if (!header.Encode(" ", json_pad_length)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + } + + // Write the binary buffer chunk. + const uint32_t bin_chunk_type = 0x004E4942; + const uint32_t gltf_bin_size = gltf_asset.Buffer()->size(); + if (!header.Encode(gltf_bin_size)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + if (!header.Encode(bin_chunk_type)) { + return Status(Status::DRACO_ERROR, "Error writing to glb file."); + } + DRACO_RETURN_IF_ERROR(process_chunk(header)); + DRACO_RETURN_IF_ERROR(process_chunk(*gltf_asset.Buffer())); + return OkStatus(); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/io/gltf_encoder.h b/contrib/draco/src/draco/io/gltf_encoder.h new file mode 100644 index 000000000..16403ac31 --- /dev/null +++ b/contrib/draco/src/draco/io/gltf_encoder.h @@ -0,0 +1,134 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_GLTF_ENCODER_H_ +#define DRACO_IO_GLTF_ENCODER_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include +#include +#include +#include + +#include "draco/core/encoder_buffer.h" +#include "draco/io/file_writer_factory.h" +#include "draco/io/file_writer_interface.h" +#include "draco/io/texture_io.h" +#include "draco/mesh/mesh.h" +#include "draco/scene/scene.h" + +namespace draco { + +// Class for encoding draco::Mesh into the glTF file format. +class GltfEncoder { + public: + // Types of output modes for the glTF data encoder. |COMPACT| will output + // required and non-default glTF data. |VERBOSE| will output required and + // default glTF data as well as readable JSON even when the output is saved in + // a glTF-Binary file. + enum OutputType { COMPACT, VERBOSE }; + + GltfEncoder(); + + // Encodes the geometry and saves it into a file. Returns false when either + // the encoding failed or when the file couldn't be opened. + template + bool EncodeToFile(const T &geometry, const std::string &file_name, + const std::string &base_dir); + + // Saves |geometry| into glTF 2.0 format. |filename| is the name of the + // glTF file. The glTF bin file (if needed) will be named stem(|filename|) + + // “.bin”. The other files (if needed) will be saved to basedir(|filename|). + // If |filename| has the extension "glb" then |filename| will be written as a + // glTF-Binary file. Otherwise |filename| will be written as non-binary glTF + // file. + template + Status EncodeFile(const T &geometry, const std::string &filename); + + // Saves |geometry| into glTF 2.0 format. |filename| is the name of the + // glTF file. |bin_filename| is the name of the glTF bin file. The other + // files (if needed) will be saved to basedir(|filename|). |bin_filename| will + // be ignored if output is glTF-Binary. + template + Status EncodeFile(const T &geometry, const std::string &filename, + const std::string &bin_filename); + + // Saves |geometry| into glTF 2.0 format. |filename| is the name of the + // glTF file. |bin_filename| is the name of the glTF bin file. The other + // files will be saved to |resource_dir|. |bin_filename| and |resource_dir| + // will be ignored if output is glTF-Binary. + template + Status EncodeFile(const T &geometry, const std::string &filename, + const std::string &bin_filename, + const std::string &resource_dir); + + // Encodes |geometry| to |out_buffer| in glTF 2.0 GLB format. + template + Status EncodeToBuffer(const T &geometry, EncoderBuffer *out_buffer); + + void set_output_type(OutputType type) { output_type_ = type; } + OutputType output_type() const { return output_type_; } + + // The name of the attribute metadata that contains the glTF attribute + // name. For application-specific generic attributes, if the metadata for + // an attribute contains this key, then the value will be used as the + // encoded attribute name in the output GLTF. + static const char kDracoMetadataGltfAttributeName[]; + + private: + // Encodes the mesh or the point cloud into a buffer. + Status EncodeToBuffer(const Mesh &mesh, class GltfAsset *gltf_asset, + EncoderBuffer *out_buffer); + Status EncodeToBuffer(const Scene &scene, class GltfAsset *gltf_asset, + EncoderBuffer *out_buffer); + + // Sets appropriate Json writer mode based on the provided |gltf_asset| + // options. + static void SetJsonWriterMode(class GltfAsset *gltf_asset); + + // Writes the ".gltf" and associted files. |gltf_asset| holds the glTF data. + // |buffer| is the encoded glTF json data. |filename| is the name of the + // ".gltf" file. |bin_filename| is the name of the glTF bin file. The other + // files will be saved to |resource_dir|. + Status WriteGltfFiles(const class GltfAsset &gltf_asset, + const EncoderBuffer &buffer, + const std::string &filename, + const std::string &bin_filename, + const std::string &resource_dir); + + // Writes the ".glb" file. |gltf_asset| holds the glTF data. |json_data| is + // the encoded glTF json data. |filename| is the name of the ".glb" file. + Status WriteGlbFile(const class GltfAsset &gltf_asset, + const EncoderBuffer &json_data, + const std::string &filename); + + // Creates GLB file chunks and passes them to |process_chunk| function for + // processing. |gltf_asset| holds the glTF data. |json_data| is the encoded + // glTF json data. + Status ProcessGlbFileChunks( + const class GltfAsset &gltf_asset, const EncoderBuffer &json_data, + const std::function &process_chunk) const; + + EncoderBuffer *out_buffer_; + OutputType output_type_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_GLTF_ENCODER_H_ diff --git a/contrib/draco/src/draco/io/gltf_encoder_test.cc b/contrib/draco/src/draco/io/gltf_encoder_test.cc new file mode 100644 index 000000000..179256dfd --- /dev/null +++ b/contrib/draco/src/draco/io/gltf_encoder_test.cc @@ -0,0 +1,1717 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/gltf_encoder.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/io/file_reader_factory.h" +#include "draco/io/file_reader_interface.h" +#include "draco/io/file_utils.h" +#include "draco/io/gltf_decoder.h" +#include "draco/io/gltf_test_helper.h" +#include "draco/io/parser_utils.h" +#include "draco/io/texture_io.h" +#include "draco/material/material_utils.h" +#include "draco/mesh/mesh_utils.h" +#include "draco/scene/mesh_group.h" +#include "draco/scene/scene.h" +#include "draco/scene/scene_utils.h" +#include "draco/texture/texture_utils.h" + +namespace draco { + +namespace { +std::unique_ptr DecodeFullPathGltfFileToScene( + const std::string &file_name) { + GltfDecoder decoder; + + auto maybe_scene = decoder.DecodeFromFileToScene(file_name); + if (!maybe_scene.ok()) { + std::cout << maybe_scene.status().error_msg_string() << std::endl; + return nullptr; + } + std::unique_ptr scene = std::move(maybe_scene).value(); + return scene; +} + +std::unique_ptr DecodeTestGltfFileToScene(const std::string &file_name) { + const std::string path = GetTestFileFullPath(file_name); + return DecodeFullPathGltfFileToScene(path); +} +} // namespace + +class GltfEncoderTest : public ::testing::Test { + protected: + // This function searches for the |search| string and checks that there are at + // least |count| occurrences. + void CheckGltfFileAtLeastStringCount(const std::string &gltf_file, + const std::string &search, int count) { + std::vector data; + ASSERT_TRUE(ReadFileToBuffer(gltf_file, &data)); + + draco::DecoderBuffer buffer; + buffer.Init(data.data(), data.size()); + + int strings_found = 0; + do { + std::string gltf_line; + draco::parser::ParseLine(&buffer, &gltf_line); + if (gltf_line.empty()) { + break; + } + + if (gltf_line.find(search) != std::string::npos) { + strings_found++; + } + // No need to keep counting pass |count|. + } while (strings_found < count); + ASSERT_GE(strings_found, count); + } + + // This function searches for the |search| string and checks that there no + // occurrences. + void CheckGltfFileNoString(const std::string &gltf_file, + const std::string &search) { + std::vector data; + ASSERT_TRUE(ReadFileToBuffer(gltf_file, &data)); + + draco::DecoderBuffer buffer; + buffer.Init(data.data(), data.size()); + + do { + std::string gltf_line; + draco::parser::ParseLine(&buffer, &gltf_line); + if (gltf_line.empty()) { + break; + } + ASSERT_TRUE(gltf_line.find(search) == std::string::npos); + } while (true); + } + + void CheckAnimationAccessors(const Scene &scene, + int expected_num_input_accessors, + int expected_num_output_accessors) { + int num_input_accessors = 0; + int num_output_accessors = 0; + + for (int i = 0; i < scene.NumAnimations(); ++i) { + const Animation *anim = scene.GetAnimation(AnimationIndex(i)); + ASSERT_NE(anim, nullptr); + + // The animation accessors in Draco are relative to the Animation object. + // While in glTF the animation accessors are relative to the global + // accessors. + std::unordered_set seen_accessors; + + for (int j = 0; j < anim->NumSamplers(); ++j) { + const AnimationSampler *const sampler = anim->GetSampler(j); + ASSERT_NE(sampler, nullptr); + + if (seen_accessors.find(sampler->input_index) == seen_accessors.end()) { + seen_accessors.insert(sampler->input_index); + num_input_accessors++; + } + if (seen_accessors.find(sampler->output_index) == + seen_accessors.end()) { + seen_accessors.insert(sampler->output_index); + num_output_accessors++; + } + } + } + + EXPECT_EQ(expected_num_input_accessors, num_input_accessors); + EXPECT_EQ(expected_num_output_accessors, num_output_accessors); + } + + void CompareMeshes(const Mesh *mesh0, const Mesh *mesh1) { + ASSERT_EQ(mesh0->num_faces(), mesh1->num_faces()); + ASSERT_EQ(mesh0->num_attributes(), mesh1->num_attributes()); + for (int att_id = 0; att_id < mesh0->num_attributes(); ++att_id) { + const GeometryAttribute::Type att_type = + mesh0->attribute(att_id)->attribute_type(); + const PointAttribute *const att = mesh1->GetNamedAttribute(att_type); + ASSERT_EQ(mesh0->attribute(att_id)->size(), att->size()) + << "Attribute id:" << att_id << " is not equal."; + } + + // Check materials are the same. + if (mesh0->GetMaterialLibrary().NumMaterials() == 0) { + // We add a default material if the source had no materials. + ASSERT_EQ(mesh1->GetMaterialLibrary().NumMaterials(), 1); + } else if (mesh1->GetMaterialLibrary().NumMaterials() == 0) { + // We add a default material if the source had no materials. + ASSERT_EQ(mesh0->GetMaterialLibrary().NumMaterials(), 1); + } else { + ASSERT_EQ(mesh0->GetMaterialLibrary().NumMaterials(), + mesh1->GetMaterialLibrary().NumMaterials()); + for (int i = 0; i < mesh0->GetMaterialLibrary().NumMaterials(); ++i) { + ASSERT_EQ(mesh0->GetMaterialLibrary().GetMaterial(i)->NumTextureMaps(), + mesh1->GetMaterialLibrary().GetMaterial(i)->NumTextureMaps()); + ASSERT_EQ(mesh0->GetMaterialLibrary().GetMaterial(i)->GetName(), + mesh1->GetMaterialLibrary().GetMaterial(i)->GetName()); + } + } + } + + void CompareScenes(const Scene *scene0, const Scene *scene1) { + ASSERT_EQ(scene0->NumMeshes(), scene1->NumMeshes()); + ASSERT_EQ(scene0->NumMeshGroups(), scene1->NumMeshGroups()); + ASSERT_EQ(scene0->NumNodes(), scene1->NumNodes()); + ASSERT_EQ(scene0->GetMaterialLibrary().NumMaterials(), + scene1->GetMaterialLibrary().NumMaterials()); + ASSERT_EQ(scene0->NumAnimations(), scene1->NumAnimations()); + ASSERT_EQ(scene0->NumSkins(), scene1->NumSkins()); + ASSERT_EQ(scene0->NumLights(), scene1->NumLights()); + + // Check materials are the same. + for (int i = 0; i < scene0->GetMaterialLibrary().NumMaterials(); ++i) { + ASSERT_EQ(scene0->GetMaterialLibrary().GetMaterial(i)->NumTextureMaps(), + scene1->GetMaterialLibrary().GetMaterial(i)->NumTextureMaps()); + ASSERT_EQ(scene0->GetMaterialLibrary().GetMaterial(i)->GetName(), + scene1->GetMaterialLibrary().GetMaterial(i)->GetName()); + } + + // Check that materials variants names are the same. + ASSERT_EQ(scene0->GetMaterialLibrary().NumMaterialsVariants(), + scene1->GetMaterialLibrary().NumMaterialsVariants()); + for (int i = 0; i < scene0->GetMaterialLibrary().NumMaterialsVariants(); + i++) { + ASSERT_EQ(scene0->GetMaterialLibrary().GetMaterialsVariantName(i), + scene1->GetMaterialLibrary().GetMaterialsVariantName(i)); + } + + // Check Nodes are the same. + for (draco::SceneNodeIndex i(0); i < scene0->NumNodes(); ++i) { + const SceneNode *const scene_node0 = scene0->GetNode(i); + const SceneNode *const scene_node1 = scene1->GetNode(i); + ASSERT_NE(scene_node0, nullptr); + ASSERT_NE(scene_node1, nullptr); + ASSERT_EQ(scene_node0->GetName(), scene_node1->GetName()); + ASSERT_EQ(scene_node0->GetLightIndex(), scene_node1->GetLightIndex()); + } + + // Check MeshGroups are the same. + for (draco::MeshGroupIndex i(0); i < scene0->NumMeshGroups(); ++i) { + const MeshGroup *const mesh_group0 = scene0->GetMeshGroup(i); + const MeshGroup *const mesh_group1 = scene1->GetMeshGroup(i); + ASSERT_NE(mesh_group0, nullptr); + ASSERT_NE(mesh_group1, nullptr); + ASSERT_EQ(mesh_group0->GetName(), mesh_group1->GetName()); + ASSERT_EQ(mesh_group0->NumMeshInstances(), + mesh_group1->NumMeshInstances()); + + // Check that mesh instanes are the same. + for (int j = 0; j < mesh_group1->NumMeshInstances(); j++) { + const MeshGroup::MeshInstance &instance0 = + mesh_group0->GetMeshInstance(j); + const MeshGroup::MeshInstance &instance1 = + mesh_group1->GetMeshInstance(j); + ASSERT_EQ(instance0.mesh_index, instance1.mesh_index); + ASSERT_EQ(instance0.material_index, instance1.material_index); + ASSERT_EQ(instance0.materials_variants_mappings.size(), + instance1.materials_variants_mappings.size()); + + // Check that materials variants mappings are the same. + for (int k = 0; k < instance0.materials_variants_mappings.size(); k++) { + const MeshGroup::MaterialsVariantsMapping &mapping0 = + instance0.materials_variants_mappings[k]; + const MeshGroup::MaterialsVariantsMapping &mapping1 = + instance1.materials_variants_mappings[k]; + ASSERT_EQ(mapping0.material, mapping1.material); + ASSERT_EQ(mapping0.variants.size(), mapping1.variants.size()); + for (int l = 0; l < mapping0.variants.size(); l++) { + ASSERT_EQ(mapping0.variants[l], mapping1.variants[l]); + } + } + } + } + + // Check Animations are the same. + for (draco::AnimationIndex i(0); i < scene0->NumAnimations(); ++i) { + const Animation *const animation0 = scene0->GetAnimation(i); + const Animation *const animation1 = scene1->GetAnimation(i); + ASSERT_NE(animation0, nullptr); + ASSERT_NE(animation1, nullptr); + ASSERT_EQ(animation0->NumSamplers(), animation1->NumSamplers()); + ASSERT_EQ(animation0->NumChannels(), animation1->NumChannels()); + ASSERT_EQ(animation0->NumNodeAnimationData(), + animation1->NumNodeAnimationData()); + } + + // Check that lights are the same. + for (draco::LightIndex i(0); i < scene0->NumLights(); ++i) { + const Light *const light0 = scene0->GetLight(i); + const Light *const light1 = scene1->GetLight(i); + ASSERT_NE(light0, nullptr); + ASSERT_NE(light1, nullptr); + ASSERT_EQ(light0->GetName(), light1->GetName()); + ASSERT_EQ(light0->GetColor(), light1->GetColor()); + ASSERT_EQ(light0->GetIntensity(), light1->GetIntensity()); + ASSERT_EQ(light0->GetType(), light1->GetType()); + ASSERT_EQ(light0->GetRange(), light1->GetRange()); + if (light0->GetType() == Light::SPOT) { + ASSERT_EQ(light0->GetInnerConeAngle(), light1->GetInnerConeAngle()); + ASSERT_EQ(light0->GetOuterConeAngle(), light1->GetOuterConeAngle()); + } + } + } + + void EncodeMeshToFile(const Mesh &mesh, + const std::string &gltf_file_full_path) { + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + ASSERT_TRUE( + gltf_encoder.EncodeToFile(mesh, gltf_file_full_path, folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + } + + void EncodeSceneToFile(const Scene &scene, + const std::string &gltf_file_full_path) { + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeToFile(scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + } + + // Encode |mesh| to a temporary glTF file. Then decode the glTF file and + // return the mesh in |mesh_gltf|. + void MeshToDecodedGltfMesh(const Mesh &mesh, + std::unique_ptr *mesh_gltf) { + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("test.gltf"); + EncodeMeshToFile(mesh, gltf_file_full_path); + *mesh_gltf = std::move(ReadMeshFromFile(gltf_file_full_path)).value(); + ASSERT_NE(*mesh_gltf, nullptr); + } + + // Encode |mesh| to a temporary glTF file. Then decode the glTF file as a + // scene and return it in |scene_gltf|. + void MeshToDecodedGltfScene(const Mesh &mesh, + std::unique_ptr *scene_gltf) { + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("test.gltf"); + EncodeMeshToFile(mesh, gltf_file_full_path); + *scene_gltf = std::move(ReadSceneFromFile(gltf_file_full_path)).value(); + ASSERT_NE(*scene_gltf, nullptr); + } + + // Encode |scene| to a temporary glTF file. Then decode the glTF file and + // return the scene in |scene_gltf|. + void SceneToDecodedGltfScene(const Scene &scene, + const std::string &temp_basename, + std::unique_ptr *scene_gltf) { + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath(temp_basename); + EncodeSceneToFile(scene, gltf_file_full_path); + + *scene_gltf = DecodeFullPathGltfFileToScene(gltf_file_full_path); + if (SceneUtils::IsDracoCompressionEnabled(scene)) { + // Two occurrences of the Draco compression string is the least amount for + // a valid Draco compressed glTF file. + const std::string khr_draco_compression = "KHR_draco_mesh_compression"; + CheckGltfFileAtLeastStringCount(gltf_file_full_path, + khr_draco_compression, 2); + } + ASSERT_NE((*scene_gltf).get(), nullptr); + } + + void SceneToDecodedGltfScene(const Scene &scene, + std::unique_ptr *scene_gltf) { + SceneToDecodedGltfScene(scene, "test.gltf", scene_gltf); + } + + void EncodeMeshToGltfAndCompare(Mesh *mesh) { + ASSERT_GT(mesh->num_faces(), 0); + + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + + mesh->DeduplicatePointIds(); + ASSERT_TRUE(mesh->DeduplicateAttributeValues()); + CompareMeshes(mesh, mesh_from_gltf.get()); + } + + void EncodeSceneToGltfAndCompare(Scene *scene) { + std::unique_ptr scene_from_gltf; + SceneToDecodedGltfScene(*scene, &scene_from_gltf); + if (!SceneUtils::IsDracoCompressionEnabled(*scene)) { + CompareScenes(scene, scene_from_gltf.get()); + } + } + + void test_encoding(const std::string &file_name) { + const std::unique_ptr mesh(ReadMeshFromTestFile(file_name, true)); + + ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name; + EncodeMeshToGltfAndCompare(mesh.get()); + } +}; + +TEST_F(GltfEncoderTest, TestGltfEncodingAll) { + // Test decoded mesh from encoded glTF file stays the same. + test_encoding("test_nm.obj.edgebreaker.cl4.2.2.drc"); + test_encoding("cube_att.drc"); + test_encoding("car.drc"); + test_encoding("bunny_gltf.drc"); +} + +TEST_F(GltfEncoderTest, ImportTangentAttribute) { + auto mesh = draco::ReadMeshFromTestFile("sphere.gltf"); + ASSERT_NE(mesh, nullptr); + + const draco::PointAttribute *const tangent_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::TANGENT); + ASSERT_NE(tangent_att, nullptr); + + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + ASSERT_EQ(mesh->num_attributes(), mesh_from_gltf->num_attributes()); +} + +TEST_F(GltfEncoderTest, EncodeColorTexture) { + const std::string tex_file_name = draco::GetTestFileFullPath("test.png"); + std::unique_ptr texture = + draco::ReadTextureFromFile(tex_file_name).value(); + ASSERT_NE(texture, nullptr); + + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + mesh->GetMaterialLibrary().MutableMaterial(0)->SetTextureMap( + std::move(texture), draco::TextureMap::COLOR, 0); + + EncodeMeshToGltfAndCompare(mesh.get()); +} + +TEST_F(GltfEncoderTest, EncodeColors) { + auto mesh = draco::ReadMeshFromTestFile("test_pos_color.ply"); + ASSERT_NE(mesh, nullptr); + + const draco::PointAttribute *const color_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::COLOR); + ASSERT_NE(color_att, nullptr); + + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + + ASSERT_EQ(mesh->num_faces(), mesh_from_gltf->num_faces()); + ASSERT_EQ(mesh->num_attributes(), mesh_from_gltf->num_attributes()); + ASSERT_EQ( + mesh->NumNamedAttributes(draco::GeometryAttribute::COLOR), + mesh_from_gltf->NumNamedAttributes(draco::GeometryAttribute::COLOR)); +} + +TEST_F(GltfEncoderTest, EncodeNamedGenericAttribute) { + // Load some base mesh. + auto mesh = draco::ReadMeshFromTestFile("test_generic.ply"); + ASSERT_NE(mesh, nullptr); + const draco::PointAttribute *const pos_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::POSITION); + ASSERT_NE(pos_att, nullptr); + int num_vertices = pos_att->size(); + + // Add two new scalar attributes where each value corresponds to the position + // value index (vertex). The first attribute will have metadata, the second + // attribute won't. + std::unique_ptr pa_0(new draco::PointAttribute()); + std::unique_ptr pa_1(new draco::PointAttribute()); + pa_0->Init(draco::GeometryAttribute::GENERIC, /* scalar */ 1, + draco::DT_FLOAT32, false, + /* one value per position value */ num_vertices); + pa_1->Init(draco::GeometryAttribute::GENERIC, /* scalar */ 1, + draco::DT_FLOAT32, false, + /* one value per position value */ num_vertices); + + // Set the values for the new attributes. + for (draco::AttributeValueIndex avi(0); avi < num_vertices; ++avi) { + const float att_value = avi.value(); + pa_0->SetAttributeValue(avi, &att_value); + pa_1->SetAttributeValue(avi, &att_value); + } + + // Add the attribute to the existing mesh. + const int new_att_id_0 = mesh->AddPerVertexAttribute(std::move(pa_0)); + const int new_att_id_1 = mesh->AddPerVertexAttribute(std::move(pa_1)); + ASSERT_NE(new_att_id_0, -1); + ASSERT_NE(new_att_id_1, -1); + + // Set metadata for first attribute so it gets written out by glTF encoder. + std::unique_ptr am(new draco::AttributeMetadata()); + constexpr char kAttributeName[] = "MyAttributeName"; + constexpr char kDracoMetadataGltfAttributeName[] = + "//GLTF/ApplicationSpecificAttributeName"; + am->AddEntryString(kDracoMetadataGltfAttributeName, kAttributeName); + mesh->AddAttributeMetadata(new_att_id_0, std::move(am)); + + // Make sure the GLTF contains a reference to the named attribute. + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("GenericAttribute.gltf"); + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeToFile(*(mesh.get()), + gltf_file_full_path, folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + CheckGltfFileAtLeastStringCount(gltf_file_full_path, kAttributeName, 1); + + // The decoder does not yet support generic attribute names, so instead of + // using the decoder we compare against a golden file. + const std::string gltf_generated_bin_filename = + draco::GetTestTempFileFullPath("buffer0.bin"); + std::vector generated_buffer; + ASSERT_TRUE(ReadFileToBuffer(gltf_generated_bin_filename, &generated_buffer)); + std::string generated_str(generated_buffer.data(), generated_buffer.size()); + + const std::string gltf_expected_bin_filename = + GetTestFileFullPath("test_generic_golden.bin"); + const bool kUpdateGoldens = false; + if (kUpdateGoldens) { + ASSERT_TRUE(WriteBufferToFile(generated_buffer.data(), + generated_buffer.size(), + gltf_expected_bin_filename)); + } + std::vector expected_buffer; + ASSERT_TRUE(ReadFileToBuffer(gltf_expected_bin_filename, &expected_buffer)); + std::string expected_str(expected_buffer.data(), expected_buffer.size()); + + EXPECT_TRUE(generated_str == expected_str); +} + +TEST_F(GltfEncoderTest, EncodeMetallicRoughnessTexture) { + const std::string tex_file_name = draco::GetTestFileFullPath("test.png"); + std::unique_ptr texture = + draco::ReadTextureFromFile(tex_file_name).value(); + ASSERT_NE(texture, nullptr); + + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + mesh->GetMaterialLibrary().MutableMaterial(0)->SetTextureMap( + std::move(texture), draco::TextureMap::METALLIC_ROUGHNESS, 0); + + EncodeMeshToGltfAndCompare(mesh.get()); +} + +TEST_F(GltfEncoderTest, EncodeOcclusionTexture) { + const std::string tex_file_name = draco::GetTestFileFullPath("test.png"); + std::unique_ptr texture = + draco::ReadTextureFromFile(tex_file_name).value(); + ASSERT_NE(texture, nullptr); + + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + mesh->GetMaterialLibrary().MutableMaterial(0)->SetTextureMap( + std::move(texture), draco::TextureMap::AMBIENT_OCCLUSION, 0); + + EncodeMeshToGltfAndCompare(mesh.get()); +} + +TEST_F(GltfEncoderTest, EncodeEmissiveTexture) { + const std::string tex_file_name = draco::GetTestFileFullPath("test.png"); + std::unique_ptr texture = + draco::ReadTextureFromFile(tex_file_name).value(); + ASSERT_NE(texture, nullptr); + + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + mesh->GetMaterialLibrary().MutableMaterial(0)->SetTextureMap( + std::move(texture), draco::TextureMap::EMISSIVE, 0); + + EncodeMeshToGltfAndCompare(mesh.get()); +} + +// Tests splitting the mesh into multiple primitives. +TEST_F(GltfEncoderTest, EncodeSplitMesh) { + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(mesh, nullptr); + const int32_t material_att_id = + mesh->GetNamedAttributeId(draco::GeometryAttribute::MATERIAL); + ASSERT_NE(material_att_id, -1); + EncodeMeshToGltfAndCompare(mesh.get()); +} + +// Tests encoding a scene from a glTF with multiple meshes and primitives, +// including mesh instances. +TEST_F(GltfEncoderTest, EncodeInstancedScene) { + const std::string file_name = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + std::unique_ptr transcoded_scene; + SceneToDecodedGltfScene(*scene, "EncodeInstancedScene.gltf", + &transcoded_scene); + ASSERT_NE(transcoded_scene, nullptr); + CompareScenes(scene.get(), transcoded_scene.get()); + EXPECT_EQ(transcoded_scene->NumAnimations(), 1); + + const int num_input_accessors = 2; + const int num_output_accessors = 2; + CheckAnimationAccessors(*transcoded_scene, num_input_accessors, + num_output_accessors); +} + +// Tests encoding a scene from a glTF with multiple meshes and primitives, +// including mesh instances. +TEST_F(GltfEncoderTest, EncodeBoneAnimation) { + const std::string file_name = "CesiumMan/glTF/CesiumMan.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + std::unique_ptr transcoded_scene; + SceneToDecodedGltfScene(*scene, "EncodeBoneAnimation.gltf", + &transcoded_scene); + ASSERT_NE(transcoded_scene, nullptr); + CompareScenes(scene.get(), transcoded_scene.get()); + EXPECT_EQ(transcoded_scene->NumAnimations(), 1); + + const Animation *anim = scene->GetAnimation(AnimationIndex(0)); + ASSERT_NE(anim, nullptr); + ASSERT_TRUE(anim->GetName().empty()); + + // TODO(b/145703399): Figure out how to test that all of the input accessors + // in animation channels in the encoded glTF file will be the same for this + // test file. + const int num_input_accessors = 57; + const int num_output_accessors = 57; + CheckAnimationAccessors(*transcoded_scene, num_input_accessors, + num_output_accessors); +} + +// Tests encoding a scene from a glTF with nodes that have names. +TEST_F(GltfEncoderTest, EncodeSceneWithNodeNames) { + const std::string file_name = "Lantern/glTF/Lantern.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + EncodeSceneToGltfAndCompare(scene.get()); +} + +// Tests encoding a simple glTF with Draco compression. +TEST_F(GltfEncoderTest, EncodeWithDracoCompression) { + const std::string file_name = "Box/glTF/Box.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + const DracoCompressionOptions options; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + EncodeSceneToGltfAndCompare(scene.get()); +} + +TEST_F(GltfEncoderTest, EncodeWeightsJointsWithDracoCompression) { + const std::string file_name = "CesiumMan/glTF/CesiumMan.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + const DracoCompressionOptions options; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + EncodeSceneToGltfAndCompare(scene.get()); +} + +TEST_F(GltfEncoderTest, EncodeTangentsWithDracoCompression) { + const std::string file_name = "Lantern/glTF/Lantern.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + const DracoCompressionOptions options; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + EncodeSceneToGltfAndCompare(scene.get()); +} + +TEST_F(GltfEncoderTest, TestDracoCompressionWithGeneratedPoints) { + const std::string basename = "test_nm.obj"; + std::unique_ptr mesh = draco::ReadMeshFromTestFile(basename); + ASSERT_NE(mesh, nullptr) << "Failed to load " << basename; + + auto maybe_scene = draco::SceneUtils::MeshToScene(std::move(mesh)); + ASSERT_TRUE(maybe_scene.ok()) << "Failed Mesh to Scene conversion."; + const std::unique_ptr scene = std::move(maybe_scene).value(); + ASSERT_NE(scene, nullptr); + const DracoCompressionOptions options; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + EncodeSceneToGltfAndCompare(scene.get()); +} + +TEST_F(GltfEncoderTest, TestDracoCompressionWithDegenerateFaces) { + const std::string basename = "deg_faces.obj"; + std::unique_ptr mesh = draco::ReadMeshFromTestFile(basename); + ASSERT_NE(mesh, nullptr) << "Failed to load " << basename; + ASSERT_EQ(mesh->num_faces(), 4); + + auto maybe_scene = draco::SceneUtils::MeshToScene(std::move(mesh)); + ASSERT_TRUE(maybe_scene.ok()) << "Failed Mesh to Scene conversion."; + const std::unique_ptr scene = std::move(maybe_scene).value(); + ASSERT_NE(scene, nullptr); + const Mesh &scene_first_mesh = scene->GetMesh(MeshIndex(0)); + ASSERT_EQ(scene_first_mesh.num_faces(), 4); + + std::unique_ptr scene_from_gltf; + const DracoCompressionOptions options; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + SceneToDecodedGltfScene(*scene, &scene_from_gltf); + const Mesh &scene_from_gltf_first_mesh = + scene_from_gltf->GetMesh(MeshIndex(0)); + ASSERT_EQ(scene_from_gltf_first_mesh.num_faces(), 3); + + CompareScenes(scene.get(), scene_from_gltf.get()); +} + +TEST_F(GltfEncoderTest, DracoCompressionCheckOptions) { + const std::string file_name = "CesiumMan/glTF/CesiumMan.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("test.gltf"); + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + DracoCompressionOptions options; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + + const std::string gltf_bin_filename = + draco::GetTestTempFileFullPath("buffer0.bin"); + const size_t default_bin_size = draco::GetFileSize(gltf_bin_filename); + + // Test applying more quantization will make the compressed size smaller. + options.quantization_position.SetQuantizationBits(6); + options.quantization_bits_normal = 6; + options.quantization_bits_tex_coord = 6; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t more_quantization_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_LT(more_quantization_bin_size, default_bin_size); + + // Test setting more weight quantization then the default makes the compressed + // size smaller. + options.quantization_bits_weight = 6; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t more_weight_quantization_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_LT(more_weight_quantization_bin_size, more_quantization_bin_size); + + options.quantization_position.SetQuantizationBits(20); + options.quantization_bits_normal = 20; + options.quantization_bits_tex_coord = 20; + options.quantization_bits_weight = 20; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t less_quantization_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_GT(less_quantization_bin_size, default_bin_size); + + DracoCompressionOptions level_options; + level_options.compression_level = 10; // compression level [0-10]. + SceneUtils::SetDracoCompressionOptions(&level_options, scene.get()); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t most_compression_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_LT(most_compression_bin_size, default_bin_size); + + level_options.compression_level = 4; + SceneUtils::SetDracoCompressionOptions(&level_options, scene.get()); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t less_compression_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_GT(less_compression_bin_size, default_bin_size); + + level_options.compression_level = 0; + SceneUtils::SetDracoCompressionOptions(&level_options, scene.get()); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t least_compression_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_GT(least_compression_bin_size, less_compression_bin_size); +} + +TEST_F(GltfEncoderTest, TestQuantizationPerAttribute) { + const std::string file_name = "sphere.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("test.gltf"); + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + DracoCompressionOptions options; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + + const std::string gltf_bin_filename = + draco::GetTestTempFileFullPath("buffer0.bin"); + const size_t default_bin_size = draco::GetFileSize(gltf_bin_filename); + + // Test setting more position quantization then the default makes the + // compressed size smaller. + options.quantization_position.SetQuantizationBits(6); + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t position_quantization_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_LT(position_quantization_bin_size, default_bin_size); + + // Test setting more normal quantization then the default makes the compressed + // size smaller. + options.quantization_bits_normal = 6; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t normal_quantization_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_LT(normal_quantization_bin_size, position_quantization_bin_size); + + // Test setting more tex_coord quantization then the default makes the + // compressed size smaller. + options.quantization_bits_tex_coord = 6; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t tex_coord_quantization_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_LT(tex_coord_quantization_bin_size, normal_quantization_bin_size); + + // Test setting more tangent quantization then the default makes the + // compressed size smaller. Weight is tested in DracoCompressionCheckOptions. + options.quantization_bits_tangent = 6; + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t tangent_quantization_bin_size = + draco::GetFileSize(gltf_bin_filename); + ASSERT_LT(tangent_quantization_bin_size, tex_coord_quantization_bin_size); +} + +// Tests encoding a glTF with multiple scaled instances with Draco compression +// using grid options for position quantization. +TEST_F(GltfEncoderTest, TestDracoCompressionWithGridOptions) { + const std::string file_name = + "SpheresScaledInstances/glTF/spheres_scaled_instances.gltf"; + std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + const auto bbox = scene->GetMesh(MeshIndex(0)).ComputeBoundingBox(); + const float mesh_size = bbox.Size().MaxCoeff(); + + // All dimensions of the original mesh are between [-1, 1]. Let's move the + // mesh to [0, 2] which will allow us to match grid quantization with the + // regular quantization (grid quantization is always aligned with 0). + Mesh &mesh = scene->GetMesh(MeshIndex(0)); + PointAttribute *pos_att = + mesh.attribute(mesh.GetNamedAttributeId(GeometryAttribute::POSITION)); + for (AttributeValueIndex avi(0); avi < pos_att->size(); ++avi) { + Vector3f pos; + pos_att->GetValue(avi, &pos[0]); + pos += Vector3f(1.f, 1.f, 1.f); + pos_att->SetAttributeValue(avi, &pos[0]); + } + + DracoCompressionOptions options; + + // First quantize the scene with 8 bits and save the result. + options.quantization_position.SetQuantizationBits(8); + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + const std::string gltf_filename = draco::GetTestTempFileFullPath("temp.glb"); + GltfEncoder encoder; + DRACO_ASSERT_OK(encoder.EncodeFile(*scene, gltf_filename)); + // Get the size of the generated file. + const size_t qb_file_size = draco::GetFileSize(gltf_filename); + + // Now set grid quantization and ensure the encoded file size is about the + // same. The max instance scale is 3 and model size is |mesh_size| so the grid + // scale must account for that. + options.quantization_position.SetGrid(mesh_size * 3. / 255.); + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + DRACO_ASSERT_OK(encoder.EncodeFile(*scene, gltf_filename)); + // Get the size of the generated file. + const size_t grid_file_size = draco::GetFileSize(gltf_filename); + + ASSERT_EQ(grid_file_size, qb_file_size); + + // Now set grid quantization to different settings and ensure the encoded size + // changed. We reduce spacing which should increase the size. + options.quantization_position.SetGrid(mesh_size * 3. / 511.); + SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + + DRACO_ASSERT_OK(encoder.EncodeFile(*scene, gltf_filename)); + + // Get the size of the generated file. + const size_t grid_file_size_2 = draco::GetFileSize(gltf_filename); + ASSERT_GT(grid_file_size_2, grid_file_size); +} + +TEST_F(GltfEncoderTest, TestOutputType) { + const std::string file_name = "sphere.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("test.gltf"); + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + + const size_t default_gltf_size = draco::GetFileSize(gltf_file_full_path); + + // Test setting VERBOSE output type will increase the size of the gltf file. + gltf_encoder.set_output_type(GltfEncoder::VERBOSE); + ASSERT_TRUE(gltf_encoder.EncodeToFile(*scene, gltf_file_full_path, + folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + const size_t verbose_gltf_size = draco::GetFileSize(gltf_file_full_path); + ASSERT_GT(verbose_gltf_size, default_gltf_size); +} + +// Tests copying the name of the input texture file to the encoded texture file. +TEST_F(GltfEncoderTest, CopyTextureName) { + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(mesh, nullptr); + + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + const Material *material = mesh->GetMaterialLibrary().GetMaterial(0); + ASSERT_NE(material, nullptr); + const Texture *texture = + mesh->GetMaterialLibrary().GetTextureLibrary().GetTexture(0); + ASSERT_NE(texture, nullptr); + ASSERT_EQ(draco::TextureUtils::GetTargetStem(*texture), "CesiumMilkTruck"); + ASSERT_EQ(draco::TextureUtils::GetTargetFormat(*texture), + draco::ImageFormat::PNG); +} + +TEST_F(GltfEncoderTest, EncodeTexCoord1) { + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("MultiUVTest/glTF/MultiUVTest.gltf"); + + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + ASSERT_EQ(mesh_from_gltf->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ( + mesh_from_gltf->GetMaterialLibrary().GetMaterial(0)->NumTextureMaps(), 2); + ASSERT_EQ( + mesh_from_gltf->GetMaterialLibrary().GetTextureLibrary().NumTextures(), + 2); + const std::vector textures = { + mesh_from_gltf->GetMaterialLibrary().GetTextureLibrary().GetTexture(0), + mesh_from_gltf->GetMaterialLibrary().GetTextureLibrary().GetTexture(1)}; + EXPECT_EQ(draco::TextureUtils::GetTargetStem(*textures[0]), "uv0"); + EXPECT_EQ(draco::TextureUtils::GetTargetStem(*textures[1]), "uv1"); + EXPECT_EQ(draco::TextureUtils::GetTargetFormat(*textures[0]), + draco::ImageFormat::PNG); + EXPECT_EQ(draco::TextureUtils::GetTargetFormat(*textures[1]), + draco::ImageFormat::PNG); + ASSERT_EQ(mesh_from_gltf->NumNamedAttributes(GeometryAttribute::TEX_COORD), + 2); + ASSERT_EQ(mesh_from_gltf->NumNamedAttributes(GeometryAttribute::POSITION), 1); + ASSERT_EQ(mesh_from_gltf->NumNamedAttributes(GeometryAttribute::NORMAL), 1); + ASSERT_EQ(mesh_from_gltf->NumNamedAttributes(GeometryAttribute::TANGENT), 1); +} + +TEST_F(GltfEncoderTest, TestEncodeFileFunctions) { + const std::string file_name = "sphere.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + // Test encoding with only the gltf filename parameter will output the correct + // bin filename and the textures will be in the same directory as the output + // glTF file. + const std::string output_gltf_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(output_gltf_filename, &output_gltf_dir, + &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeFile(*scene, output_gltf_filename).ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + + const std::string output_bin_filename = + draco::GetTestTempFileFullPath("encoded_example.bin"); + const size_t output_bin_size = draco::GetFileSize(output_bin_filename); + ASSERT_GT(output_bin_size, 0); + const std::string output_png_filename = + draco::GetTestTempFileFullPath("sphere_Texture0_Normal.png"); + const size_t output_png_size = draco::GetFileSize(output_png_filename); + ASSERT_GT(output_png_size, 0); + + // Test encoding with the gltf and bin filename parameter, the textures will + // be in the same directory as the output glTF file. + const std::string new_bin_filename = + draco::GetTestTempFileFullPath("different_stem_name.bin"); + ASSERT_TRUE( + gltf_encoder + .EncodeFile(*scene, output_gltf_filename, new_bin_filename) + .ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + + const size_t new_bin_size = draco::GetFileSize(new_bin_filename); + ASSERT_GT(new_bin_size, 0); + ASSERT_EQ(new_bin_size, output_bin_size); + + // Test encoding with the gltf and bin filename and resource_dir parameter, + // the textures will be in the resource_dir directory. + const std::string new_resource_dir = output_gltf_dir + "/textures"; + ASSERT_TRUE(gltf_encoder + .EncodeFile(*scene, output_gltf_filename, + new_bin_filename, new_resource_dir) + .ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + + const std::string new_png_filename = + draco::GetTestTempFileFullPath("textures/sphere_Texture0_Normal.png"); + const size_t newest_bin_size = draco::GetFileSize(new_bin_filename); + ASSERT_GT(new_bin_size, 0); + ASSERT_EQ(new_bin_size, output_bin_size); + ASSERT_EQ(newest_bin_size, new_bin_size); + const size_t new_png_size = draco::GetFileSize(new_png_filename); + ASSERT_GT(new_png_size, 0); + ASSERT_EQ(new_png_size, output_png_size); +} + +TEST_F(GltfEncoderTest, DoubleSidedMaterial) { + const std::string file_name = "TwoSidedPlane/glTF/TwoSidedPlane.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + EXPECT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ(scene->GetMaterialLibrary().GetMaterial(0)->GetDoubleSided(), true); + + std::unique_ptr scene_from_gltf; + SceneToDecodedGltfScene(*scene, &scene_from_gltf); + EXPECT_EQ(scene_from_gltf->GetMaterialLibrary().NumMaterials(), 1); + EXPECT_EQ( + scene_from_gltf->GetMaterialLibrary().GetMaterial(0)->GetDoubleSided(), + true); +} + +TEST_F(GltfEncoderTest, EncodeGlb) { + const std::string file_name = "sphere.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + std::unique_ptr scene_from_gltf; + SceneToDecodedGltfScene(*scene, "temp.gltf", &scene_from_gltf); + + std::unique_ptr scene_from_glb; + SceneToDecodedGltfScene(*scene, "temp.glb", &scene_from_glb); + + CompareScenes(scene_from_gltf.get(), scene_from_glb.get()); +} + +TEST_F(GltfEncoderTest, EncodeVertexColor) { + const std::string file_name = "VertexColorTest/glTF/VertexColorTest.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + EXPECT_EQ(scene->NumMeshes(), 2); + const Mesh &mesh = scene->GetMesh(MeshIndex(1)); + EXPECT_EQ(mesh.NumNamedAttributes(GeometryAttribute::COLOR), 1); + + std::unique_ptr scene_from_gltf; + SceneToDecodedGltfScene(*scene, "temp.gltf", &scene_from_gltf); + EXPECT_EQ(scene_from_gltf->NumMeshes(), 2); + const Mesh &encoded_mesh = scene_from_gltf->GetMesh(MeshIndex(1)); + EXPECT_EQ(encoded_mesh.NumNamedAttributes(GeometryAttribute::COLOR), 1); +} + +TEST_F(GltfEncoderTest, InterpolationTest) { + const std::string file_name = "InterpolationTest/glTF/InterpolationTest.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + std::unique_ptr transcoded_scene; + SceneToDecodedGltfScene(*scene, "InterpolationTest.gltf", &transcoded_scene); + ASSERT_NE(transcoded_scene, nullptr); + CompareScenes(scene.get(), transcoded_scene.get()); + EXPECT_EQ(transcoded_scene->NumAnimations(), 9); + + const std::vector animation_names{ + "Step Scale", "Linear Scale", + "CubicSpline Scale", "Step Rotation", + "CubicSpline Rotation", "Linear Rotation", + "Step Translation", "CubicSpline Translation", + "Linear Translation"}; + for (int i = 0; i < scene->NumAnimations(); ++i) { + const Animation *const anim = scene->GetAnimation(AnimationIndex(i)); + ASSERT_NE(anim, nullptr); + ASSERT_EQ(anim->GetName(), animation_names[i]); + } + + // Currently all animation data is unique. See b/145703399. + const int num_input_accessors = 9; + const int num_output_accessors = 9; + CheckAnimationAccessors(*transcoded_scene, num_input_accessors, + num_output_accessors); +} + +TEST_F(GltfEncoderTest, KhrMaterialUnlit) { + const std::string filename = + "KhronosSampleModels/UnlitTest/glTF/UnlitTest.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene, nullptr); + + const std::string output_gltf_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(output_gltf_filename, &output_gltf_dir, + &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeFile(*scene, output_gltf_filename).ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + // glTF file should have four occurences of "KHR_materials_unlit". Two in the + // materials and one in extensionsUsed and one in extensionsRequired. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "KHR_materials_unlit", + 4); +} + +TEST_F(GltfEncoderTest, OneMaterialUnlitWithFallback) { + const std::string filename = + "UnlitWithFallback/one_material_all_fallback/" + "one_material_all_fallback.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene, nullptr); + + const std::string output_gltf_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(output_gltf_filename, &output_gltf_dir, + &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeFile(*scene, output_gltf_filename).ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + + // glTF file should have two occurences of "KHR_materials_unlit". One in the + // materials and one in extensionsUsed. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "KHR_materials_unlit", + 2); + + // The glTF file should provide a fallback to "KHR_materials_unlit", so there + // should be no "extensionsRequired" element. + CheckGltfFileNoString(output_gltf_filename, "extensionsRequired"); +} + +TEST_F(GltfEncoderTest, MultipleMaterialsUnlitWithFallback) { + std::string filename = + "UnlitWithFallback/three_materials_all_fallback/" + "three_materials_all_fallback.gltf"; + const std::unique_ptr scene_all_fallback( + DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene_all_fallback, nullptr); + + const std::string output_gltf_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(output_gltf_filename, &output_gltf_dir, + &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE( + gltf_encoder.EncodeFile(*scene_all_fallback, output_gltf_filename) + .ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + + // glTF file should have four occurences of "KHR_materials_unlit". Three in + // the materials and one in extensionsUsed. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "KHR_materials_unlit", + 4); + + // The glTF file should provide a fallback to "KHR_materials_unlit", so there + // should be no "extensionsRequired" element. + CheckGltfFileNoString(output_gltf_filename, "extensionsRequired"); + + filename = + "UnlitWithFallback/three_materials_one_fallback/" + "three_materials_one_fallback.gltf"; + const std::unique_ptr scene_one_fallback( + DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene_one_fallback, nullptr); + + ASSERT_TRUE( + gltf_encoder.EncodeFile(*scene_one_fallback, output_gltf_filename) + .ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + + // glTF file should have three occurences of "KHR_materials_unlit". One in the + // materials, one in extensionsUsed, and one in extensionsRequired. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "KHR_materials_unlit", + 3); + + // The glTF file only has one material with a fallback for + // "KHR_materials_unlit". The other two materials have "KHR_materials_unlit" + // set without a fallback, so there should be an "extensionsRequired" element. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "extensionsRequired", + 1); +} + +TEST_F(GltfEncoderTest, KhrMaterialsSheenExtension) { + const std::string filename = + "KhronosSampleModels/SheenCloth/glTF/SheenCloth.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene, nullptr); + + const std::string out_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(out_filename, &output_gltf_dir, &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeFile(*scene, out_filename).ok()) + << "Failed to encode glTF filename:" << out_filename; + + // The "KHR_materials_sheen" should be in material and in extensionsUsed. + CheckGltfFileAtLeastStringCount(out_filename, "KHR_materials_sheen", 2); + CheckGltfFileAtLeastStringCount(out_filename, "sheenColorFactor", 1); + CheckGltfFileAtLeastStringCount(out_filename, "sheenColorTexture", 1); + CheckGltfFileAtLeastStringCount(out_filename, "sheenRoughnessFactor", 1); + CheckGltfFileAtLeastStringCount(out_filename, "sheenRoughnessTexture", 1); +} + +TEST_F(GltfEncoderTest, PbrNextExtensions) { + // Check that a model with PBR material extensions is encoded correctly. This + // is done by encoding an original model with all PBR material extension + // properties and textures, then decoding it and checking that it matches the + // original model. + // TODO(vytyaz): Test multiple materials with various sets of extensions. + + // Read the original model. + const std::string orig_name = "pbr_next/sphere/glTF/sphere.gltf"; + const std::unique_ptr original(DecodeTestGltfFileToScene(orig_name)); + ASSERT_NE(original, nullptr); + const Material &original_mat = *original->GetMaterialLibrary().GetMaterial(0); + + // Check that the original material has PBR extensions. + EXPECT_TRUE(original_mat.HasSheen()); + EXPECT_TRUE(original_mat.HasTransmission()); + EXPECT_TRUE(original_mat.HasClearcoat()); + EXPECT_TRUE(original_mat.HasVolume()); + EXPECT_TRUE(original_mat.HasIor()); + EXPECT_TRUE(original_mat.HasSpecular()); + + // Write the original model to a temporary file. + GltfEncoder encoder; + const std::string tmp_name = draco::GetTestTempFileFullPath("tmp.gltf"); + DRACO_ASSERT_OK(encoder.EncodeFile(*original, tmp_name)); + + // Read model from the temporay file. + GltfDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(auto encoded, decoder.DecodeFromFileToScene(tmp_name)); + ASSERT_NE(encoded, nullptr); +} + +TEST_F(GltfEncoderTest, KhrTextureTransformWithoutFallback) { + // This is the example from Khronos, which should have "KHR_texture_transform" + // listed in the extensionsRequired, but does not for testing out client + // implementations. + const std::string filename = + "KhronosSampleModels/TextureTransformTest/glTF/TextureTransformTest.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene, nullptr); + + const std::string output_gltf_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(output_gltf_filename, &output_gltf_dir, + &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeFile(*scene, output_gltf_filename).ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + // glTF file should have eight occurences of "KHR_materials_unlit". Six in the + // materials and one in extensionsUsed and one in extensionsRequired. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "KHR_texture_transform", + 8); + + // glTF file should still contain only two occurences of '"sampler": 0'. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "\"sampler\": 0", 2); + + // glTF file should have one occurence of "wrapS", "wrapT", "minFilter", and + // "magFilter". + CheckGltfFileAtLeastStringCount(output_gltf_filename, "wrapS", 1); + CheckGltfFileAtLeastStringCount(output_gltf_filename, "wrapT", 1); + CheckGltfFileAtLeastStringCount(output_gltf_filename, "minFilter", 1); + CheckGltfFileAtLeastStringCount(output_gltf_filename, "magFilter", 1); +} + +TEST_F(GltfEncoderTest, KhrTextureTransformWithoutFallbackRequried) { + // This is the example from Khronos, changed to list "KHR_texture_transform" + // in extensionsRequired. + const std::string filename = + "glTF/TextureTransformTestWithRequired/" + "TextureTransformTestWithRequired.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene, nullptr); + + const std::string output_gltf_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(output_gltf_filename, &output_gltf_dir, + &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeFile(*scene, output_gltf_filename).ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + // glTF file should have eight occurences of "KHR_materials_unlit". Six in the + // materials and one in extensionsUsed and one in extensionsRequired. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "KHR_texture_transform", + 8); +} + +TEST_F(GltfEncoderTest, KhrTextureTransformWithFallback) { + // This is an example of "KHR_texture_transform" extension with fallback data. + const std::string filename = + "glTF/KhrTextureTransformWithFallback/" + "KhrTextureTransformWithFallback.gltf"; + const std::unique_ptr scene(DecodeTestGltfFileToScene(filename)); + ASSERT_NE(scene, nullptr); + + const std::string output_gltf_filename = + draco::GetTestTempFileFullPath("encoded_example.gltf"); + std::string output_gltf_dir; + std::string output_gltf_basename; + draco::SplitPath(output_gltf_filename, &output_gltf_dir, + &output_gltf_basename); + + GltfEncoder gltf_encoder; + ASSERT_TRUE(gltf_encoder.EncodeFile(*scene, output_gltf_filename).ok()) + << "Failed to encode glTF filename:" << output_gltf_filename; + // glTF file should have two occurences of "KHR_materials_unlit". One in the + // materials and one in extensionsUsed. + CheckGltfFileAtLeastStringCount(output_gltf_filename, "KHR_texture_transform", + 2); +} + +// Tests if the source file has a node with an identity matrix, that we do not +// output the identiy matrix. +TEST_F(GltfEncoderTest, MeshWithIdentityTransformation) { + const std::string gltf_source_full_path = + GetTestFileFullPath("Triangle/glTF/Triangle_identity_matrix.gltf"); + + // Check that the source file contains one "matrix" and no "translation" + // strings. + CheckGltfFileAtLeastStringCount(gltf_source_full_path, "matrix", 1); + CheckGltfFileNoString(gltf_source_full_path, "translation"); + + std::unique_ptr scene = draco::ReadSceneFromTestFile( + "Triangle/glTF/Triangle_identity_matrix.gltf"); + ASSERT_NE(scene, nullptr); + SceneNode *scene_node = scene->GetNode(SceneNodeIndex(0)); + ASSERT_NE(scene_node, nullptr); + const TrsMatrix &trs_matrix = scene_node->GetTrsMatrix(); + + // gltf_decoder will not set the trs matrix if the matrix is identity. + ASSERT_FALSE(trs_matrix.MatrixSet()); + + // Add the identity matrix. + TrsMatrix trsm; + trsm.SetMatrix(Eigen::Matrix4d::Identity()); + scene_node->SetTrsMatrix(trsm); + + const TrsMatrix &check_trs_matrix = scene_node->GetTrsMatrix(); + ASSERT_TRUE(check_trs_matrix.MatrixSet()); + ASSERT_EQ(check_trs_matrix.IsMatrixIdentity(), true); + + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("MeshWithIdentityTransformation.gltf"); + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + + ASSERT_TRUE(gltf_encoder.EncodeToFile( + *scene.get(), gltf_file_full_path, folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + std::unique_ptr scene_gltf = + std::move(ReadSceneFromFile(gltf_file_full_path)).value(); + ASSERT_NE(scene_gltf, nullptr); + // Check that the output file contains no "matrix" or "translation" strings. + CheckGltfFileNoString(gltf_file_full_path, "matrix"); + CheckGltfFileNoString(gltf_file_full_path, "translation"); +} + +// Tests if the source file has a node with a matrix that only has the +// translation values set. If it does then instead of outputting the full matrix +// we only output the "translation" glTF element. +TEST_F(GltfEncoderTest, MeshWithTranslationOnlyMatrix) { + std::unique_ptr scene = draco::ReadSceneFromTestFile( + "Triangle/glTF/Triangle_translation_only_matrix.gltf"); + ASSERT_NE(scene, nullptr); + SceneNode *scene_node = scene->GetNode(SceneNodeIndex(0)); + ASSERT_NE(scene_node, nullptr); + const TrsMatrix &input_trs_matrix = scene_node->GetTrsMatrix(); + ASSERT_TRUE(input_trs_matrix.MatrixSet()); + ASSERT_FALSE(input_trs_matrix.TranslationSet()); + ASSERT_FALSE(input_trs_matrix.RotationSet()); + ASSERT_FALSE(input_trs_matrix.ScaleSet()); + ASSERT_TRUE(input_trs_matrix.IsMatrixTranslationOnly()); + + const std::string gltf_file_full_path = + draco::GetTestTempFileFullPath("MeshWithTranslationOnlyMatrix.gltf"); + std::string folder_path; + std::string gltf_file_name; + draco::SplitPath(gltf_file_full_path, &folder_path, &gltf_file_name); + GltfEncoder gltf_encoder; + + ASSERT_TRUE(gltf_encoder.EncodeToFile( + *scene.get(), gltf_file_full_path, folder_path)) + << "Failed gltf_file_full_path:" << gltf_file_full_path + << " folder_path:" << folder_path; + std::unique_ptr scene_gltf = + std::move(ReadSceneFromFile(gltf_file_full_path)).value(); + ASSERT_NE(scene_gltf, nullptr); + SceneNode *output_scene_node = scene_gltf->GetNode(SceneNodeIndex(0)); + ASSERT_NE(output_scene_node, nullptr); + const TrsMatrix &output_trs_matrix = output_scene_node->GetTrsMatrix(); + ASSERT_FALSE(output_trs_matrix.MatrixSet()); + ASSERT_TRUE(output_trs_matrix.TranslationSet()); + ASSERT_FALSE(output_trs_matrix.RotationSet()); + ASSERT_FALSE(output_trs_matrix.ScaleSet()); +} + +// Tests that a scene can be encoded to buffer in GLB format. +TEST_F(GltfEncoderTest, EncodeToBuffer) { + // Load scene from file. + const std::string file_name = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + const std::unique_ptr scene = ReadSceneFromTestFile(file_name); + ASSERT_NE(scene, nullptr); + + // Encode scene to buffer in GLB format. + GltfEncoder encoder; + EncoderBuffer buffer; + DRACO_ASSERT_OK(encoder.EncodeToBuffer(*scene, &buffer)); + ASSERT_NE(buffer.size(), 0); + + // Write scene to file in GLB format. + const std::string glb_file_path = draco::GetTestTempFileFullPath("temp.glb"); + std::string folder_path; + std::string glb_file_name; + draco::SplitPath(glb_file_path, &folder_path, &glb_file_name); + encoder.EncodeToFile(*scene, glb_file_path, folder_path); + + // Check that the buffer contents match the GLB file contents. + ASSERT_EQ(buffer.size(), draco::GetFileSize(glb_file_path)); + std::vector file_data; + ASSERT_TRUE(ReadFileToBuffer(glb_file_path, &file_data)); + ASSERT_EQ(std::memcmp(file_data.data(), buffer.data(), buffer.size()), 0); +} + +// Tests that a scene with lights can be encoded into a file. +TEST_F(GltfEncoderTest, EncodeLights) { + const std::string file_name = "sphere_lights.gltf"; + const std::unique_ptr scene = ReadSceneFromTestFile(file_name); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumLights(), 4); + EncodeSceneToGltfAndCompare(scene.get()); +} + +// Helper method for adding mesh group GPU instancing to the milk truck scene. +draco::Status AddGpuInstancingToMilkTruck(draco::Scene *scene) { + // Create an instance and set its transformation TRS vectors. + draco::InstanceArray::Instance instance_0; + instance_0.trs.SetTranslation(Eigen::Vector3d(-0.2, 0.0, 0.0)); + instance_0.trs.SetScale(Eigen::Vector3d(1.0, 1.0, 1.0)); + + // Create another instance. + draco::InstanceArray::Instance instance_1; + instance_1.trs.SetTranslation(Eigen::Vector3d(1.0, 0.0, 0.0)); + instance_1.trs.SetScale(Eigen::Vector3d(2.0, 2.0, 2.0)); + + // Add an empty GPU instancing object to the scene. + const draco::InstanceArrayIndex index = scene->AddInstanceArray(); + draco::InstanceArray *gpu_instancing = scene->GetInstanceArray(index); + + // Add two instances to the GPU instancing object stored in the scene. + DRACO_RETURN_IF_ERROR(gpu_instancing->AddInstance(instance_0)); + DRACO_RETURN_IF_ERROR(gpu_instancing->AddInstance(instance_1)); + + // Assign the GPU instancing object to two mesh groups in two scene nodes. + scene->GetNode(draco::SceneNodeIndex(2))->SetInstanceArrayIndex(index); + scene->GetNode(draco::SceneNodeIndex(4))->SetInstanceArrayIndex(index); + + return draco::OkStatus(); +} + +// Tests that a scene with instance arrays can be encoded into a file. Decoder +// has no GPU instancing support, so we will compare encoded file to a golden +// file. +TEST_F(GltfEncoderTest, EncodeInstanceArrays) { + // Read the milk truck. + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + + // Add GPU instancing to the scene for testing. + DRACO_ASSERT_OK(AddGpuInstancingToMilkTruck(scene.get())); + ASSERT_EQ(scene->NumInstanceArrays(), 1); + ASSERT_EQ(scene->NumNodes(), 5); + + // Prepare file paths. + const std::string temp_path = draco::GetTestTempFileFullPath("Truck.glb"); + const std::string golden_path = + GetTestFileFullPath("CesiumRowingTruckWithGpuInstancing.glb"); + + // Encode scene to a temporary file in GLB format. + std::string folder; + std::string name; + draco::SplitPath(temp_path, &folder, &name); + GltfEncoder encoder; + ASSERT_TRUE(encoder.EncodeToFile(*scene, temp_path, folder)) + << "Failed to encode to temporary file:" << temp_path; + + // Read encoded file to buffer. + std::vector encoded_data; + ASSERT_TRUE(ReadFileToBuffer(temp_path, &encoded_data)); +} + +// Tests that a scene with materials variants can be encoded into a file. +TEST_F(GltfEncoderTest, EncodeMaterialsVariants) { + const std::string file_name = + "KhronosSampleModels/DragonAttenuation/glTF/DragonAttenuation.gltf"; + const std::unique_ptr scene = ReadSceneFromTestFile(file_name); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterialsVariants(), 2); + EncodeSceneToGltfAndCompare(scene.get()); +} + +// Tests encoding of draco::Scene to glTF with various mesh feature ID sets and +// structural metadata property table. +TEST_F(GltfEncoderTest, EncodeSceneWithMeshFeaturesWithStructuralMetadata) { + const std::string file_name = "BoxMeta/glTF/BoxMeta.gltf"; + constexpr bool kHasMeshFeatures = true; + constexpr bool kHasStructuralMetadata = true; + constexpr bool kHasDracoCompression = false; + + // Read test file from file. + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + // Encode the scene to glTF and decode it back to draco::Scene and check. + std::unique_ptr scene_from_gltf; + SceneToDecodedGltfScene(*scene, &scene_from_gltf); + ASSERT_NE(scene_from_gltf, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*scene_from_gltf, + kHasDracoCompression); + GltfTestHelper::CheckBoxMetaStructuralMetadata(*scene_from_gltf); +} + +// Tests encoding of draco::Scene with Draco compression to glTF with various +// mesh feature ID sets. +TEST_F(GltfEncoderTest, EncodeSceneWithMeshFeaturesWithDracoCompression) { + const std::string file_name = "BoxMetaDraco/glTF/BoxMetaDraco.gltf"; + constexpr bool kHasMeshFeatures = true; + constexpr bool kHasStructuralMetadata = false; + constexpr bool kHasDracoCompression = true; + + // Read test file from file. + const std::unique_ptr scene(DecodeTestGltfFileToScene(file_name)); + ASSERT_NE(scene, nullptr); + + // Encode the scene to glTF and decode it back to draco::Scene and check. + std::unique_ptr scene_from_gltf; + SceneToDecodedGltfScene(*scene, &scene_from_gltf); + ASSERT_NE(scene_from_gltf, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*scene_from_gltf, + kHasDracoCompression); +} + +// Tests encoding of draco::Mesh to glTF with various mesh feature ID sets and +// structural metadata property table. +TEST_F(GltfEncoderTest, EncodeMeshWithMeshFeaturesWithStructuralMetadata) { + const std::string file_name = "BoxMeta/glTF/BoxMeta.gltf"; + constexpr bool kHasDracoCompression = false; + + // Read test file from file. + const std::unique_ptr mesh(ReadMeshFromTestFile(file_name)); + ASSERT_NE(mesh, nullptr); + + // Encode the scene to glTF and decode it back to draco::Mesh and check. + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + ASSERT_NE(mesh_from_gltf, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*mesh_from_gltf, + kHasDracoCompression); + GltfTestHelper::CheckBoxMetaStructuralMetadata(*mesh_from_gltf); +} + +// Tests encoding of draco::Mesh with Draco compression to glTF with various +// mesh feature ID sets. +TEST_F(GltfEncoderTest, EncodeMeshWithMeshFeaturesWithDracoCompression) { + constexpr bool kHasDracoCompression = true; + const std::string file_name = "BoxMetaDraco/glTF/BoxMetaDraco.gltf"; + + // Read test file from file. + const std::unique_ptr mesh(ReadMeshFromTestFile(file_name)); + ASSERT_NE(mesh, nullptr); + + // Encode the scene to glTF and decode it back to draco::Mesh and check. + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + ASSERT_NE(mesh_from_gltf, nullptr); + GltfTestHelper::CheckBoxMetaMeshFeatures(*mesh_from_gltf, + kHasDracoCompression); +} + +// Tests encoding of draco::Mesh with mesh features associated with different +// mesh primitives. +TEST_F(GltfEncoderTest, EncodeMeshWithMeshFeaturesWithMultiplePrimitives) { + const std::string file_name = "BoxesMeta/glTF/BoxesMeta.gltf"; + + // Read test file from file. + const std::unique_ptr mesh(ReadMeshFromTestFile(file_name)); + ASSERT_NE(mesh, nullptr); + // All mesh features should share two textures. + ASSERT_EQ(mesh->GetNonMaterialTextureLibrary().NumTextures(), 2); + + // Encode the scene to glTF and decode it back to draco::Mesh and check. + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + ASSERT_NE(mesh_from_gltf, nullptr); + + ASSERT_EQ(mesh_from_gltf->GetMaterialLibrary().NumMaterials(), 2); + ASSERT_EQ(mesh_from_gltf->NumMeshFeatures(), 5); + + // First two mesh features should be used by material 0 and the reamining by + // material 1. + for (draco::MeshFeaturesIndex mfi(0); mfi < 5; ++mfi) { + // Each mesh feature should be used by a single material. + ASSERT_EQ(mesh_from_gltf->NumMeshFeaturesMaterialMasks(mfi), 1); + if (mfi.value() < 2) { + ASSERT_EQ(mesh_from_gltf->GetMeshFeaturesMaterialMask(mfi, 0), 0); + } else { + ASSERT_EQ(mesh_from_gltf->GetMeshFeaturesMaterialMask(mfi, 0), 1); + } + } + // All mesh features should share two textures. + ASSERT_EQ(mesh_from_gltf->GetNonMaterialTextureLibrary().NumTextures(), 2); + + // Ensure it still works correctly when we re-encode the source |mesh| as a + // scene. + std::unique_ptr scene_from_gltf; + MeshToDecodedGltfScene(*mesh, &scene_from_gltf); + ASSERT_NE(scene_from_gltf, nullptr); + + ASSERT_EQ(scene_from_gltf->NumMeshes(), 2); + + // First mesh should have 2 mesh features and the other one 3 mesh features. + ASSERT_EQ(scene_from_gltf->GetMesh(draco::MeshIndex(0)).NumMeshFeatures(), 2); + ASSERT_EQ(scene_from_gltf->GetMesh(draco::MeshIndex(1)).NumMeshFeatures(), 3); + + // All mesh features should share two textures. + ASSERT_EQ(scene_from_gltf->GetNonMaterialTextureLibrary().NumTextures(), 2); +} + +// Tests encoding of draco::Mesh containing a point cloud and two materials. +TEST_F(GltfEncoderTest, EncodePointCloudWithMaterials) { + const std::string file_name = + "SphereTwoMaterials/sphere_two_materials_point_cloud.gltf"; + + // Read test file from file. + const std::unique_ptr mesh(ReadMeshFromTestFile(file_name)); + ASSERT_NE(mesh, nullptr); + + // Input should have no faces. + ASSERT_EQ(mesh->num_faces(), 0); + + // There should be two materials + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 2); + + // Encode the mesh to glTF and decode it back to draco::Mesh and check. + std::unique_ptr mesh_from_gltf; + MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf); + ASSERT_NE(mesh_from_gltf, nullptr); + + ASSERT_EQ(mesh_from_gltf->num_faces(), 0); + ASSERT_EQ(mesh_from_gltf->GetMaterialLibrary().NumMaterials(), 2); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/io/gltf_test_helper.cc b/contrib/draco/src/draco/io/gltf_test_helper.cc new file mode 100644 index 000000000..13cce6f4e --- /dev/null +++ b/contrib/draco/src/draco/io/gltf_test_helper.cc @@ -0,0 +1,823 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/gltf_test_helper.h" + +#include +#include +#include +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/metadata/property_table.h" +#include "draco/texture/texture_library.h" + +namespace draco { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +void GltfTestHelper::AddBoxMetaMeshFeatures(Scene *scene) { + // Check the scene. + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 1); + TextureLibrary &texture_library = scene->GetNonMaterialTextureLibrary(); + ASSERT_EQ(texture_library.NumTextures(), 0); + + // Check the mesh. + Mesh &mesh = scene->GetMesh(MeshIndex(0)); + ASSERT_EQ(mesh.num_faces(), 12); + ASSERT_EQ(mesh.num_attributes(), 2); + ASSERT_EQ(mesh.num_points(), 24); + + // Get mesh element counts. + const int num_faces = mesh.num_faces(); + const int num_corners = 3 * mesh.num_faces(); + const int num_vertices = + mesh.GetNamedAttribute(GeometryAttribute::POSITION)->size(); + + // Add feature ID set with per-face Uint8 attribute named _FEATURE_ID_0. + { + // Create feature ID attribute. + constexpr DataType kType = DataType::DT_UINT8; + std::unique_ptr pa(new PointAttribute()); + pa->Init(GeometryAttribute::GENERIC, 1, kType, false, mesh.num_faces()); + for (AttributeValueIndex avi(0); avi < num_faces; ++avi) { + const int8_t val = avi.value(); + pa->SetAttributeValue(avi, &val); + } + const int att_id = mesh.AddPerFaceAttribute(std::move(pa)); + std::unique_ptr metadata(new AttributeMetadata()); + metadata->AddEntryString("attribute_name", "_FEATURE_ID_0"); + mesh.AddAttributeMetadata(att_id, std::move(metadata)); + + // Add feature ID set to the mesh. + std::unique_ptr features(new MeshFeatures()); + features->SetLabel("faces"); + features->SetFeatureCount(num_faces); + features->SetNullFeatureId(100); + features->SetPropertyTableIndex(0); + features->SetAttributeIndex(0); + mesh.AddMeshFeatures(std::move(features)); + } + + // Add feature ID set with per-vertex Uint16 attribute named _FEATURE_ID_1. + { + // Create feature ID attribute. + constexpr DataType kType = DataType::DT_UINT16; + std::unique_ptr pa(new PointAttribute()); + pa->Init(GeometryAttribute::GENERIC, 1, kType, false, num_vertices); + for (AttributeValueIndex avi(0); avi < num_vertices; ++avi) { + const uint16_t val = avi.value(); + pa->SetAttributeValue(avi, &val); + } + const int att_id = mesh.AddPerVertexAttribute(std::move(pa)); + std::unique_ptr metadata(new AttributeMetadata()); + metadata->AddEntryString("attribute_name", "_FEATURE_ID_1"); + mesh.AddAttributeMetadata(att_id, std::move(metadata)); + + // Add feature ID set to the mesh. + std::unique_ptr features(new MeshFeatures()); + features->SetLabel("vertices"); + features->SetFeatureCount(num_vertices); + features->SetNullFeatureId(101); + features->SetPropertyTableIndex(1); + features->SetAttributeIndex(1); + mesh.AddMeshFeatures(std::move(features)); + } + + // Add feature ID set with per-corner Float attribute named _FEATURE_ID_2. + { + // Create feature ID attribute. + constexpr DataType kType = DataType::DT_FLOAT32; + std::unique_ptr pa(new PointAttribute()); + pa->Init(GeometryAttribute::GENERIC, 1, kType, false, num_corners); + IndexTypeVector corner_to_value( + num_corners); + for (AttributeValueIndex avi(0); avi < num_corners; ++avi) { + const float val = avi.value(); + pa->SetAttributeValue(avi, &val); + corner_to_value[CornerIndex(avi.value())] = avi; + } + const int att_id = + mesh.AddAttributeWithConnectivity(std::move(pa), corner_to_value); + std::unique_ptr metadata(new AttributeMetadata()); + metadata->AddEntryString("attribute_name", "_FEATURE_ID_2"); + mesh.AddAttributeMetadata(att_id, std::move(metadata)); + + // Add feature ID set to the mesh. + std::unique_ptr features(new MeshFeatures()); + features->SetFeatureCount(num_corners); + features->SetAttributeIndex(2); + mesh.AddMeshFeatures(std::move(features)); + } + + // Add feature ID set with the IDs stored in the R texture channel and + // accessible via the first texture coordinate attribute. + { + // Add the first texture coordinate attribute. + constexpr DataType kType = DataType::DT_FLOAT32; + std::unique_ptr pa(new PointAttribute()); + pa->Init(GeometryAttribute::TEX_COORD, 2, kType, false, num_vertices); + std::vector> uv = { + {0.0000f, 0.0000f}, {0.0000f, 0.5000f}, {0.0000f, 1.0000f}, + {0.5000f, 0.0000f}, {0.5000f, 0.5000f}, {0.5000f, 1.0000f}, + {1.0000f, 0.0000f}, {1.0000f, 0.5000f}}; + for (AttributeValueIndex avi(0); avi < num_vertices; ++avi) { + const int index = avi.value(); + pa->SetAttributeValue(avi, uv[index].data()); + } + mesh.AddPerVertexAttribute(std::move(pa)); + } + + // Add feature ID set with the IDs stored in the GBA texture channels and + // accessible via the second texture coordinate attribute. + { + // Add the second texture coordinate attribute. + constexpr DataType kType = DataType::DT_FLOAT32; + std::unique_ptr pa(new PointAttribute()); + pa->Init(GeometryAttribute::TEX_COORD, 2, kType, false, num_vertices); + std::vector> uv = { + {0.0000f, 0.0000f}, {0.0000f, 0.5000f}, {0.0000f, 1.0000f}, + {0.5000f, 0.0000f}, {0.5000f, 0.5000f}, {0.5000f, 1.0000f}, + {1.0000f, 0.0000f}, {1.0000f, 0.5000f}}; + for (AttributeValueIndex avi(0); avi < num_vertices; ++avi) { + const int index = avi.value(); + pa->SetAttributeValue(avi, uv[index].data()); + } + mesh.AddPerVertexAttribute(std::move(pa)); + ASSERT_EQ(mesh.NumNamedAttributes(GeometryAttribute::TEX_COORD), 2); + } +} + +void GltfTestHelper::AddBoxMetaStructuralMetadata(Scene *scene) { + // Add structural metadata property table schema in the following JSON: + // "schema": { + // "id": "galaxy", + // "classes": { + // "planet": { + // "properties": { + // "color": { + // "componentType": "UINT8", + // "description": "The RGB color.", + // "required": true, + // "type": "VEC3" + // }, + // "name": { + // "description": "The name.", + // "required": true, + // "type": "STRING" + // } + // "sequence": { + // "description": "The number sequence.", + // "required": false, + // "type": "SCALAR" + // } + // } + // } + // }, + // "enums": { + // "classifications": { + // "description": "Classifications of planets.", + // "name": "classifications", + // "values": [ + // { "name": "Unspecified", "value": 0 }, + // { "name": "Gas Giant", "value": 1 }, + // { "name": "Waterworld", "value": 2 }, + // { "name": "Agriworld", "value": 3 }, + // { "name": "Ordnance", "value": 4 } + // ] + // } + // } + // } + typedef PropertyTable::Schema::Object Object; + PropertyTable::Schema schema; + Object &json = schema.json; + json.SetObjects().emplace_back("id", "galaxy"); + json.SetObjects().emplace_back("classes"); + json.SetObjects().back().SetObjects().emplace_back("planet"); + Object &planet = json.SetObjects().back().SetObjects().back(); + planet.SetObjects().emplace_back("properties"); + Object &properties = planet.SetObjects().back(); + + properties.SetObjects().emplace_back("color"); + Object &color = properties.SetObjects().back(); + color.SetObjects().emplace_back("componentType", "UINT8"); + color.SetObjects().emplace_back("description", "The RGB color."); + color.SetObjects().emplace_back("required", true); + color.SetObjects().emplace_back("type", "VEC3"); + + properties.SetObjects().emplace_back("name"); + Object &name = properties.SetObjects().back(); + name.SetObjects().emplace_back("description", "The name."); + name.SetObjects().emplace_back("required", true); + name.SetObjects().emplace_back("type", "STRING"); + + properties.SetObjects().emplace_back("sequence"); + Object &sequence = properties.SetObjects().back(); + sequence.SetObjects().emplace_back("description", "The number sequence."); + sequence.SetObjects().emplace_back("required", false); + sequence.SetObjects().emplace_back("type", "SCALAR"); + + json.SetObjects().emplace_back("enums"); + json.SetObjects().back().SetObjects().emplace_back("classifications"); + Object &classifications = json.SetObjects().back().SetObjects().back(); + classifications.SetObjects().emplace_back("description", + "Classifications of planets."); + classifications.SetObjects().emplace_back("name", "classifications"); + classifications.SetObjects().emplace_back("values"); + Object &values = classifications.SetObjects().back(); + + values.SetArray().emplace_back(); + values.SetArray().back().SetObjects().emplace_back("name", "Unspecified"); + values.SetArray().back().SetObjects().emplace_back("value", 0); + + values.SetArray().emplace_back(); + values.SetArray().back().SetObjects().emplace_back("name", "Gas Giant"); + values.SetArray().back().SetObjects().emplace_back("value", 1); + + values.SetArray().emplace_back(); + values.SetArray().back().SetObjects().emplace_back("name", "Waterworld"); + values.SetArray().back().SetObjects().emplace_back("value", 2); + + values.SetArray().emplace_back(); + values.SetArray().back().SetObjects().emplace_back("name", "Agriworld"); + values.SetArray().back().SetObjects().emplace_back("value", 3); + + values.SetArray().emplace_back(); + values.SetArray().back().SetObjects().emplace_back("name", "Ordnance"); + values.SetArray().back().SetObjects().emplace_back("value", 4); + + // Add property table schema to the scene. + scene->GetStructuralMetadata().SetPropertyTableSchema(schema); + + // Add structural metadata property table. + std::unique_ptr table(new PropertyTable()); + table->SetName("Galaxy far far away."); + table->SetClass("planet"); + table->SetCount(16); + + // Add property describing RGB color components of the planet class. + { + std::unique_ptr property( + new PropertyTable::Property()); + property->SetName("color"); + property->GetData().target = 34962; // ARRAY_BUFFER. + property->GetData().data = {94, 94, 194, // Tatooine + 94, 145, 161, // Corusant + 118, 171, 91, // Naboo + 103, 139, 178, // Alderaan + 83, 98, 154, // Dagobah + 91, 177, 175, // Mandalore + 190, 92, 108, // Corellia + 72, 69, 169, // Kamino + 154, 90, 101, // Kashyyyk + 174, 85, 175, // Dantooine + 184, 129, 96, // Hoth + 185, 91, 180, // Mustafar + 194, 150, 83, // Bespin + 204, 111, 134, // Yavin + 182, 90, 89, // Geonosis + 0, 0, 0}; // UNLABELED + table->AddProperty(std::move(property)); + } + + // Add property that describes names of the planet class. + { + std::unique_ptr property( + new PropertyTable::Property()); + property->SetName("name"); + property->GetData().target = 34963; // ELEMENT_ARRAY_BUFFER. + const std::string data = + "named_class:Tatooine" + "named_class:Corusant" + "named_class:Naboo" + "named_class:Alderaan" + "named_class:Dagobah" + "named_class:Mandalore" + "named_class:Corellia" + "named_class:Kamino" + "named_class:Kashyyyk" + "named_class:Dantooine" + "named_class:Hoth" + "named_class:Mustafar" + "named_class:Bespin" + "named_class:Yavin" + "named_class:Geonosis" + "UNLABELED"; + property->GetData().data.assign(data.begin(), data.end()); + property->GetStringOffsets().type = "UINT32"; + property->GetStringOffsets().data.target = 34963; // ELEMENT_ARRAY_BUFFER. + property->GetStringOffsets().data.data = {0, 0, 0, 0, // Tatooine + 20, 0, 0, 0, // Corusant + 40, 0, 0, 0, // Naboo + 57, 0, 0, 0, // Alderaan + 77, 0, 0, 0, // Dagobah + 96, 0, 0, 0, // Mandalore + 117, 0, 0, 0, // Corellia + 137, 0, 0, 0, // Kamino + 155, 0, 0, 0, // Kashyyyk + 175, 0, 0, 0, // Dantooine + 196, 0, 0, 0, // Hoth + 212, 0, 0, 0, // Mustafar + 232, 0, 0, 0, // Bespin + 250, 0, 0, 0, // Yavin + 12, 1, 0, 0, // Geonosis + 32, 1, 0, 0, // UNLABELED + 41, 1, 0, 0}; + table->AddProperty(std::move(property)); + } + + // Add property that contains variable-length number sequence of the planet + // class. + { + std::unique_ptr property( + new PropertyTable::Property()); + property->SetName("sequence"); + property->GetData().target = 34963; // ELEMENT_ARRAY_BUFFER. + const std::vector data = { + 0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f, // Tatooine + 6.5f, 7.5f, // Corusant + 8.5f, // Naboo + 9.5f, // Alderaan + 10.5f, 11.5f, // Dagobah + 12.5f, 13.5f, 14.5f, 15.5f, // Mandalore + 16.5f, 17.5f, // Corellia + 18.5f, 19.5f, // Kamino + 20.5f, 21.5f, 22.5f, // Kashyyyk + 23.5f, 24.5f, 25.5f, // Dantooine + 26.5f, 27.5f, // Hoth + 28.5f, 29.5f, // Mustafar + 30.5f, 31.5f, 32.5f, // Bespin + 33.5f, 34.5f, 35.5f, // Yavin + 36.5f, 37.5f, 38.5f, 39.5f, 40.5f // Geonosis + }; // UNLABELED (empty array). + property->GetData().data.resize(4 * data.size()); + memcpy(property->GetData().data.data(), data.data(), 4 * data.size()); + property->GetArrayOffsets().type = "UINT8"; + property->GetArrayOffsets().data.target = 34963; // ELEMENT_ARRAY_BUFFER. + property->GetArrayOffsets().data.data = { + 0 * 4, // Tatooine + 6 * 4, // Corusant + 8 * 4, // Naboo + 9 * 4, // Alderaan + 10 * 4, // Dagobah + 12 * 4, // Mandalore + 16 * 4, // Corellia + 18 * 4, // Kamino + 20 * 4, // Kashyyyk + 23 * 4, // Dantooine + 26 * 4, // Hoth + 28 * 4, // Mustafar + 30 * 4, // Bespin + 33 * 4, // Yavin + 36 * 4, // Geonosis + 41 * 4, // UNLABELED (empty array). + 41 * 4}; + table->AddProperty(std::move(property)); + } + + // Add property table to the scene. + scene->GetStructuralMetadata().AddPropertyTable(std::move(table)); +} + +template <> +void GltfTestHelper::CheckBoxMetaMeshFeatures(const Mesh &geometry, + bool has_draco_compression) { + CheckBoxMetaMeshFeatures(geometry, geometry.GetNonMaterialTextureLibrary(), + has_draco_compression); +} + +template <> +void GltfTestHelper::CheckBoxMetaMeshFeatures(const Scene &geometry, + bool has_draco_compression) { + ASSERT_EQ(geometry.NumMeshes(), 1); + CheckBoxMetaMeshFeatures(geometry.GetMesh(MeshIndex(0)), + geometry.GetNonMaterialTextureLibrary(), + has_draco_compression); +} + +void GltfTestHelper::CheckBoxMetaMeshFeatures(const Mesh &mesh, + const TextureLibrary &texture_lib, + bool has_draco_compression) { + // Check texture library. + ASSERT_EQ(texture_lib.NumTextures(), 2); + + // Check basic mesh properties. + ASSERT_EQ(mesh.NumMeshFeatures(), 5); + ASSERT_EQ(mesh.num_faces(), 12); + ASSERT_EQ(mesh.num_attributes(), 7); + ASSERT_EQ(mesh.num_points(), 36); + ASSERT_EQ(mesh.NumNamedAttributes(GeometryAttribute::GENERIC), 3); + ASSERT_EQ(mesh.NumNamedAttributes(GeometryAttribute::TEX_COORD), 2); + + // Get mesh element counts. + const int num_faces = mesh.num_faces(); + const int num_corners = 3 * mesh.num_faces(); + const int num_vertices = + mesh.GetNamedAttribute(GeometryAttribute::POSITION)->size(); + + // Check mesh feature ID set at index 0. + { + // Check mesh features. + const MeshFeatures &features = mesh.GetMeshFeatures(MeshFeaturesIndex(0)); + ASSERT_EQ(features.GetLabel(), "faces"); + ASSERT_EQ(features.GetFeatureCount(), num_faces); + ASSERT_EQ(features.GetNullFeatureId(), 100); + ASSERT_EQ(features.GetPropertyTableIndex(), 0); + ASSERT_EQ(features.GetAttributeIndex(), 0); + ASSERT_TRUE(features.GetTextureChannels().empty()); + ASSERT_EQ(features.GetTextureMap().texture(), nullptr); + ASSERT_EQ(features.GetTextureMap().tex_coord_index(), -1); + + // Check per-face Uint8 attribute named _FEATURE_ID_0. + const int att_id = + mesh.GetAttributeIdByMetadataEntry("attribute_name", "_FEATURE_ID_0"); + auto att = mesh.GetAttributeByUniqueId(att_id); + ASSERT_NE(att, nullptr); + ASSERT_EQ(att->attribute_type(), GeometryAttribute::GENERIC); + ASSERT_EQ(att->data_type(), DataType::DT_UINT8); + ASSERT_EQ(att->num_components(), 1); + ASSERT_EQ(att->size(), num_faces); + ASSERT_EQ(att->indices_map_size(), num_corners); + + // Check that the values are all the numbers from 0 to 12. + const std::vector expected_values = + has_draco_compression + ? std::vector{7, 11, 10, 3, 2, 5, 4, 1, 6, 9, 8, 0} + : std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; + for (int i = 0; i < num_faces; i++) { + uint8_t val; + att->GetValue(AttributeValueIndex(i), &val); + ASSERT_EQ(val, expected_values[i]); + } + + // Check that the corners of each face have a common value. + for (int i = 0; i < num_faces; i++) { + const auto face = mesh.face(FaceIndex(i)); + ASSERT_EQ(*att->GetAddressOfMappedIndex(face[0]), + *att->GetAddressOfMappedIndex(face[1])); + ASSERT_EQ(*att->GetAddressOfMappedIndex(face[0]), + *att->GetAddressOfMappedIndex(face[2])); + } + } + + // Check the 2nd mesh feature ID set at index 1. + { + // Check mesh features. + const MeshFeatures &features = mesh.GetMeshFeatures(MeshFeaturesIndex(1)); + ASSERT_EQ(features.GetLabel(), "vertices"); + ASSERT_EQ(features.GetFeatureCount(), num_vertices); + ASSERT_EQ(features.GetNullFeatureId(), 101); + ASSERT_EQ(features.GetPropertyTableIndex(), 1); + ASSERT_EQ(features.GetAttributeIndex(), 1); + ASSERT_TRUE(features.GetTextureChannels().empty()); + ASSERT_EQ(features.GetTextureMap().texture(), nullptr); + ASSERT_EQ(features.GetTextureMap().tex_coord_index(), -1); + + // Check per-vertex Uint16 attribute named _FEATURE_ID_1. + const int att_id = + mesh.GetAttributeIdByMetadataEntry("attribute_name", "_FEATURE_ID_1"); + auto att = mesh.GetAttributeByUniqueId(att_id); + ASSERT_NE(att, nullptr); + ASSERT_EQ(att->attribute_type(), GeometryAttribute::GENERIC); + ASSERT_EQ(att->data_type(), DataType::DT_UINT16); + ASSERT_EQ(att->num_components(), 1); + ASSERT_EQ(att->size(), num_vertices); + ASSERT_EQ(att->indices_map_size(), num_corners); + + // Check that the values are all the numbers from 0 to 7. + const std::vector expected_values = + has_draco_compression ? std::vector{3, 6, 7, 4, 5, 0, 1, 2} + : std::vector{0, 1, 2, 3, 4, 5, 6, 7}; + for (int i = 0; i < num_vertices; i++) { + uint16_t val; + att->GetValue(AttributeValueIndex(i), &val); + ASSERT_EQ(val, expected_values[i]); + } + + // Check that the corners of a face have unique values. + for (int i = 0; i < num_faces; i++) { + const auto face = mesh.face(FaceIndex(i)); + ASSERT_NE(*att->GetAddressOfMappedIndex(face[0]), + *att->GetAddressOfMappedIndex(face[1])); + ASSERT_NE(*att->GetAddressOfMappedIndex(face[1]), + *att->GetAddressOfMappedIndex(face[2])); + ASSERT_NE(*att->GetAddressOfMappedIndex(face[2]), + *att->GetAddressOfMappedIndex(face[0])); + } + } + + // Check the 3rd mesh feature ID set at index 2. + { + // Check mesh features. + const MeshFeatures &features = mesh.GetMeshFeatures(MeshFeaturesIndex(2)); + ASSERT_TRUE(features.GetLabel().empty()); + ASSERT_EQ(features.GetFeatureCount(), num_corners); + ASSERT_EQ(features.GetNullFeatureId(), -1); + ASSERT_EQ(features.GetPropertyTableIndex(), -1); + ASSERT_EQ(features.GetAttributeIndex(), 2); + ASSERT_TRUE(features.GetTextureChannels().empty()); + ASSERT_EQ(features.GetTextureMap().texture(), nullptr); + ASSERT_EQ(features.GetTextureMap().tex_coord_index(), -1); + + // Check per-corner Float attribute named _FEATURE_ID_2. + const int att_id = + mesh.GetAttributeIdByMetadataEntry("attribute_name", "_FEATURE_ID_2"); + auto att = mesh.GetAttributeByUniqueId(att_id); + ASSERT_NE(att, nullptr); + ASSERT_EQ(att->attribute_type(), GeometryAttribute::GENERIC); + ASSERT_EQ(att->data_type(), DataType::DT_FLOAT32); + ASSERT_EQ(att->num_components(), 1); + ASSERT_EQ(att->size(), num_corners); + ASSERT_EQ(att->indices_map_size(), 0); + ASSERT_TRUE(att->is_mapping_identity()); + + // Check that the values are from 0 to 35. + const std::vector expected_values = + has_draco_compression + ? std::vector{23, 21, 22, 33, 34, 35, 31, 32, 30, 9, 10, 11, + 7, 8, 6, 15, 16, 17, 14, 12, 13, 5, 3, 4, + 19, 20, 18, 27, 28, 29, 26, 24, 25, 1, 2, 0} + : std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35}; + for (int i = 0; i < num_corners; i++) { + float val; + att->GetValue(AttributeValueIndex(i), &val); + ASSERT_EQ(val, expected_values[i]); + } + + // Check that the corners have unique values. + for (int i = 0; i < num_faces; i++) { + const auto face = mesh.face(FaceIndex(i)); + float v0, v1, v2; + att->GetMappedValue(face[0], &v0); + att->GetMappedValue(face[1], &v1); + att->GetMappedValue(face[2], &v2); + ASSERT_EQ(v0, expected_values[3 * i + 0]); + ASSERT_EQ(v1, expected_values[3 * i + 1]); + ASSERT_EQ(v2, expected_values[3 * i + 2]); + } + } + + // Check mesh feature ID set at index 3. + { + // Check mesh features. + const MeshFeatures &features = mesh.GetMeshFeatures(MeshFeaturesIndex(3)); + ASSERT_TRUE(features.GetLabel().empty()); + ASSERT_EQ(features.GetFeatureCount(), 6); + ASSERT_EQ(features.GetNullFeatureId(), -1); + ASSERT_EQ(features.GetPropertyTableIndex(), -1); + ASSERT_EQ(features.GetAttributeIndex(), -1); + } + + // Check mesh feature ID set at index 4. + { + // Check mesh features. + const MeshFeatures &features = mesh.GetMeshFeatures(MeshFeaturesIndex(4)); + ASSERT_EQ(features.GetLabel(), "water"); + ASSERT_EQ(features.GetFeatureCount(), 2); + ASSERT_EQ(features.GetNullFeatureId(), -1); + ASSERT_EQ(features.GetPropertyTableIndex(), -1); + ASSERT_EQ(features.GetAttributeIndex(), -1); + } +} + +void GltfTestHelper::CheckBoxMetaStructuralMetadata( + const StructuralMetadata &structural_metadata) { + // Check property table schema. + { + const PropertyTable::Schema &schema = + structural_metadata.GetPropertyTableSchema(); + ASSERT_FALSE(schema.Empty()); + const PropertyTable::Schema::Object &json = schema.json; + ASSERT_EQ(json.GetObjects().size(), 3); + ASSERT_EQ(json.GetObjects()[0].GetName(), "classes"); + ASSERT_EQ(json.GetObjects()[0].GetObjects().size(), 1); + ASSERT_EQ(json.GetObjects()[0].GetObjects()[0].GetName(), "planet"); + ASSERT_EQ(json.GetObjects()[0].GetObjects()[0].GetObjects().size(), 1); + + const auto &properties = + json.GetObjects()[0].GetObjects()[0].GetObjects()[0]; + ASSERT_EQ(properties.GetName(), "properties"); + ASSERT_EQ(properties.GetObjects().size(), 3); + + const auto &color = properties.GetObjects()[0]; + ASSERT_EQ(color.GetName(), "color"); + ASSERT_EQ(color.GetObjects().size(), 4); + ASSERT_EQ(color.GetObjects()[0].GetName(), "componentType"); + ASSERT_EQ(color.GetObjects()[1].GetName(), "description"); + ASSERT_EQ(color.GetObjects()[2].GetName(), "required"); + ASSERT_EQ(color.GetObjects()[3].GetName(), "type"); + ASSERT_EQ(color.GetObjects()[0].GetString(), "UINT8"); + ASSERT_EQ(color.GetObjects()[1].GetString(), "The RGB color."); + ASSERT_TRUE(color.GetObjects()[2].GetBoolean()); + ASSERT_EQ(color.GetObjects()[3].GetString(), "VEC3"); + + const auto &name = properties.GetObjects()[1]; + ASSERT_EQ(name.GetName(), "name"); + ASSERT_EQ(name.GetObjects().size(), 3); + ASSERT_EQ(name.GetObjects()[0].GetName(), "description"); + ASSERT_EQ(name.GetObjects()[1].GetName(), "required"); + ASSERT_EQ(name.GetObjects()[2].GetName(), "type"); + ASSERT_EQ(name.GetObjects()[0].GetString(), "The name."); + ASSERT_TRUE(name.GetObjects()[1].GetBoolean()); + ASSERT_EQ(name.GetObjects()[2].GetString(), "STRING"); + + const auto &sequence = properties.GetObjects()[2]; + ASSERT_EQ(sequence.GetName(), "sequence"); + ASSERT_EQ(sequence.GetObjects().size(), 3); + ASSERT_EQ(sequence.GetObjects()[0].GetName(), "description"); + ASSERT_EQ(sequence.GetObjects()[1].GetName(), "required"); + ASSERT_EQ(sequence.GetObjects()[2].GetName(), "type"); + ASSERT_EQ(sequence.GetObjects()[0].GetString(), "The number sequence."); + ASSERT_FALSE(sequence.GetObjects()[1].GetBoolean()); + ASSERT_EQ(sequence.GetObjects()[2].GetString(), "SCALAR"); + + ASSERT_EQ(json.GetObjects()[1].GetName(), "enums"); + const auto &classifications = json.GetObjects()[1].GetObjects()[0]; + ASSERT_EQ(classifications.GetName(), "classifications"); + ASSERT_EQ(classifications.GetObjects()[0].GetName(), "description"); + ASSERT_EQ(classifications.GetObjects()[0].GetString(), + "Classifications of planets."); + ASSERT_EQ(classifications.GetObjects()[1].GetName(), "name"); + ASSERT_EQ(classifications.GetObjects()[1].GetString(), "classifications"); + ASSERT_EQ(classifications.GetObjects()[2].GetName(), "values"); + const auto &values = classifications.GetObjects()[2]; + ASSERT_EQ(values.GetArray()[0].GetObjects()[0].GetName(), "name"); + ASSERT_EQ(values.GetArray()[1].GetObjects()[0].GetName(), "name"); + ASSERT_EQ(values.GetArray()[2].GetObjects()[0].GetName(), "name"); + ASSERT_EQ(values.GetArray()[3].GetObjects()[0].GetName(), "name"); + ASSERT_EQ(values.GetArray()[4].GetObjects()[0].GetName(), "name"); + ASSERT_EQ(values.GetArray()[0].GetObjects()[0].GetString(), "Unspecified"); + ASSERT_EQ(values.GetArray()[1].GetObjects()[0].GetString(), "Gas Giant"); + ASSERT_EQ(values.GetArray()[2].GetObjects()[0].GetString(), "Waterworld"); + ASSERT_EQ(values.GetArray()[3].GetObjects()[0].GetString(), "Agriworld"); + ASSERT_EQ(values.GetArray()[4].GetObjects()[0].GetString(), "Ordnance"); + ASSERT_EQ(values.GetArray()[0].GetObjects()[1].GetName(), "value"); + ASSERT_EQ(values.GetArray()[1].GetObjects()[1].GetName(), "value"); + ASSERT_EQ(values.GetArray()[2].GetObjects()[1].GetName(), "value"); + ASSERT_EQ(values.GetArray()[3].GetObjects()[1].GetName(), "value"); + ASSERT_EQ(values.GetArray()[4].GetObjects()[1].GetName(), "value"); + ASSERT_EQ(values.GetArray()[0].GetObjects()[1].GetInteger(), 0); + ASSERT_EQ(values.GetArray()[1].GetObjects()[1].GetInteger(), 1); + ASSERT_EQ(values.GetArray()[2].GetObjects()[1].GetInteger(), 2); + ASSERT_EQ(values.GetArray()[3].GetObjects()[1].GetInteger(), 3); + ASSERT_EQ(values.GetArray()[4].GetObjects()[1].GetInteger(), 4); + + ASSERT_EQ(json.GetObjects()[2].GetName(), "id"); + ASSERT_EQ(json.GetObjects()[2].GetString(), "galaxy"); + } + + // Check property table. + constexpr int kRows = 16; + ASSERT_EQ(structural_metadata.NumPropertyTables(), 1); + const PropertyTable &table = structural_metadata.GetPropertyTable(0); + ASSERT_EQ(table.GetName(), "Galaxy far far away."); + ASSERT_EQ(table.GetClass(), "planet"); + ASSERT_EQ(table.GetCount(), kRows); + ASSERT_EQ(table.NumProperties(), 3); + + // Check property that describes RGB color components of the planet class. + { + const PropertyTable::Property &property = table.GetProperty(0); + ASSERT_EQ(property.GetName(), "color"); + + ASSERT_EQ(property.GetData().data.size(), kRows * 3); // RGB components. + ASSERT_EQ(property.GetData().target, 34962); // ARRAY_BUFFER. + + ASSERT_EQ(property.GetData().data[0], 94); // Tatooine [94, 94, 194]. + ASSERT_EQ(property.GetData().data[1], 94); + ASSERT_EQ(property.GetData().data[2], 194); + ASSERT_EQ(property.GetData().data[18], 190); // Corellia [190, 92, 108]. + ASSERT_EQ(property.GetData().data[19], 92); + ASSERT_EQ(property.GetData().data[20], 108); + ASSERT_EQ(property.GetData().data[45], 0); // UNLABELED [0, 0, 0]. + ASSERT_EQ(property.GetData().data[46], 0); + ASSERT_EQ(property.GetData().data[47], 0); + + ASSERT_TRUE(property.GetArrayOffsets().type.empty()); + ASSERT_TRUE(property.GetArrayOffsets().data.data.empty()); + ASSERT_EQ(property.GetArrayOffsets().data.target, 0); + ASSERT_TRUE(property.GetStringOffsets().type.empty()); + ASSERT_TRUE(property.GetStringOffsets().data.data.empty()); + ASSERT_EQ(property.GetStringOffsets().data.target, 0); + } + + // Check property that describes names of the planet class. + { + const PropertyTable::Property &property = table.GetProperty(1); + ASSERT_EQ(property.GetName(), "name"); + const std::vector &data = property.GetData().data; + const std::vector &offsets = property.GetStringOffsets().data.data; + + ASSERT_EQ(data.size(), 296); // Concatenated label strings. + ASSERT_EQ(property.GetData().target, 34963); // ELEMENT_ARRAY_BUFFER. + + ASSERT_EQ(property.GetStringOffsets().type, "UINT32"); + ASSERT_EQ(offsets.size(), 4 * (kRows + 1)); + ASSERT_EQ(property.GetStringOffsets().data.target, 34963); + + ASSERT_EQ(offsets[0], 0); // Tatooine 0. + ASSERT_EQ(offsets[1], 0); + ASSERT_EQ(offsets[2], 0); + ASSERT_EQ(offsets[3], 0); + ASSERT_EQ(offsets[60], 32); // UNLABELED 287. + ASSERT_EQ(offsets[61], 1); + ASSERT_EQ(offsets[62], 0); + ASSERT_EQ(offsets[63], 0); + ASSERT_EQ(offsets[64], 41); // Beyond UNLABELED 296. + ASSERT_EQ(offsets[65], 1); + ASSERT_EQ(offsets[66], 0); + ASSERT_EQ(offsets[67], 0); + + struct Name { + static std::string Extract(const std::vector &data, + const std::vector &offsets, int row) { + const int b = offsets[4 * (row + 0)] + 255 * offsets[4 * (row + 0) + 1]; + const int e = offsets[4 * (row + 1)] + 255 * offsets[4 * (row + 1) + 1]; + return std::string(data.begin() + b, data.begin() + e); + } + }; + + // Check that the names can be extracted from the data. + ASSERT_EQ(Name::Extract(data, offsets, 0), "named_class:Tatooine"); + ASSERT_EQ(Name::Extract(data, offsets, 6), "named_class:Corellia"); + ASSERT_EQ(Name::Extract(data, offsets, 15), "UNLABELED"); + + ASSERT_TRUE(property.GetArrayOffsets().type.empty()); + ASSERT_TRUE(property.GetArrayOffsets().data.data.empty()); + ASSERT_EQ(property.GetArrayOffsets().data.target, 0); + } + + // Check property that describes number sequence of the planet class. + { + const PropertyTable::Property &property = table.GetProperty(2); + ASSERT_EQ(property.GetName(), "sequence"); + const std::vector &data = property.GetData().data; + const std::vector &offsets = property.GetArrayOffsets().data.data; + + ASSERT_EQ(data.size(), 41 * 4); // Concatenated float arrays. + ASSERT_EQ(property.GetData().target, 34963); // ELEMENT_ARRAY_BUFFER. + + ASSERT_EQ(property.GetArrayOffsets().type, "UINT8"); + ASSERT_EQ(offsets.size(), 20); // kRows + 1 + padding. + ASSERT_EQ(property.GetArrayOffsets().data.target, 34963); + + ASSERT_EQ(offsets[0], 0 * 4); // Tatooine + ASSERT_EQ(offsets[1], 6 * 4); // Corusant + ASSERT_EQ(offsets[6], 16 * 4); // Corellia + ASSERT_EQ(offsets[14], 36 * 4); // Geonosis + ASSERT_EQ(offsets[15], 41 * 4); // UNLABELED (empty array). + ASSERT_EQ(offsets[16], 41 * 4); // Beyond UNLABELED (empty array). + + struct Sequence { + static std::vector Extract(const std::vector &data, + const std::vector &offsets, + int row) { + const int n = (offsets[row + 1] - offsets[row]) / 4; + std::vector result; + result.reserve(n); + for (int i = 0; i < n; ++i) { + const void *const pointer = &data[offsets[row] + 4 * i]; + result.push_back(*static_cast(pointer)); + } + return result; + } + }; + + // Check that the number sequence arrays can be extracted from the data. + ASSERT_EQ( + Sequence::Extract(data, offsets, 0), + (std::vector{0.5f, 1.5f, 2.5f, 3.5f, 4.5f, 5.5f})); // Tatooine + ASSERT_EQ(Sequence::Extract(data, offsets, 1), + (std::vector{6.5f, 7.5f})); // Corusant + ASSERT_EQ( + Sequence::Extract(data, offsets, 14), + (std::vector{36.5f, 37.5f, 38.5f, 39.5f, 40.5f})); // Geonosis + ASSERT_TRUE(Sequence::Extract(data, offsets, 15) + .empty()); // UNLABELED (empty array). + + ASSERT_TRUE(property.GetStringOffsets().type.empty()); + ASSERT_TRUE(property.GetStringOffsets().data.data.empty()); + ASSERT_EQ(property.GetStringOffsets().data.target, 0); + } +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace draco diff --git a/contrib/draco/src/draco/io/gltf_test_helper.h b/contrib/draco/src/draco/io/gltf_test_helper.h new file mode 100644 index 000000000..91aec9b08 --- /dev/null +++ b/contrib/draco/src/draco/io/gltf_test_helper.h @@ -0,0 +1,61 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_GLTF_DECODER_TEST_HELPER_H_ +#define DRACO_IO_GLTF_DECODER_TEST_HELPER_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/scene/scene.h" + +namespace draco { + +// Helper class for testing Draco glTF encoder and decoder. +class GltfTestHelper { + public: + // Adds various mesh feature ID sets (via attributes and via textures) and + // structural metadata property table and property table schema to the box + // |scene| loaded from the test file testdata/Box/glTF/Box.gltf. + static void AddBoxMetaMeshFeatures(Scene *scene); + static void AddBoxMetaStructuralMetadata(Scene *scene); + + // Checks the box |geometry| (draco::Mesh or draco::Scene) with mesh features + // loaded from one of these test files, with or without Draco compression: + // 1. testdata/BoxMeta/glTF/BoxMeta.gltf + // 2. testdata/BoxMetaDraco/glTF/BoxMetaDraco.gltf + template + static void CheckBoxMetaMeshFeatures(const GeometryT &geometry, + bool has_draco_compression); + + // Checks the box |geometry| (draco::Mesh or draco::Scene) with structural + // metadata that includes property table and property table schema loaded from + // test file testdata/BoxMeta/glTF/BoxMeta.gltf. + template + static void CheckBoxMetaStructuralMetadata(const GeometryT &geometry) { + CheckBoxMetaStructuralMetadata(geometry.GetStructuralMetadata()); + } + + private: + static void CheckBoxMetaMeshFeatures(const Mesh &mesh, + const TextureLibrary &texture_lib, + bool has_draco_compression); + static void CheckBoxMetaStructuralMetadata( + const StructuralMetadata &structural_metadata); +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_GLTF_DECODER_TEST_HELPER_H_ diff --git a/contrib/draco/src/draco/io/gltf_utils.cc b/contrib/draco/src/draco/io/gltf_utils.cc new file mode 100644 index 000000000..bf5c048ef --- /dev/null +++ b/contrib/draco/src/draco/io/gltf_utils.cc @@ -0,0 +1,154 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/gltf_utils.h" + +#include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED +namespace draco { + +std::ostream &operator<<(std::ostream &os, const GltfValue &value) { + if (value.type_ == GltfValue::INT) { + os << value.value_int_; + } else { + os << value.value_double_; + } + return os; +} + +Indent::Indent() : indent_space_count_(2) {} + +void Indent::Increase() { indent_ += std::string(indent_space_count_, ' '); } + +void Indent::Decrease() { indent_.erase(0, indent_space_count_); } + +std::ostream &operator<<(std::ostream &os, const Indent &indent) { + return os << indent.indent_; +} + +std::ostream &operator<<(std::ostream &os, + const JsonWriter::IndentWrapper &indent) { + if (indent.writer.mode_ == JsonWriter::READABLE) { + os << indent.writer.indent_writer_; + } + return os; +} + +std::ostream &operator<<(std::ostream &os, + const JsonWriter::Separator &separator) { + if (separator.writer.mode_ == JsonWriter::READABLE) { + os << " "; + } + return os; +} + +void JsonWriter::Reset() { + last_type_ = START; + o_.clear(); + o_.str(""); +} + +void JsonWriter::BeginObject() { BeginObject(""); } + +void JsonWriter::BeginObject(const std::string &name) { + FinishPreviousLine(BEGIN); + o_ << indent_; + if (!name.empty()) { + o_ << "\"" << name << "\":" << separator_; + } + o_ << "{"; + indent_writer_.Increase(); +} + +void JsonWriter::EndObject() { + FinishPreviousLine(END); + indent_writer_.Decrease(); + o_ << indent_ << "}"; +} + +void JsonWriter::BeginArray(const std::string &name) { + FinishPreviousLine(BEGIN); + o_ << indent_ << "\"" << name << "\":" << separator_ << "["; + indent_writer_.Increase(); +} + +void JsonWriter::EndArray() { + FinishPreviousLine(END); + indent_writer_.Decrease(); + o_ << indent_ << "]"; +} + +void JsonWriter::FinishPreviousLine(OutputType curr_type) { + if (last_type_ != START) { + if ((last_type_ == VALUE && curr_type == VALUE) || + (last_type_ == VALUE && curr_type == BEGIN) || + (last_type_ == END && curr_type == BEGIN) || + (last_type_ == END && curr_type == VALUE)) { + o_ << ","; + } + if (mode_ == READABLE) { + o_ << std::endl; + } + } + last_type_ = curr_type; +} + +std::string JsonWriter::MoveData() { + const std::string str = o_.str(); + o_.str(""); + return str; +} + +std::string JsonWriter::EscapeCharacter(const std::string &str, + const char character) { + size_t start = 0; + if ((start = str.find(character, start)) != std::string::npos) { + std::string s = str; + std::string escaped_character = "\\"; + escaped_character += character; + do { + s.replace(start, 1, escaped_character); + start += escaped_character.length(); + } while ((start = s.find(character, start)) != std::string::npos); + return s; + } + return str; +} + +std::string JsonWriter::EscapeJsonSpecialCharacters(const std::string &str) { + std::string s = str; + const char backspace = '\b'; + const char form_feed = '\f'; + const char newline = '\n'; + const char carriage_return = '\r'; + const char tab = '\t'; + const char double_quote = '\"'; + const char backslash = '\\'; + + // Backslash must come first. + s = EscapeCharacter(s, backslash); + s = EscapeCharacter(s, backspace); + s = EscapeCharacter(s, form_feed); + s = EscapeCharacter(s, newline); + s = EscapeCharacter(s, carriage_return); + s = EscapeCharacter(s, tab); + s = EscapeCharacter(s, double_quote); + return s; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/io/gltf_utils.h b/contrib/draco/src/draco/io/gltf_utils.h new file mode 100644 index 000000000..2cf12fdc7 --- /dev/null +++ b/contrib/draco/src/draco/io/gltf_utils.h @@ -0,0 +1,186 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_GLTF_UTILS_H_ +#define DRACO_IO_GLTF_UTILS_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include + +namespace draco { + +// Class used to store integer or float values supported by glTF. +class GltfValue { + public: + enum ValueType { INT, DOUBLE }; + + explicit GltfValue(int8_t value) + : type_(INT), value_int_(value), value_double_(-1.0) {} + + explicit GltfValue(uint8_t value) + : type_(INT), value_int_(value), value_double_(-1.0) {} + + explicit GltfValue(int16_t value) + : type_(INT), value_int_(value), value_double_(-1.0) {} + + explicit GltfValue(uint16_t value) + : type_(INT), value_int_(value), value_double_(-1.0) {} + + explicit GltfValue(uint32_t value) + : type_(INT), value_int_(value), value_double_(-1.0) {} + + explicit GltfValue(float value) + : type_(DOUBLE), value_int_(-1), value_double_(value) {} + + friend std::ostream &operator<<(std::ostream &os, const GltfValue &value); + + private: + ValueType type_; + int64_t value_int_; + double value_double_; +}; + +// Utility class used to help with indentation of glTF file. +class Indent { + public: + Indent(); + + void Increase(); + void Decrease(); + + friend std::ostream &operator<<(std::ostream &os, const Indent &indent); + + private: + // Variables used for spacing of the glTF file. + std::string indent_; + const int indent_space_count_; +}; + +// Class used to keep track of the json state. +class JsonWriter { + public: + enum OutputType { START, BEGIN, END, VALUE }; + enum Mode { READABLE, COMPACT }; + + JsonWriter() + : last_type_(START), mode_(READABLE), indent_(*this), separator_(*this) {} + void SetMode(Mode mode) { mode_ = mode; } + + // Clear the stringstream and set last type to START. + void Reset(); + + // Every call to BeginObject should have a matching call to EndObject. + void BeginObject(); + void BeginObject(const std::string &name); + void EndObject(); + + // Every call to BeginArray should have a matching call to EndArray. + void BeginArray(const std::string &name); + void EndArray(); + + template + void OutputValue(const T &value) { + FinishPreviousLine(VALUE); + o_ << indent_ << std::setprecision(17) << value; + } + + void OutputValue(const bool &value) { + FinishPreviousLine(VALUE); + o_ << indent_ << ToString(value); + } + + void OutputValue(const std::string &name) { + const std::string escaped_name = EscapeJsonSpecialCharacters(name); + FinishPreviousLine(VALUE); + o_ << indent_ << "\"" << escaped_name << "\""; + } + + void OutputValue(const std::string &name, const std::string &value) { + const std::string escaped_name = EscapeJsonSpecialCharacters(name); + const std::string escaped_value = EscapeJsonSpecialCharacters(value); + FinishPreviousLine(VALUE); + o_ << indent_ << "\"" << escaped_name << "\":" << separator_ << "\"" + << escaped_value << "\""; + } + + void OutputValue(const std::string &name, const char *value) { + const std::string escaped_name = EscapeJsonSpecialCharacters(name); + const std::string escaped_value = EscapeJsonSpecialCharacters(value); + FinishPreviousLine(VALUE); + o_ << indent_ << "\"" << escaped_name << "\":" << separator_ << "\"" + << escaped_value << "\""; + } + + template + void OutputValue(const std::string &name, const T &value) { + const std::string escaped_name = EscapeJsonSpecialCharacters(name); + FinishPreviousLine(VALUE); + o_ << indent_ << "\"" << escaped_name << "\":" << separator_ << value; + } + + void OutputValue(const std::string &name, const bool &value) { + const std::string escaped_name = EscapeJsonSpecialCharacters(name); + FinishPreviousLine(VALUE); + o_ << indent_ << "\"" << escaped_name << "\":" << separator_ + << ToString(value); + } + + // Return the current output and then clear the stringstream. + std::string MoveData(); + + private: + // Check if a comma needs to be added to the output and then add a new line. + void FinishPreviousLine(OutputType curr_type); + + // Returns a string escaping all instances of |character| in |str|. + std::string EscapeCharacter(const std::string &str, const char character); + + // Returns a string escaping all of the Json special characters in |str|. + // Carriage return is not handled. + std::string EscapeJsonSpecialCharacters(const std::string &str); + + // Returns string representation of a Boolean |value|. + static std::string ToString(bool value) { return value ? "true" : "false"; } + + // Helper struct used for conditional indent writing to the output stream. + struct IndentWrapper { + explicit IndentWrapper(const JsonWriter &writer) : writer(writer) {} + const JsonWriter &writer; + }; + friend std::ostream &operator<<(std::ostream &os, + const IndentWrapper &indent); + + // Helper struct used for conditional separator writing to the output stream. + struct Separator { + explicit Separator(const JsonWriter &writer) : writer(writer) {} + const JsonWriter &writer; + }; + friend std::ostream &operator<<(std::ostream &os, const Separator &separator); + + std::stringstream o_; + Indent indent_writer_; + OutputType last_type_; + Mode mode_; + IndentWrapper indent_; + Separator separator_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_GLTF_UTILS_H_ diff --git a/contrib/draco/src/draco/io/gltf_utils_test.cc b/contrib/draco/src/draco/io/gltf_utils_test.cc new file mode 100644 index 000000000..01a2d144c --- /dev/null +++ b/contrib/draco/src/draco/io/gltf_utils_test.cc @@ -0,0 +1,366 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/gltf_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace draco { + +class GltfUtilsTest : public ::testing::Test { + protected: + void CompareGolden(JsonWriter *json_writer, const std::string &golden_str) { + const std::string json = json_writer->MoveData(); + ASSERT_EQ(golden_str, json); + } +}; + +TEST_F(GltfUtilsTest, TestNoData) { + const std::string golden = ""; + JsonWriter json_writer; + CompareGolden(&json_writer, golden); +} + +TEST_F(GltfUtilsTest, TestValues) { + JsonWriter json_writer; + json_writer.OutputValue(0); + CompareGolden(&json_writer, "0"); + + json_writer.Reset(); + json_writer.OutputValue(1); + CompareGolden(&json_writer, "1"); + + json_writer.Reset(); + json_writer.OutputValue(-1); + CompareGolden(&json_writer, "-1"); + + json_writer.Reset(); + json_writer.OutputValue(0.0); + CompareGolden(&json_writer, "0"); + + json_writer.Reset(); + json_writer.OutputValue(1.0); + CompareGolden(&json_writer, "1"); + + json_writer.Reset(); + json_writer.OutputValue(0.25); + CompareGolden(&json_writer, "0.25"); + + json_writer.Reset(); + json_writer.OutputValue(-0.25); + CompareGolden(&json_writer, "-0.25"); + + json_writer.Reset(); + json_writer.OutputValue(false); + CompareGolden(&json_writer, "false"); + + json_writer.Reset(); + json_writer.OutputValue(true); + CompareGolden(&json_writer, "true"); + + json_writer.Reset(); + json_writer.OutputValue("test int", -1); + CompareGolden(&json_writer, "\"test int\": -1"); + + json_writer.Reset(); + json_writer.OutputValue("test float", -10.25); + CompareGolden(&json_writer, "\"test float\": -10.25"); + + json_writer.Reset(); + json_writer.OutputValue("test char*", "I am the string!"); + CompareGolden(&json_writer, "\"test char*\": \"I am the string!\""); + + json_writer.Reset(); + const std::string value = "I am the string!"; + json_writer.OutputValue("test string", value); + CompareGolden(&json_writer, "\"test string\": \"I am the string!\""); + + json_writer.Reset(); + json_writer.OutputValue("test bool", false); + CompareGolden(&json_writer, "\"test bool\": false"); + + json_writer.Reset(); + json_writer.OutputValue("test bool", true); + CompareGolden(&json_writer, "\"test bool\": true"); +} + +TEST_F(GltfUtilsTest, TestSpecialCharacters) { + JsonWriter json_writer; + const std::string test_double_quote = "I am double quote\""; + json_writer.OutputValue("test double quote", test_double_quote); + CompareGolden(&json_writer, + "\"test double quote\": \"I am double quote\\\"\""); + + json_writer.Reset(); + const std::string test_backspace = "I am backspace\b"; + json_writer.OutputValue("test backspace", test_backspace); + CompareGolden(&json_writer, "\"test backspace\": \"I am backspace\\\b\""); + + json_writer.Reset(); + const std::string test_form_feed = "I am form feed\f"; + json_writer.OutputValue("test form feed", test_form_feed); + CompareGolden(&json_writer, "\"test form feed\": \"I am form feed\\\f\""); + + json_writer.Reset(); + const std::string test_newline = "I am newline\n"; + json_writer.OutputValue("test newline", test_newline); + CompareGolden(&json_writer, "\"test newline\": \"I am newline\\\n\""); + + json_writer.Reset(); + const std::string test_tab = "I am tab\t"; + json_writer.OutputValue("test tab", test_tab); + CompareGolden(&json_writer, "\"test tab\": \"I am tab\\\t\""); + + json_writer.Reset(); + const std::string test_backslash = "I am backslash\\"; + json_writer.OutputValue("test backslash", test_backslash); + CompareGolden(&json_writer, "\"test backslash\": \"I am backslash\\\\\""); + + json_writer.Reset(); + const std::string test_multiple_special_characters = "\"break\"and\\more\"\\"; + json_writer.OutputValue("test multiple_special_characters", + test_multiple_special_characters); + CompareGolden(&json_writer, + "\"test multiple_special_characters\": " + "\"\\\"break\\\"and\\\\more\\\"\\\\\""); +} + +TEST_F(GltfUtilsTest, TestObjects) { + JsonWriter json_writer; + json_writer.BeginObject(); + json_writer.EndObject(); + CompareGolden(&json_writer, "{\n}"); + + json_writer.Reset(); + json_writer.BeginObject("object"); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object\": {\n}"); + + json_writer.Reset(); + json_writer.BeginObject("object"); + json_writer.OutputValue(0); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object\": {\n 0\n}"); + + json_writer.Reset(); + json_writer.BeginObject("object"); + json_writer.OutputValue(0); + json_writer.OutputValue(1); + json_writer.OutputValue(2); + json_writer.OutputValue(3); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object\": {\n 0,\n 1,\n 2,\n 3\n}"); + + json_writer.Reset(); + json_writer.BeginObject("object1"); + json_writer.EndObject(); + json_writer.BeginObject("object2"); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object1\": {\n},\n\"object2\": {\n}"); + + json_writer.Reset(); + json_writer.BeginObject("object1"); + json_writer.BeginObject("object2"); + json_writer.EndObject(); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object1\": {\n \"object2\": {\n }\n}"); +} + +TEST_F(GltfUtilsTest, TestArrays) { + JsonWriter json_writer; + json_writer.BeginArray("array"); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array\": [\n]"); + + json_writer.Reset(); + json_writer.BeginArray("array"); + json_writer.OutputValue(0); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array\": [\n 0\n]"); + + json_writer.Reset(); + json_writer.BeginArray("array"); + json_writer.OutputValue(0); + json_writer.OutputValue(1); + json_writer.OutputValue(2); + json_writer.OutputValue(3); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array\": [\n 0,\n 1,\n 2,\n 3\n]"); + + json_writer.Reset(); + json_writer.BeginArray("array1"); + json_writer.EndArray(); + json_writer.BeginArray("array2"); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array1\": [\n],\n\"array2\": [\n]"); + + json_writer.Reset(); + json_writer.BeginArray("array1"); + json_writer.BeginArray("array2"); + json_writer.EndArray(); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array1\": [\n \"array2\": [\n ]\n]"); +} + +TEST_F(GltfUtilsTest, TestGltfValues) { + JsonWriter json_writer; + const int8_t int8_value_min = std::numeric_limits::min(); + const int8_t int8_value_max = std::numeric_limits::max(); + const GltfValue int8_value_low(int8_value_min); + const GltfValue int8_value_high(int8_value_max); + json_writer.OutputValue(int8_value_low); + json_writer.OutputValue(int8_value_high); + CompareGolden(&json_writer, "-128,\n127"); + + json_writer.Reset(); + const uint8_t uint8_value_min = std::numeric_limits::min(); + const uint8_t uint8_value_max = std::numeric_limits::max(); + const GltfValue uint8_value_low(uint8_value_min); + const GltfValue uint8_value_high(uint8_value_max); + json_writer.OutputValue(uint8_value_low); + json_writer.OutputValue(uint8_value_high); + CompareGolden(&json_writer, "0,\n255"); + + json_writer.Reset(); + const int16_t int16_value_min = std::numeric_limits::min(); + const int16_t int16_value_max = std::numeric_limits::max(); + const GltfValue int16_value_low(int16_value_min); + const GltfValue int16_value_high(int16_value_max); + json_writer.OutputValue(int16_value_low); + json_writer.OutputValue(int16_value_high); + CompareGolden(&json_writer, "-32768,\n32767"); + + json_writer.Reset(); + const uint16_t uint16_value_min = std::numeric_limits::min(); + const uint16_t uint16_value_max = std::numeric_limits::max(); + const GltfValue uint16_value_low(uint16_value_min); + const GltfValue uint16_value_high(uint16_value_max); + json_writer.OutputValue(uint16_value_low); + json_writer.OutputValue(uint16_value_high); + CompareGolden(&json_writer, "0,\n65535"); + + json_writer.Reset(); + const uint32_t uint32_value_min = std::numeric_limits::min(); + const uint32_t uint32_value_max = std::numeric_limits::max(); + const GltfValue uint32_value_low(uint32_value_min); + const GltfValue uint32_value_high(uint32_value_max); + json_writer.OutputValue(uint32_value_low); + json_writer.OutputValue(uint32_value_high); + CompareGolden(&json_writer, "0,\n4294967295"); + + json_writer.Reset(); + const float float_value_min = std::numeric_limits::min(); + const float float_value_max = std::numeric_limits::max(); + const GltfValue float_value_low(float_value_min); + const GltfValue float_value_high(float_value_max); + json_writer.OutputValue(float_value_low); + json_writer.OutputValue(float_value_high); + CompareGolden(&json_writer, + "1.1754943508222875e-38,\n3.4028234663852886e+38"); + + json_writer.Reset(); + const GltfValue float_value_0(0.1f); + const GltfValue float_value_1(1.f); + json_writer.OutputValue(float_value_0); + json_writer.OutputValue(float_value_1); + CompareGolden(&json_writer, "0.10000000149011612,\n1"); +} + +TEST_F(GltfUtilsTest, TestObjectsCompact) { + JsonWriter json_writer; + json_writer.SetMode(JsonWriter::COMPACT); + json_writer.BeginObject(); + json_writer.EndObject(); + CompareGolden(&json_writer, "{}"); + + json_writer.Reset(); + json_writer.BeginObject("object"); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object\":{}"); + + json_writer.Reset(); + json_writer.BeginObject("object"); + json_writer.OutputValue(0); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object\":{0}"); + + json_writer.Reset(); + json_writer.BeginObject("object"); + json_writer.OutputValue(0); + json_writer.OutputValue(1); + json_writer.OutputValue(2); + json_writer.OutputValue(3); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object\":{0,1,2,3}"); + + json_writer.Reset(); + json_writer.BeginObject("object1"); + json_writer.EndObject(); + json_writer.BeginObject("object2"); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object1\":{},\"object2\":{}"); + + json_writer.Reset(); + json_writer.BeginObject("object1"); + json_writer.BeginObject("object2"); + json_writer.EndObject(); + json_writer.EndObject(); + CompareGolden(&json_writer, "\"object1\":{\"object2\":{}}"); +} + +TEST_F(GltfUtilsTest, TestArraysCompact) { + JsonWriter json_writer; + json_writer.SetMode(JsonWriter::COMPACT); + json_writer.BeginArray("array"); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array\":[]"); + + json_writer.Reset(); + json_writer.BeginArray("array"); + json_writer.OutputValue(0); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array\":[0]"); + + json_writer.Reset(); + json_writer.BeginArray("array"); + json_writer.OutputValue(0); + json_writer.OutputValue(1); + json_writer.OutputValue(2); + json_writer.OutputValue(3); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array\":[0,1,2,3]"); + + json_writer.Reset(); + json_writer.BeginArray("array1"); + json_writer.EndArray(); + json_writer.BeginArray("array2"); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array1\":[],\"array2\":[]"); + + json_writer.Reset(); + json_writer.BeginArray("array1"); + json_writer.BeginArray("array2"); + json_writer.EndArray(); + json_writer.EndArray(); + CompareGolden(&json_writer, "\"array1\":[\"array2\":[]]"); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/io/image_compression_options.h b/contrib/draco/src/draco/io/image_compression_options.h new file mode 100644 index 000000000..722bdbd64 --- /dev/null +++ b/contrib/draco/src/draco/io/image_compression_options.h @@ -0,0 +1,31 @@ +// Copyright 2020 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_IMAGE_COMPRESSION_OPTIONS_H_ +#define DRACO_IO_IMAGE_COMPRESSION_OPTIONS_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +namespace draco { + +// Enum defining image compression formats. +enum class ImageFormat { NONE, PNG, JPEG, BASIS, WEBP }; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_IMAGE_COMPRESSION_OPTIONS_H_ diff --git a/contrib/draco/src/draco/io/mesh_io.cc b/contrib/draco/src/draco/io/mesh_io.cc index e0dc69c6f..4975d9236 100644 --- a/contrib/draco/src/draco/io/mesh_io.cc +++ b/contrib/draco/src/draco/io/mesh_io.cc @@ -18,8 +18,18 @@ #include #include "draco/io/file_utils.h" +#include "draco/io/file_writer_interface.h" #include "draco/io/obj_decoder.h" #include "draco/io/ply_decoder.h" +#include "draco/io/stl_decoder.h" +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/compression/draco_compression_options.h" +#include "draco/compression/encode.h" +#include "draco/io/gltf_decoder.h" +#include "draco/io/gltf_encoder.h" +#include "draco/io/obj_encoder.h" +#include "draco/io/ply_encoder.h" +#endif namespace draco { @@ -46,27 +56,40 @@ StatusOr> ReadMeshFromFile( std::unique_ptr mesh(new Mesh()); // Analyze file extension. const std::string extension = LowercaseFileExtension(file_name); - if (extension != "gltf" && mesh_files) { - // The GLTF decoder will fill |mesh_files|, but for other file types we set - // the root file here to avoid duplicating code. + if (extension != "gltf" && extension != "obj" && mesh_files) { + // The GLTF/OBJ decoder will fill |mesh_files|, but for other file types we + // set the root file here to avoid duplicating code. mesh_files->push_back(file_name); } if (extension == "obj") { // Wavefront OBJ file format. ObjDecoder obj_decoder; obj_decoder.set_use_metadata(options.GetBool("use_metadata", false)); - const Status obj_status = obj_decoder.DecodeFromFile(file_name, mesh.get()); + obj_decoder.set_preserve_polygons(options.GetBool("preserve_polygons")); + const Status obj_status = + obj_decoder.DecodeFromFile(file_name, mesh.get(), mesh_files); if (!obj_status.ok()) { return obj_status; } return std::move(mesh); } if (extension == "ply") { - // Wavefront PLY file format. + // Stanford PLY file format. PlyDecoder ply_decoder; DRACO_RETURN_IF_ERROR(ply_decoder.DecodeFromFile(file_name, mesh.get())); return std::move(mesh); } + if (extension == "stl") { + // STL file format. + StlDecoder stl_decoder; + return stl_decoder.DecodeFromFile(file_name); + } +#ifdef DRACO_TRANSCODER_SUPPORTED + if (extension == "gltf" || extension == "glb") { + GltfDecoder gltf_decoder; + return gltf_decoder.DecodeFromFile(file_name, mesh_files); + } +#endif // Otherwise not an obj file. Assume the file was encoded with one of the // draco encoding methods. diff --git a/contrib/draco/src/draco/io/obj_decoder.cc b/contrib/draco/src/draco/io/obj_decoder.cc index 9b4eab626..c233c2b56 100644 --- a/contrib/draco/src/draco/io/obj_decoder.cc +++ b/contrib/draco/src/draco/io/obj_decoder.cc @@ -14,8 +14,10 @@ // #include "draco/io/obj_decoder.h" +#include #include #include +#include #include "draco/io/file_utils.h" #include "draco/io/parser_utils.h" @@ -36,15 +38,25 @@ ObjDecoder::ObjDecoder() norm_att_id_(-1), material_att_id_(-1), sub_obj_att_id_(-1), + added_edge_att_id_(-1), deduplicate_input_values_(true), last_material_id_(0), use_metadata_(false), + preserve_polygons_(false), + has_polygons_(false), + mesh_files_(nullptr), out_mesh_(nullptr), out_point_cloud_(nullptr) {} Status ObjDecoder::DecodeFromFile(const std::string &file_name, Mesh *out_mesh) { + return DecodeFromFile(file_name, out_mesh, nullptr); +} + +Status ObjDecoder::DecodeFromFile(const std::string &file_name, Mesh *out_mesh, + std::vector *mesh_files) { out_mesh_ = out_mesh; + mesh_files_ = mesh_files; return DecodeFromFile(file_name, static_cast(out_mesh)); } @@ -90,6 +102,10 @@ Status ObjDecoder::DecodeInternal() { return status; } + if (mesh_files_ && !input_file_name_.empty()) { + mesh_files_->push_back(input_file_name_); + } + bool use_identity_mapping = false; if (num_obj_faces_ == 0) { // Mesh has no faces. In this case we try to read the geometry as a point @@ -146,6 +162,24 @@ Status ObjDecoder::DecodeInternal() { norm_att_id_ = out_point_cloud_->AddAttribute(va, use_identity_mapping, num_normals_); } + if (preserve_polygons_ && has_polygons_) { + // Create attribute for polygon reconstruction. + GeometryAttribute va; + va.Init(GeometryAttribute::GENERIC, nullptr, 1, DT_UINT8, false, 1, 0); + PointCloud *const pc = out_point_cloud_; + added_edge_att_id_ = pc->AddAttribute(va, false, 2); + + // Set attribute values to zero and one representing old edge and new edge. + for (const uint8_t i : {0, 1}) { + const AttributeValueIndex avi(i); + pc->attribute(added_edge_att_id_)->SetAttributeValue(avi, &i); + } + + // Add attribute metadata with name. + std::unique_ptr metadata(new draco::AttributeMetadata()); + metadata->AddEntryString("name", "added_edges"); + pc->AddAttributeMetadata(added_edge_att_id_, std::move(metadata)); + } if (num_materials_ > 0 && num_obj_faces_ > 0) { GeometryAttribute va; const auto geometry_attribute_type = GeometryAttribute::GENERIC; @@ -381,6 +415,7 @@ bool ObjDecoder::ParseTexCoord(Status *status) { } bool ObjDecoder::ParseFace(Status *status) { + constexpr int kMaxCorners = 8; char c; if (!buffer()->Peek(&c)) { return false; @@ -391,37 +426,35 @@ bool ObjDecoder::ParseFace(Status *status) { // Face definition found! buffer()->Advance(1); if (!counting_mode_) { - std::array indices[4]; - // Parse face indices (we try to look for up to four to support quads). + std::array indices[kMaxCorners]; + // Parse face indices. int num_valid_indices = 0; - for (int i = 0; i < 4; ++i) { + for (int i = 0; i < kMaxCorners; ++i) { if (!ParseVertexIndices(&indices[i])) { - if (i == 3) { - break; // It's OK if there is no fourth vertex index. + if (i >= 3) { + break; // It's OK if there is no fourth or higher vertex index. } *status = Status(Status::DRACO_ERROR, "Failed to parse vertex indices"); return true; } ++num_valid_indices; } - // Process the first face. - for (int i = 0; i < 3; ++i) { - const PointIndex vert_id(3 * num_obj_faces_ + i); - MapPointToVertexIndices(vert_id, indices[i]); - } - ++num_obj_faces_; - if (num_valid_indices == 4) { - // Add an additional triangle for the quad. - // - // 3----2 - // | / | - // | / | - // 0----1 - // - const PointIndex vert_id(3 * num_obj_faces_); - MapPointToVertexIndices(vert_id, indices[0]); - MapPointToVertexIndices(vert_id + 1, indices[2]); - MapPointToVertexIndices(vert_id + 2, indices[3]); + // Split quads and other n-gons into n - 2 triangles. + const int nt = num_valid_indices - 2; + // Iterate over triangles. + for (int t = 0; t < nt; t++) { + // Iterate over corners. + for (int c = 0; c < 3; c++) { + const PointIndex vert_id(3 * num_obj_faces_ + c); + const int triangulated_index = Triangulate(t, c); + MapPointToVertexIndices(vert_id, indices[triangulated_index]); + // Save info about new edges that will allow us to reconstruct polygons. + if (added_edge_att_id_ >= 0) { + const AttributeValueIndex avi(IsNewEdge(nt, t, c)); + out_point_cloud_->attribute(added_edge_att_id_) + ->SetPointMapEntry(vert_id, avi); + } + } ++num_obj_faces_; } } else { @@ -443,12 +476,14 @@ bool ObjDecoder::ParseFace(Status *status) { } } } - if (num_indices < 3 || num_indices > 4) { - *status = - Status(Status::DRACO_ERROR, "Invalid number of indices on a face"); + if (num_indices > 3) { + has_polygons_ = true; + } + if (num_indices < 3 || num_indices > kMaxCorners) { + *status = ErrorStatus("Invalid number of indices on a face"); return false; } - // Either one or two new triangles. + // Either one or more new triangles. num_obj_faces_ += num_indices - 2; } parser::SkipLine(buffer()); @@ -478,6 +513,9 @@ bool ObjDecoder::ParseMaterialLib(Status *status) { parser::SkipLine(&line_buffer); if (!material_file_name_.empty()) { + if (mesh_files_) { + mesh_files_->push_back(material_file_name_); + } if (!ParseMaterialFile(material_file_name_, status)) { // Silently ignore problems with material files for now. return true; @@ -705,4 +743,44 @@ bool ObjDecoder::ParseMaterialFileDefinition(Status * /* status */) { return true; } +// Methods Triangulate() and IsNewEdge() are used for polygon triangulation and +// representation as an attribute for reconstruction in the decoder. +// +// Polygon reconstruction attribute is associated with every triangle corner and +// has values zero or one. Zero indicates that an edge opposite to the corner is +// present in the original mesh (dashed lines), and one indicates that the +// opposite edge has been added during polygon triangulation (dotted lines). +// +// Polygon triangulation is illustrated below. Pentagon ABCDE is split into +// three triangles ABC, ACD, ADE. It is sufficient to set polygon reconstruction +// attribute at corners ABC and ACD. The attribute at the second corner of all +// triangles except for the last is set to one. +// +// C D +// * --------- * +// /. 1 0 .| +// / . . | +// / . . | +// / 0 . . 0 | +// / . . | +// B * 1 . . | +// \ . . | +// \ 0 . 0 . | +// \ . . | +// \ . . | +// \.. 0 0 | +// *-----------* +// A E +// +inline int ObjDecoder::Triangulate(int tri_index, int tri_corner) { + return tri_corner == 0 ? 0 : tri_index + tri_corner; +} + +inline bool ObjDecoder::IsNewEdge(int tri_count, int tri_index, + int tri_corner) { + // All but the last triangle of the triangulated polygon have an added edge + // opposite of corner 1. + return tri_index != tri_count - 1 && tri_corner == 1; +} + } // namespace draco diff --git a/contrib/draco/src/draco/io/obj_decoder.h b/contrib/draco/src/draco/io/obj_decoder.h index baeab5b0c..18dc9aadd 100644 --- a/contrib/draco/src/draco/io/obj_decoder.h +++ b/contrib/draco/src/draco/io/obj_decoder.h @@ -34,8 +34,12 @@ class ObjDecoder { ObjDecoder(); // Decodes an obj file stored in the input file. - // Returns nullptr if the decoding failed. + // Optional argument |mesh_files| will be populated with all paths to files + // relevant to the loaded mesh. Status DecodeFromFile(const std::string &file_name, Mesh *out_mesh); + Status DecodeFromFile(const std::string &file_name, Mesh *out_mesh, + std::vector *mesh_files); + Status DecodeFromFile(const std::string &file_name, PointCloud *out_point_cloud); @@ -50,6 +54,8 @@ class ObjDecoder { // Flag for whether using metadata to record other information in the obj // file, e.g. material names, object names. void set_use_metadata(bool flag) { use_metadata_ = flag; } + // Enables preservation of polygons. + void set_preserve_polygons(bool flag) { preserve_polygons_ = flag; } protected: Status DecodeInternal(); @@ -88,6 +94,11 @@ class ObjDecoder { bool ParseMaterialFile(const std::string &file_name, Status *status); bool ParseMaterialFileDefinition(Status *status); + // Methods related to polygon triangulation and preservation. + static int Triangulate(int tri_index, int tri_corner); + static bool IsNewEdge(int tri_count, int tri_index, int tri_corner); + + private: // If set to true, the parser will count the number of various definitions // but it will not parse the actual data or add any new entries to the mesh. bool counting_mode_; @@ -102,7 +113,8 @@ class ObjDecoder { int tex_att_id_; int norm_att_id_; int material_att_id_; - int sub_obj_att_id_; // Attribute id for storing sub-objects. + int sub_obj_att_id_; // Attribute id for storing sub-objects. + int added_edge_att_id_; // Attribute id for polygon reconstruction. bool deduplicate_input_values_; @@ -116,6 +128,12 @@ class ObjDecoder { bool use_metadata_; + // Polygon preservation flags. + bool preserve_polygons_; + bool has_polygons_; + + std::vector *mesh_files_; + DecoderBuffer buffer_; // Data structure that stores the decoded data. |out_point_cloud_| must be diff --git a/contrib/draco/src/draco/io/obj_decoder_test.cc b/contrib/draco/src/draco/io/obj_decoder_test.cc index b19fe6e2c..a46a15a8b 100644 --- a/contrib/draco/src/draco/io/obj_decoder_test.cc +++ b/contrib/draco/src/draco/io/obj_decoder_test.cc @@ -54,6 +54,20 @@ class ObjDecoderTest : public ::testing::Test { return geometry; } + template + std::unique_ptr DecodeObjWithPolygons( + const std::string &file_name, bool regularize_quads, + bool store_added_edges_per_vertex) const { + const std::string path = GetTestFileFullPath(file_name); + ObjDecoder decoder; + decoder.set_preserve_polygons(true); + std::unique_ptr geometry(new Geometry()); + if (!decoder.DecodeFromFile(path, geometry.get()).ok()) { + return nullptr; + } + return geometry; + } + void test_decoding(const std::string &file_name) { const std::unique_ptr mesh(DecodeObj(file_name)); ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name; @@ -113,7 +127,7 @@ TEST_F(ObjDecoderTest, SubObjectsWithMetadata) { ASSERT_EQ(sub_obj_id, 2); } -TEST_F(ObjDecoderTest, QuadOBJ) { +TEST_F(ObjDecoderTest, QuadTriangulateOBJ) { // Tests loading an Obj with quad faces. const std::string file_name = "cube_quads.obj"; const std::unique_ptr mesh(DecodeObj(file_name)); @@ -124,11 +138,114 @@ TEST_F(ObjDecoderTest, QuadOBJ) { ASSERT_EQ(mesh->num_points(), 4 * 6); // Four points per quad face. } -TEST_F(ObjDecoderTest, ComplexPolyOBJ) { - // Tests that we fail to load an obj with complex polygon (expected failure). - const std::string file_name = "invalid/complex_poly.obj"; +TEST_F(ObjDecoderTest, QuadPreserveOBJ) { + // Tests loading an Obj with quad faces preserved as an attribute. + const std::string file_name = "cube_quads.obj"; + constexpr bool kRegularizeQuads = false; + constexpr bool kStoreAddedEdgesPerVertex = false; + const std::unique_ptr mesh(DecodeObjWithPolygons( + file_name, kRegularizeQuads, kStoreAddedEdgesPerVertex)); + ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name; + ASSERT_EQ(mesh->num_faces(), 12); + + ASSERT_EQ(mesh->num_attributes(), 4); + ASSERT_EQ(mesh->num_points(), 4 * 6); // Four points per quad face. + + // Expect a new generic attribute. + ASSERT_EQ(mesh->attribute(3)->attribute_type(), GeometryAttribute::GENERIC); + + // Expect the new attribute to have two values to describe old and new edge. + ASSERT_EQ(mesh->attribute(3)->size(), 2); + const auto new_edge_value = + mesh->attribute(3)->GetValue(AttributeValueIndex(0))[0]; + const auto old_edge_value = + mesh->attribute(3)->GetValue(AttributeValueIndex(1))[0]; + ASSERT_EQ(new_edge_value, 0); + ASSERT_EQ(old_edge_value, 1); + + // Expect one new edge on each of the six cube quads. + for (int i = 0; i < 6; i++) { + ASSERT_EQ(mesh->attribute(3)->mapped_index(PointIndex(4 * i + 0)), 0); + // New edge. + ASSERT_EQ(mesh->attribute(3)->mapped_index(PointIndex(4 * i + 1)), 1); + ASSERT_EQ(mesh->attribute(3)->mapped_index(PointIndex(4 * i + 2)), 0); + ASSERT_EQ(mesh->attribute(3)->mapped_index(PointIndex(4 * i + 3)), 0); + } + + // Expect metadata entry on the new attribute. + const AttributeMetadata *const metadata = + mesh->GetAttributeMetadataByAttributeId(3); + ASSERT_NE(metadata, nullptr); + ASSERT_TRUE(metadata->sub_metadatas().empty()); + ASSERT_EQ(metadata->entries().size(), 1); + std::string name; + metadata->GetEntryString("name", &name); + ASSERT_EQ(name, "added_edges"); +} + +TEST_F(ObjDecoderTest, OctagonTriangulatedOBJ) { + // Tests that we can load an obj with an octagon triangulated. + const std::string file_name = "octagon.obj"; const std::unique_ptr mesh(DecodeObj(file_name)); - ASSERT_EQ(mesh, nullptr); + ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name; + + ASSERT_EQ(mesh->num_attributes(), 1); + ASSERT_EQ(mesh->num_points(), 8); + ASSERT_EQ(mesh->attribute(0)->attribute_type(), GeometryAttribute::POSITION); + ASSERT_EQ(mesh->attribute(0)->size(), 8); +} + +TEST_F(ObjDecoderTest, OctagonPreservedOBJ) { + // Tests that we can load an obj with an octagon preserved as an attribute. + const std::string file_name = "octagon.obj"; + constexpr bool kRegularizeQuads = false; + constexpr bool kStoreAddedEdgesPerVertex = false; + const std::unique_ptr mesh(DecodeObjWithPolygons( + file_name, kRegularizeQuads, kStoreAddedEdgesPerVertex)); + ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name; + + ASSERT_EQ(mesh->num_attributes(), 2); + ASSERT_EQ(mesh->attribute(0)->attribute_type(), GeometryAttribute::POSITION); + ASSERT_EQ(mesh->attribute(0)->size(), 8); + + // Expect a new generic attribute. + ASSERT_EQ(mesh->attribute(1)->attribute_type(), GeometryAttribute::GENERIC); + + // There are four vertices with both old and new edges in their ring. + ASSERT_EQ(mesh->num_points(), 8 + 4); + + // Expect the new attribute to have two values to describe old and new edge. + ASSERT_EQ(mesh->attribute(1)->size(), 2); + const auto new_edge_value = + mesh->attribute(1)->GetValue(AttributeValueIndex(0))[0]; + const auto old_edge_value = + mesh->attribute(1)->GetValue(AttributeValueIndex(1))[0]; + ASSERT_EQ(new_edge_value, 0); + ASSERT_EQ(old_edge_value, 1); + + // Five new edges are introduced while triangulating as octagon. + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(0)), 0); + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(1)), 1); // New edge. + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(2)), 0); + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(3)), 1); // New edge. + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(4)), 0); + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(5)), 1); // New edge. + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(6)), 0); + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(7)), 1); // New edge. + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(8)), 0); + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(9)), 1); // New edge. + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(10)), 0); + ASSERT_EQ(mesh->attribute(1)->mapped_index(PointIndex(11)), 0); + + // Expect metadata entry on the new attribute. + const AttributeMetadata *const metadata = + mesh->GetAttributeMetadataByAttributeId(1); + ASSERT_NE(metadata, nullptr); + ASSERT_TRUE(metadata->sub_metadatas().empty()); + ASSERT_EQ(metadata->entries().size(), 1); + std::string name; + metadata->GetEntryString("name", &name); + ASSERT_EQ(name, "added_edges"); } TEST_F(ObjDecoderTest, EmptyNameOBJ) { @@ -167,7 +284,6 @@ TEST_F(ObjDecoderTest, WrongAttributeMapping) { TEST_F(ObjDecoderTest, TestObjDecodingAll) { // test if we can read all obj that are currently in test folder. test_decoding("bunny_norm.obj"); - // test_decoding("complex_poly.obj"); // not supported see test above test_decoding("cube_att.obj"); test_decoding("cube_att_partial.obj"); test_decoding("cube_att_sub_o.obj"); diff --git a/contrib/draco/src/draco/io/obj_encoder.cc b/contrib/draco/src/draco/io/obj_encoder.cc index 29c6ca8f0..1ddfd92bd 100644 --- a/contrib/draco/src/draco/io/obj_encoder.cc +++ b/contrib/draco/src/draco/io/obj_encoder.cc @@ -16,8 +16,10 @@ #include +#include "draco/attributes/geometry_attribute.h" #include "draco/io/file_writer_factory.h" #include "draco/io/file_writer_interface.h" +#include "draco/mesh/mesh_misc_functions.h" #include "draco/metadata/geometry_metadata.h" namespace draco { @@ -28,6 +30,7 @@ ObjEncoder::ObjEncoder() normal_att_(nullptr), material_att_(nullptr), sub_obj_att_(nullptr), + added_edges_att_(nullptr), out_buffer_(nullptr), in_point_cloud_(nullptr), in_mesh_(nullptr), @@ -78,11 +81,15 @@ bool ObjEncoder::EncodeInternal() { normal_att_ = nullptr; material_att_ = nullptr; sub_obj_att_ = nullptr; + added_edges_att_ = nullptr; current_sub_obj_id_ = -1; current_material_id_ = -1; if (!GetSubObjects()) { return false; } + if (in_mesh_ && !GetAddedEdges()) { + return false; + } if (!EncodeMaterialFileName()) { return false; } @@ -110,12 +117,38 @@ bool ObjEncoder::ExitAndCleanup(bool return_value) { normal_att_ = nullptr; material_att_ = nullptr; sub_obj_att_ = nullptr; + added_edges_att_ = nullptr; current_sub_obj_id_ = -1; current_material_id_ = -1; file_name_.clear(); return return_value; } +bool ObjEncoder::GetAddedEdges() { + const GeometryMetadata *mesh_metadata = in_mesh_->GetMetadata(); + if (!mesh_metadata) { + return true; + } + + // Try to get a per-corner attribute describing added edges. + { + const AttributeMetadata *att_metadata = + mesh_metadata->GetAttributeMetadataByStringEntry("name", "added_edges"); + if (att_metadata) { + const auto att = + in_mesh_->GetAttributeByUniqueId(att_metadata->att_unique_id()); + if (att->size() == 0 || att->num_components() != 1 || + att->data_type() != DataType::DT_UINT8) { + return false; + } + added_edges_att_ = att; + return true; + } + } + + return true; +} + bool ObjEncoder::GetSubObjects() { const GeometryMetadata *pc_metadata = in_point_cloud_->GetMetadata(); if (!pc_metadata) { @@ -137,7 +170,8 @@ bool ObjEncoder::GetSubObjects() { } sub_obj_att_ = in_point_cloud_->GetAttributeByUniqueId( sub_obj_metadata->att_unique_id()); - if (sub_obj_att_ == nullptr || sub_obj_att_->size() == 0) { + if (sub_obj_att_ == nullptr || sub_obj_att_->size() == 0 || + sub_obj_att_->num_components() != 1) { return false; } return true; @@ -236,17 +270,11 @@ bool ObjEncoder::EncodeNormals() { } bool ObjEncoder::EncodeFaces() { + if (added_edges_att_ != nullptr) { + return EncodePolygonalFaces(); + } for (FaceIndex i(0); i < in_mesh_->num_faces(); ++i) { - if (sub_obj_att_) { - if (!EncodeSubObject(i)) { - return false; - } - } - if (material_att_) { - if (!EncodeMaterial(i)) { - return false; - } - } + EncodeFaceAttributes(i); buffer()->Encode('f'); for (int j = 0; j < 3; ++j) { if (!EncodeFaceCorner(i, j)) { @@ -258,6 +286,56 @@ bool ObjEncoder::EncodeFaces() { return true; } +bool ObjEncoder::EncodePolygonalFaces() { + // TODO(vytyaz): This could be a much smaller set of visited face indices. + std::vector triangle_visited(in_mesh_->num_faces(), false); + PolygonEdges polygon_edges; + std::unique_ptr corner_table = + CreateCornerTableFromPositionAttribute(in_mesh_); + for (FaceIndex fi(0); fi < in_mesh_->num_faces(); ++fi) { + EncodeFaceAttributes(fi); + // Reconstruct polygon from the added edges attribute if available. + polygon_edges.clear(); + FindOriginalFaceEdges(fi, *corner_table, &triangle_visited, &polygon_edges); + + // Polygon edges could be empty if this triangle has been visited as part + // of a polygon discovery that started from an earler face. + if (polygon_edges.empty()) { + continue; + } + + // Traverse a polygon by following its edges. The starting point is not + // guaranteed to be the same as in the original polygon. It is + // deterministic, however, and defined by std::map behavior. + const AttributeValueIndex first_position_index = + polygon_edges.begin()->first; + AttributeValueIndex position_index = first_position_index; + buffer()->Encode('f'); + do { + // Get the next polygon point index by following polygon edge. + const PointIndex pi = polygon_edges[position_index]; + EncodeFaceCorner(pi); + position_index = pos_att_->mapped_index(pi).value(); + } while (position_index != first_position_index); + buffer()->Encode("\n", 1); + } + return true; +} + +bool ObjEncoder::EncodeFaceAttributes(FaceIndex face_id) { + if (sub_obj_att_) { + if (!EncodeSubObject(face_id)) { + return false; + } + } + if (material_att_) { + if (!EncodeMaterial(face_id)) { + return false; + } + } + return true; +} + bool ObjEncoder::EncodeMaterial(FaceIndex face_id) { int material_id = 0; // Pick the first corner, all corners of a face should have same id. @@ -304,8 +382,12 @@ bool ObjEncoder::EncodeSubObject(FaceIndex face_id) { } bool ObjEncoder::EncodeFaceCorner(FaceIndex face_id, int local_corner_id) { - buffer()->Encode(' '); const PointIndex vert_index = in_mesh_->face(face_id)[local_corner_id]; + return EncodeFaceCorner(vert_index); +} + +bool ObjEncoder::EncodeFaceCorner(PointIndex vert_index) { + buffer()->Encode(' '); // Note that in the OBJ format, all indices are encoded starting from index 1. // Encode position index. EncodeInt(pos_att_->mapped_index(vert_index).value() + 1); @@ -343,4 +425,67 @@ void ObjEncoder::EncodeInt(int32_t val) { buffer()->Encode(num_buffer_, strlen(num_buffer_)); } +bool ObjEncoder::IsNewEdge(const CornerTable &ct, CornerIndex ci) const { + const PointIndex pi = in_mesh_->CornerToPointId(ci); + if (added_edges_att_ != nullptr) { + uint8_t value; + added_edges_att_->GetMappedValue(pi, &value); + return value == 1; + } + return false; +} + +void ObjEncoder::FindOriginalFaceEdges(FaceIndex face_index, + const CornerTable &corner_table, + std::vector *triangle_visited, + PolygonEdges *polygon_edges) { + // Do not add any edges if this triangular face has already been visited. + if ((*triangle_visited)[face_index.value()]) { + return; + } + (*triangle_visited)[face_index.value()] = true; + const Mesh::Face &face = in_mesh_->face(face_index); + for (size_t c = 0; c < 3; c++) { + // Check for added edge using this corner. + const CornerIndex ci = corner_table.FirstCorner(face_index) + c; + const CornerIndex co = corner_table.Opposite(ci); + bool is_new_edge = IsNewEdge(corner_table, ci); + + // Check for the new edge using the opposite corner. + if (!is_new_edge && co != kInvalidCornerIndex) { + is_new_edge = IsNewEdge(corner_table, co); + } + // The new edge may become a boundary edge when a degenerate triangle + // created by polygon triangulation is removed by Draco encoder, hence |co| + // is checked below. This can happen when an isolated (boundary) quad only + // has three distinct vertex positions. + // + // TODO(vytyaz): Fix polygon reconstruction with other possible cases of + // degenerate triangles. There are two known sources of degenerate triangles + // that affect polygon reconstruction: + // + // 1. Degenerate triangles created during polygon triangulation are removed + // by Draco encoder, which invalidates the "added_edges" attribute. + // Solution is to discard those triangles before creating the attribute. + // + // 2. Degenerate triangles created by position quantization are encoded and + // decoded by Draco, but not captured into the |corner_table|, causing a + // mismatch between the corner table and the "added_edges" attribute. + // Solution is to use corner table from draco::MeshDecoder here. + // + if (is_new_edge && co != kInvalidCornerIndex) { + // Visit triangle across the new edge. + const FaceIndex opposite_face_index = corner_table.Face(co); + FindOriginalFaceEdges(opposite_face_index, corner_table, triangle_visited, + polygon_edges); + } else { + // Insert the original edge to the map. + const PointIndex point_from = face[(c + 1) % 3]; + const PointIndex point_to = face[(c + 2) % 3]; + polygon_edges->insert( + {PositionIndex(pos_att_->mapped_index(point_from)), point_to}); + } + } +} + } // namespace draco diff --git a/contrib/draco/src/draco/io/obj_encoder.h b/contrib/draco/src/draco/io/obj_encoder.h index 509d39baf..1d67b5306 100644 --- a/contrib/draco/src/draco/io/obj_encoder.h +++ b/contrib/draco/src/draco/io/obj_encoder.h @@ -18,6 +18,7 @@ #include #include "draco/core/encoder_buffer.h" +#include "draco/mesh/corner_table.h" #include "draco/mesh/mesh.h" namespace draco { @@ -44,19 +45,30 @@ class ObjEncoder { bool ExitAndCleanup(bool return_value); private: + typedef AttributeValueIndex PositionIndex; + typedef std::map PolygonEdges; + bool GetAddedEdges(); bool GetSubObjects(); bool EncodeMaterialFileName(); bool EncodePositions(); bool EncodeTextureCoordinates(); bool EncodeNormals(); bool EncodeFaces(); + bool EncodePolygonalFaces(); + bool EncodeFaceAttributes(FaceIndex face_id); bool EncodeSubObject(FaceIndex face_id); bool EncodeMaterial(FaceIndex face_id); bool EncodeFaceCorner(FaceIndex face_id, int local_corner_id); + bool EncodeFaceCorner(PointIndex vert_index); void EncodeFloat(float val); void EncodeFloatList(float *vals, int num_vals); void EncodeInt(int32_t val); + bool IsNewEdge(const CornerTable &ct, CornerIndex ci) const; + void FindOriginalFaceEdges(FaceIndex face_index, + const CornerTable &corner_table, + std::vector *triangle_visited, + PolygonEdges *polygon_edges); // Various attributes used by the encoder. If an attribute is not used, it is // set to nullptr. @@ -66,6 +78,9 @@ class ObjEncoder { const PointAttribute *material_att_; const PointAttribute *sub_obj_att_; + // Stores per-corner triangulation information for polygon reconstruction. + const PointAttribute *added_edges_att_; + // Buffer used for encoding float/int numbers. char num_buffer_[20]; diff --git a/contrib/draco/src/draco/io/obj_encoder_test.cc b/contrib/draco/src/draco/io/obj_encoder_test.cc index 4838e56ca..782983fad 100644 --- a/contrib/draco/src/draco/io/obj_encoder_test.cc +++ b/contrib/draco/src/draco/io/obj_encoder_test.cc @@ -16,10 +16,12 @@ #include +#include "draco/attributes/geometry_attribute.h" #include "draco/core/draco_test_base.h" #include "draco/core/draco_test_utils.h" #include "draco/io/file_reader_factory.h" #include "draco/io/file_reader_interface.h" +#include "draco/io/file_utils.h" #include "draco/io/obj_decoder.h" namespace draco { @@ -27,6 +29,8 @@ namespace draco { class ObjEncoderTest : public ::testing::Test { protected: void CompareMeshes(const Mesh *mesh0, const Mesh *mesh1) { + ASSERT_NE(mesh0, nullptr); + ASSERT_NE(mesh1, nullptr); ASSERT_EQ(mesh0->num_faces(), mesh1->num_faces()); ASSERT_EQ(mesh0->num_attributes(), mesh1->num_attributes()); for (size_t att_id = 0; att_id < mesh0->num_attributes(); ++att_id) { @@ -107,4 +111,34 @@ TEST_F(ObjEncoderTest, TestObjEncodingAll) { test_encoding("two_faces_312.obj"); } +TEST_F(ObjEncoderTest, TestObjOctagonPreserved) { + // Test verifies that OBJ encoder can reconstruct and encode an octagon. + // Decode triangulated octagon and an extra attribute for reconstruction. + std::unique_ptr mesh = + ReadMeshFromTestFile("octagon_preserved.drc"); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->num_faces(), 6); + ASSERT_EQ(mesh->NumNamedAttributes(GeometryAttribute::GENERIC), 1); + ASSERT_NE(mesh->GetMetadata()->GetAttributeMetadataByStringEntry( + "name", "added_edges"), + nullptr); + + // Reconstruct octagon and encode it into an OBJ file. + draco::ObjEncoder obj_encoder; + ASSERT_TRUE(obj_encoder.EncodeToFile( + *mesh, draco::GetTestTempFileFullPath("encoded.obj"))); + + // Read encoded OBJ file and golden OBJ file contents into buffers. + std::vector data_encoded; + std::vector data_golden; + ASSERT_TRUE( + ReadFileToBuffer(GetTestTempFileFullPath("encoded.obj"), &data_encoded)); + ASSERT_TRUE(ReadFileToBuffer(GetTestFileFullPath("octagon_preserved.obj"), + &data_golden)); + + // Check that encoded OBJ file contents are correct. + ASSERT_EQ(data_encoded.size(), data_golden.size()); + ASSERT_EQ(data_encoded, data_golden); +} + } // namespace draco diff --git a/contrib/draco/src/draco/io/parser_utils.cc b/contrib/draco/src/draco/io/parser_utils.cc index 12afacff6..378de7378 100644 --- a/contrib/draco/src/draco/io/parser_utils.cc +++ b/contrib/draco/src/draco/io/parser_utils.cc @@ -203,31 +203,40 @@ void ParseLine(DecoderBuffer *buffer, std::string *out_string) { out_string->clear(); } char c; - bool delim_reached = false; + int num_delims = 0; + char last_delim; while (buffer->Peek(&c)) { - // Check if |c| is a delimeter. We want to parse all delimeters until we - // reach a non-delimeter symbol. (E.g. we want to ignore '\r\n' at the end - // of the line). + // Check if |c| is a delimiter symbol. We want to identify all possible + // delimiters that can occur on different platforms (i.e. we want to detect + // '\r\n', '\r', '\n'). const bool is_delim = (c == '\r' || c == '\n'); - // If |c| is a delimeter or it is a non-delimeter symbol before any - // delimeter was found, we advance the buffer to the next character. - if (is_delim || !delim_reached) { - buffer->Advance(1); + if (is_delim) { + if (num_delims == 0) { + last_delim = c; + } else if (num_delims == 1) { + // We already parsed either '\r' or '\n'. Ensure the new delim symbol is + // '\n' and different from the previous symbol. + if (c == last_delim || c != '\n') { + return; // Same delimiter symbol already processed. + } + } else { + // Too many delimiter symbols. + return; + } + num_delims++; } - if (is_delim) { - // Mark that we found a delimeter symbol. - delim_reached = true; - continue; - } - if (delim_reached) { - // We reached a non-delimeter symbol after a delimeter was already found. + if (!is_delim && num_delims > 0) { + // We reached a non-delimiter symbol after a delimiter was already found. // Stop the parsing. return; } - // Otherwise we put the non-delimeter symbol into the output string. - if (out_string) { + + buffer->Advance(1); + + // We put the non-delimiter symbol into the output string. + if (!is_delim && out_string) { out_string->push_back(c); } } diff --git a/contrib/draco/src/draco/io/ply_decoder_test.cc b/contrib/draco/src/draco/io/ply_decoder_test.cc index 97977c8cc..1dd70d5cb 100644 --- a/contrib/draco/src/draco/io/ply_decoder_test.cc +++ b/contrib/draco/src/draco/io/ply_decoder_test.cc @@ -88,6 +88,7 @@ TEST_F(PlyDecoderTest, TestPlyDecodingAll) { // test_decoding("test_pos_color.ply"); // tested test_decoding("cube_quads.ply"); test_decoding("Box.ply"); + test_decoding("delim_test.ply"); } } // namespace draco diff --git a/contrib/draco/src/draco/io/ply_encoder.cc b/contrib/draco/src/draco/io/ply_encoder.cc index 2f6a1a2a8..0fe611f1c 100644 --- a/contrib/draco/src/draco/io/ply_encoder.cc +++ b/contrib/draco/src/draco/io/ply_encoder.cc @@ -143,7 +143,8 @@ bool PlyEncoder::EncodeInternal() { buffer()->Encode(header_str.data(), header_str.length()); // Store point attributes. - for (PointIndex v(0); v < in_point_cloud_->num_points(); ++v) { + const int num_points = in_point_cloud_->num_points(); + for (PointIndex v(0); v < num_points; ++v) { const auto *const pos_att = in_point_cloud_->attribute(pos_att_id); buffer()->Encode(pos_att->GetAddress(pos_att->mapped_index(v)), pos_att->byte_stride()); @@ -166,9 +167,13 @@ bool PlyEncoder::EncodeInternal() { buffer()->Encode(static_cast(3)); const auto &f = in_mesh_->face(i); - buffer()->Encode(f[0]); - buffer()->Encode(f[1]); - buffer()->Encode(f[2]); + for (int c = 0; c < 3; ++c) { + if (f[c] >= num_points) { + // Invalid point stored on the |in_mesh_| face. + return false; + } + buffer()->Encode(f[c]); + } if (tex_coord_att_id >= 0) { // Two coordinates for every corner -> 6. diff --git a/contrib/draco/src/draco/io/ply_reader_test.cc b/contrib/draco/src/draco/io/ply_reader_test.cc index 05ff63dd4..9612f6377 100644 --- a/contrib/draco/src/draco/io/ply_reader_test.cc +++ b/contrib/draco/src/draco/io/ply_reader_test.cc @@ -39,7 +39,7 @@ TEST_F(PlyReaderTest, TestReader) { buf.Init(data.data(), data.size()); PlyReader reader; Status status = reader.Read(&buf); - ASSERT_TRUE(status.ok()) << status; + DRACO_ASSERT_OK(status); ASSERT_EQ(reader.num_elements(), 2); ASSERT_EQ(reader.element(0).num_properties(), 7); ASSERT_EQ(reader.element(1).num_properties(), 1); @@ -64,14 +64,14 @@ TEST_F(PlyReaderTest, TestReaderAscii) { buf.Init(data.data(), data.size()); PlyReader reader; Status status = reader.Read(&buf); - ASSERT_TRUE(status.ok()) << status; + DRACO_ASSERT_OK(status); const std::string file_name_ascii = "test_pos_color_ascii.ply"; const std::vector data_ascii = ReadPlyFile(file_name_ascii); buf.Init(data_ascii.data(), data_ascii.size()); PlyReader reader_ascii; status = reader_ascii.Read(&buf); - ASSERT_TRUE(status.ok()) << status; + DRACO_ASSERT_OK(status); ASSERT_EQ(reader.num_elements(), reader_ascii.num_elements()); ASSERT_EQ(reader.element(0).num_properties(), reader_ascii.element(0).num_properties()); @@ -96,7 +96,7 @@ TEST_F(PlyReaderTest, TestReaderExtraWhitespace) { buf.Init(data.data(), data.size()); PlyReader reader; Status status = reader.Read(&buf); - ASSERT_TRUE(status.ok()) << status; + DRACO_ASSERT_OK(status); ASSERT_EQ(reader.num_elements(), 2); ASSERT_EQ(reader.element(0).num_properties(), 7); @@ -122,7 +122,7 @@ TEST_F(PlyReaderTest, TestReaderMoreDataTypes) { buf.Init(data.data(), data.size()); PlyReader reader; Status status = reader.Read(&buf); - ASSERT_TRUE(status.ok()) << status; + DRACO_ASSERT_OK(status); ASSERT_EQ(reader.num_elements(), 2); ASSERT_EQ(reader.element(0).num_properties(), 7); diff --git a/contrib/draco/src/draco/io/scene_io.cc b/contrib/draco/src/draco/io/scene_io.cc new file mode 100644 index 000000000..e41d2e1fa --- /dev/null +++ b/contrib/draco/src/draco/io/scene_io.cc @@ -0,0 +1,127 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/scene_io.h" + +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/io/file_utils.h" +#include "draco/io/gltf_decoder.h" +#include "draco/io/gltf_encoder.h" +#include "draco/io/obj_encoder.h" +#include "draco/io/ply_encoder.h" + +namespace draco { + +enum SceneFileFormat { UNKNOWN, GLTF, USD, PLY, OBJ }; + +SceneFileFormat GetSceneFileFormat(const std::string &file_name) { + const std::string extension = LowercaseFileExtension(file_name); + if (extension == "gltf" || extension == "glb") { + return GLTF; + } + if (extension == "usd" || extension == "usda" || extension == "usdc" || + extension == "usdz") { + return USD; + } + if (extension == "obj") { + return OBJ; + } + if (extension == "ply") { + return PLY; + } + return UNKNOWN; +} + +StatusOr> ReadSceneFromFile( + const std::string &file_name) { + return ReadSceneFromFile(file_name, nullptr); +} + +StatusOr> ReadSceneFromFile( + const std::string &file_name, std::vector *scene_files) { + std::unique_ptr scene(new Scene()); + switch (GetSceneFileFormat(file_name)) { + case GLTF: { + GltfDecoder decoder; + return decoder.DecodeFromFileToScene(file_name, scene_files); + } + case USD: { + return Status(Status::DRACO_ERROR, "USD is not supported yet."); + } + default: { + return Status(Status::DRACO_ERROR, "Unknown input file format."); + } + } +} + +Status WriteSceneToFile(const std::string &file_name, const Scene &scene) { + Options options; + return WriteSceneToFile(file_name, scene, options); +} + +Status WriteSceneToFile(const std::string &file_name, const Scene &scene, + const Options &options) { + const std::string extension = LowercaseFileExtension(file_name); + std::string folder_path; + std::string out_file_name; + draco::SplitPath(file_name, &folder_path, &out_file_name); + const auto format = GetSceneFileFormat(file_name); + switch (format) { + case GLTF: { + GltfEncoder encoder; + if (!encoder.EncodeToFile(scene, file_name, folder_path)) { + return Status(Status::DRACO_ERROR, "Failed to encode the scene."); + } + return OkStatus(); + } + case USD: { + return Status(Status::DRACO_ERROR, "USD is not supported yet."); + } + case PLY: + case OBJ: { + // Convert the scene to mesh and save the scene as a mesh. For now we do + // that by converting the scene to GLB and decoding the GLB into a mesh. + GltfEncoder gltf_encoder; + EncoderBuffer buffer; + DRACO_RETURN_IF_ERROR(gltf_encoder.EncodeToBuffer(scene, &buffer)); + GltfDecoder gltf_decoder; + DecoderBuffer dec_buffer; + dec_buffer.Init(buffer.data(), buffer.size()); + DRACO_ASSIGN_OR_RETURN(auto mesh, + gltf_decoder.DecodeFromBuffer(&dec_buffer)); + if (format == PLY) { + PlyEncoder ply_encoder; + if (!ply_encoder.EncodeToFile(*mesh, file_name)) { + return ErrorStatus("Failed to encode the scene as PLY."); + } + } + if (format == OBJ) { + ObjEncoder obj_encoder; + if (!obj_encoder.EncodeToFile(*mesh, file_name)) { + return ErrorStatus("Failed to encode the scene as OBJ."); + } + } + return OkStatus(); + } + default: { + return Status(Status::DRACO_ERROR, "Unknown output file format."); + } + } +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/io/scene_io.h b/contrib/draco/src/draco/io/scene_io.h new file mode 100644 index 000000000..964faac3c --- /dev/null +++ b/contrib/draco/src/draco/io/scene_io.h @@ -0,0 +1,55 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_SCENE_IO_H_ +#define DRACO_IO_SCENE_IO_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/core/options.h" +#include "draco/core/status_or.h" +#include "draco/scene/scene.h" + +namespace draco { + +// Reads a scene from a file. Currently only GLTF 2.0 scene files are supported. +// The second form returns the files associated with the scene via the +// |scene_files| argument. +StatusOr> ReadSceneFromFile( + const std::string &file_name); +StatusOr> ReadSceneFromFile( + const std::string &file_name, std::vector *scene_files); + +// Writes a scene into a file. +Status WriteSceneToFile(const std::string &file_name, const Scene &scene); + +// Writes a scene into a file, configurable with |options|. +// +// Supported options: +// +// force_usd_vertex_interpolation= - forces implicit vertex +// interpolation while exporting to USD +// (default = false) +// +Status WriteSceneToFile(const std::string &file_name, const Scene &scene, + const Options &options); + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_SCENE_IO_H_ diff --git a/contrib/draco/src/draco/io/scene_io_test.cc b/contrib/draco/src/draco/io/scene_io_test.cc new file mode 100644 index 000000000..828065693 --- /dev/null +++ b/contrib/draco/src/draco/io/scene_io_test.cc @@ -0,0 +1,86 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/scene_io.h" + +#include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/draco_test_utils.h" +#include "draco/io/file_utils.h" +#include "draco/io/mesh_io.h" + +namespace { + +TEST(SceneTest, TestSceneIO) { + // A simple test that verifies that the scene is loaded and saved using the + // scene_io.h API. + const std::string file_name = + draco::GetTestFileFullPath("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + draco::StatusOr> maybe_scene = + draco::ReadSceneFromFile(file_name); + ASSERT_TRUE(maybe_scene.status().ok()); + std::unique_ptr scene = std::move(maybe_scene).value(); + ASSERT_NE(scene, nullptr); + + const std::string out_file_name = + draco::GetTestTempFileFullPath("out_scene.gltf"); + ASSERT_TRUE(draco::WriteSceneToFile(out_file_name, *scene).ok()); + + // Ensure all files related to the scene are saved. + ASSERT_GT(draco::GetFileSize(out_file_name), 0); + ASSERT_GT( + draco::GetFileSize(draco::GetTestTempFileFullPath("CesiumMilkTruck.png")), + 0); + ASSERT_GT(draco::GetFileSize(draco::GetTestTempFileFullPath("buffer0.bin")), + 0); +} + +TEST(SceneTest, TestSaveToPly) { + // A simple test that verifies that a loaded scene can be stored in a PLY file + // format. + const std::string file_name = + draco::GetTestFileFullPath("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr scene, + draco::ReadSceneFromFile(file_name)); + + const std::string out_file_name = + draco::GetTestTempFileFullPath("out_scene.ply"); + DRACO_ASSERT_OK(draco::WriteSceneToFile(out_file_name, *scene)); + + // Verify that we can read the saved mesh. + DRACO_ASSIGN_OR_ASSERT(auto mesh, draco::ReadMeshFromFile(out_file_name)); + ASSERT_NE(mesh, nullptr); +} + +TEST(SceneTest, TestSaveToObj) { + // A simple test that verifies that a loaded scene can be stored in an OBJ + // file format. + const std::string file_name = + draco::GetTestFileFullPath("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr scene, + draco::ReadSceneFromFile(file_name)); + + const std::string out_file_name = + draco::GetTestTempFileFullPath("out_scene.obj"); + DRACO_ASSERT_OK(draco::WriteSceneToFile(out_file_name, *scene)); + + // Verify that we can read the saved mesh. + DRACO_ASSIGN_OR_ASSERT(auto mesh, draco::ReadMeshFromFile(out_file_name)); + ASSERT_NE(mesh, nullptr); +} + +} // namespace +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/io/stdio_file_reader_test.cc b/contrib/draco/src/draco/io/stdio_file_reader_test.cc index 487819a02..212945f90 100644 --- a/contrib/draco/src/draco/io/stdio_file_reader_test.cc +++ b/contrib/draco/src/draco/io/stdio_file_reader_test.cc @@ -9,7 +9,7 @@ namespace { TEST(StdioFileReaderTest, FailOpen) { EXPECT_EQ(StdioFileReader::Open(""), nullptr); - EXPECT_EQ(StdioFileReader::Open("fake file"), nullptr); + EXPECT_EQ(StdioFileReader::Open("stdio reader fake file"), nullptr); } TEST(StdioFileReaderTest, Open) { diff --git a/contrib/draco/src/draco/io/stl_decoder.cc b/contrib/draco/src/draco/io/stl_decoder.cc new file mode 100644 index 000000000..1e5d3a938 --- /dev/null +++ b/contrib/draco/src/draco/io/stl_decoder.cc @@ -0,0 +1,77 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/stl_decoder.h" + +#include + +#include "draco/core/macros.h" +#include "draco/core/status.h" +#include "draco/core/status_or.h" +#include "draco/io/file_utils.h" +#include "draco/mesh/triangle_soup_mesh_builder.h" + +namespace draco { + +StatusOr> StlDecoder::DecodeFromFile( + const std::string &file_name) { + std::vector data; + if (!ReadFileToBuffer(file_name, &data)) { + return Status(Status::IO_ERROR, "Unable to read input file."); + } + DecoderBuffer buffer; + buffer.Init(data.data(), data.size()); + return DecodeFromBuffer(&buffer); +} + +StatusOr> StlDecoder::DecodeFromBuffer( + DecoderBuffer *buffer) { + if (!strncmp(buffer->data_head(), "solid ", 6)) { + return Status(Status::IO_ERROR, + "Currently only binary STL files are supported."); + } + buffer->Advance(80); + uint32_t face_count; + buffer->Decode(&face_count, 4); + + TriangleSoupMeshBuilder builder; + builder.Start(face_count); + + const int32_t pos_att_id = + builder.AddAttribute(GeometryAttribute::POSITION, 3, DT_FLOAT32); + const int32_t norm_att_id = + builder.AddAttribute(GeometryAttribute::NORMAL, 3, DT_FLOAT32); + + for (uint32_t i = 0; i < face_count; i++) { + float data[48]; + buffer->Decode(data, 48); + uint16_t unused; + buffer->Decode(&unused, 2); + + builder.SetPerFaceAttributeValueForFace( + norm_att_id, draco::FaceIndex(i), + draco::Vector3f(data[0], data[1], data[2]).data()); + + builder.SetAttributeValuesForFace( + pos_att_id, draco::FaceIndex(i), + draco::Vector3f(data[3], data[4], data[5]).data(), + draco::Vector3f(data[6], data[7], data[8]).data(), + draco::Vector3f(data[9], data[10], data[11]).data()); + } + + std::unique_ptr mesh = builder.Finalize(); + return mesh; +} + +} // namespace draco diff --git a/contrib/draco/src/draco/io/stl_decoder.h b/contrib/draco/src/draco/io/stl_decoder.h new file mode 100644 index 000000000..44b35d849 --- /dev/null +++ b/contrib/draco/src/draco/io/stl_decoder.h @@ -0,0 +1,38 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_STL_DECODER_H_ +#define DRACO_IO_STL_DECODER_H_ + +#include + +#include "draco/core/decoder_buffer.h" +#include "draco/core/status.h" +#include "draco/core/status_or.h" +#include "draco/draco_features.h" +#include "draco/mesh/mesh.h" + +namespace draco { + +// Decodes an STL file into draco::Mesh (or draco::PointCloud if the +// connectivity data is not needed). +class StlDecoder { + public: + StatusOr> DecodeFromFile(const std::string &file_name); + StatusOr> DecodeFromBuffer(DecoderBuffer *buffer); +}; + +} // namespace draco + +#endif // DRACO_IO_STL_DECODER_H_ diff --git a/contrib/draco/src/draco/io/stl_decoder_test.cc b/contrib/draco/src/draco/io/stl_decoder_test.cc new file mode 100644 index 000000000..886881925 --- /dev/null +++ b/contrib/draco/src/draco/io/stl_decoder_test.cc @@ -0,0 +1,49 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/stl_decoder.h" + +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace draco { + +class StlDecoderTest : public ::testing::Test { + protected: + void test_decoding(const std::string &file_name) { + const std::string path = GetTestFileFullPath(file_name); + StlDecoder decoder; + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr mesh, + decoder.DecodeFromFile(path)); + ASSERT_GT(mesh->num_faces(), 0); + ASSERT_GT(mesh->num_points(), 0); + } + + void test_decoding_should_fail(const std::string &file_name) { + StlDecoder decoder; + StatusOr> statusOrMesh = + decoder.DecodeFromFile(GetTestFileFullPath(file_name)); + ASSERT_FALSE(statusOrMesh.ok()); + } +}; + +TEST_F(StlDecoderTest, TestStlDecoding) { + test_decoding("STL/bunny.stl"); + test_decoding("STL/test_sphere.stl"); + test_decoding_should_fail("STL/test_sphere_ascii.stl"); +} + +} // namespace draco diff --git a/contrib/draco/src/draco/io/stl_encoder.cc b/contrib/draco/src/draco/io/stl_encoder.cc new file mode 100644 index 000000000..5aa4a0a97 --- /dev/null +++ b/contrib/draco/src/draco/io/stl_encoder.cc @@ -0,0 +1,111 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/stl_encoder.h" + +#include +#include +#include +#include + +#include "draco/io/file_writer_factory.h" +#include "draco/io/file_writer_interface.h" + +namespace draco { + +StlEncoder::StlEncoder() + : out_buffer_(nullptr), in_point_cloud_(nullptr), in_mesh_(nullptr) {} + +Status StlEncoder::EncodeToFile(const Mesh &mesh, + const std::string &file_name) { + in_mesh_ = &mesh; + std::unique_ptr file = + FileWriterFactory::OpenWriter(file_name); + if (!file) { + return Status(Status::IO_ERROR, "File couldn't be opened"); + } + // Encode the mesh into a buffer. + EncoderBuffer buffer; + DRACO_RETURN_IF_ERROR(EncodeToBuffer(mesh, &buffer)); + // Write the buffer into the file. + file->Write(buffer.data(), buffer.size()); + return OkStatus(); +} + +Status StlEncoder::EncodeToBuffer(const Mesh &mesh, EncoderBuffer *out_buffer) { + in_mesh_ = &mesh; + out_buffer_ = out_buffer; + Status s = EncodeInternal(); + in_mesh_ = nullptr; // cleanup + in_point_cloud_ = nullptr; + out_buffer_ = nullptr; + return s; +} + +Status StlEncoder::EncodeInternal() { + // Write STL header. + std::stringstream out; + out << std::left << std::setw(80) + << "generated using Draco"; // header is 80 bytes fixed size. + const std::string header_str = out.str(); + buffer()->Encode(header_str.data(), header_str.length()); + + uint32_t num_faces = in_mesh_->num_faces(); + buffer()->Encode(&num_faces, 4); + + std::vector stl_face; + + const int pos_att_id = + in_mesh_->GetNamedAttributeId(GeometryAttribute::POSITION); + + if (pos_att_id < 0) { + return ErrorStatus("Mesh is missing the position attribute."); + } + + if (in_mesh_->attribute(pos_att_id)->data_type() != DT_FLOAT32) { + return ErrorStatus("Mesh position attribute is not of type float32."); + } + + uint16_t unused = 0; + + if (in_mesh_) { + for (FaceIndex i(0); i < in_mesh_->num_faces(); ++i) { + const auto &f = in_mesh_->face(i); + const auto *const pos_att = in_mesh_->attribute(pos_att_id); + + // The normal attribute can contain arbitrary normals that may not + // correspond to the winding of the face. + // Therefor we simply always calculate them + // using the points of the triangle face: norm(cross(p2-p1, p3-p1)) + + Vector3f pos[3]; + pos_att->GetMappedValue(f[0], &pos[0][0]); + pos_att->GetMappedValue(f[1], &pos[1][0]); + pos_att->GetMappedValue(f[2], &pos[2][0]); + Vector3f norm = CrossProduct(pos[1] - pos[0], pos[2] - pos[0]); + norm.Normalize(); + buffer()->Encode(norm.data(), sizeof(float) * 3); + + for (int c = 0; c < 3; ++c) { + buffer()->Encode(pos_att->GetAddress(pos_att->mapped_index(f[c])), + pos_att->byte_stride()); + } + + buffer()->Encode(&unused, 2); + } + } + return OkStatus(); +} + +} // namespace draco diff --git a/contrib/draco/src/draco/io/stl_encoder.h b/contrib/draco/src/draco/io/stl_encoder.h new file mode 100644 index 000000000..8b185b738 --- /dev/null +++ b/contrib/draco/src/draco/io/stl_encoder.h @@ -0,0 +1,52 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_STL_ENCODER_H_ +#define DRACO_IO_STL_ENCODER_H_ + +#include + +#include "draco/core/encoder_buffer.h" +#include "draco/draco_features.h" +#include "draco/mesh/mesh.h" + +namespace draco { + +// Class for encoding draco::Mesh into the STL file format. +class StlEncoder { + public: + StlEncoder(); + + // Encodes the mesh and saves it into a file. + // Returns false when either the encoding failed or when the file couldn't be + // opened. + Status EncodeToFile(const Mesh &mesh, const std::string &file_name); + + // Encodes the mesh into a buffer. + Status EncodeToBuffer(const Mesh &mesh, EncoderBuffer *out_buffer); + + protected: + Status EncodeInternal(); + EncoderBuffer *buffer() const { return out_buffer_; } + + private: + EncoderBuffer *out_buffer_; + + const PointCloud *in_point_cloud_; + const Mesh *in_mesh_; +}; + +} // namespace draco + +#endif // DRACO_IO_STL_ENCODER_H_ diff --git a/contrib/draco/src/draco/io/stl_encoder_test.cc b/contrib/draco/src/draco/io/stl_encoder_test.cc new file mode 100644 index 000000000..da6298d64 --- /dev/null +++ b/contrib/draco/src/draco/io/stl_encoder_test.cc @@ -0,0 +1,78 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/stl_encoder.h" + +#include +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/io/file_reader_factory.h" +#include "draco/io/file_reader_interface.h" +#include "draco/io/stl_decoder.h" + +namespace draco { + +class StlEncoderTest : public ::testing::Test { + protected: + void CompareMeshes(const Mesh *mesh0, const Mesh *mesh1) { + ASSERT_EQ(mesh0->num_faces(), mesh1->num_faces()); + ASSERT_EQ(mesh0->num_attributes(), mesh1->num_attributes()); + for (size_t att_id = 0; att_id < mesh0->num_attributes(); ++att_id) { + ASSERT_EQ(mesh0->attribute(att_id)->size(), + mesh1->attribute(att_id)->size()); + } + } + + // Encode a mesh using the StlEncoder and then decode to verify the encoding. + std::unique_ptr EncodeAndDecodeMesh(const Mesh *mesh) { + EncoderBuffer encoder_buffer; + StlEncoder encoder; + Status status = encoder.EncodeToBuffer(*mesh, &encoder_buffer); + if (!status.ok()) { + return nullptr; + } + + DecoderBuffer decoder_buffer; + decoder_buffer.Init(encoder_buffer.data(), encoder_buffer.size()); + StlDecoder decoder; + StatusOr> status_or_mesh = + decoder.DecodeFromBuffer(&decoder_buffer); + if (!status_or_mesh.ok()) { + return nullptr; + } + std::unique_ptr decoded_mesh = std::move(status_or_mesh).value(); + return decoded_mesh; + } + + void test_encoding(const std::string &file_name) { + const std::unique_ptr mesh(ReadMeshFromTestFile(file_name, true)); + + ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name; + ASSERT_GT(mesh->num_faces(), 0); + + const std::unique_ptr decoded_mesh = EncodeAndDecodeMesh(mesh.get()); + CompareMeshes(mesh.get(), decoded_mesh.get()); + } +}; + +TEST_F(StlEncoderTest, TestStlEncoding) { + // Test decoded mesh from encoded stl file stays the same. + test_encoding("STL/bunny.stl"); + test_encoding("STL/test_sphere.stl"); +} + +} // namespace draco diff --git a/contrib/draco/src/draco/io/texture_io.cc b/contrib/draco/src/draco/io/texture_io.cc new file mode 100644 index 000000000..cbe2915b0 --- /dev/null +++ b/contrib/draco/src/draco/io/texture_io.cc @@ -0,0 +1,94 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/texture_io.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include "draco/io/file_utils.h" + +namespace draco { + +namespace { + +StatusOr> CreateDracoTextureInternal( + const std::vector &image_data, SourceImage *out_source_image) { + std::unique_ptr draco_texture(new Texture()); + out_source_image->MutableEncodedData() = image_data; + return std::move(draco_texture); +} + +} // namespace + +StatusOr> ReadTextureFromFile( + const std::string &file_name) { + std::vector image_data; + if (!ReadFileToBuffer(file_name, &image_data)) { + return Status(Status::IO_ERROR, "Unable to read input texture file."); + } + + SourceImage source_image; + DRACO_ASSIGN_OR_RETURN(auto texture, + CreateDracoTextureInternal(image_data, &source_image)); + source_image.set_filename(file_name); + const std::string extension = LowercaseFileExtension(file_name); + const std::string mime_type = + "image/" + (extension == "jpg" ? "jpeg" : extension); + source_image.set_mime_type(mime_type); + texture->set_source_image(source_image); + return texture; +} + +StatusOr> ReadTextureFromBuffer( + const uint8_t *buffer, size_t buffer_size, const std::string &mime_type) { + SourceImage source_image; + std::vector image_data(buffer, buffer + buffer_size); + DRACO_ASSIGN_OR_RETURN(auto texture, + CreateDracoTextureInternal(image_data, &source_image)); + source_image.set_mime_type(mime_type); + texture->set_source_image(source_image); + return texture; +} + +Status WriteTextureToFile(const std::string &file_name, + const Texture &texture) { + std::vector buffer; + DRACO_RETURN_IF_ERROR(WriteTextureToBuffer(texture, &buffer)); + + if (!WriteBufferToFile(buffer.data(), buffer.size(), file_name)) { + return Status(Status::DRACO_ERROR, "Failed to write image."); + } + + return OkStatus(); +} + +Status WriteTextureToBuffer(const Texture &texture, + std::vector *buffer) { + // Copy data from the encoded source image if possible, otherwise load the + // data from the source file. + if (!texture.source_image().encoded_data().empty()) { + *buffer = texture.source_image().encoded_data(); + } else if (!texture.source_image().filename().empty()) { + if (!ReadFileToBuffer(texture.source_image().filename(), buffer)) { + return Status(Status::IO_ERROR, "Unable to read input texture file."); + } + } else { + return Status(Status::DRACO_ERROR, "Invalid source data for the texture."); + } + return OkStatus(); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/io/texture_io.h b/contrib/draco/src/draco/io/texture_io.h new file mode 100644 index 000000000..4dbea7554 --- /dev/null +++ b/contrib/draco/src/draco/io/texture_io.h @@ -0,0 +1,56 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_TEXTURE_IO_H_ +#define DRACO_IO_TEXTURE_IO_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +#include "draco/core/draco_types.h" +#include "draco/core/status_or.h" +#include "draco/texture/texture.h" + +namespace draco { + +// Reads a texture from a file. Reads PNG, JPEG and WEBP texture files. +// Returns nullptr with an error status if the decoding failed. +StatusOr> ReadTextureFromFile( + const std::string &file_name); + +// Same as ReadTextureFromFile() but the texture data is parsed from a |buffer|. +// |mime_type| should be set to a type of the texture encoded in |buffer|. +// Supported mime types are "image/jpeg", "image/png" and "image/webp". +// TODO(ostava): We should be able to get the mime type directly from the +// |buffer| but our image decoding library doesn't support this at this time. +StatusOr> ReadTextureFromBuffer( + const uint8_t *buffer, size_t buffer_size, const std::string &mime_type); + +// Writes a texture into a file. Can write PNG, JPEG, WEBP, and KTX2 (with Basis +// compression) texture files depending on the extension specified in +// |file_name| and image format specified in |texture|. Note that images with +// Basis compression can only be saved to files in KTX2 format and not to files +// with "basis" extension. Returns an error status if the writing failed. +Status WriteTextureToFile(const std::string &file_name, const Texture &texture); + +// Writes a |texture| into |buffer|. +Status WriteTextureToBuffer(const Texture &texture, + std::vector *buffer); + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_TEXTURE_IO_H_ diff --git a/contrib/draco/src/draco/io/texture_io_test.cc b/contrib/draco/src/draco/io/texture_io_test.cc new file mode 100644 index 000000000..13f36e44a --- /dev/null +++ b/contrib/draco/src/draco/io/texture_io_test.cc @@ -0,0 +1,55 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/texture_io.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include +#include +#include + +#include "draco/core/draco_test_utils.h" +#include "draco/io/file_utils.h" + +namespace { + +// Tests loading of textures from a buffer. +TEST(TextureIoTest, TestLoadFromBuffer) { + const std::string file_name = draco::GetTestFileFullPath("test.png"); + std::vector image_data; + ASSERT_TRUE(draco::ReadFileToBuffer(file_name, &image_data)); + + DRACO_ASSIGN_OR_ASSERT( + std::unique_ptr texture, + draco::ReadTextureFromBuffer(image_data.data(), image_data.size(), + "image/png")); + ASSERT_NE(texture, nullptr); + + ASSERT_EQ(texture->source_image().mime_type(), "image/png"); + + // Re-encode the texture again to ensure the content hasn't changed. + std::vector encoded_buffer; + DRACO_ASSERT_OK(draco::WriteTextureToBuffer(*texture, &encoded_buffer)); + + ASSERT_EQ(image_data.size(), encoded_buffer.size()); + for (int i = 0; i < encoded_buffer.size(); ++i) { + ASSERT_EQ(image_data[i], encoded_buffer[i]); + } +} + +} // namespace + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/io/tiny_gltf_utils.cc b/contrib/draco/src/draco/io/tiny_gltf_utils.cc new file mode 100644 index 000000000..d57e1093c --- /dev/null +++ b/contrib/draco/src/draco/io/tiny_gltf_utils.cc @@ -0,0 +1,230 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/io/tiny_gltf_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/animation/animation.h" +#include "draco/animation/node_animation_data.h" +#include "draco/core/status.h" +#include "draco/core/vector_d.h" +#include "tiny_gltf.h" + +namespace draco { + +int TinyGltfUtils::GetNumComponentsForType(int type) { + switch (type) { + case TINYGLTF_TYPE_SCALAR: + return 1; + case TINYGLTF_TYPE_VEC2: + return 2; + case TINYGLTF_TYPE_VEC3: + return 3; + case TINYGLTF_TYPE_VEC4: + case TINYGLTF_TYPE_MAT2: + return 4; + case TINYGLTF_TYPE_MAT3: + return 9; + case TINYGLTF_TYPE_MAT4: + return 16; + } + return 0; +} + +Material::TransparencyMode TinyGltfUtils::TextToMaterialMode( + const std::string &mode) { + if (mode == "MASK") { + return Material::TRANSPARENCY_MASK; + } else if (mode == "BLEND") { + return Material::TRANSPARENCY_BLEND; + } else { + return Material::TRANSPARENCY_OPAQUE; + } +} + +AnimationSampler::SamplerInterpolation +TinyGltfUtils::TextToSamplerInterpolation(const std::string &interpolation) { + if (interpolation == "STEP") { + return AnimationSampler::SamplerInterpolation::STEP; + } else if (interpolation == "CUBICSPLINE") { + return AnimationSampler::SamplerInterpolation::CUBICSPLINE; + } else { + return AnimationSampler::SamplerInterpolation::LINEAR; + } +} + +AnimationChannel::ChannelTransformation +TinyGltfUtils::TextToChannelTransformation(const std::string &path) { + if (path == "rotation") { + return AnimationChannel::ChannelTransformation::ROTATION; + } else if (path == "scale") { + return AnimationChannel::ChannelTransformation::SCALE; + } else if (path == "weights") { + return AnimationChannel::ChannelTransformation::WEIGHTS; + } else { + return AnimationChannel::ChannelTransformation::TRANSLATION; + } +} + +Status TinyGltfUtils::AddChannelToAnimation( + const tinygltf::Model &model, const tinygltf::Animation &input_animation, + const tinygltf::AnimationChannel &channel, int node_index, + Animation *animation) { + std::unique_ptr new_channel(new AnimationChannel()); + + const tinygltf::AnimationSampler &sampler = + input_animation.samplers[channel.sampler]; + // Add the sampler associated with the channel. + DRACO_RETURN_IF_ERROR( + TinyGltfUtils::AddSamplerToAnimation(model, sampler, animation)); + new_channel->sampler_index = animation->NumSamplers() - 1; + new_channel->target_index = node_index; + new_channel->transformation_type = + TinyGltfUtils::TextToChannelTransformation(channel.target_path); + + animation->AddChannel(std::move(new_channel)); + return OkStatus(); +} + +Status TinyGltfUtils::AddSamplerToAnimation( + const tinygltf::Model &model, const tinygltf::AnimationSampler &sampler, + Animation *animation) { + std::unique_ptr node_animation_data( + new NodeAnimationData()); + // TODO(fgalligan): Add support to not copy the accessor data if it is + // referenced more than once. Currently we duplicate all animation data so + // that it is referenced only once in the glTF file. + const tinygltf::Accessor &input_accessor = model.accessors[sampler.input]; + DRACO_RETURN_IF_ERROR(AddAccessorToAnimationData(model, input_accessor, + node_animation_data.get())); + animation->AddNodeAnimationData(std::move(node_animation_data)); + std::unique_ptr new_sampler(new AnimationSampler()); + new_sampler->input_index = animation->NumNodeAnimationData() - 1; + + node_animation_data.reset(new NodeAnimationData()); + const tinygltf::Accessor &output_accessor = model.accessors[sampler.output]; + DRACO_RETURN_IF_ERROR(AddAccessorToAnimationData(model, output_accessor, + node_animation_data.get())); + animation->AddNodeAnimationData(std::move(node_animation_data)); + new_sampler->output_index = animation->NumNodeAnimationData() - 1; + + new_sampler->interpolation_type = + TinyGltfUtils::TextToSamplerInterpolation(sampler.interpolation); + animation->AddSampler(std::move(new_sampler)); + return OkStatus(); +} + +// Specialization for returning the data from |accessor| as a vector of float. +template <> +StatusOr> TinyGltfUtils::CopyDataAsFloat( + const tinygltf::Model &model, const tinygltf::Accessor &accessor) { + const int num_components = GetNumComponentsForType(accessor.type); + if (num_components != 1) { + return Status(Status::DRACO_ERROR, + "Dimension does not equal num components."); + } + return CopyDataAsFloatImpl(model, accessor); +} + +// Specialization for returing the data from |accessor| as a vector of +// Matrix4x4. +template <> +StatusOr> TinyGltfUtils::CopyDataAsFloat( + const tinygltf::Model &model, const tinygltf::Accessor &accessor) { + const int num_components = GetNumComponentsForType(accessor.type); + if (num_components != 16) { + return Status(Status::DRACO_ERROR, + "Dimension does not equal num components."); + } + return CopyDataAsFloatImpl(model, accessor); +} + +Status TinyGltfUtils::AddAccessorToAnimationData( + const tinygltf::Model &model, const tinygltf::Accessor &accessor, + NodeAnimationData *node_animation_data) { + if (accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) { + return Status(Status::DRACO_ERROR, + "Unsupported ComponentType for NodeAnimationData."); + } + + std::vector *dest_data = node_animation_data->GetMutableData(); + if (accessor.type == TINYGLTF_TYPE_SCALAR) { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAsFloat(model, accessor)); + + for (int i = 0; i < data.size(); ++i) { + dest_data->push_back(data[i]); + } + node_animation_data->SetType(NodeAnimationData::Type::SCALAR); + } else if (accessor.type == TINYGLTF_TYPE_VEC3) { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAsFloat(model, accessor)); + + for (int i = 0; i < data.size(); ++i) { + for (int j = 0; j < 3; ++j) { + dest_data->push_back(data[i][j]); + } + } + node_animation_data->SetType(NodeAnimationData::Type::VEC3); + } else if (accessor.type == TINYGLTF_TYPE_VEC4) { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAsFloat(model, accessor)); + + for (int i = 0; i < data.size(); ++i) { + for (int j = 0; j < 4; ++j) { + dest_data->push_back(data[i][j]); + } + } + node_animation_data->SetType(NodeAnimationData::Type::VEC4); + } else if (accessor.type == TINYGLTF_TYPE_MAT4) { + DRACO_ASSIGN_OR_RETURN(std::vector data, + CopyDataAsFloat(model, accessor)); + + for (int i = 0; i < data.size(); ++i) { + for (int j = 0; j < 16; ++j) { + dest_data->push_back(data[i](j)); + } + } + node_animation_data->SetType(NodeAnimationData::Type::MAT4); + } else { + return Status(Status::DRACO_ERROR, + "Unsupported Type for GltfNodeAnimationData."); + } + node_animation_data->SetCount(accessor.count); + node_animation_data->SetNormalized(accessor.normalized); + return OkStatus(); +} + +template <> +void TinyGltfUtils::SetDataImpl(float value, int index, float *values) { + *values = value; +} + +template <> +void TinyGltfUtils::SetDataImpl(float value, int index, + Eigen::Matrix4f *values) { + (*values)(index) = value; +} + +} // namespace draco + +// Actual definitions needed by the tinygltf library using our configuration. +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION +#define TINYGLTF_ENABLE_DRACO +#define TINYGLTF_IMPLEMENTATION + +#include "tiny_gltf.h" + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/io/tiny_gltf_utils.h b/contrib/draco/src/draco/io/tiny_gltf_utils.h new file mode 100644 index 000000000..a536a70fb --- /dev/null +++ b/contrib/draco/src/draco/io/tiny_gltf_utils.h @@ -0,0 +1,140 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_IO_TINY_GLTF_UTILS_H_ +#define DRACO_IO_TINY_GLTF_UTILS_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "Eigen/Geometry" +#include "draco/animation/animation.h" +#include "draco/core/status.h" +#include "draco/core/status_or.h" +#include "draco/material/material.h" + +#define TINYGLTF_ENCLOSING_NAMESPACE draco +#include "tiny_gltf.h" + +namespace draco { + +class TinyGltfUtils { + public: + TinyGltfUtils() {} + + // Returns the number of components for the attribute type. + static int GetNumComponentsForType(int type); + + // Returns the material transparency mode in |mode|. + static Material::TransparencyMode TextToMaterialMode(const std::string &mode); + + // Returns the animation sampler interpolation in |interpolation|. + static AnimationSampler::SamplerInterpolation TextToSamplerInterpolation( + const std::string &interpolation); + + // Returns the animation channel transformation in |path|. + static AnimationChannel::ChannelTransformation TextToChannelTransformation( + const std::string &path); + + // Adds all of the animation data associated with a channel. + // The channel references a sampler, whose data will be added to the + // |animation|. The sampler references input and output accessors, + // whose data will be added to the |animation|. + static Status AddChannelToAnimation( + const tinygltf::Model &model, const tinygltf::Animation &input_animation, + const tinygltf::AnimationChannel &channel, int node_index, + Animation *animation); + + // Adds all of the sampler data. The sampler references + // input and output accessors, whose data will be added to the |animation|. + static Status AddSamplerToAnimation(const tinygltf::Model &model, + const tinygltf::AnimationSampler &sampler, + Animation *animation); + + // Converts the gltf2 animation accessor and adds it to + // |node_animation_data|. + static Status AddAccessorToAnimationData( + const tinygltf::Model &model, const tinygltf::Accessor &accessor, + NodeAnimationData *node_animation_data); + + // Returns the data from |accessor| as a vector of |T|. + template + static StatusOr> CopyDataAsFloat( + const tinygltf::Model &model, const tinygltf::Accessor &accessor) { + const int num_components = GetNumComponentsForType(accessor.type); + if (num_components != T::dimension) { + return Status(Status::DRACO_ERROR, + "Dimension does not equal num components."); + } + return CopyDataAsFloatImpl(model, accessor); + } + + private: + template + static StatusOr> CopyDataAsFloatImpl( + const tinygltf::Model &model, const tinygltf::Accessor &accessor) { + if (accessor.componentType != TINYGLTF_COMPONENT_TYPE_FLOAT) { + return Status(Status::DRACO_ERROR, + "Non-float data is not supported by CopyDataAsFloat()."); + } + if (accessor.bufferView < 0) { + return Status(Status::DRACO_ERROR, + "Error CopyDataAsFloat() bufferView < 0."); + } + + const tinygltf::BufferView &buffer_view = + model.bufferViews[accessor.bufferView]; + if (buffer_view.buffer < 0) { + return Status(Status::DRACO_ERROR, "Error CopyDataAsFloat() buffer < 0."); + } + + const tinygltf::Buffer &buffer = model.buffers[buffer_view.buffer]; + + const unsigned char *const data_start = + buffer.data.data() + buffer_view.byteOffset + accessor.byteOffset; + const int byte_stride = accessor.ByteStride(buffer_view); + const int component_size = + tinygltf::GetComponentSizeInBytes(accessor.componentType); + + std::vector output; + output.resize(accessor.count); + + const int num_components = GetNumComponentsForType(accessor.type); + const unsigned char *data = data_start; + for (int i = 0; i < accessor.count; ++i) { + T values; + + for (int c = 0; c < num_components; ++c) { + float value = 0.0f; + memcpy(&value, data + (c * component_size), component_size); + SetDataImpl(value, c, &values); + } + + output[i] = values; + data += byte_stride; + } + + return output; + } + + template + static void SetDataImpl(float value, int index, T *values) { + (*values)[index] = value; + } +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_IO_TINY_GLTF_UTILS_H_ diff --git a/contrib/draco/src/draco/javascript/emscripten/animation_decoder_webidl_wrapper.cc b/contrib/draco/src/draco/javascript/emscripten/animation_decoder_webidl_wrapper.cc deleted file mode 100644 index 7e9e6d15d..000000000 --- a/contrib/draco/src/draco/javascript/emscripten/animation_decoder_webidl_wrapper.cc +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2017 The Draco Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -#include "draco/javascript/emscripten/animation_decoder_webidl_wrapper.h" - -#include - -#include "draco/compression/decode.h" -#include "draco/mesh/mesh.h" -#include "draco/mesh/mesh_stripifier.h" - -using draco::DecoderBuffer; -using draco::PointAttribute; -using draco::Status; - -DracoFloat32Array::DracoFloat32Array() {} - -float DracoFloat32Array::GetValue(int index) const { return values_[index]; } - -bool DracoFloat32Array::SetValues(const float *values, int count) { - if (values) { - values_.assign(values, values + count); - } else { - values_.resize(count); - } - return true; -} - -AnimationDecoder::AnimationDecoder() {} - -// Decodes animation data from the provided buffer. -const draco::Status *AnimationDecoder::DecodeBufferToKeyframeAnimation( - draco::DecoderBuffer *in_buffer, draco::KeyframeAnimation *animation) { - draco::DecoderOptions dec_options; - last_status_ = decoder_.Decode(dec_options, in_buffer, animation); - return &last_status_; -} - -bool AnimationDecoder::GetTimestamps(const draco::KeyframeAnimation &animation, - DracoFloat32Array *timestamp) { - if (!timestamp) { - return false; - } - const int num_frames = animation.num_frames(); - const draco::PointAttribute *timestamp_att = animation.timestamps(); - // Timestamp attribute has only 1 component, so the number of components is - // equal to the number of frames. - timestamp->SetValues(nullptr, num_frames); - int entry_id = 0; - float timestamp_value = -1.0; - for (draco::PointIndex i(0); i < num_frames; ++i) { - const draco::AttributeValueIndex val_index = timestamp_att->mapped_index(i); - if (!timestamp_att->ConvertValue(val_index, ×tamp_value)) { - return false; - } - timestamp->SetValue(entry_id++, timestamp_value); - } - return true; -} - -bool AnimationDecoder::GetKeyframes(const draco::KeyframeAnimation &animation, - int keyframes_id, - DracoFloat32Array *animation_data) { - const int num_frames = animation.num_frames(); - // Get animation data. - const draco::PointAttribute *animation_data_att = - animation.keyframes(keyframes_id); - if (!animation_data_att) { - return false; - } - - const int components = animation_data_att->num_components(); - const int num_entries = num_frames * components; - const int kMaxAttributeFloatValues = 4; - - std::vector values(components, -1.0); - int entry_id = 0; - animation_data->SetValues(nullptr, num_entries); - for (draco::PointIndex i(0); i < num_frames; ++i) { - const draco::AttributeValueIndex val_index = - animation_data_att->mapped_index(i); - if (!animation_data_att->ConvertValue(val_index, &values[0])) { - return false; - } - for (int j = 0; j < components; ++j) { - animation_data->SetValue(entry_id++, values[j]); - } - } - return true; -} diff --git a/contrib/draco/src/draco/javascript/emscripten/animation_decoder_webidl_wrapper.h b/contrib/draco/src/draco/javascript/emscripten/animation_decoder_webidl_wrapper.h deleted file mode 100644 index 7486d1503..000000000 --- a/contrib/draco/src/draco/javascript/emscripten/animation_decoder_webidl_wrapper.h +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2017 The Draco Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -#ifndef DRACO_JAVASCRIPT_EMSCRIPTEN_ANIMATION_DECODER_WEBIDL_WRAPPER_H_ -#define DRACO_JAVASCRIPT_EMSCRIPTEN_ANIMATION_DECODER_WEBIDL_WRAPPER_H_ - -#include - -#include "draco/animation/keyframe_animation_decoder.h" -#include "draco/attributes/attribute_transform_type.h" -#include "draco/attributes/point_attribute.h" -#include "draco/compression/config/compression_shared.h" -#include "draco/compression/decode.h" -#include "draco/core/decoder_buffer.h" - -typedef draco::AttributeTransformType draco_AttributeTransformType; -typedef draco::GeometryAttribute draco_GeometryAttribute; -typedef draco_GeometryAttribute::Type draco_GeometryAttribute_Type; -typedef draco::EncodedGeometryType draco_EncodedGeometryType; -typedef draco::Status draco_Status; -typedef draco::Status::Code draco_StatusCode; - -class DracoFloat32Array { - public: - DracoFloat32Array(); - float GetValue(int index) const; - - // In case |values| is nullptr, the data is allocated but not initialized. - bool SetValues(const float *values, int count); - - // Directly sets a value for a specific index. The array has to be already - // allocated at this point (using SetValues() method). - void SetValue(int index, float val) { values_[index] = val; } - - int size() const { return values_.size(); } - - private: - std::vector values_; -}; - -// Class used by emscripten WebIDL Binder [1] to wrap calls to decode animation -// data. -class AnimationDecoder { - public: - AnimationDecoder(); - - // Decodes animation data from the provided buffer. - const draco::Status *DecodeBufferToKeyframeAnimation( - draco::DecoderBuffer *in_buffer, draco::KeyframeAnimation *animation); - - static bool GetTimestamps(const draco::KeyframeAnimation &animation, - DracoFloat32Array *timestamp); - - static bool GetKeyframes(const draco::KeyframeAnimation &animation, - int keyframes_id, DracoFloat32Array *animation_data); - - private: - draco::KeyframeAnimationDecoder decoder_; - draco::Status last_status_; -}; - -#endif // DRACO_JAVASCRIPT_EMSCRIPTEN_ANIMATION_DECODER_WEBIDL_WRAPPER_H_ diff --git a/contrib/draco/src/draco/javascript/emscripten/animation_encoder_webidl_wrapper.cc b/contrib/draco/src/draco/javascript/emscripten/animation_encoder_webidl_wrapper.cc deleted file mode 100644 index 53a10e5e4..000000000 --- a/contrib/draco/src/draco/javascript/emscripten/animation_encoder_webidl_wrapper.cc +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2017 The Draco Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -#include "draco/javascript/emscripten/animation_encoder_webidl_wrapper.h" - -#include "draco/animation/keyframe_animation.h" -#include "draco/animation/keyframe_animation_encoder.h" - -DracoInt8Array::DracoInt8Array() {} - -int DracoInt8Array::GetValue(int index) const { return values_[index]; } - -bool DracoInt8Array::SetValues(const char *values, int count) { - values_.assign(values, values + count); - return true; -} - -AnimationBuilder::AnimationBuilder() {} - -bool AnimationBuilder::SetTimestamps(draco::KeyframeAnimation *animation, - long num_frames, const float *timestamps) { - if (!animation || !timestamps) { - return false; - } - std::vector timestamps_arr( - timestamps, timestamps + num_frames); - return animation->SetTimestamps(timestamps_arr); -} - -int AnimationBuilder::AddKeyframes(draco::KeyframeAnimation *animation, - long num_frames, long num_components, - const float *animation_data) { - if (!animation || !animation_data) { - return -1; - } - std::vector keyframes_arr( - animation_data, animation_data + num_frames * num_components); - return animation->AddKeyframes(draco::DT_FLOAT32, num_components, - keyframes_arr); -} - -AnimationEncoder::AnimationEncoder() - : timestamps_quantization_bits_(-1), - keyframes_quantization_bits_(-1), - options_(draco::EncoderOptions::CreateDefaultOptions()) {} - -void AnimationEncoder::SetTimestampsQuantization(long quantization_bits) { - timestamps_quantization_bits_ = quantization_bits; -} - -void AnimationEncoder::SetKeyframesQuantization(long quantization_bits) { - keyframes_quantization_bits_ = quantization_bits; -} - -int AnimationEncoder::EncodeAnimationToDracoBuffer( - draco::KeyframeAnimation *animation, DracoInt8Array *draco_buffer) { - if (!animation) { - return 0; - } - draco::EncoderBuffer buffer; - - if (timestamps_quantization_bits_ > 0) { - options_.SetAttributeInt(0, "quantization_bits", - timestamps_quantization_bits_); - } - if (keyframes_quantization_bits_ > 0) { - for (int i = 1; i <= animation->num_animations(); ++i) { - options_.SetAttributeInt(i, "quantization_bits", - keyframes_quantization_bits_); - } - } - if (!encoder_.EncodeKeyframeAnimation(*animation, options_, &buffer).ok()) { - return 0; - } - - draco_buffer->SetValues(buffer.data(), buffer.size()); - return buffer.size(); -} diff --git a/contrib/draco/src/draco/javascript/emscripten/animation_encoder_webidl_wrapper.h b/contrib/draco/src/draco/javascript/emscripten/animation_encoder_webidl_wrapper.h deleted file mode 100644 index f2ac733d1..000000000 --- a/contrib/draco/src/draco/javascript/emscripten/animation_encoder_webidl_wrapper.h +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2017 The Draco Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -#ifndef DRACO_JAVASCRIPT_EMSCRIPTEN_ANIMATION_ENCODER_WEBIDL_WRAPPER_H_ -#define DRACO_JAVASCRIPT_EMSCRIPTEN_ANIMATION_ENCODER_WEBIDL_WRAPPER_H_ - -#include - -#include "draco/animation/keyframe_animation_encoder.h" -#include "draco/attributes/point_attribute.h" -#include "draco/compression/config/compression_shared.h" -#include "draco/compression/config/encoder_options.h" -#include "draco/compression/encode.h" - -class DracoInt8Array { - public: - DracoInt8Array(); - int GetValue(int index) const; - bool SetValues(const char *values, int count); - - size_t size() { return values_.size(); } - - private: - std::vector values_; -}; - -class AnimationBuilder { - public: - AnimationBuilder(); - - bool SetTimestamps(draco::KeyframeAnimation *animation, long num_frames, - const float *timestamps); - - int AddKeyframes(draco::KeyframeAnimation *animation, long num_frames, - long num_components, const float *animation_data); -}; - -class AnimationEncoder { - public: - AnimationEncoder(); - - void SetTimestampsQuantization(long quantization_bits); - // TODO: Use expert encoder to set per attribute quantization. - void SetKeyframesQuantization(long quantization_bits); - int EncodeAnimationToDracoBuffer(draco::KeyframeAnimation *animation, - DracoInt8Array *draco_buffer); - - private: - draco::KeyframeAnimationEncoder encoder_; - long timestamps_quantization_bits_; - long keyframes_quantization_bits_; - draco::EncoderOptions options_; -}; - -#endif // DRACO_JAVASCRIPT_EMSCRIPTEN_ANIMATION_ENCODER_WEBIDL_WRAPPER_H_ diff --git a/contrib/draco/src/draco/javascript/emscripten/decoder_webidl_wrapper.cc b/contrib/draco/src/draco/javascript/emscripten/decoder_webidl_wrapper.cc index 66fe77dbd..034f3c3b4 100644 --- a/contrib/draco/src/draco/javascript/emscripten/decoder_webidl_wrapper.cc +++ b/contrib/draco/src/draco/javascript/emscripten/decoder_webidl_wrapper.cc @@ -221,14 +221,13 @@ bool Decoder::GetAttributeFloatForAllPoints(const PointCloud &pc, const int components = pa.num_components(); const int num_points = pc.num_points(); const int num_entries = num_points * components; - const int kMaxAttributeFloatValues = 4; - float values[kMaxAttributeFloatValues] = {-2.0, -2.0, -2.0, -2.0}; + std::vector values(components, -2.f); int entry_id = 0; out_values->Resize(num_entries); for (draco::PointIndex i(0); i < num_points; ++i) { const draco::AttributeValueIndex val_index = pa.mapped_index(i); - if (!pa.ConvertValue(val_index, values)) { + if (!pa.ConvertValue(val_index, &values[0])) { return false; } for (int j = 0; j < components; ++j) { @@ -249,17 +248,16 @@ bool Decoder::GetAttributeFloatArrayForAllPoints(const PointCloud &pc, return false; } const bool requested_type_is_float = pa.data_type() == draco::DT_FLOAT32; - const int kMaxAttributeFloatValues = 4; - float values[kMaxAttributeFloatValues] = {-2.0, -2.0, -2.0, -2.0}; + std::vector values(components, -2.f); int entry_id = 0; float *const floats = reinterpret_cast(out_values); for (draco::PointIndex i(0); i < num_points; ++i) { const draco::AttributeValueIndex val_index = pa.mapped_index(i); if (requested_type_is_float) { - pa.GetValue(val_index, values); + pa.GetValue(val_index, &values[0]); } else { - if (!pa.ConvertValue(val_index, values)) { + if (!pa.ConvertValue(val_index, &values[0])) { return false; } } diff --git a/contrib/draco/src/draco/javascript/emscripten/draco_animation_decoder_glue_wrapper.cc b/contrib/draco/src/draco/javascript/emscripten/draco_animation_decoder_glue_wrapper.cc deleted file mode 100644 index 83ed98fdc..000000000 --- a/contrib/draco/src/draco/javascript/emscripten/draco_animation_decoder_glue_wrapper.cc +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2017 The Draco Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file is used by emscripten's WebIDL Binder. -// http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html -#include "draco/attributes/attribute_octahedron_transform.h" -#include "draco/attributes/attribute_quantization_transform.h" -#include "draco/attributes/geometry_attribute.h" -#include "draco/attributes/point_attribute.h" -#include "draco/compression/decode.h" -#include "draco/core/decoder_buffer.h" -#include "draco/javascript/emscripten/animation_decoder_webidl_wrapper.h" -#include "draco/mesh/mesh.h" -#include "draco/point_cloud/point_cloud.h" - -// glue_animation_decoder.cpp is generated by Makefile.emcc build_glue target. -#include "glue_animation_decoder.cpp" diff --git a/contrib/draco/src/draco/javascript/emscripten/draco_animation_web_decoder.idl b/contrib/draco/src/draco/javascript/emscripten/draco_animation_web_decoder.idl deleted file mode 100644 index c9fe76b59..000000000 --- a/contrib/draco/src/draco/javascript/emscripten/draco_animation_web_decoder.idl +++ /dev/null @@ -1,52 +0,0 @@ -// Interface exposed to emscripten's WebIDL Binder. -// http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html -[Prefix="draco::"] -interface DecoderBuffer { - void DecoderBuffer(); - void Init([Const] byte[] data, unsigned long data_size); -}; - -enum draco_StatusCode { - "draco_Status::OK", - "draco_Status::DRACO_ERROR", - "draco_Status::IO_ERROR", - "draco_Status::INVALID_PARAMETER", - "draco_Status::UNSUPPORTED_VERSION", - "draco_Status::UNKNOWN_VERSION", -}; - -[Prefix="draco::"] -interface Status { - draco_StatusCode code(); - boolean ok(); - [Const] DOMString error_msg(); -}; - -// Draco version of typed arrays. The memory of these arrays is allocated on the -// emscripten heap. -interface DracoFloat32Array { - void DracoFloat32Array(); - float GetValue(long index); - long size(); -}; - -[Prefix="draco::"] -interface KeyframeAnimation { - void KeyframeAnimation(); - long num_frames(); - long num_animations(); -}; - -interface AnimationDecoder { - void AnimationDecoder(); - - [Const] Status DecodeBufferToKeyframeAnimation(DecoderBuffer in_buffer, - KeyframeAnimation animation); - - boolean GetTimestamps([Ref, Const] KeyframeAnimation animation, - DracoFloat32Array timestamp); - - boolean GetKeyframes([Ref, Const] KeyframeAnimation animation, - long keyframes_id, - DracoFloat32Array animation_data); -}; diff --git a/contrib/draco/src/draco/javascript/emscripten/draco_animation_web_encoder.idl b/contrib/draco/src/draco/javascript/emscripten/draco_animation_web_encoder.idl deleted file mode 100644 index e74a4c9e4..000000000 --- a/contrib/draco/src/draco/javascript/emscripten/draco_animation_web_encoder.idl +++ /dev/null @@ -1,34 +0,0 @@ -// Interface exposed to emscripten's WebIDL Binder. -// http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html -// Draco version of typed arrays. The memory of these arrays is allocated on the -// emscripten heap. -interface DracoInt8Array { - void DracoInt8Array(); - long GetValue(long index); - long size(); -}; - -[Prefix="draco::"] -interface KeyframeAnimation { - void KeyframeAnimation(); - long num_frames(); -}; - -interface AnimationBuilder { - void AnimationBuilder(); - boolean SetTimestamps(KeyframeAnimation animation, long num_frames, - [Const] float[] timestamps); - - long AddKeyframes(KeyframeAnimation animation, long num_frames, - long num_components, [Const] float[] animation_data); -}; - -interface AnimationEncoder { - void AnimationEncoder(); - - void SetTimestampsQuantization(long quantization_bits); - void SetKeyframesQuantization(long quantization_bits); - - long EncodeAnimationToDracoBuffer(KeyframeAnimation animation, - DracoInt8Array encoded_data); -}; diff --git a/contrib/draco/src/draco/javascript/emscripten/version.js b/contrib/draco/src/draco/javascript/emscripten/version.js index 46fb25271..b21f3b5e2 100644 --- a/contrib/draco/src/draco/javascript/emscripten/version.js +++ b/contrib/draco/src/draco/javascript/emscripten/version.js @@ -19,7 +19,7 @@ function isVersionSupported(versionString) { const version = versionString.split('.'); if (version.length < 2 || version.length > 3) return false; // Unexpected version string. - if (version[0] == 1 && version[1] >= 0 && version[1] <= 4) + if (version[0] == 1 && version[1] >= 0 && version[1] <= 5) return true; if (version[0] != 0 || version[1] > 10) return false; diff --git a/contrib/draco/src/draco/material/material.cc b/contrib/draco/src/draco/material/material.cc new file mode 100644 index 000000000..da854a172 --- /dev/null +++ b/contrib/draco/src/draco/material/material.cc @@ -0,0 +1,258 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/material/material.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +Material::Material() : Material(nullptr) {} + +Material::Material(TextureLibrary *texture_library) + : texture_library_(texture_library) { + Clear(); +} + +void Material::Copy(const Material &src) { + name_ = src.name_; + color_factor_ = src.color_factor_; + metallic_factor_ = src.metallic_factor_; + roughness_factor_ = src.roughness_factor_; + emissive_factor_ = src.emissive_factor_; + transparency_mode_ = src.transparency_mode_; + alpha_cutoff_ = src.alpha_cutoff_; + double_sided_ = src.double_sided_; + normal_texture_scale_ = src.normal_texture_scale_; + + // Copy properties of material extensions. + unlit_ = src.unlit_; + has_sheen_ = src.has_sheen_; + sheen_color_factor_ = src.sheen_color_factor_; + sheen_roughness_factor_ = src.sheen_roughness_factor_; + has_transmission_ = src.has_transmission_; + transmission_factor_ = src.transmission_factor_; + has_clearcoat_ = src.has_clearcoat_; + clearcoat_factor_ = src.clearcoat_factor_; + clearcoat_roughness_factor_ = src.clearcoat_roughness_factor_; + has_volume_ = src.has_volume_; + thickness_factor_ = src.thickness_factor_; + attenuation_distance_ = src.attenuation_distance_; + attenuation_color_ = src.attenuation_color_; + has_ior_ = src.has_ior_; + ior_ = src.ior_; + has_specular_ = src.has_specular_; + specular_factor_ = src.specular_factor_; + specular_color_factor_ = src.specular_color_factor_; + + // Copy texture maps. + texture_map_type_to_index_map_ = src.texture_map_type_to_index_map_; + texture_maps_.resize(src.texture_maps_.size()); + for (int i = 0; i < texture_maps_.size(); ++i) { + texture_maps_[i] = std::unique_ptr(new TextureMap()); + texture_maps_[i]->Copy(*src.texture_maps_[i]); + } +} + +void Material::Clear() { + ClearTextureMaps(); + + // Defaults correspond to the GLTF 2.0 spec. + name_.clear(); + color_factor_ = Vector4f(1.f, 1.f, 1.f, 1.f); + metallic_factor_ = 1.f; + roughness_factor_ = 1.f; + emissive_factor_ = Vector3f(0.f, 0.f, 0.f); + transparency_mode_ = TRANSPARENCY_OPAQUE; + alpha_cutoff_ = 0.5f; + double_sided_ = false; + normal_texture_scale_ = 1.0f; + + // Clear properties of material extensions to glTF 2.0 spec defaults. + unlit_ = false; + has_sheen_ = false; + sheen_color_factor_ = Vector3f(0.f, 0.f, 0.f); + sheen_roughness_factor_ = 0.f; + has_transmission_ = false; + transmission_factor_ = 0.f; + has_clearcoat_ = false; + clearcoat_factor_ = 0.f; + clearcoat_roughness_factor_ = 0.f; + has_volume_ = false; + thickness_factor_ = 0.f; + attenuation_distance_ = std::numeric_limits::max(); // Infinity. + attenuation_color_ = Vector3f(1.f, 1.f, 1.f); + has_ior_ = false; + ior_ = 1.5f; + has_specular_ = false; + specular_factor_ = 1.f; + specular_color_factor_ = Vector3f(1.f, 1.f, 1.f); +} + +void Material::ClearTextureMaps() { + texture_maps_.clear(); + texture_map_type_to_index_map_.clear(); +} + +void Material::SetTextureMap(TextureMap &&texture_map) { + std::unique_ptr new_texture_map(new TextureMap); + *new_texture_map = std::move(texture_map); + SetTextureMap(std::move(new_texture_map)); +} + +void Material::SetTextureMap(std::unique_ptr texture_map) { + const TextureMap::Type type = texture_map->type(); + const auto it = texture_map_type_to_index_map_.find(type); + // Only one texture of a given type is allowed to exist. + if (it == texture_map_type_to_index_map_.end()) { + texture_maps_.push_back(std::move(texture_map)); + texture_map_type_to_index_map_[type] = texture_maps_.size() - 1; + } else { + texture_maps_[it->second] = std::move(texture_map); + } +} + +void Material::SetTextureMap(std::unique_ptr texture, + TextureMap::Type texture_map_type, + int tex_coord_index) { + SetTextureMap(std::move(texture), texture_map_type, + TextureMap::WrappingMode(TextureMap::CLAMP_TO_EDGE), + tex_coord_index); +} + +void Material::SetTextureMap(std::unique_ptr texture, + TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + int tex_coord_index) { + std::unique_ptr texture_map(new TextureMap); + texture_map->SetProperties(texture_map_type, wrapping_mode, tex_coord_index); + + if (texture_library_) { + texture_map->SetTexture(texture.get()); + texture_library_->PushTexture(std::move(texture)); + } else { + texture_map->SetTexture(std::move(texture)); + } + SetTextureMap(std::move(texture_map)); +} + +Status Material::SetTextureMap(Texture *texture, + TextureMap::Type texture_map_type, + int tex_coord_index) { + return SetTextureMap(texture, texture_map_type, + TextureMap::WrappingMode(TextureMap::CLAMP_TO_EDGE), + TextureMap::UNSPECIFIED, TextureMap::UNSPECIFIED, + tex_coord_index); +} + +Status Material::SetTextureMap(Texture *texture, + TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + int tex_coord_index) { + std::unique_ptr texture_map(new TextureMap); + return SetTextureMap(std::move(texture_map), texture, texture_map_type, + wrapping_mode, TextureMap::UNSPECIFIED, + TextureMap::UNSPECIFIED, tex_coord_index); +} + +Status Material::SetTextureMap(Texture *texture, + TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + TextureMap::FilterType min_filter, + TextureMap::FilterType mag_filter, + int tex_coord_index) { + std::unique_ptr texture_map(new TextureMap); + return SetTextureMap(std::move(texture_map), texture, texture_map_type, + wrapping_mode, min_filter, mag_filter, tex_coord_index); +} + +Status Material::SetTextureMap(Texture *texture, + TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + TextureMap::FilterType min_filter, + TextureMap::FilterType mag_filter, + const TextureTransform &transform, + int tex_coord_index) { + std::unique_ptr texture_map(new TextureMap); + texture_map->SetTransform(transform); + return SetTextureMap(std::move(texture_map), texture, texture_map_type, + wrapping_mode, min_filter, mag_filter, tex_coord_index); +} + +Status Material::SetTextureMap(std::unique_ptr texture_map, + Texture *texture, + TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + TextureMap::FilterType min_filter, + TextureMap::FilterType mag_filter, + int tex_coord_index) { + if (!IsTextureOwned(*texture)) { + return Status(Status::DRACO_ERROR, + "Provided texture is not owned by the material."); + } + texture_map->SetProperties(texture_map_type, wrapping_mode, tex_coord_index, + min_filter, mag_filter); + texture_map->SetTexture(texture); + SetTextureMap(std::move(texture_map)); + return OkStatus(); +} + +bool Material::IsTextureOwned(const Texture &texture) { + if (texture_library_) { + // Ensure the texture is owned by the texture library. + for (int ti = 0; ti < texture_library_->NumTextures(); ++ti) { + if (texture_library_->GetTexture(ti) == &texture) { + return true; + } + } + return false; + } + // Else we need to check every texture map of this material. + for (int ti = 0; ti < NumTextureMaps(); ++ti) { + if (GetTextureMapByIndex(ti)->texture() == &texture) { + return true; + } + } + return false; +} + +std::unique_ptr Material::RemoveTextureMapByIndex(int index) { + if (index < 0 || index >= texture_maps_.size()) { + return nullptr; + } + std::unique_ptr ret = std::move(texture_maps_[index]); + texture_maps_.erase(texture_maps_.begin() + index); + // A texture map was removed and we need to update + // |texture_map_type_to_index_map_| to reflect the changes. + for (int i = index; i < texture_maps_.size(); ++i) { + texture_map_type_to_index_map_[texture_maps_[i]->type()] = i; + } + // Delete the removed texture map type. + texture_map_type_to_index_map_.erase( + texture_map_type_to_index_map_.find(ret->type())); + return ret; +} + +std::unique_ptr Material::RemoveTextureMapByType( + TextureMap::Type texture_type) { + const auto it = texture_map_type_to_index_map_.find(texture_type); + if (it == texture_map_type_to_index_map_.end()) { + return nullptr; + } + return RemoveTextureMapByIndex(it->second); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/material/material.h b/contrib/draco/src/draco/material/material.h new file mode 100644 index 000000000..7c405b45c --- /dev/null +++ b/contrib/draco/src/draco/material/material.h @@ -0,0 +1,276 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_MATERIAL_MATERIAL_H_ +#define DRACO_MATERIAL_MATERIAL_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/core/status.h" +#include "draco/core/vector_d.h" +#include "draco/texture/texture_library.h" +#include "draco/texture/texture_map.h" + +namespace draco { + +// Material specification for Draco geometry. Parameters are based on the +// metallic-roughness PBR model adopted by GLTF 2.0 standard: +// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#materials +class Material { + public: + enum TransparencyMode { + TRANSPARENCY_OPAQUE = 0, + TRANSPARENCY_MASK, + TRANSPARENCY_BLEND + }; + + Material(); + explicit Material(TextureLibrary *texture_library); + + // Copies all material data from the |src| material to this material. + void Copy(const Material &src); + + // Deletes all texture maps and resets all material properties to default + // values. + void Clear(); + + // Deletes all texture maps from the material while keeping other material + // properties unchanged. + void ClearTextureMaps(); + + const std::string &GetName() const { return name_; } + void SetName(const std::string &name) { name_ = name; } + Vector4f GetColorFactor() const { return color_factor_; } + void SetColorFactor(const Vector4f &color_factor) { + color_factor_ = color_factor; + } + float GetMetallicFactor() const { return metallic_factor_; } + void SetMetallicFactor(float metallic_factor) { + metallic_factor_ = metallic_factor; + } + float GetRoughnessFactor() const { return roughness_factor_; } + void SetRoughnessFactor(float roughness_factor) { + roughness_factor_ = roughness_factor; + } + Vector3f GetEmissiveFactor() const { return emissive_factor_; } + void SetEmissiveFactor(const Vector3f &emissive_factor) { + emissive_factor_ = emissive_factor; + } + bool GetDoubleSided() const { return double_sided_; } + void SetDoubleSided(bool double_sided) { double_sided_ = double_sided; } + TransparencyMode GetTransparencyMode() const { return transparency_mode_; } + void SetTransparencyMode(TransparencyMode mode) { transparency_mode_ = mode; } + float GetAlphaCutoff() const { return alpha_cutoff_; } + void SetAlphaCutoff(float alpha_cutoff) { alpha_cutoff_ = alpha_cutoff; } + float GetNormalTextureScale() const { return normal_texture_scale_; } + void SetNormalTextureScale(float scale) { normal_texture_scale_ = scale; } + + // Properties of glTF material extension KHR_materials_unlit. + bool GetUnlit() const { return unlit_; } + void SetUnlit(bool unlit) { unlit_ = unlit; } + + // Properties of glTF material extension KHR_materials_sheen. + bool HasSheen() const { return has_sheen_; } + void SetHasSheen(bool value) { has_sheen_ = value; } + Vector3f GetSheenColorFactor() const { return sheen_color_factor_; } + void SetSheenColorFactor(const Vector3f &value) { + sheen_color_factor_ = value; + } + float GetSheenRoughnessFactor() const { return sheen_roughness_factor_; } + void SetSheenRoughnessFactor(float value) { sheen_roughness_factor_ = value; } + + // Properties of glTF material extension KHR_materials_transmission. + bool HasTransmission() const { return has_transmission_; } + void SetHasTransmission(bool value) { has_transmission_ = value; } + float GetTransmissionFactor() const { return transmission_factor_; } + void SetTransmissionFactor(float value) { transmission_factor_ = value; } + + // Properties of glTF material extension KHR_materials_clearcoat. + bool HasClearcoat() const { return has_clearcoat_; } + void SetHasClearcoat(bool value) { has_clearcoat_ = value; } + float GetClearcoatFactor() const { return clearcoat_factor_; } + void SetClearcoatFactor(float value) { clearcoat_factor_ = value; } + float GetClearcoatRoughnessFactor() const { + return clearcoat_roughness_factor_; + } + void SetClearcoatRoughnessFactor(float value) { + clearcoat_roughness_factor_ = value; + } + + // Properties of glTF material extension KHR_materials_volume. + bool HasVolume() const { return has_volume_; } + void SetHasVolume(bool value) { has_volume_ = value; } + float GetThicknessFactor() const { return thickness_factor_; } + void SetThicknessFactor(float value) { thickness_factor_ = value; } + float GetAttenuationDistance() const { return attenuation_distance_; } + void SetAttenuationDistance(float value) { attenuation_distance_ = value; } + Vector3f GetAttenuationColor() const { return attenuation_color_; } + void SetAttenuationColor(const Vector3f &value) { + attenuation_color_ = value; + } + + // Properties of glTF material extension KHR_materials_ior. + bool HasIor() const { return has_ior_; } + void SetHasIor(bool value) { has_ior_ = value; } + float GetIor() const { return ior_; } + void SetIor(float value) { ior_ = value; } + + // Properties of glTF material extension KHR_materials_specular. + bool HasSpecular() const { return has_specular_; } + void SetHasSpecular(bool value) { has_specular_ = value; } + float GetSpecularFactor() const { return specular_factor_; } + void SetSpecularFactor(float value) { specular_factor_ = value; } + Vector3f GetSpecularColorFactor() const { return specular_color_factor_; } + void SetSpecularColorFactor(const Vector3f &value) { + specular_color_factor_ = value; + } + + // Methods for working with texture maps. + size_t NumTextureMaps() const { return texture_maps_.size(); } + const TextureMap *GetTextureMapByIndex(int index) const { + return texture_maps_[index].get(); + } + TextureMap *GetTextureMapByIndex(int index) { + return texture_maps_[index].get(); + } + const TextureMap *GetTextureMapByType(TextureMap::Type texture_type) const { + const auto it = texture_map_type_to_index_map_.find(texture_type); + if (it == texture_map_type_to_index_map_.end()) { + return nullptr; + } + return GetTextureMapByIndex(it->second); + } + TextureMap *GetTextureMapByType(TextureMap::Type texture_type) { + const auto it = texture_map_type_to_index_map_.find(texture_type); + if (it == texture_map_type_to_index_map_.end()) { + return nullptr; + } + return GetTextureMapByIndex(it->second); + } + + // TODO(b/146061359): Refactor the set texture map code. + // Specifies a new texture map using a texture with a given type. + // |tex_coord_index| defines which texture coordinate attribute should be used + // to map the texture on the underlying geometry (e.g. tex_coord_index 0 would + // use the first texture coordinate attribute). + void SetTextureMap(std::unique_ptr texture, + TextureMap::Type texture_map_type, int tex_coord_index); + void SetTextureMap(std::unique_ptr texture, + TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + int tex_coord_index); + + // Sets a new texture map using a |texture| that is already owned by this + // material (that is by one of its texture maps or by the unerlying + // |texture_library_|). |transform| is the texture map's transform if set. + // |min_filter| and |mag_filter| are the texture filter types. Returns error + // status if provided |texture| is not owned by the material. + Status SetTextureMap(Texture *texture, TextureMap::Type texture_map_type, + int tex_coord_index); + Status SetTextureMap(Texture *texture, TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + int tex_coord_index); + Status SetTextureMap(Texture *texture, TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + TextureMap::FilterType min_filter, + TextureMap::FilterType mag_filter, int tex_coord_index); + Status SetTextureMap(Texture *texture, TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + TextureMap::FilterType min_filter, + TextureMap::FilterType mag_filter, + const TextureTransform &transform, int tex_coord_index); + + // Removes a texture map from the material based on its index or texture type. + // The material releases the ownership of the texture map and returns it as + // a unique_ptr to allow the caller to use the texture map for other purposes. + std::unique_ptr RemoveTextureMapByIndex(int index); + std::unique_ptr RemoveTextureMapByType( + TextureMap::Type texture_type); + + private: + void SetTextureMap(TextureMap &&texture_map); + void SetTextureMap(std::unique_ptr texture_map); + Status SetTextureMap(std::unique_ptr texture_map, + Texture *texture, TextureMap::Type texture_map_type, + TextureMap::WrappingMode wrapping_mode, + TextureMap::FilterType min_filter, + TextureMap::FilterType mag_filter, int tex_coord_index); + + // Returns true if the |texture| is owned by the material. + bool IsTextureOwned(const Texture &texture); + + private: + std::string name_; + Vector4f color_factor_; + float metallic_factor_; + float roughness_factor_; + Vector3f emissive_factor_; + bool double_sided_; + TransparencyMode transparency_mode_; + float alpha_cutoff_; + float normal_texture_scale_; + + // Properties of glTF material extension KHR_materials_unlit. + bool unlit_; + + // Properties of glTF material extension KHR_materials_sheen. + bool has_sheen_; + Vector3f sheen_color_factor_; + float sheen_roughness_factor_; + + // Properties of glTF material extension KHR_materials_transmission. + bool has_transmission_; + float transmission_factor_; + + // Properties of glTF material extension KHR_materials_clearcoat. + bool has_clearcoat_; + float clearcoat_factor_; + float clearcoat_roughness_factor_; + + // Properties of glTF material extension KHR_materials_volume. + bool has_volume_; + float thickness_factor_; + float attenuation_distance_; + Vector3f attenuation_color_; + + // Properties of glTF material extension KHR_materials_ior. + bool has_ior_; + float ior_; + + // Properties of glTF material extension KHR_materials_specular. + bool has_specular_; + float specular_factor_; + Vector3f specular_color_factor_; + + // Texture maps. + std::vector> texture_maps_; + + // Map between a texture type to texture index in |texture_maps_|. Allows fast + // retrieval of texture maps based on their type. + std::unordered_map texture_map_type_to_index_map_; + + // Optional pointer to a library that holds ownership of textures used for + // this material. If set to nullptr, the texture ownership will be assigned + // to the newly created TextureMaps directly. + TextureLibrary *texture_library_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_MATERIAL_MATERIAL_H_ diff --git a/contrib/draco/src/draco/material/material_library.cc b/contrib/draco/src/draco/material/material_library.cc new file mode 100644 index 000000000..f2165295f --- /dev/null +++ b/contrib/draco/src/draco/material/material_library.cc @@ -0,0 +1,125 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/material/material_library.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +namespace draco { + +void MaterialLibrary::Copy(const MaterialLibrary &src) { + Clear(); + Append(src); +} + +void MaterialLibrary::Append(const MaterialLibrary &src) { + const size_t old_num_materials = materials_.size(); + materials_.resize(old_num_materials + src.materials_.size()); + for (int i = 0; i < src.materials_.size(); ++i) { + materials_[old_num_materials + i] = + std::unique_ptr(new Material(&texture_library_)); + materials_[old_num_materials + i]->Copy(*src.materials_[i]); + } + + const size_t old_num_textures = texture_library_.NumTextures(); + texture_library_.Append(src.texture_library_); + for (int i = 0; i < src.materials_variants_names_.size(); i++) { + materials_variants_names_.push_back(src.materials_variants_names_[i]); + } + + // Remap all texture maps to the textures in the new texture library. + + // First gather mapping between texture maps and textures in the old material + // library. + const auto texture_map_to_index = + ComputeTextureMapToTextureIndexMapping(src.texture_library_); + + // Remap all texture maps to textures stored in the new texture library. + for (auto it = texture_map_to_index.begin(); it != texture_map_to_index.end(); + ++it) { + TextureMap *const texture_map = it->first; + const int texture_index = old_num_textures + it->second; + texture_map->SetTexture(texture_library_.GetTexture(texture_index)); + } +} + +std::unique_ptr MaterialLibrary::RemoveMaterial(int index) { + std::unique_ptr ret = std::move(materials_[index]); + materials_.erase(materials_.begin() + index); + return ret; +} + +void MaterialLibrary::RemoveUnusedTextures() { + const auto texture_map_to_index = + ComputeTextureMapToTextureIndexMapping(texture_library_); + + // Mark which textures are used. + std::vector is_texture_used(texture_library_.NumTextures(), false); + for (auto it = texture_map_to_index.begin(); it != texture_map_to_index.end(); + ++it) { + is_texture_used[it->second] = true; + } + + // Remove all textures that are not used (from backwards to avoid updating + // entries in the |is_texture_used| vector). + for (int i = texture_library_.NumTextures() - 1; i >= 0; --i) { + if (!is_texture_used[i]) { + texture_library_.RemoveTexture(i); + } + } +} + +std::map +MaterialLibrary::ComputeTextureMapToTextureIndexMapping( + const TextureLibrary &library) const { + std::map map_to_index; + for (int mi = 0; mi < materials_.size(); ++mi) { + for (int ti = 0; ti < materials_[mi]->NumTextureMaps(); ++ti) { + TextureMap *const texture_map = materials_[mi]->GetTextureMapByIndex(ti); + for (int tli = 0; tli < library.NumTextures(); ++tli) { + if (library.GetTexture(tli) != texture_map->texture()) { + continue; + } + map_to_index[texture_map] = tli; + break; + } + } + } + return map_to_index; +} + +void MaterialLibrary::Clear() { + materials_.clear(); + texture_library_.Clear(); + materials_variants_names_.clear(); +} + +Material *MaterialLibrary::MutableMaterial(int index) { + if (index < 0) { + return nullptr; + } + if (materials_.size() <= index) { + const int old_size = materials_.size(); + materials_.resize(index + 1); + // Ensure all newly created materials are valid. + for (int i = old_size; i < index + 1; ++i) { + materials_[i] = + std::unique_ptr(new Material(&texture_library_)); + } + } + return materials_[index].get(); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/material/material_library.h b/contrib/draco/src/draco/material/material_library.h new file mode 100644 index 000000000..574d86b23 --- /dev/null +++ b/contrib/draco/src/draco/material/material_library.h @@ -0,0 +1,104 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_MATERIAL_MATERIAL_LIBRARY_H_ +#define DRACO_MATERIAL_MATERIAL_LIBRARY_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include + +#include "draco/material/material.h" +#include "draco/texture/texture_library.h" + +namespace draco { + +// MaterialLibrary holds an array of materials that are applied to a single +// model. +class MaterialLibrary { + public: + MaterialLibrary() = default; + + // Copies the |src| into this instance. + void Copy(const MaterialLibrary &src); + + // Appends materials from the |src| library to this library. All materials + // and textures are copied over. + void Append(const MaterialLibrary &src); + + // Deletes all materials from the material library. + void Clear(); + + // The number of materials stored in the library. All materials are stored + // with indices <0, num_materials() - 1>. + size_t NumMaterials() const { return materials_.size(); } + + // Returns a material with a given index or nullptr if the index is not valid. + const Material *GetMaterial(int index) const { + if (index < 0 || index >= materials_.size()) { + return nullptr; + } + return materials_[index].get(); + } + + // Returns a mutable pointer to a given material. If the material with the + // specified |index| does not exist, it is automatically created. + Material *MutableMaterial(int index); + + // Removes a material with a given index and returns it. Caller can ignore the + // returned value, in which case the material will be automatically deleted. + // Index of all subsequent materials will be decremented by one. + std::unique_ptr RemoveMaterial(int index); + + const TextureLibrary &GetTextureLibrary() const { return texture_library_; } + TextureLibrary &MutableTextureLibrary() { return texture_library_; } + + // Removes all textures that are not referenced by a TextureMap from the + // texture library. + void RemoveUnusedTextures(); + + // Returns a map between each TextureMap object and associated texture index + // in the texture |library|. + std::map ComputeTextureMapToTextureIndexMapping( + const TextureLibrary &library) const; + + // Creates a named materials variant and returns its index. + int AddMaterialsVariant(const std::string &name) { + materials_variants_names_.push_back(name); + return materials_variants_names_.size() - 1; + } + + // Returns the number of materials variants. + int NumMaterialsVariants() const { return materials_variants_names_.size(); } + + // Returns the name of a materials variant. + const std::string &GetMaterialsVariantName(int index) const { + return materials_variants_names_[index]; + } + + private: + std::vector> materials_; + std::vector materials_variants_names_; + + // Container for storing all textures used by materials of this library. + TextureLibrary texture_library_; +}; + +} // namespace draco + +#endif // DRACO_MATERIAL_MATERIAL_LIBRARY_H_ +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/material/material_library_test.cc b/contrib/draco/src/draco/material/material_library_test.cc new file mode 100644 index 000000000..a110fa4db --- /dev/null +++ b/contrib/draco/src/draco/material/material_library_test.cc @@ -0,0 +1,155 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/material/material_library.h" + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED +TEST(MaterialLibraryTest, TestMaterials) { + // Test verifies that we can modify materials in a library. + draco::MaterialLibrary library; + ASSERT_EQ(library.NumMaterials(), 0); + + // Add a new material to the library. + const draco::Material *const new_mat = library.MutableMaterial(0); + ASSERT_NE(new_mat, nullptr); + ASSERT_EQ(library.NumMaterials(), 1); + + const draco::Material *const new_mat2 = library.MutableMaterial(2); + ASSERT_NE(new_mat2, nullptr); + ASSERT_EQ(library.NumMaterials(), 3); + ASSERT_EQ(library.GetMaterial(2), new_mat2); + + // Ensure that even though we call mutable_material multiple times, it does + // not increase the number of materials associated to the library. + for (int i = 0; i < library.NumMaterials(); ++i) { + ASSERT_NE(library.MutableMaterial(i), nullptr); + } + ASSERT_EQ(library.NumMaterials(), 3); + + // Check that material variants can be added and cleared. + library.AddMaterialsVariant("Milk Truck"); + library.AddMaterialsVariant("Ice Cream Truck"); + ASSERT_EQ(library.NumMaterialsVariants(), 2); + ASSERT_EQ(library.GetMaterialsVariantName(0), "Milk Truck"); + ASSERT_EQ(library.GetMaterialsVariantName(1), "Ice Cream Truck"); + + library.Clear(); + ASSERT_EQ(library.NumMaterials(), 0); + ASSERT_EQ(library.NumMaterialsVariants(), 0); +} + +TEST(MaterialLibraryTest, TestMaterialsCopy) { + // Test verifies that we can copy a material library. + draco::MaterialLibrary library; + library.MutableMaterial(0)->SetMetallicFactor(2.4f); + library.MutableMaterial(3)->SetRoughnessFactor(1.2f); + library.AddMaterialsVariant("Milk Truck"); + library.AddMaterialsVariant("Ice Cream Truck"); + + draco::MaterialLibrary new_library; + new_library.Copy(library); + ASSERT_EQ(library.NumMaterials(), new_library.NumMaterials()); + ASSERT_EQ(library.GetMaterial(0)->GetMetallicFactor(), + new_library.GetMaterial(0)->GetMetallicFactor()); + ASSERT_EQ(library.GetMaterial(3)->GetRoughnessFactor(), + new_library.GetMaterial(3)->GetRoughnessFactor()); + ASSERT_EQ(new_library.NumMaterialsVariants(), 2); + ASSERT_EQ(new_library.GetMaterialsVariantName(0), "Milk Truck"); + ASSERT_EQ(new_library.GetMaterialsVariantName(1), "Ice Cream Truck"); +} + +TEST(MaterialLibraryTest, TestTextureLibrary) { + // Tests that texture library is properly updated when we add new textures + // to a material belonging to the material library. + std::unique_ptr texture_0(new draco::Texture()); + std::unique_ptr texture_1(new draco::Texture()); + + draco::MaterialLibrary library; + library.MutableMaterial(0)->SetTextureMap(std::move(texture_0), + draco::TextureMap::COLOR, 0); + ASSERT_EQ(library.GetTextureLibrary().NumTextures(), 1); + library.MutableMaterial(3)->SetTextureMap(std::move(texture_1), + draco::TextureMap::COLOR, 0); + ASSERT_EQ(library.GetTextureLibrary().NumTextures(), 2); +} + +TEST(MaterialLibraryTest, RemoveUnusedTextures) { + // Test verifies that we can remove unusued textures from the material + // library. + draco::MaterialLibrary library; + + // Create dummy textures. + std::unique_ptr texture_0(new draco::Texture()); + std::unique_ptr texture_1(new draco::Texture()); + std::unique_ptr texture_2(new draco::Texture()); + + // Add them to the materials of the library. + library.MutableMaterial(0)->SetTextureMap(std::move(texture_0), + draco::TextureMap::COLOR, 0); + library.MutableMaterial(0)->SetTextureMap( + std::move(texture_1), draco::TextureMap::METALLIC_ROUGHNESS, 0); + library.MutableMaterial(1)->SetTextureMap(std::move(texture_2), + draco::TextureMap::COLOR, 0); + + ASSERT_EQ(library.GetTextureLibrary().NumTextures(), 3); + + library.RemoveUnusedTextures(); + ASSERT_EQ(library.GetTextureLibrary().NumTextures(), 3); + + // Remove texture map from a material. + library.MutableMaterial(0)->RemoveTextureMapByType( + draco::TextureMap::METALLIC_ROUGHNESS); + library.RemoveUnusedTextures(); + ASSERT_EQ(library.GetTextureLibrary().NumTextures(), 2); + + library.MutableMaterial(1)->RemoveTextureMapByType(draco::TextureMap::COLOR); + library.RemoveUnusedTextures(); + ASSERT_EQ(library.GetTextureLibrary().NumTextures(), 1); + + library.MutableMaterial(0)->RemoveTextureMapByType(draco::TextureMap::COLOR); + library.RemoveUnusedTextures(); + ASSERT_EQ(library.GetTextureLibrary().NumTextures(), 0); +} + +TEST(MaterialLibraryTest, RemoveMaterial) { + // Tests that we can safely remove materials from the material library. + draco::MaterialLibrary library; + library.MutableMaterial(0)->SetMetallicFactor(0.f); + library.MutableMaterial(1)->SetMetallicFactor(1.f); + library.MutableMaterial(2)->SetMetallicFactor(2.f); + library.MutableMaterial(3)->SetMetallicFactor(3.f); + + ASSERT_EQ(library.NumMaterials(), 4); + + ASSERT_EQ(library.RemoveMaterial(0)->GetMetallicFactor(), 0.f); + ASSERT_EQ(library.NumMaterials(), 3); + + ASSERT_EQ(library.RemoveMaterial(1)->GetMetallicFactor(), 2.f); + ASSERT_EQ(library.NumMaterials(), 2); + + ASSERT_EQ(library.RemoveMaterial(1)->GetMetallicFactor(), 3.f); + ASSERT_EQ(library.NumMaterials(), 1); + + ASSERT_EQ(library.RemoveMaterial(0)->GetMetallicFactor(), 1.f); + ASSERT_EQ(library.NumMaterials(), 0); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/contrib/draco/src/draco/material/material_test.cc b/contrib/draco/src/draco/material/material_test.cc new file mode 100644 index 000000000..8c999a532 --- /dev/null +++ b/contrib/draco/src/draco/material/material_test.cc @@ -0,0 +1,320 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/material/material.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/draco_test_utils.h" +#include "draco/io/texture_io.h" + +namespace { + +TEST(MaterialTest, TestMaterialAccess) { + // Tests that we can set and get material properties. + draco::Material material; + + material.SetName("Superalloy"); + ASSERT_EQ(material.GetName(), "Superalloy"); + material.SetColorFactor(draco::Vector4f(1.f, 0.2f, 0.1f, 0.9f)); + ASSERT_EQ(material.GetColorFactor(), draco::Vector4f(1.f, 0.2f, 0.1f, 0.9f)); + material.SetMetallicFactor(0.3f); + ASSERT_EQ(material.GetMetallicFactor(), 0.3f); + material.SetRoughnessFactor(0.2f); + ASSERT_EQ(material.GetRoughnessFactor(), 0.2f); + material.SetEmissiveFactor(draco::Vector3f(0.2f, 0.f, 0.1f)); + ASSERT_EQ(material.GetEmissiveFactor(), draco::Vector3f(0.2f, 0.f, 0.1f)); + + // Set and check the properties of material extensions. + material.SetUnlit(true); + ASSERT_TRUE(material.GetUnlit()); + material.SetHasSheen(true); + ASSERT_TRUE(material.HasSheen()); + material.SetSheenColorFactor(draco::Vector3f(0.4f, 0.2f, 0.8f)); + ASSERT_EQ(material.GetSheenColorFactor(), draco::Vector3f(0.4f, 0.2f, 0.8f)); + material.SetSheenRoughnessFactor(0.428f); + ASSERT_EQ(material.GetSheenRoughnessFactor(), 0.428f); + material.SetHasTransmission(true); + ASSERT_TRUE(material.HasTransmission()); + material.SetTransmissionFactor(0.5f); + ASSERT_EQ(material.GetTransmissionFactor(), 0.5f); + material.SetHasClearcoat(true); + ASSERT_TRUE(material.HasClearcoat()); + material.SetClearcoatFactor(0.6f); + ASSERT_EQ(material.GetClearcoatFactor(), 0.6f); + material.SetClearcoatRoughnessFactor(0.7f); + ASSERT_EQ(material.GetClearcoatRoughnessFactor(), 0.7f); + material.SetHasVolume(true); + ASSERT_TRUE(material.HasVolume()); + material.SetThicknessFactor(0.8f); + ASSERT_EQ(material.GetThicknessFactor(), 0.8f); + material.SetAttenuationDistance(0.9f); + ASSERT_EQ(material.GetAttenuationDistance(), 0.9f); + material.SetAttenuationColor(draco::Vector3f(0.2f, 0.5f, 0.8f)); + ASSERT_EQ(material.GetAttenuationColor(), draco::Vector3f(0.2f, 0.5f, 0.8f)); + material.SetHasIor(true); + ASSERT_TRUE(material.HasIor()); + material.SetIor(1.1f); + ASSERT_EQ(material.GetIor(), 1.1f); + material.SetHasSpecular(true); + ASSERT_TRUE(material.HasSpecular()); + material.SetSpecularFactor(0.01f); + ASSERT_EQ(material.GetSpecularFactor(), 0.01f); + material.SetSpecularColorFactor(draco::Vector3f(0.4f, 1.f, 1.f)); + ASSERT_EQ(material.GetSpecularColorFactor(), draco::Vector3f(0.4f, 1.f, 1.f)); + + ASSERT_EQ(material.GetTextureMapByType(draco::TextureMap::COLOR), nullptr); + ASSERT_EQ(material.NumTextureMaps(), 0); + + std::unique_ptr texture = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + ASSERT_NE(texture, nullptr); + + material.SetTextureMap(std::move(texture), draco::TextureMap::COLOR, 0); + + ASSERT_NE(material.GetTextureMapByType(draco::TextureMap::COLOR), nullptr); + ASSERT_EQ(material.NumTextureMaps(), 1); + ASSERT_EQ(material.GetTextureMapByIndex(0), + material.GetTextureMapByType(draco::TextureMap::COLOR)); + + std::unique_ptr texture2 = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + ASSERT_NE(texture2, nullptr); + material.SetTextureMap(std::move(texture2), draco::TextureMap::EMISSIVE, 1); + + ASSERT_NE(material.GetTextureMapByType(draco::TextureMap::EMISSIVE), nullptr); + ASSERT_EQ(material.GetTextureMapByType(draco::TextureMap::EMISSIVE) + ->tex_coord_index(), + 1); + ASSERT_EQ(material.NumTextureMaps(), 2); + + // Try to add the emissive texture one more time. This should replace the + // previous instance of the emissive texture on the material. + std::unique_ptr texture3 = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + ASSERT_NE(texture3, nullptr); + material.SetTextureMap(std::move(texture2), draco::TextureMap::EMISSIVE, 2); + ASSERT_EQ(material.NumTextureMaps(), 2); + ASSERT_EQ(material.GetTextureMapByType(draco::TextureMap::EMISSIVE) + ->tex_coord_index(), + 2); + + std::unique_ptr texture_map4(new draco::TextureMap()); + std::unique_ptr texture4 = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + material.SetTextureMap(std::move(texture4), draco::TextureMap::ROUGHNESS, 0); + ASSERT_EQ(material.NumTextureMaps(), 3); + ASSERT_NE(material.GetTextureMapByType(draco::TextureMap::ROUGHNESS), + nullptr); + + material.SetTransparencyMode(draco::Material::TRANSPARENCY_BLEND); + ASSERT_EQ(material.GetTransparencyMode(), + draco::Material::TRANSPARENCY_BLEND); + material.SetAlphaCutoff(0.2f); + ASSERT_EQ(material.GetAlphaCutoff(), 0.2f); + material.SetNormalTextureScale(0.75f); + ASSERT_EQ(material.GetNormalTextureScale(), 0.75f); + + material.ClearTextureMaps(); + ASSERT_EQ(material.NumTextureMaps(), 0); + ASSERT_EQ(material.GetTextureMapByType(draco::TextureMap::COLOR), nullptr); + + // Metallic factor should be unchanged. + ASSERT_EQ(material.GetMetallicFactor(), 0.3f); + + material.Clear(); + // Metallic factor should be reset to default. + ASSERT_NE(material.GetMetallicFactor(), 0.3f); + + ASSERT_EQ(material.GetDoubleSided(), false); + material.SetDoubleSided(true); + ASSERT_EQ(material.GetDoubleSided(), true); +} + +TEST(MaterialTest, TestMaterialCopy) { + draco::Material material; + material.SetName("Antimatter"); + material.SetColorFactor(draco::Vector4f(0.3f, 0.2f, 0.4f, 0.9f)); + material.SetMetallicFactor(0.2f); + material.SetRoughnessFactor(0.4f); + material.SetEmissiveFactor(draco::Vector3f(0.3f, 0.1f, 0.2f)); + material.SetTransparencyMode(draco::Material::TRANSPARENCY_MASK); + material.SetAlphaCutoff(0.25f); + material.SetDoubleSided(true); + material.SetNormalTextureScale(0.75f); + + // Set the properties of material extensions. + material.SetUnlit(true); + material.SetHasSheen(true); + material.SetSheenColorFactor(draco::Vector3f(0.4f, 0.2f, 0.8f)); + material.SetSheenRoughnessFactor(0.428f); + material.SetHasTransmission(true); + material.SetTransmissionFactor(0.5f); + material.SetHasClearcoat(true); + material.SetClearcoatFactor(0.6f); + material.SetClearcoatRoughnessFactor(0.7f); + material.SetHasVolume(true); + material.SetThicknessFactor(0.8f); + material.SetAttenuationDistance(0.9f); + material.SetAttenuationColor(draco::Vector3f(0.2f, 0.5f, 0.8f)); + material.SetHasIor(true); + material.SetIor(1.1f); + material.SetHasSpecular(true); + material.SetSpecularFactor(0.01f); + material.SetSpecularColorFactor(draco::Vector3f(0.4f, 1.f, 1.f)); + + std::unique_ptr texture = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + ASSERT_NE(texture, nullptr); + material.SetTextureMap(std::move(texture), draco::TextureMap::EMISSIVE, 2); + + draco::Material new_material; + new_material.Copy(material); + + ASSERT_EQ(material.GetName(), new_material.GetName()); + ASSERT_EQ(material.GetColorFactor(), new_material.GetColorFactor()); + ASSERT_EQ(material.GetMetallicFactor(), new_material.GetMetallicFactor()); + ASSERT_EQ(material.GetRoughnessFactor(), new_material.GetRoughnessFactor()); + ASSERT_EQ(material.GetEmissiveFactor(), new_material.GetEmissiveFactor()); + ASSERT_EQ(material.GetTransparencyMode(), new_material.GetTransparencyMode()); + ASSERT_EQ(material.GetAlphaCutoff(), new_material.GetAlphaCutoff()); + ASSERT_EQ(material.GetDoubleSided(), new_material.GetDoubleSided()); + ASSERT_EQ(material.GetNormalTextureScale(), + new_material.GetNormalTextureScale()); + + // Check that the properties of material extensions have been copied. + ASSERT_EQ(material.GetUnlit(), new_material.GetUnlit()); + ASSERT_EQ(material.HasSheen(), new_material.HasSheen()); + ASSERT_EQ(material.GetSheenColorFactor(), new_material.GetSheenColorFactor()); + ASSERT_EQ(material.GetSheenRoughnessFactor(), + new_material.GetSheenRoughnessFactor()); + ASSERT_TRUE(material.HasTransmission()); + ASSERT_EQ(material.GetTransmissionFactor(), + new_material.GetTransmissionFactor()); + ASSERT_TRUE(material.HasClearcoat()); + ASSERT_EQ(material.GetClearcoatFactor(), new_material.GetClearcoatFactor()); + ASSERT_EQ(material.GetClearcoatRoughnessFactor(), + new_material.GetClearcoatRoughnessFactor()); + ASSERT_TRUE(material.HasVolume()); + ASSERT_EQ(material.GetThicknessFactor(), new_material.GetThicknessFactor()); + ASSERT_EQ(material.GetAttenuationDistance(), + new_material.GetAttenuationDistance()); + ASSERT_EQ(material.GetAttenuationColor(), new_material.GetAttenuationColor()); + ASSERT_TRUE(material.HasIor()); + ASSERT_EQ(material.GetIor(), new_material.GetIor()); + ASSERT_TRUE(material.HasSpecular()); + ASSERT_EQ(material.GetSpecularFactor(), new_material.GetSpecularFactor()); + ASSERT_EQ(material.GetSpecularColorFactor(), + new_material.GetSpecularColorFactor()); + + for (int i = 0; i < draco::TextureMap::TEXTURE_TYPES_COUNT; ++i) { + const draco::TextureMap::Type texture_map_type = + static_cast(i); + if (material.GetTextureMapByType(texture_map_type) == nullptr) { + ASSERT_EQ(new_material.GetTextureMapByType(texture_map_type), nullptr); + continue; + } + if (material.GetTextureMapByType(texture_map_type)->texture() == nullptr) { + ASSERT_EQ(new_material.GetTextureMapByType(texture_map_type)->texture(), + nullptr); + } else { + ASSERT_NE(new_material.GetTextureMapByType(texture_map_type)->texture(), + nullptr); + ASSERT_EQ( + material.GetTextureMapByType(texture_map_type)->tex_coord_index(), + new_material.GetTextureMapByType(texture_map_type) + ->tex_coord_index()); + } + } + + ASSERT_EQ(material.NumTextureMaps(), new_material.NumTextureMaps()); + for (int i = 0; i < material.NumTextureMaps(); ++i) { + const draco::TextureMap *const tm0 = material.GetTextureMapByIndex(i); + const draco::TextureMap *const tm1 = new_material.GetTextureMapByIndex(i); + ASSERT_NE(tm0, nullptr); + ASSERT_NE(tm1, nullptr); + ASSERT_EQ(tm0->type(), tm1->type()); + } +} + +TEST(MaterialTest, RemoveTextureMap) { + // Tests that we can remove existing texture maps from a material. + draco::Material material; + + // Add some dummy textures to the material. + std::unique_ptr texture = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + ASSERT_NE(texture, nullptr); + material.SetTextureMap(std::move(texture), draco::TextureMap::COLOR, 0); + + std::unique_ptr texture_2 = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + + material.SetTextureMap(std::move(texture), draco::TextureMap::EMISSIVE, 0); + + ASSERT_EQ(material.NumTextureMaps(), 2); + + // Try to delete the color texture. + std::unique_ptr removed_texture = + material.RemoveTextureMapByType(draco::TextureMap::COLOR); + ASSERT_NE(removed_texture, nullptr); + ASSERT_EQ(removed_texture->type(), draco::TextureMap::COLOR); + ASSERT_EQ(material.NumTextureMaps(), 1); + ASSERT_NE(material.GetTextureMapByType(draco::TextureMap::EMISSIVE), nullptr); + ASSERT_EQ(material.GetTextureMapByIndex(0)->type(), + draco::TextureMap::EMISSIVE); + ASSERT_EQ(material.GetTextureMapByType(draco::TextureMap::COLOR), nullptr); + + removed_texture = material.RemoveTextureMapByIndex(0); + ASSERT_NE(removed_texture, nullptr); + ASSERT_EQ(removed_texture->type(), draco::TextureMap::EMISSIVE); + ASSERT_EQ(material.NumTextureMaps(), 0); + ASSERT_EQ(material.GetTextureMapByType(draco::TextureMap::EMISSIVE), nullptr); +} + +TEST(MaterialTest, SharedTexture) { + // Tests adding shared textures. + draco::Material material; + + // Add some dummy textures to the material. + std::unique_ptr texture = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + ASSERT_NE(texture, nullptr); + draco::Texture *texture_raw = texture.get(); + material.SetTextureMap(std::move(texture), draco::TextureMap::COLOR, 0); + + DRACO_ASSERT_OK( + material.SetTextureMap(texture_raw, draco::TextureMap::EMISSIVE, 0)); + + ASSERT_EQ(material.NumTextureMaps(), 2); + + // Read a new texture. + texture = draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png")) + .value(); + // Texture is not owned by the material so we expect a failure. + ASSERT_FALSE( + material + .SetTextureMap(texture.get(), draco::TextureMap::AMBIENT_OCCLUSION, 0) + .ok()); +} + +} // namespace +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/material/material_utils.cc b/contrib/draco/src/draco/material/material_utils.cc new file mode 100644 index 000000000..7f9fcb621 --- /dev/null +++ b/contrib/draco/src/draco/material/material_utils.cc @@ -0,0 +1,14 @@ +// Copyright 2020 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// diff --git a/contrib/draco/src/draco/material/material_utils.h b/contrib/draco/src/draco/material/material_utils.h new file mode 100644 index 000000000..7f9fcb621 --- /dev/null +++ b/contrib/draco/src/draco/material/material_utils.h @@ -0,0 +1,14 @@ +// Copyright 2020 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// diff --git a/contrib/draco/src/draco/material/material_utils_test.cc b/contrib/draco/src/draco/material/material_utils_test.cc new file mode 100644 index 000000000..82a1227a2 --- /dev/null +++ b/contrib/draco/src/draco/material/material_utils_test.cc @@ -0,0 +1,24 @@ +// Copyright 2020 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/material/material_utils.h" + +#include +#include +#include + +#include "draco/core/draco_test_utils.h" +#include "draco/io/texture_io.h" + +namespace {} // namespace diff --git a/contrib/draco/src/draco/mesh/corner_table.cc b/contrib/draco/src/draco/mesh/corner_table.cc index 3f92f651a..6494c1572 100644 --- a/contrib/draco/src/draco/mesh/corner_table.cc +++ b/contrib/draco/src/draco/mesh/corner_table.cc @@ -15,6 +15,7 @@ #include "draco/mesh/corner_table.h" #include +#include #include "draco/attributes/geometry_indices.h" #include "draco/mesh/corner_table_iterators.h" diff --git a/contrib/draco/src/draco/mesh/corner_table.h b/contrib/draco/src/draco/mesh/corner_table.h index 3aa720fde..3088931c1 100644 --- a/contrib/draco/src/draco/mesh/corner_table.h +++ b/contrib/draco/src/draco/mesh/corner_table.h @@ -21,6 +21,7 @@ #include "draco/attributes/geometry_indices.h" #include "draco/core/draco_index_type_vector.h" #include "draco/core/macros.h" +#include "draco/draco_features.h" #include "draco/mesh/valence_cache.h" namespace draco { diff --git a/contrib/draco/src/draco/mesh/corner_table_iterators.h b/contrib/draco/src/draco/mesh/corner_table_iterators.h index 7122aa1be..72c70ac32 100644 --- a/contrib/draco/src/draco/mesh/corner_table_iterators.h +++ b/contrib/draco/src/draco/mesh/corner_table_iterators.h @@ -15,15 +15,23 @@ #ifndef DRACO_MESH_CORNER_TABLE_ITERATORS_H_ #define DRACO_MESH_CORNER_TABLE_ITERATORS_H_ +#include + #include "draco/mesh/corner_table.h" namespace draco { // Class for iterating over vertices in a 1-ring around the specified vertex. template -class VertexRingIterator - : public std::iterator { +class VertexRingIterator { public: + // Iterator traits expected by std libraries. + using iterator_category = std::forward_iterator_tag; + using value_type = VertexIndex; + using difference_type = std::ptrdiff_t; + using pointer = VertexIndex *; + using reference = VertexIndex &; + // std::iterator interface requires a default constructor. VertexRingIterator() : corner_table_(nullptr), @@ -111,9 +119,15 @@ class VertexRingIterator // Class for iterating over faces adjacent to the specified input face. template -class FaceAdjacencyIterator - : public std::iterator { +class FaceAdjacencyIterator { public: + // Iterator traits expected by std libraries. + using iterator_category = std::forward_iterator_tag; + using value_type = FaceIndex; + using difference_type = std::ptrdiff_t; + using pointer = FaceIndex *; + using reference = FaceIndex &; + // std::iterator interface requires a default constructor. FaceAdjacencyIterator() : corner_table_(nullptr), @@ -193,9 +207,15 @@ class FaceAdjacencyIterator // Class for iterating over corners attached to a specified vertex. template -class VertexCornersIterator - : public std::iterator { +class VertexCornersIterator { public: + // Iterator traits expected by std libraries. + using iterator_category = std::forward_iterator_tag; + using value_type = CornerIndex; + using difference_type = std::ptrdiff_t; + using pointer = CornerIndex *; + using reference = CornerIndex &; + // std::iterator interface requires a default constructor. VertexCornersIterator() : corner_table_(nullptr), diff --git a/contrib/draco/src/draco/mesh/corner_table_test.cc b/contrib/draco/src/draco/mesh/corner_table_test.cc new file mode 100644 index 000000000..f88d3ec96 --- /dev/null +++ b/contrib/draco/src/draco/mesh/corner_table_test.cc @@ -0,0 +1,126 @@ +#include "draco/mesh/corner_table.h" + +#include + +#include "draco/core/draco_test_utils.h" +#include "draco/io/obj_decoder.h" +#include "draco/mesh/mesh_connected_components.h" +#include "draco/mesh/mesh_misc_functions.h" + +namespace draco { + +class CornerTableTest : public ::testing::Test { + protected: + std::unique_ptr DecodeObj(const std::string &file_name) const { + const std::string path = GetTestFileFullPath(file_name); + ObjDecoder decoder; + std::unique_ptr mesh(new Mesh()); + if (!decoder.DecodeFromFile(path, mesh.get()).ok()) { + return nullptr; + } + return mesh; + } + + void TestEncodingCube() { + // Build a CornerTable looking at the mesh and verify that the caching of + // valences are reasonably correct and within range of expectations. This + // test is built specifically for working with 'cubes' and has expectations + // about the degree of each corner. + const std::string file_name = "cube_att.obj"; + std::unique_ptr in_mesh = DecodeObj(file_name); + ASSERT_NE(in_mesh, nullptr) << "Failed to load test model " << file_name; + draco::Mesh *mesh = nullptr; + mesh = in_mesh.get(); + + std::unique_ptr utable = + draco::CreateCornerTableFromPositionAttribute(mesh); + draco::CornerTable *table = utable.get(); + + table->GetValenceCache().CacheValences(); + table->GetValenceCache().CacheValencesInaccurate(); + + for (VertexIndex index = static_cast(0); + index < static_cast(table->num_vertices()); index++) { + const auto valence = table->Valence(index); + const auto valence2 = table->GetValenceCache().ValenceFromCache(index); + const auto valence3 = + table->GetValenceCache().ValenceFromCacheInaccurate(index); + ASSERT_EQ(valence, valence2); + ASSERT_GE(valence, valence3); // may be clipped. + + // No more than 6 triangles can touch a cube corner. + ASSERT_LE(valence, 6); + ASSERT_LE(valence2, 6); + ASSERT_LE(valence3, 6); + + // No less than 3 triangles can touch a cube corner. + ASSERT_GE(valence, 3); + ASSERT_GE(valence2, 3); + ASSERT_GE(valence3, 3); + } + + for (CornerIndex index = static_cast(0); + index < static_cast(table->num_corners()); index++) { + const auto valence = table->Valence(index); + const auto valence2 = table->GetValenceCache().ValenceFromCache(index); + const auto valence3 = + table->GetValenceCache().ValenceFromCacheInaccurate(index); + ASSERT_EQ(valence, valence2); + ASSERT_GE(valence, valence3); // may be clipped. + + // No more than 6 triangles can touch a cube corner, 6 edges result. + ASSERT_LE(valence, 6); + ASSERT_LE(valence2, 6); + ASSERT_LE(valence3, 6); + + // No less than 3 triangles can touch a cube corner, 3 edges result. + ASSERT_GE(valence, 3); + ASSERT_GE(valence2, 3); + ASSERT_GE(valence3, 3); + } + + table->GetValenceCache().ClearValenceCache(); + table->GetValenceCache().ClearValenceCacheInaccurate(); + } +}; + +TEST_F(CornerTableTest, NormalWithSeams) { TestEncodingCube(); } + +TEST_F(CornerTableTest, TestNonManifoldEdges) { + std::unique_ptr mesh = DecodeObj("non_manifold_wrap.obj"); + ASSERT_NE(mesh, nullptr); + std::unique_ptr ct = + draco::CreateCornerTableFromPositionAttribute(mesh.get()); + ASSERT_NE(ct, nullptr); + + MeshConnectedComponents connected_components; + connected_components.FindConnectedComponents(ct.get()); + ASSERT_EQ(connected_components.NumConnectedComponents(), 2); +} + +TEST_F(CornerTableTest, TestNewFace) { + // Tests that we can add a new face to the corner table. + const std::string file_name = "cube_att.obj"; + std::unique_ptr mesh = DecodeObj(file_name); + ASSERT_NE(mesh, nullptr); + + std::unique_ptr ct = + draco::CreateCornerTableFromPositionAttribute(mesh.get()); + ASSERT_NE(ct, nullptr); + ASSERT_EQ(ct->num_faces(), 12); + ASSERT_EQ(ct->num_corners(), 3 * 12); + ASSERT_EQ(ct->num_vertices(), 8); + + const VertexIndex new_vi = ct->AddNewVertex(); + ASSERT_EQ(ct->num_vertices(), 9); + + ASSERT_EQ(ct->AddNewFace({VertexIndex(6), VertexIndex(7), new_vi}), 12); + ASSERT_EQ(ct->num_faces(), 13); + ASSERT_EQ(ct->num_corners(), 3 * 13); + + ASSERT_EQ(ct->Vertex(CornerIndex(3 * 12 + 0)), 6); + ASSERT_EQ(ct->Vertex(CornerIndex(3 * 12) + 1), 7); + ASSERT_EQ(ct->Vertex(CornerIndex(3 * 12) + 2), new_vi); +} + +} // namespace draco diff --git a/contrib/draco/src/draco/mesh/mesh.cc b/contrib/draco/src/draco/mesh/mesh.cc index 3be4b1494..b287ecb45 100644 --- a/contrib/draco/src/draco/mesh/mesh.cc +++ b/contrib/draco/src/draco/mesh/mesh.cc @@ -15,6 +15,10 @@ #include "draco/mesh/mesh.h" #include +#include +#include +#include +#include namespace draco { @@ -22,7 +26,436 @@ namespace draco { template using conditional_t = typename std::conditional::type; +#ifdef DRACO_TRANSCODER_SUPPORTED +Mesh::Mesh() : compression_enabled_(false) {} +#else Mesh::Mesh() {} +#endif + +#ifdef DRACO_TRANSCODER_SUPPORTED +void Mesh::Copy(const Mesh &src) { + PointCloud::Copy(src); + name_ = src.name_; + faces_ = src.faces_; + attribute_data_ = src.attribute_data_; + material_library_.Copy(src.material_library_); + compression_enabled_ = src.compression_enabled_; + compression_options_ = src.compression_options_; + + // Copy mesh feature ID sets. + mesh_features_.clear(); + for (MeshFeaturesIndex i(0); i < src.NumMeshFeatures(); i++) { + std::unique_ptr mesh_features(new MeshFeatures()); + mesh_features->Copy(src.GetMeshFeatures(i)); + AddMeshFeatures(std::move(mesh_features)); + } + mesh_features_material_mask_ = src.mesh_features_material_mask_; + + // Copy non-material textures. + non_material_texture_library_.Copy(src.non_material_texture_library_); + + // Update pointers to non-material textures in mesh feature ID sets. + if (non_material_texture_library_.NumTextures() != 0) { + const auto texture_to_index_map = + src.non_material_texture_library_.ComputeTextureToIndexMap(); + for (MeshFeaturesIndex j(0); j < NumMeshFeatures(); ++j) { + Mesh::UpdateMeshFeaturesTexturePointer(texture_to_index_map, + &non_material_texture_library_, + &GetMeshFeatures(j)); + } + } + + // Copy structural metadata. + structural_metadata_.Copy(src.structural_metadata_); +} + +namespace { +// A helper struct that augments a point index with an attribute value index. +// A unique combination of |point_index| and |attribute_value_index| +// corresponds to a unique point on the mesh. Used to identify unique points +// after a new attribute is added to the mesh. +struct AugmentedPointData { + PointIndex point_index; + AttributeValueIndex attribute_value_index; + bool operator<(const AugmentedPointData &pd) const { + if (point_index < pd.point_index) { + return true; + } + if (point_index > pd.point_index) { + return false; + } + return attribute_value_index < pd.attribute_value_index; + } +}; +} // namespace + +int32_t Mesh::AddAttributeWithConnectivity( + std::unique_ptr att, + const IndexTypeVector &corner_to_value) { + // Map between augmented point and new point indices (one augmented point + // corresponds to one PointIndex). + std::map old_to_new_point_map; + + // Map between corners and the new point indices. + IndexTypeVector corner_to_point(num_faces() * 3, + kInvalidPointIndex); + + // Flag whether a given existing point index has been used. Used to ensure + // that mapping between existing and new point indices that are smaller + // than num_points() is identity. In other words, we want to keep indices of + // the existing points intact and add new points to end. + IndexTypeVector is_point_used(num_points(), false); + + int new_num_points = num_points(); + for (CornerIndex ci(0); ci < num_faces() * 3; ++ci) { + AugmentedPointData apd; + apd.point_index = CornerToPointId(ci); + apd.attribute_value_index = corner_to_value[ci]; + const auto it = old_to_new_point_map.find(apd); + if (it != old_to_new_point_map.end()) { + // Augmented point is already mapped to a point index. Reuse it. + corner_to_point[ci] = it->second; + } else { + // New combination of point index + attribute value index. Map it to a + // unique point index. + PointIndex new_point_index; + if (!is_point_used[apd.point_index]) { + // Reuse the existing (old) point index. + new_point_index = apd.point_index; + is_point_used[apd.point_index] = true; + } else { + // Add a new point index to the end. + new_point_index = PointIndex(new_num_points++); + } + old_to_new_point_map[apd] = new_point_index; + corner_to_point[ci] = new_point_index; + } + } + + // Update point to attribute value mapping for the new attribute. + att->SetExplicitMapping(new_num_points); + for (CornerIndex ci(0); ci < num_faces() * 3; ++ci) { + att->SetPointMapEntry(corner_to_point[ci], corner_to_value[ci]); + } + + // Update point to attribute value mapping on the remaining attributes if + // needed. + if (new_num_points > num_points()) { + set_num_points(new_num_points); + + // Setup attributes for the new number of points. + for (int ai = 0; ai < num_attributes(); ++ai) { + const bool mapping_was_identity = attribute(ai)->is_mapping_identity(); + attribute(ai)->SetExplicitMapping(new_num_points); + if (mapping_was_identity) { + // Convert all old points from identity to explicit mapping. + for (AttributeValueIndex avi(0); avi < attribute(ai)->size(); ++avi) { + attribute(ai)->SetPointMapEntry(PointIndex(avi.value()), avi); + } + } + } + + for (CornerIndex ci(0); ci < num_faces() * 3; ++ci) { + const PointIndex old_point_index = CornerToPointId(ci); + const PointIndex new_point_index = corner_to_point[ci]; + if (old_point_index == new_point_index) { + continue; + } + // Update point to value mapping for all existing attributes. + for (int ai = 0; ai < num_attributes(); ++ai) { + attribute(ai)->SetPointMapEntry( + new_point_index, attribute(ai)->mapped_index(old_point_index)); + } + // Update mapping between the corner and the new point index. + faces_[FaceIndex(ci.value() / 3)][ci.value() % 3] = new_point_index; + } + } + + // If any of the old points have not been used, initialize dummy mapping for + // the new attribute. + for (PointIndex pi(0); pi < is_point_used.size(); ++pi) { + if (!is_point_used[pi]) { + att->SetPointMapEntry(pi, AttributeValueIndex(0)); + } + } + + return PointCloud::AddAttribute(std::move(att)); +} + +int32_t Mesh::AddPerVertexAttribute(std::unique_ptr att) { + const PointAttribute *const pos_att = + GetNamedAttribute(GeometryAttribute::POSITION); + if (pos_att == nullptr) { + return -1; + } + if (att->size() != pos_att->size()) { + return -1; // Number of values must be same as in the position attribute. + } + + if (pos_att->is_mapping_identity()) { + att->SetIdentityMapping(); + } else { + // Copy point to attribute value mapping from the position attribute to + // |att|. + att->SetExplicitMapping(num_points()); + for (PointIndex pi(0); pi < num_points(); ++pi) { + att->SetPointMapEntry(pi, pos_att->mapped_index(pi)); + } + } + + return PointCloud::AddAttribute(std::move(att)); +} + +void Mesh::RemoveIsolatedPoints() { + // For each point, check if it is mapped to a face. + IndexTypeVector is_point_used(num_points(), false); + int num_used_points = 0; + for (FaceIndex fi(0); fi < num_faces(); ++fi) { + const auto &f = face(fi); + for (int c = 0; c < 3; ++c) { + if (!is_point_used[f[c]]) { + num_used_points++; + is_point_used[f[c]] = true; + } + } + } + if (num_used_points == num_points()) { + return; // All points are used. + } + + // Create mapping between the old and new point indices. + IndexTypeVector old_to_new_point_map( + num_points(), kInvalidPointIndex); + PointIndex new_point_index(0); + for (PointIndex pi(0); pi < num_points(); ++pi) { + if (is_point_used[pi]) { + old_to_new_point_map[pi] = new_point_index++; + } + } + + // Update point to attribute value index map for all attributes. + for (int ai = 0; ai < num_attributes(); ++ai) { + PointAttribute *att = attribute(ai); + if (att->is_mapping_identity()) { + // When the attribute uses identity mapping we need to reorder to the + // attribute values to match the new point indices. + for (PointIndex pi(0); pi < num_points(); ++pi) { + const PointIndex new_pi = old_to_new_point_map[pi]; + if (new_pi == pi || new_pi == kInvalidPointIndex) { + continue; + } + att->SetAttributeValue( + AttributeValueIndex(new_pi.value()), + att->GetAddress(AttributeValueIndex(pi.value()))); + } + att->Resize(num_used_points); + } else { + // For explicitly mapped attributes, we first update the point to + // attribute value mapping and then we remove all unused values from the + // attribute. + for (PointIndex pi(0); pi < num_points(); ++pi) { + const PointIndex new_pi = old_to_new_point_map[pi]; + if (new_pi == pi || new_pi == kInvalidPointIndex) { + continue; + } + att->SetPointMapEntry(new_pi, att->mapped_index(pi)); + } + att->SetExplicitMapping(num_used_points); + + att->RemoveUnusedValues(); + } + } + + // Update the mapping between faces and point indices. + for (FaceIndex fi(0); fi < num_faces(); ++fi) { + auto &f = faces_[fi]; + for (int c = 0; c < 3; ++c) { + f[c] = old_to_new_point_map[f[c]]; + } + } + + set_num_points(num_used_points); +} + +void Mesh::RemoveUnusedMaterials() { RemoveUnusedMaterials(true); } + +void Mesh::RemoveUnusedMaterials(bool remove_unused_material_indices) { + const int mat_att_index = GetNamedAttributeId(GeometryAttribute::MATERIAL); + if (mat_att_index == -1) { + // Remove all materials except for the first one. + while (GetMaterialLibrary().NumMaterials() > 1) { + GetMaterialLibrary().RemoveMaterial(1); + } + GetMaterialLibrary().RemoveUnusedTextures(); + return; + } + auto mat_att = attribute(mat_att_index); + + // Deduplicate attribute values in the material attribute to ensure that one + // attribute value index corresponds to one unique material index. + // Note that this does not remove unused material indices. + mat_att->DeduplicateValues(*mat_att); + + // Gather all material indices that are referenced by faces of the mesh. + const int num_materials = GetMaterialLibrary().NumMaterials(); + std::vector is_material_used(num_materials, false); + int num_used_materials = 0; + + // Helper function that updates |is_material_used| for the processed mesh. + auto update_used_materials = [&is_material_used, &num_used_materials, mat_att, + num_materials](PointIndex pi) { + uint32_t mat_index = 0; + mat_att->GetMappedValue(pi, &mat_index); + if (mat_index < num_materials) { + if (!is_material_used[mat_index]) { + is_material_used[mat_index] = true; + num_used_materials++; + } + } + }; + + if (num_faces() > 0) { + for (FaceIndex fi(0); fi < num_faces(); ++fi) { + update_used_materials(faces_[fi][0]); + } + } else { + // Handle the mesh as a point cloud and check materials used by points. + for (PointIndex pi(0); pi < num_points(); ++pi) { + update_used_materials(pi); + } + } + + // Check if any of the (unused) materials is used by mesh features. If so, + // user should remove unused mesh features first. + for (MeshFeaturesIndex mfi(0); mfi < NumMeshFeatures(); ++mfi) { + for (int mask_index = 0; mask_index < NumMeshFeaturesMaterialMasks(mfi); + ++mask_index) { + const int mat_index = GetMeshFeaturesMaterialMask(mfi, mask_index); + if (mat_index < num_materials && !is_material_used[mat_index]) { + is_material_used[mat_index] = true; + num_used_materials++; + } + } + } + + if (num_used_materials == num_materials) { + return; // All materials are used, don't do anything. + } + + // Remove unused materials from the material library or replace them with + // default materials if we do not remove unused material indices. + for (int mi = num_materials - 1; mi >= 0; --mi) { + if (!is_material_used[mi] && mi < GetMaterialLibrary().NumMaterials()) { + if (remove_unused_material_indices) { + GetMaterialLibrary().RemoveMaterial(mi); + } else { + GetMaterialLibrary().MutableMaterial(mi)->Clear(); + } + } + } + GetMaterialLibrary().RemoveUnusedTextures(); + + if (!remove_unused_material_indices) { + // All the code below handles updating of material indices. Since we do not + // want to update them, we can return early. + return; + } + + // Compute map between old and new material indices. + std::vector old_to_new_material_index_map(num_materials, -1); + for (int mi = 0, new_material_index = 0; mi < num_materials; ++mi) { + if (is_material_used[mi]) { + old_to_new_material_index_map[mi] = new_material_index; + ++new_material_index; + } + } + IndexTypeVector + old_to_new_material_attribute_value_index_map(mat_att->size(), -1); + for (AttributeValueIndex avi(0); avi < mat_att->size(); ++avi) { + uint32_t mat_index = 0; + mat_att->GetValue(avi, &mat_index); + if (mat_index < num_materials && is_material_used[mat_index]) { + old_to_new_material_attribute_value_index_map[avi] = + old_to_new_material_index_map[mat_index]; + } + } + + // Update attribute values with the new number of materials. + mat_att->Reset(num_used_materials); + + // Set identity mapping between AttributeValueIndex and material indices. + for (AttributeValueIndex avi(0); avi < mat_att->size(); ++avi) { + const uint32_t mat_index = avi.value(); + mat_att->SetAttributeValue(avi, &mat_index); + } + + // Update mapping between points and attribute values. + for (PointIndex pi(0); pi < num_points(); ++pi) { + const AttributeValueIndex old_avi = mat_att->mapped_index(pi); + mat_att->SetPointMapEntry( + pi, AttributeValueIndex( + old_to_new_material_attribute_value_index_map[old_avi])); + } + + // Update material indices on mesh features. + for (MeshFeaturesIndex mfi(0); mfi < NumMeshFeatures(); ++mfi) { + for (int mask_index = 0; mask_index < NumMeshFeaturesMaterialMasks(mfi); + ++mask_index) { + const int old_mat_index = GetMeshFeaturesMaterialMask(mfi, mask_index); + if (old_mat_index < num_materials && is_material_used[old_mat_index]) { + mesh_features_material_mask_[mfi][mask_index] = + old_to_new_material_index_map[old_mat_index]; + } + } + } +} + +void Mesh::UpdateMeshFeaturesTexturePointer( + const std::unordered_map &texture_to_index_map, + TextureLibrary *texture_library, MeshFeatures *mesh_features) { + TextureMap &texture_map = mesh_features->GetTextureMap(); + if (texture_map.texture() == nullptr) { + return; + } + const auto it = texture_to_index_map.find(texture_map.texture()); + DRACO_DCHECK(it != texture_to_index_map.end()); + const int texture_index = it->second; + DRACO_DCHECK(texture_index < texture_library->NumTextures()); + texture_map.SetTexture(texture_library->GetTexture(texture_index)); +} + +void Mesh::CopyMeshFeaturesForMaterial(const Mesh &source_mesh, + Mesh *target_mesh, int material_index) { + for (MeshFeaturesIndex mfi(0); mfi < source_mesh.NumMeshFeatures(); ++mfi) { + // Mesh features is used if it doesn't have any material mask or if one + // of the material masks matches |material_index|. + bool is_used = source_mesh.NumMeshFeaturesMaterialMasks(mfi) == 0; + for (int mask_index = 0; + !is_used && mask_index < source_mesh.NumMeshFeaturesMaterialMasks(mfi); + ++mask_index) { + if (source_mesh.GetMeshFeaturesMaterialMask(mfi, mask_index) == + material_index) { + is_used = true; + } + } + if (is_used) { + // Copy over the mesh features to the target mesh. Note that texture + // pointers are not updated at this step. + std::unique_ptr new_mf(new MeshFeatures()); + new_mf->Copy(source_mesh.GetMeshFeatures(mfi)); + target_mesh->AddMeshFeatures(std::move(new_mf)); + } + } +} + +int32_t Mesh::AddPerFaceAttribute(std::unique_ptr att) { + IndexTypeVector corner_map(num_faces() * 3); + for (CornerIndex ci(0); ci < num_faces() * 3; ++ci) { + corner_map[ci] = AttributeValueIndex(ci.value() / 3); + } + return AddAttributeWithConnectivity(std::move(att), corner_map); +} +#endif // DRACO_TRANSCODER_SUPPORTED #ifdef DRACO_ATTRIBUTE_INDICES_DEDUPLICATION_SUPPORTED void Mesh::ApplyPointIdDeduplication( diff --git a/contrib/draco/src/draco/mesh/mesh.h b/contrib/draco/src/draco/mesh/mesh.h index f4506da81..652c2c010 100644 --- a/contrib/draco/src/draco/mesh/mesh.h +++ b/contrib/draco/src/draco/mesh/mesh.h @@ -16,12 +16,20 @@ #define DRACO_MESH_MESH_H_ #include +#include #include "draco/attributes/geometry_indices.h" #include "draco/core/hash_utils.h" #include "draco/core/macros.h" #include "draco/core/status.h" #include "draco/draco_features.h" +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/compression/draco_compression_options.h" +#include "draco/material/material_library.h" +#include "draco/mesh/mesh_features.h" +#include "draco/mesh/mesh_indices.h" +#include "draco/metadata/structural_metadata.h" +#endif #include "draco/point_cloud/point_cloud.h" namespace draco { @@ -47,6 +55,11 @@ class Mesh : public PointCloud { Mesh(); +#ifdef DRACO_TRANSCODER_SUPPORTED + // Copies all data from the |src| mesh. + void Copy(const Mesh &src); +#endif + void AddFace(const Face &face) { faces_.push_back(face); } void SetFace(FaceIndex face_id, const Face &face) { @@ -83,6 +96,38 @@ class Mesh : public PointCloud { } } +#ifdef DRACO_TRANSCODER_SUPPORTED + // Adds a point attribute |att| to the mesh and returns the index of the + // newly inserted attribute. Attribute connectivity data is specified in + // |corner_to_value| array that contains mapping between face corners and + // attribute value indices. + // The purpose of this function is to allow users to add attributes with + // arbitrary connectivity to an existing mesh. New points will be + // automatically created if needed. + int32_t AddAttributeWithConnectivity( + std::unique_ptr att, + const IndexTypeVector &corner_to_value); + + // Adds a point attribute |att| to the mesh and returns the index of the + // newly inserted attribute. The inserted attribute must have the same + // connectivity as the position attribute of the mesh (that is, the attribute + // values are defined per-vertex). Each attribute value entry in |att| + // corresponds to the corresponding attribute value entry in the position + // attribute (AttributeValueIndex in both attributes refer to the same + // spatial vertex). + // Returns -1 in case of error. + int32_t AddPerVertexAttribute(std::unique_ptr att); + + // Removes points that are not mapped to any face of the mesh. All attribute + // values are going to be removed as well. + void RemoveIsolatedPoints(); + + // Adds a point attribute |att| to the mesh and returns the index of the + // newly inserted attribute. Attribute values are mapped 1:1 to face indices. + // Returns -1 in case of error. + int32_t AddPerFaceAttribute(std::unique_ptr att); +#endif // DRACO_TRANSCODER_SUPPORTED + MeshAttributeElementType GetAttributeElementType(int att_id) const { return attribute_data_[att_id].element_type; } @@ -109,6 +154,103 @@ class Mesh : public PointCloud { MeshAttributeElementType element_type; }; +#ifdef DRACO_TRANSCODER_SUPPORTED + void SetName(const std::string &name) { name_ = name; } + const std::string &GetName() const { return name_; } + const MaterialLibrary &GetMaterialLibrary() const { + return material_library_; + } + MaterialLibrary &GetMaterialLibrary() { return material_library_; } + + // Removes all materials that are not referenced by any face of the mesh. + // Optional argument |remove_unused_material_indices| can be used to control + // whether unusued material indices are removed as well (default = true). + // If material indices are not removed, the unused material indices will + // point to empty (default) materials. + void RemoveUnusedMaterials(); + void RemoveUnusedMaterials(bool remove_unused_material_indices); + + // Enables or disables Draco geometry compression for this mesh. + void SetCompressionEnabled(bool enabled) { compression_enabled_ = enabled; } + bool IsCompressionEnabled() const { return compression_enabled_; } + + // Sets |options| that configure Draco geometry compression. This does not + // enable or disable compression. + void SetCompressionOptions(const DracoCompressionOptions &options) { + compression_options_ = options; + } + const DracoCompressionOptions &GetCompressionOptions() const { + return compression_options_; + } + DracoCompressionOptions &GetCompressionOptions() { + return compression_options_; + } + + // Library that contains non-material textures. + const TextureLibrary &GetNonMaterialTextureLibrary() const { + return non_material_texture_library_; + } + TextureLibrary &GetNonMaterialTextureLibrary() { + return non_material_texture_library_; + } + + // Mesh feature ID sets as defined by EXT_mesh_features glTF extension. + MeshFeaturesIndex AddMeshFeatures( + std::unique_ptr mesh_features) { + mesh_features_.push_back(std::move(mesh_features)); + mesh_features_material_mask_.push_back({}); + return MeshFeaturesIndex(mesh_features_.size() - 1); + } + int NumMeshFeatures() const { return mesh_features_.size(); } + const MeshFeatures &GetMeshFeatures(MeshFeaturesIndex index) const { + return *mesh_features_[index]; + } + MeshFeatures &GetMeshFeatures(MeshFeaturesIndex index) { + return *mesh_features_[index]; + } + void RemoveMeshFeatures(MeshFeaturesIndex index) { + mesh_features_.erase(mesh_features_.begin() + index.value()); + mesh_features_material_mask_.erase(mesh_features_material_mask_.begin() + + index.value()); + } + + // Restricts given mesh features to faces mapped to a material with + // |material_index|. Note that single mesh features can be restricted to + // multiple materials. + void AddMeshFeaturesMaterialMask(MeshFeaturesIndex index, + int material_index) { + mesh_features_material_mask_[index].push_back(material_index); + } + + size_t NumMeshFeaturesMaterialMasks(MeshFeaturesIndex index) const { + return mesh_features_material_mask_[index].size(); + } + int GetMeshFeaturesMaterialMask(MeshFeaturesIndex index, + int mask_index) const { + return mesh_features_material_mask_[index][mask_index]; + } + + // Updates mesh features texture pointer to point to a new |texture_library|. + // The current texture pointer is used to determine the texture index in the + // new texture library via a given |texture_to_index_map|. + static void UpdateMeshFeaturesTexturePointer( + const std::unordered_map &texture_to_index_map, + TextureLibrary *texture_library, MeshFeatures *mesh_features); + + // Copies over mesh features from |source_mesh| and stores them in + // |target_mesh| as long as the mesh features material mask is valid for + // given |material_index|. + static void CopyMeshFeaturesForMaterial(const Mesh &source_mesh, + Mesh *target_mesh, + int material_index); + + // Structural metadata. + const StructuralMetadata &GetStructuralMetadata() const { + return structural_metadata_; + } + StructuralMetadata &GetStructuralMetadata() { return structural_metadata_; } +#endif // DRACO_TRANSCODER_SUPPORTED + protected: #ifdef DRACO_ATTRIBUTE_INDICES_DEDUPLICATION_SUPPORTED // Extends the point deduplication to face corners. This method is called from @@ -119,6 +261,10 @@ class Mesh : public PointCloud { const std::vector &unique_point_ids) override; #endif + // Exposes |faces_|. Use |faces_| at your own risk. DO NOT store the + // reference: the |faces_| object is destroyed with the mesh. + IndexTypeVector &faces() { return faces_; } + private: // Mesh specific per-attribute data. std::vector attribute_data_; @@ -127,6 +273,40 @@ class Mesh : public PointCloud { // that converts vertex indices into attribute indices. IndexTypeVector faces_; +#ifdef DRACO_TRANSCODER_SUPPORTED + // Mesh name. + std::string name_; + + // Materials applied to to this mesh. + MaterialLibrary material_library_; + + // Compression options for this mesh. + // TODO(vytyaz): Store encoded bitstream that this mesh compresses into. + bool compression_enabled_; + DracoCompressionOptions compression_options_; + + // Sets of feature IDs as defined by EXT_mesh_features glTF extension. + IndexTypeVector> + mesh_features_; + + // When the Mesh contains multiple materials, |mesh_features_material_mask_| + // can be used to limit specific MeshFeaturesIndex to a vector of material + // indices. If for a given mesh feature index, the material indices are empty, + // the corresponding mesh features are applied to the entire mesh. + IndexTypeVector> + mesh_features_material_mask_; + + // Texture library for storing non-material textures used by this mesh, e.g., + // textures containing mesh feature IDs of EXT_mesh_features glTF extension. + // If the mesh is part of the scene then the textures are stored in the scene. + // Note that mesh features contain pointers to non-material textures. It is + // responsibility of class user to update these pointers when updating the + // textures. See Mesh::Copy() for example. + TextureLibrary non_material_texture_library_; + + // Structural metadata defined by the EXT_structural_metadata glTF extension. + StructuralMetadata structural_metadata_; +#endif // DRACO_TRANSCODER_SUPPORTED friend struct MeshHasher; }; diff --git a/contrib/draco/src/draco/mesh/mesh_are_equivalent.cc b/contrib/draco/src/draco/mesh/mesh_are_equivalent.cc index b832379af..305811f10 100644 --- a/contrib/draco/src/draco/mesh/mesh_are_equivalent.cc +++ b/contrib/draco/src/draco/mesh/mesh_are_equivalent.cc @@ -15,6 +15,9 @@ #include "draco/mesh/mesh_are_equivalent.h" #include +#include + +#include "draco/texture/texture_utils.h" namespace draco { @@ -114,6 +117,55 @@ bool MeshAreEquivalent::operator()(const Mesh &mesh0, const Mesh &mesh1) { // face with respect to lex order. Init(mesh0, mesh1); +#ifdef DRACO_TRANSCODER_SUPPORTED + // Compare geometry compression settings. + if (mesh0.IsCompressionEnabled() != mesh1.IsCompressionEnabled()) { + return false; + } + if (mesh0.GetCompressionOptions() != mesh1.GetCompressionOptions()) { + return false; + } + + // Compare non-material texture library sizes. + if (mesh0.GetNonMaterialTextureLibrary().NumTextures() != + mesh1.GetNonMaterialTextureLibrary().NumTextures()) { + return false; + } + + // Compare mesh feature ID sets. + if (mesh0.NumMeshFeatures() != mesh1.NumMeshFeatures()) { + return false; + } + for (MeshFeaturesIndex i(0); i < mesh0.NumMeshFeatures(); ++i) { + const MeshFeatures &features0 = mesh0.GetMeshFeatures(i); + const MeshFeatures &features1 = mesh1.GetMeshFeatures(i); + if (features0.GetAttributeIndex() != features1.GetAttributeIndex()) { + return false; + } + if (features0.GetFeatureCount() != features1.GetFeatureCount()) { + return false; + } + if (features0.GetLabel() != features1.GetLabel()) { + return false; + } + if (features0.GetNullFeatureId() != features1.GetNullFeatureId()) { + return false; + } + if (features0.GetTextureChannels() != features1.GetTextureChannels()) { + return false; + } + if (features0.GetPropertyTableIndex() != + features1.GetPropertyTableIndex()) { + return false; + } + const TextureMap &map0 = features0.GetTextureMap(); + const TextureMap &map1 = features1.GetTextureMap(); + if (map0.tex_coord_index() != map1.tex_coord_index()) { + return false; + } + } +#endif // DRACO_TRANSCODER_SUPPORTED + // Check for every attribute that is valid that every corner is identical. typedef GeometryAttribute::Type AttributeType; const int att_max = AttributeType::NAMED_ATTRIBUTES_COUNT; diff --git a/contrib/draco/src/draco/mesh/mesh_are_equivalent_test.cc b/contrib/draco/src/draco/mesh/mesh_are_equivalent_test.cc index 74db3f7de..94d8c9c16 100644 --- a/contrib/draco/src/draco/mesh/mesh_are_equivalent_test.cc +++ b/contrib/draco/src/draco/mesh/mesh_are_equivalent_test.cc @@ -15,6 +15,7 @@ #include "draco/mesh/mesh_are_equivalent.h" #include +#include #include "draco/core/draco_test_base.h" #include "draco/core/draco_test_utils.h" @@ -30,6 +31,14 @@ TEST_F(MeshAreEquivalentTest, TestOnIndenticalMesh) { const std::string file_name = "test_nm.obj"; const std::unique_ptr mesh(ReadMeshFromTestFile(file_name)); ASSERT_NE(mesh, nullptr) << "Failed to load test model." << file_name; + +#ifdef DRACO_TRANSCODER_SUPPORTED + // Add mesh feature ID set to the mesh. + std::unique_ptr mesh_features(new MeshFeatures()); + mesh->AddMeshFeatures(std::move(mesh_features)); +#endif + + // Check that mesh is equivalent to itself. MeshAreEquivalent equiv; ASSERT_TRUE(equiv(*mesh, *mesh)); } @@ -95,4 +104,32 @@ TEST_F(MeshAreEquivalentTest, TestOnBigMesh) { ASSERT_TRUE(equiv(*mesh0, *mesh1)); } +#ifdef DRACO_TRANSCODER_SUPPORTED + +TEST_F(MeshAreEquivalentTest, TestMeshFeatures) { + const std::string file_name = "test_nm.obj"; + const std::unique_ptr mesh0(ReadMeshFromTestFile(file_name)); + const std::unique_ptr mesh1(ReadMeshFromTestFile(file_name)); + ASSERT_NE(mesh0, nullptr); + ASSERT_NE(mesh1, nullptr); + + // Add identical mesh feature ID sets to meshes. + mesh0->AddMeshFeatures(std::unique_ptr(new MeshFeatures())); + mesh1->AddMeshFeatures(std::unique_ptr(new MeshFeatures())); + + // Empty feature sets should match. + MeshAreEquivalent equiv; + ASSERT_TRUE(equiv(*mesh0, *mesh1)); + + // Make mesh features different and check that the meshes are not equivalent. + mesh0->GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(5); + mesh1->GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(6); + ASSERT_FALSE(equiv(*mesh0, *mesh1)); + + // Make mesh features identical and check that the meshes are equivalent. + mesh0->GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(1); + mesh1->GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(1); + ASSERT_TRUE(equiv(*mesh0, *mesh1)); +} +#endif // DRACO_TRANSCODER_SUPPORTED } // namespace draco diff --git a/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.cc b/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.cc index 28b68d5fd..54801ce5c 100644 --- a/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.cc +++ b/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.cc @@ -126,18 +126,18 @@ void MeshAttributeCornerTable::AddSeamEdge(CornerIndex c) { } } -void MeshAttributeCornerTable::RecomputeVertices(const Mesh *mesh, +bool MeshAttributeCornerTable::RecomputeVertices(const Mesh *mesh, const PointAttribute *att) { DRACO_DCHECK(GetValenceCache().IsCacheEmpty()); if (mesh != nullptr && att != nullptr) { - RecomputeVerticesInternal(mesh, att); + return RecomputeVerticesInternal(mesh, att); } else { - RecomputeVerticesInternal(nullptr, nullptr); + return RecomputeVerticesInternal(nullptr, nullptr); } } template -void MeshAttributeCornerTable::RecomputeVerticesInternal( +bool MeshAttributeCornerTable::RecomputeVerticesInternal( const Mesh *mesh, const PointAttribute *att) { DRACO_DCHECK(GetValenceCache().IsCacheEmpty()); vertex_to_attribute_entry_id_map_.clear(); @@ -167,6 +167,11 @@ void MeshAttributeCornerTable::RecomputeVerticesInternal( while (act_c != kInvalidCornerIndex) { first_c = act_c; act_c = SwingLeft(act_c); + if (act_c == c) { + // We reached the initial corner which shouldn't happen when we swing + // left from |c|. + return false; + } } } corner_to_vertex_map_[first_c.value()] = VertexIndex(first_vert_id.value()); @@ -189,6 +194,7 @@ void MeshAttributeCornerTable::RecomputeVerticesInternal( act_c = corner_table_->SwingRight(act_c); } } + return true; } int MeshAttributeCornerTable::Valence(VertexIndex v) const { diff --git a/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.h b/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.h index 7dad25cf1..c60be7c86 100644 --- a/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.h +++ b/contrib/draco/src/draco/mesh/mesh_attribute_corner_table.h @@ -40,7 +40,7 @@ class MeshAttributeCornerTable { // whenever the seam edges are updated). // |mesh| and |att| can be null, in which case mapping between vertices and // attribute value ids is set to identity. - void RecomputeVertices(const Mesh *mesh, const PointAttribute *att); + bool RecomputeVertices(const Mesh *mesh, const PointAttribute *att); inline bool IsCornerOppositeToSeamEdge(CornerIndex corner) const { return is_edge_on_seam_[corner.value()]; @@ -130,6 +130,12 @@ class MeshAttributeCornerTable { return false; } + bool IsDegenerated(FaceIndex face) const { + // Introducing seams can't change the degeneracy of the individual faces, + // therefore we can delegate the check to the original |corner_table_|. + return corner_table_->IsDegenerated(face); + } + bool no_interior_seams() const { return no_interior_seams_; } const CornerTable *corner_table() const { return corner_table_; } @@ -166,7 +172,7 @@ class MeshAttributeCornerTable { private: template - void RecomputeVerticesInternal(const Mesh *mesh, const PointAttribute *att); + bool RecomputeVerticesInternal(const Mesh *mesh, const PointAttribute *att); std::vector is_edge_on_seam_; std::vector is_vertex_on_seam_; diff --git a/contrib/draco/src/draco/mesh/mesh_cleanup.cc b/contrib/draco/src/draco/mesh/mesh_cleanup.cc index 75b55f045..a6dc1823e 100644 --- a/contrib/draco/src/draco/mesh/mesh_cleanup.cc +++ b/contrib/draco/src/draco/mesh/mesh_cleanup.cc @@ -14,21 +14,25 @@ // #include "draco/mesh/mesh_cleanup.h" +#include +#include #include +#include +#include #include "draco/core/hash_utils.h" namespace draco { -bool MeshCleanup::operator()(Mesh *mesh, const MeshCleanupOptions &options) { +Status MeshCleanup::Cleanup(Mesh *mesh, const MeshCleanupOptions &options) { if (!options.remove_degenerated_faces && !options.remove_unused_attributes && !options.remove_duplicate_faces && !options.make_geometry_manifold) { - return true; // Nothing to cleanup. + return OkStatus(); // Nothing to cleanup. } const PointAttribute *const pos_att = mesh->GetNamedAttribute(GeometryAttribute::POSITION); if (pos_att == nullptr) { - return false; + return Status(Status::DRACO_ERROR, "Missing position attribute."); } if (options.remove_degenerated_faces) { @@ -43,7 +47,7 @@ bool MeshCleanup::operator()(Mesh *mesh, const MeshCleanupOptions &options) { RemoveUnusedAttributes(mesh); } - return true; + return OkStatus(); } void MeshCleanup::RemoveDegeneratedFaces(Mesh *mesh) { diff --git a/contrib/draco/src/draco/mesh/mesh_cleanup.h b/contrib/draco/src/draco/mesh/mesh_cleanup.h index 09aae2e1c..c6bdfc6c0 100644 --- a/contrib/draco/src/draco/mesh/mesh_cleanup.h +++ b/contrib/draco/src/draco/mesh/mesh_cleanup.h @@ -16,42 +16,38 @@ #define DRACO_MESH_MESH_CLEANUP_H_ #include "draco/core/status.h" +#include "draco/draco_features.h" #include "draco/mesh/mesh.h" namespace draco { // Options used by the MeshCleanup class. struct MeshCleanupOptions { - MeshCleanupOptions() - : remove_degenerated_faces(true), - remove_duplicate_faces(true), - remove_unused_attributes(true), - make_geometry_manifold(false) {} // If true, the cleanup tool removes any face where two or more vertices // share the same position index. - bool remove_degenerated_faces; + bool remove_degenerated_faces = true; // If true, the cleanup tool removes all duplicate faces. A pair of faces is // duplicate if both faces share the same position indices on all vertices // (that is, position values have to be duduplicated). Note that all // non-position properties are currently ignored. - bool remove_duplicate_faces; + bool remove_duplicate_faces = true; // If true, the cleanup tool removes any unused attribute value or unused // point id. For example, it can be used to remove isolated vertices. - bool remove_unused_attributes; + bool remove_unused_attributes = true; // If true, the cleanup tool splits vertices along non-manifold edges and // vertices. This ensures that the connectivity defined by position indices // is manifold. - bool make_geometry_manifold; + bool make_geometry_manifold = false; }; // Tool that can be used for removing bad or unused data from draco::Meshes. class MeshCleanup { public: // Performs in-place cleanup of the input mesh according to the input options. - bool operator()(Mesh *mesh, const MeshCleanupOptions &options); + static Status Cleanup(Mesh *mesh, const MeshCleanupOptions &options); private: static void RemoveDegeneratedFaces(Mesh *mesh); diff --git a/contrib/draco/src/draco/mesh/mesh_cleanup_test.cc b/contrib/draco/src/draco/mesh/mesh_cleanup_test.cc index 89c350e94..76e5206ae 100644 --- a/contrib/draco/src/draco/mesh/mesh_cleanup_test.cc +++ b/contrib/draco/src/draco/mesh/mesh_cleanup_test.cc @@ -15,6 +15,7 @@ #include "draco/mesh/mesh_cleanup.h" #include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" #include "draco/core/vector_d.h" #include "draco/mesh/triangle_soup_mesh_builder.h" @@ -43,9 +44,7 @@ TEST_F(MeshCleanupTest, TestDegneratedFaces) { ASSERT_NE(mesh, nullptr) << "Failed to build the test mesh."; ASSERT_EQ(mesh->num_faces(), 2) << "Wrong number of faces in the input mesh."; MeshCleanupOptions cleanup_options; - MeshCleanup cleanup; - ASSERT_TRUE(cleanup(mesh.get(), cleanup_options)) - << "Failed to cleanup the mesh."; + DRACO_ASSERT_OK(MeshCleanup::Cleanup(mesh.get(), cleanup_options)); ASSERT_EQ(mesh->num_faces(), 1) << "Failed to remove degenerated faces."; } @@ -89,9 +88,7 @@ TEST_F(MeshCleanupTest, TestDegneratedFacesAndIsolatedVertices) { << "Wrong number of point ids in the input mesh."; ASSERT_EQ(mesh->attribute(int_att_id)->size(), 3); const MeshCleanupOptions cleanup_options; - MeshCleanup cleanup; - ASSERT_TRUE(cleanup(mesh.get(), cleanup_options)) - << "Failed to cleanup the mesh."; + DRACO_ASSERT_OK(MeshCleanup::Cleanup(mesh.get(), cleanup_options)); ASSERT_EQ(mesh->num_faces(), 1) << "Failed to remove degenerated faces."; ASSERT_EQ(mesh->num_points(), 3) << "Failed to remove isolated attribute indices."; @@ -133,9 +130,7 @@ TEST_F(MeshCleanupTest, TestAttributes) { ASSERT_EQ(mesh->attribute(1)->size(), 2u) << "Wrong number of generic attribute entries."; const MeshCleanupOptions cleanup_options; - MeshCleanup cleanup; - ASSERT_TRUE(cleanup(mesh.get(), cleanup_options)) - << "Failed to cleanup the mesh."; + DRACO_ASSERT_OK(MeshCleanup::Cleanup(mesh.get(), cleanup_options)); ASSERT_EQ(mesh->num_faces(), 1) << "Failed to remove degenerated faces."; ASSERT_EQ(mesh->num_points(), 3) << "Failed to remove isolated attribute indices."; @@ -184,8 +179,7 @@ TEST_F(MeshCleanupTest, TestDuplicateFaces) { ASSERT_NE(mesh, nullptr); ASSERT_EQ(mesh->num_faces(), 5); const MeshCleanupOptions cleanup_options; - MeshCleanup cleanup; - ASSERT_TRUE(cleanup(mesh.get(), cleanup_options)); + DRACO_ASSERT_OK(MeshCleanup::Cleanup(mesh.get(), cleanup_options)); ASSERT_EQ(mesh->num_faces(), 2); } diff --git a/contrib/draco/src/draco/mesh/mesh_connected_components.h b/contrib/draco/src/draco/mesh/mesh_connected_components.h new file mode 100644 index 000000000..6ee30551e --- /dev/null +++ b/contrib/draco/src/draco/mesh/mesh_connected_components.h @@ -0,0 +1,161 @@ +// Copyright 2016 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_MESH_MESH_CONNECTED_COMPONENTS_H_ +#define DRACO_MESH_MESH_CONNECTED_COMPONENTS_H_ + +#include + +#include "draco/mesh/corner_table.h" + +namespace draco { + +// Class for detecting connected components on an input mesh defined by a +// corner table. Degenerated faces and their vertices are not assigned to any +// component. +class MeshConnectedComponents { + public: + MeshConnectedComponents() = default; + + // Initializes the class with the component data of the input mesh. No other + // method should be called before this one. + template + void FindConnectedComponents(const CornerTableT *corner_table); + int NumConnectedComponents() const { return components_.size(); } + + struct ConnectedComponent { + std::vector vertices; + std::vector faces; + std::vector boundary_edges; + }; + + const ConnectedComponent &GetConnectedComponent(int index) const { + return components_[index]; + } + + // Returns the id of an component attached to a given vertex. Returns -1 when + // the vertex was not assigned to any component. + int GetConnectedComponentIdAtVertex(int vertex_id) const { + return vertex_to_component_map_[vertex_id]; + } + + // Returns the number of vertices that belong to the input component. + int NumConnectedComponentVertices(int component_id) const { + return components_[component_id].vertices.size(); + } + + // Returns the i-th vertex of the input component. + int GetConnectedComponentVertex(int component_id, int i) const { + return components_[component_id].vertices[i]; + } + + // Returns the id of an component attached to a given face. Returns -1 when + // the face was not assigned to any component. + int GetConnectedComponentIdAtFace(int face_id) const { + return face_to_component_map_[face_id]; + } + + // Returns the number of faces that belong to the input component. + int NumConnectedComponentFaces(int component_id) const { + return components_[component_id].faces.size(); + } + + // Returns the i-th face of the input component. + int GetConnectedComponentFace(int component_id, int i) const { + return components_[component_id].faces[i]; + } + + // Returns the number of boundary edges that belong to the input component. + int NumConnectedComponentBoundaryEdges(int component_id) const { + return components_[component_id].boundary_edges.size(); + } + + // Returns the i-th boundary edge of the input component. + int GetConnectedComponentBoundaryEdge(int component_id, int i) const { + return components_[component_id].boundary_edges[i]; + } + + private: + std::vector vertex_to_component_map_; + std::vector face_to_component_map_; + std::vector boundary_corner_to_component_map_; + std::vector components_; +}; + +template +void MeshConnectedComponents::FindConnectedComponents( + const CornerTableT *corner_table) { + components_.clear(); + vertex_to_component_map_.assign(corner_table->num_vertices(), -1); + face_to_component_map_.assign(corner_table->num_faces(), -1); + boundary_corner_to_component_map_.assign(corner_table->num_corners(), -1); + std::vector is_face_visited(corner_table->num_faces(), false); + std::vector face_stack; + // Go over all faces of the mesh and for each unvisited face, recursively + // traverse its neighborhood and mark all traversed faces as visited. All + // faces visited during one traversal belong to one mesh component. + for (int face_id = 0; face_id < corner_table->num_faces(); ++face_id) { + if (is_face_visited[face_id]) { + continue; + } + if (corner_table->IsDegenerated(FaceIndex(face_id))) { + continue; + } + const int component_id = components_.size(); + components_.push_back(ConnectedComponent()); + face_stack.push_back(face_id); + is_face_visited[face_id] = true; + while (!face_stack.empty()) { + const int act_face_id = face_stack.back(); + if (face_to_component_map_[act_face_id] == -1) { + face_to_component_map_[act_face_id] = component_id; + components_[component_id].faces.push_back(act_face_id); + } + face_stack.pop_back(); + // Gather all neighboring faces. + std::array corners = + corner_table->AllCorners(FaceIndex(act_face_id)); + for (int c = 0; c < 3; ++c) { + // Update vertex to component mapping. + const int vertex_id = corner_table->Vertex(corners[c]).value(); + if (vertex_to_component_map_[vertex_id] == -1) { + vertex_to_component_map_[vertex_id] = component_id; + components_[component_id].vertices.push_back(vertex_id); + } + // Traverse component to neighboring faces (add the faces to the stack). + const CornerIndex opp_corner = corner_table->Opposite(corners[c]); + if (opp_corner == kInvalidCornerIndex) { + if (boundary_corner_to_component_map_[corners[c].value()] == -1) { + boundary_corner_to_component_map_[corners[c].value()] = + component_id; + components_[component_id].boundary_edges.push_back( + corners[c].value()); + } + continue; // Invalid corner (mesh boundary). + } + + const int opp_face_id = corner_table->Face(opp_corner).value(); + if (is_face_visited[opp_face_id]) { + continue; // Opposite face has been already reached. + } + is_face_visited[opp_face_id] = true; + face_stack.push_back(opp_face_id); + } + } + } +} + +} // namespace draco + +#endif // DRACO_MESH_MESH_CONNECTED_COMPONENTS_H_ diff --git a/contrib/draco/src/draco/mesh/mesh_features.cc b/contrib/draco/src/draco/mesh/mesh_features.cc new file mode 100644 index 000000000..f859ae411 --- /dev/null +++ b/contrib/draco/src/draco/mesh/mesh_features.cc @@ -0,0 +1,98 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/mesh/mesh_features.h" + +#include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +MeshFeatures::MeshFeatures() + : feature_count_(0), + null_feature_id_(-1), + attribute_index_(-1), + property_table_index_(-1) {} + +void MeshFeatures::Copy(const MeshFeatures &src) { + label_ = src.label_; + feature_count_ = src.feature_count_; + null_feature_id_ = src.null_feature_id_; + attribute_index_ = src.attribute_index_; + texture_map_.Copy(src.texture_map_); + texture_channels_ = src.texture_channels_; + property_table_index_ = src.property_table_index_; +} + +void MeshFeatures::SetLabel(const std::string &label) { label_ = label; } + +const std::string &MeshFeatures::GetLabel() const { return label_; } + +void MeshFeatures::SetFeatureCount(int feature_count) { + feature_count_ = feature_count; +} + +int MeshFeatures::GetFeatureCount() const { return feature_count_; } + +void MeshFeatures::SetNullFeatureId(int null_feature_id) { + null_feature_id_ = null_feature_id; +} + +int MeshFeatures::GetNullFeatureId() const { return null_feature_id_; } + +void MeshFeatures::SetAttributeIndex(int attribute_index) { + attribute_index_ = attribute_index; +} + +int MeshFeatures::GetAttributeIndex() const { return attribute_index_; } + +void MeshFeatures::SetTextureMap(const TextureMap &texture_map) { + texture_map_.Copy(texture_map); +} + +void MeshFeatures::SetTextureMap(Texture *texture, int tex_coord_index) { + texture_map_.SetProperties(TextureMap::GENERIC, tex_coord_index); + texture_map_.SetTexture(texture); +} + +const TextureMap &MeshFeatures::GetTextureMap() const { return texture_map_; } + +TextureMap &MeshFeatures::GetTextureMap() { return texture_map_; } + +void MeshFeatures::SetTextureChannels( + const std::vector &texture_channels) { + texture_channels_ = texture_channels; +} + +const std::vector &MeshFeatures::GetTextureChannels() const { + return texture_channels_; +} + +std::vector &MeshFeatures::GetTextureChannels() { + return texture_channels_; +} + +void MeshFeatures::SetPropertyTableIndex(int property_table_index) { + property_table_index_ = property_table_index; +} + +int MeshFeatures::GetPropertyTableIndex() const { + return property_table_index_; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/mesh/mesh_features.h b/contrib/draco/src/draco/mesh/mesh_features.h new file mode 100644 index 000000000..af024013f --- /dev/null +++ b/contrib/draco/src/draco/mesh/mesh_features.h @@ -0,0 +1,93 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_MESH_MESH_FEATURES_H_ +#define DRACO_MESH_MESH_FEATURES_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/texture/texture_library.h" +#include "draco/texture/texture_map.h" + +namespace draco { + +// Describes a mesh feature ID set according to the EXT_mesh_features glTF +// extension. Feature IDs are either associated with geometry vertices or with +// texture pixels and stored in a geometry attribute or in texture channels, +// respectively. Optionally, the feature ID set may be associated with a +// property table defined in the EXT_structural_metadata glTF extension. +class MeshFeatures { + public: + // Creates an empty feature ID set that is associated neither with vertices, + // nor with texture pixels, nor with property tables. + MeshFeatures(); + + // Copies all data from |src| mesh feature ID set. + void Copy(const MeshFeatures &src); + + // Label assigned to this feature ID set. + void SetLabel(const std::string &label); + const std::string &GetLabel() const; + + // The number of unique features in this feature ID set. + void SetFeatureCount(int feature_count); + int GetFeatureCount() const; + + // Non-negative null feature ID value indicating the absence of an associated + // feature. The value of -1 indicates that the null feature ID is not set. + void SetNullFeatureId(int null_feature_id); + int GetNullFeatureId() const; + + // Index of the feature ID vertex attribute, e.g., 5 for an attribute named + // _FEATURE_ID_5, or -1 if the feature ID is not associated with vertices. + void SetAttributeIndex(int attribute_index); + int GetAttributeIndex() const; + + // Feature ID texture map and texture channels containing feature IDs + // associated with texture pixels. Only used when |attribute_index_| is -1. + // The RGBA channels are numbered from 0 to 3. See the glTF extension + // documentation for reconstruction of feature ID from the channel values. + void SetTextureMap(const TextureMap &texture_map); + void SetTextureMap(Texture *texture, int tex_coord_index); + const TextureMap &GetTextureMap() const; + TextureMap &GetTextureMap(); + void SetTextureChannels(const std::vector &texture_channels); + const std::vector &GetTextureChannels() const; + std::vector &GetTextureChannels(); + + // Non-negative index of the property table this feature ID set is associated + // with. Property tables are defined in the EXT_structural_metadata glTF + // extension. The value of -1 indicates that this feature ID set is not + // associated with any property tables. + void SetPropertyTableIndex(int property_table_index); + int GetPropertyTableIndex() const; + + private: + std::string label_; + int feature_count_; + int null_feature_id_; + int attribute_index_; + TextureMap texture_map_; + std::vector texture_channels_; + int property_table_index_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_MESH_MESH_FEATURES_H_ diff --git a/contrib/draco/src/draco/mesh/mesh_features_test.cc b/contrib/draco/src/draco/mesh/mesh_features_test.cc new file mode 100644 index 000000000..0e67af2b1 --- /dev/null +++ b/contrib/draco/src/draco/mesh/mesh_features_test.cc @@ -0,0 +1,98 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/mesh/mesh_features.h" + +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/texture/texture_map.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +TEST(MeshFeaturesTest, TestDefaults) { + // Test construction of an empty feature ID set. + draco::MeshFeatures mesh_features; + ASSERT_TRUE(mesh_features.GetLabel().empty()); + ASSERT_EQ(mesh_features.GetFeatureCount(), 0); + ASSERT_EQ(mesh_features.GetNullFeatureId(), -1); + ASSERT_EQ(mesh_features.GetAttributeIndex(), -1); + ASSERT_EQ(mesh_features.GetPropertyTableIndex(), -1); + ASSERT_TRUE(mesh_features.GetTextureChannels().empty()); + ASSERT_EQ(mesh_features.GetTextureMap().texture(), nullptr); + ASSERT_EQ(mesh_features.GetTextureMap().type(), draco::TextureMap::GENERIC); +} + +TEST(MeshFeaturesTest, TestSettersAndGetters) { + // Test setter and getter methods of the feature ID set. + draco::MeshFeatures mesh_features; + mesh_features.SetLabel("continent"); + mesh_features.SetFeatureCount(8); + mesh_features.SetNullFeatureId(0); + mesh_features.SetAttributeIndex(2); + mesh_features.SetPropertyTableIndex(10); + std::vector channels = {2, 3}; + mesh_features.SetTextureChannels({2, 3}); + draco::TextureMap texture_map; + texture_map.SetProperties(draco::TextureMap::GENERIC, 1); + std::unique_ptr texture(new draco::Texture()); + texture_map.SetTexture(texture.get()); + mesh_features.SetTextureMap(texture_map); + + // Check that mesh feature set properties can be accessed via getters. + ASSERT_EQ(mesh_features.GetLabel(), "continent"); + ASSERT_EQ(mesh_features.GetFeatureCount(), 8); + ASSERT_EQ(mesh_features.GetNullFeatureId(), 0); + ASSERT_EQ(mesh_features.GetAttributeIndex(), 2); + ASSERT_EQ(mesh_features.GetPropertyTableIndex(), 10); + ASSERT_EQ(mesh_features.GetTextureChannels(), channels); + ASSERT_EQ(mesh_features.GetTextureMap().texture(), texture.get()); + ASSERT_EQ(mesh_features.GetTextureMap().type(), draco::TextureMap::GENERIC); +} + +TEST(MeshFeaturesTest, TestCopy) { + // Test that feature ID set can be copied. + draco::MeshFeatures mesh_features; + mesh_features.SetLabel("continent"); + mesh_features.SetFeatureCount(8); + mesh_features.SetNullFeatureId(0); + mesh_features.SetAttributeIndex(2); + mesh_features.SetPropertyTableIndex(10); + std::vector channels = {2, 3}; + mesh_features.SetTextureChannels({2, 3}); + std::unique_ptr texture(new draco::Texture()); + mesh_features.SetTextureMap(texture.get(), 1); + + // Make a copy. + draco::MeshFeatures copy; + copy.Copy(mesh_features); + + // Check the copy. + ASSERT_EQ(copy.GetLabel(), "continent"); + ASSERT_EQ(copy.GetFeatureCount(), 8); + ASSERT_EQ(copy.GetNullFeatureId(), 0); + ASSERT_EQ(copy.GetAttributeIndex(), 2); + ASSERT_EQ(copy.GetPropertyTableIndex(), 10); + ASSERT_EQ(copy.GetTextureChannels(), channels); + ASSERT_EQ(copy.GetTextureMap().texture(), texture.get()); + ASSERT_EQ(copy.GetTextureMap().type(), draco::TextureMap::GENERIC); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/contrib/draco/src/draco/mesh/mesh_indices.h b/contrib/draco/src/draco/mesh/mesh_indices.h new file mode 100644 index 000000000..5df28d550 --- /dev/null +++ b/contrib/draco/src/draco/mesh/mesh_indices.h @@ -0,0 +1,37 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifdef DRACO_TRANSCODER_SUPPORTED +#ifndef DRACO_MESH_MESH_INDICES_H_ +#define DRACO_MESH_MESH_INDICES_H_ + +#include + +#include + +#include "draco/core/draco_index_type.h" + +namespace draco { + +// Index of a mesh feature ID set. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, MeshFeaturesIndex) + +// Constants denoting invalid indices. +static constexpr MeshFeaturesIndex kInvalidMeshFeaturesIndex( + std::numeric_limits::max()); + +} // namespace draco + +#endif // DRACO_MESH_MESH_INDICES_H_ +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/mesh/mesh_misc_functions.h b/contrib/draco/src/draco/mesh/mesh_misc_functions.h index b450bc80c..0a3bcf497 100644 --- a/contrib/draco/src/draco/mesh/mesh_misc_functions.h +++ b/contrib/draco/src/draco/mesh/mesh_misc_functions.h @@ -67,7 +67,6 @@ inline bool IsCornerOppositeToAttributeSeam(CornerIndex ci, // Interpolates an attribute value on a face using given barycentric // coordinates. InterpolatedVectorT should be a VectorD that corresponds to the // values stored in the attribute. -// TODO(ostava): Find a better place for this. template InterpolatedVectorT ComputeInterpolatedAttributeValueOnMeshFace( const Mesh &mesh, const PointAttribute &attribute, FaceIndex fi, diff --git a/contrib/draco/src/draco/mesh/mesh_splitter.cc b/contrib/draco/src/draco/mesh/mesh_splitter.cc new file mode 100644 index 000000000..ac3c4661c --- /dev/null +++ b/contrib/draco/src/draco/mesh/mesh_splitter.cc @@ -0,0 +1,451 @@ +// Copyright 2017 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/mesh/mesh_splitter.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include + +#include "draco/mesh/mesh_utils.h" +#include "draco/mesh/triangle_soup_mesh_builder.h" +#include "draco/point_cloud/point_cloud_builder.h" + +namespace draco { + +// Helper class that handles splitting of meshes with faces / without faces, +// i.e. point clouds. +template +class MeshSplitterInternal { + public: + struct WorkData : public MeshSplitter::WorkData { + // TriangleSoupMeshBuilder or PointCloudBuilder. + std::vector builders; + }; + + // Computes number of elements (faces or points) for each sub-mesh. + Status InitializeWorkDataNumElements(const Mesh &mesh, int split_attribute_id, + WorkData *work_data) const; + // Initializes a builder for a given sub-mesh. + void InitializeBuilder(int b_index, int num_elements, const Mesh &mesh, + int ignored_attribute_id, WorkData *work_data) const; + // Add all faces or points to the builders. + void AddElementsToBuilder(const Mesh &mesh, + const PointAttribute *split_attribute, + WorkData *work_data) const; + // Builds the meshes from the data accumulated in the builders. + StatusOr BuildMeshes(const Mesh &mesh, + WorkData *work_data) const; +}; + +namespace { + +// Helper functions for copying single element from source |mesh| to a target +// builder |b_index| stored in |work_data|. +void AddElementToBuilder( + int b_index, FaceIndex source_i, FaceIndex target_i, const Mesh &mesh, + MeshSplitterInternal::WorkData *work_data); +void AddElementToBuilder( + int b_index, PointIndex source_i, PointIndex target_i, const Mesh &mesh, + MeshSplitterInternal::WorkData *work_data); +} // namespace + +MeshSplitter::MeshSplitter() + : preserve_materials_(false), + remove_unused_material_indices_(true), + preserve_mesh_features_(false) {} + +StatusOr MeshSplitter::SplitMesh( + const Mesh &mesh, uint32_t split_attribute_id) { + if (mesh.num_attributes() <= split_attribute_id) { + return Status(Status::DRACO_ERROR, "Invalid attribute id."); + } + if (mesh.num_faces() == 0) { + return SplitMeshInternal(mesh, split_attribute_id); + } else { + return SplitMeshInternal(mesh, split_attribute_id); + } +} + +template +StatusOr MeshSplitter::SplitMeshInternal( + const Mesh &mesh, int split_attribute_id) { + const PointAttribute *const split_attribute = + mesh.attribute(split_attribute_id); + + // Preserve the split attribute only if it is the material attribute and the + // |preserve_materials_| flag is set. Othwerwise the split attribute will get + // discarded. + // TODO(ostava): We may revisit this later and add an option to always + // preserve the split attribute. + const bool preserve_split_attribute = + preserve_materials_ && + split_attribute->attribute_type() == GeometryAttribute::MATERIAL; + + const int num_out_meshes = split_attribute->size(); + MeshSplitterInternal splitter_internal; + typename MeshSplitterInternal::WorkData work_data; + work_data.num_sub_mesh_elements.resize(num_out_meshes, 0); + work_data.split_by_materials = + (split_attribute->attribute_type() == GeometryAttribute::MATERIAL); + + DRACO_RETURN_IF_ERROR(splitter_internal.InitializeWorkDataNumElements( + mesh, split_attribute_id, &work_data)); + + // Create the sub-meshes. + work_data.builders.resize(num_out_meshes); + // Map between attribute ids of the input and output meshes. + work_data.att_id_map.resize(mesh.num_attributes(), -1); + const int ignored_att_id = + (!preserve_split_attribute ? split_attribute_id : -1); + for (int mi = 0; mi < num_out_meshes; ++mi) { + if (work_data.num_sub_mesh_elements[mi] == 0) { + continue; // Empty mesh, don't initialize it. + } + + const int num_elements = work_data.num_sub_mesh_elements[mi]; + splitter_internal.InitializeBuilder(mi, num_elements, mesh, ignored_att_id, + &work_data); + + // Reset the element counter for the sub-mesh. It will be used to keep track + // of number of elements added to the sub-mesh. + work_data.num_sub_mesh_elements[mi] = 0; + } + + splitter_internal.AddElementsToBuilder(mesh, split_attribute, &work_data); + + DRACO_ASSIGN_OR_RETURN(MeshVector out_meshes, + splitter_internal.BuildMeshes(mesh, &work_data)); + return FinalizeMeshes(mesh, work_data, std::move(out_meshes)); +} + +template <> +Status +MeshSplitterInternal::InitializeWorkDataNumElements( + const Mesh &mesh, int split_attribute_id, WorkData *work_data) const { + const PointAttribute *const split_attribute = + mesh.attribute(split_attribute_id); + // Verify that the attribute values are defined "per-face", i.e., all points + // on a face are always mapped to the same attribute value. + for (FaceIndex fi(0); fi < mesh.num_faces(); ++fi) { + const auto face = mesh.face(fi); + const AttributeValueIndex avi = split_attribute->mapped_index(face[0]); + for (int c = 1; c < 3; ++c) { + if (split_attribute->mapped_index(face[c]) != avi) { + return Status(Status::DRACO_ERROR, + "Attribute values not consistent on a face."); + } + } + work_data->num_sub_mesh_elements[avi.value()] += 1; + } + return OkStatus(); +} + +template <> +Status MeshSplitterInternal::InitializeWorkDataNumElements( + const Mesh &mesh, int split_attribute_id, WorkData *work_data) const { + const PointAttribute *const split_attribute = + mesh.attribute(split_attribute_id); + // Each point can have a different value. Just accumulate the number of points + // with the same attribute value index. + for (PointIndex pi(0); pi < mesh.num_points(); ++pi) { + const AttributeValueIndex avi = split_attribute->mapped_index(pi); + work_data->num_sub_mesh_elements[avi.value()] += 1; + } + return OkStatus(); +} + +template +void MeshSplitterInternal::InitializeBuilder( + int b_index, int num_elements, const Mesh &mesh, int ignored_attribute_id, + WorkData *work_data) const { + work_data->builders[b_index].Start(num_elements); + + // Add all attributes. + for (int ai = 0; ai < mesh.num_attributes(); ++ai) { + if (ai == ignored_attribute_id) { + continue; + } + const GeometryAttribute *const src_att = mesh.attribute(ai); + work_data->att_id_map[ai] = work_data->builders[b_index].AddAttribute( + src_att->attribute_type(), src_att->num_components(), + src_att->data_type()); + } +} + +template <> +void MeshSplitterInternal::AddElementsToBuilder( + const Mesh &mesh, const PointAttribute *split_attribute, + WorkData *work_data) const { + // Go over all faces of the input mesh and add them to the appropriate + // sub-mesh. + for (FaceIndex fi(0); fi < mesh.num_faces(); ++fi) { + const auto face = mesh.face(fi); + const int sub_mesh_id = split_attribute->mapped_index(face[0]).value(); + const FaceIndex target_fi(work_data->num_sub_mesh_elements[sub_mesh_id]++); + AddElementToBuilder(sub_mesh_id, fi, target_fi, mesh, work_data); + } +} + +template <> +void MeshSplitterInternal::AddElementsToBuilder( + const Mesh &mesh, const PointAttribute *split_attribute, + WorkData *work_data) const { + // Go over all points of the input mesh and add them to the appropriate + // sub-mesh. + for (PointIndex pi(0); pi < mesh.num_points(); ++pi) { + const int sub_mesh_id = split_attribute->mapped_index(pi).value(); + const PointIndex target_pi(work_data->num_sub_mesh_elements[sub_mesh_id]++); + AddElementToBuilder(sub_mesh_id, pi, target_pi, mesh, work_data); + } +} + +namespace { + +void AddElementToBuilder( + int b_index, FaceIndex source_i, FaceIndex target_i, const Mesh &mesh, + MeshSplitterInternal::WorkData *work_data) { + const auto &face = mesh.face(source_i); + for (int ai = 0; ai < mesh.num_attributes(); ++ai) { + const PointAttribute *const src_att = mesh.attribute(ai); + const int target_att_id = work_data->att_id_map[ai]; + if (target_att_id == -1) { + continue; + } + // Add value for each corner of the face. + work_data->builders[b_index].SetAttributeValuesForFace( + target_att_id, target_i, src_att->GetAddressOfMappedIndex(face[0]), + src_att->GetAddressOfMappedIndex(face[1]), + src_att->GetAddressOfMappedIndex(face[2])); + } +} + +void AddElementToBuilder( + int b_index, PointIndex source_i, PointIndex target_i, const Mesh &mesh, + MeshSplitterInternal::WorkData *work_data) { + for (int ai = 0; ai < mesh.num_attributes(); ++ai) { + const PointAttribute *const src_att = mesh.attribute(ai); + const int target_att_id = work_data->att_id_map[ai]; + if (target_att_id == -1) { + continue; + } + // Add value for the point |target_i|. + work_data->builders[b_index].SetAttributeValueForPoint( + target_att_id, target_i, src_att->GetAddressOfMappedIndex(source_i)); + } +} + +} // namespace + +template <> +StatusOr +MeshSplitterInternal::BuildMeshes( + const Mesh &mesh, WorkData *work_data) const { + const int num_out_meshes = work_data->builders.size(); + MeshSplitter::MeshVector out_meshes(num_out_meshes); + for (int mi = 0; mi < num_out_meshes; ++mi) { + if (work_data->num_sub_mesh_elements[mi] == 0) { + continue; + } + out_meshes[mi] = work_data->builders[mi].Finalize(); + if (out_meshes[mi] == nullptr) { + continue; + } + } + return out_meshes; +} + +template <> +StatusOr +MeshSplitterInternal::BuildMeshes( + const Mesh &mesh, WorkData *work_data) const { + const int num_out_meshes = work_data->builders.size(); + MeshSplitter::MeshVector out_meshes(num_out_meshes); + for (int mi = 0; mi < num_out_meshes; ++mi) { + if (work_data->num_sub_mesh_elements[mi] == 0) { + continue; + } + // For point clouds, we first build a point cloud and copy it over into + // a draco::Mesh. + std::unique_ptr pc = work_data->builders[mi].Finalize(true); + if (pc == nullptr) { + continue; + } + std::unique_ptr mesh(new Mesh()); + PointCloud *mesh_pc = mesh.get(); + mesh_pc->Copy(*pc); + out_meshes[mi] = std::move(mesh); + } + return out_meshes; +} + +StatusOr MeshSplitter::FinalizeMeshes( + const Mesh &mesh, const WorkData &work_data, MeshVector out_meshes) const { + // Finalize meshes. + const int num_out_meshes = out_meshes.size(); + + // If we are going to preserve mesh features, we will need to update texture + // pointers for all mesh feature textures. Here we store the mapping between + // the old texture pointers and their indices. + std::unordered_map features_texture_to_index_map; + if (preserve_mesh_features_) { + features_texture_to_index_map = + mesh.GetNonMaterialTextureLibrary().ComputeTextureToIndexMap(); + } + + for (int mi = 0; mi < num_out_meshes; ++mi) { + if (out_meshes[mi] == nullptr) { + continue; + } + out_meshes[mi]->SetName(mesh.GetName()); + if (preserve_materials_) { + out_meshes[mi]->GetMaterialLibrary().Copy(mesh.GetMaterialLibrary()); + } + + // Copy metadata of the original mesh to the output meshes. + if (mesh.GetMetadata() != nullptr) { + const GeometryMetadata &metadata = *mesh.GetMetadata(); + out_meshes[mi]->AddMetadata( + std::unique_ptr(new GeometryMetadata(metadata))); + } + + // Copy over attribute unique ids. + for (int att_id = 0; att_id < mesh.num_attributes(); ++att_id) { + const int mapped_att_id = work_data.att_id_map[att_id]; + if (mapped_att_id == -1) { + continue; + } + const PointAttribute *const src_att = mesh.attribute(att_id); + PointAttribute *const dst_att = out_meshes[mi]->attribute(mapped_att_id); + dst_att->set_unique_id(src_att->unique_id()); + } + + // Copy compression settings of the original mesh to the output meshes. + out_meshes[mi]->SetCompressionEnabled(mesh.IsCompressionEnabled()); + out_meshes[mi]->SetCompressionOptions(mesh.GetCompressionOptions()); + + if (preserve_mesh_features_) { + // Copy mesh features from the source |mesh| to the |out_meshes[mi]|. + for (MeshFeaturesIndex mfi(0); mfi < mesh.NumMeshFeatures(); ++mfi) { + if (work_data.split_by_materials) { + // Copy over only those mesh features that were masked to the material + // corresponding to |mi|. + bool is_used = false; + if (mesh.NumMeshFeaturesMaterialMasks(mfi) == 0) { + is_used = true; + } else { + for (int mask_index = 0; + mask_index < mesh.NumMeshFeaturesMaterialMasks(mfi); + ++mask_index) { + if (mesh.GetMeshFeaturesMaterialMask(mfi, mask_index) == mi) { + is_used = true; + break; + } + } + } + if (!is_used) { + // Ignore this mesh features. + continue; + } + } + // Create a copy of source mesh features. + std::unique_ptr mf(new MeshFeatures()); + mf->Copy(mesh.GetMeshFeatures(mfi)); + const MeshFeaturesIndex new_mfi = + out_meshes[mi]->AddMeshFeatures(std::move(mf)); + if (work_data.split_by_materials && !preserve_materials_) { + // If the input |mesh| was split by materials and we didn't preserve + // the materials, all mesh features must be masked to material 0. + out_meshes[mi]->AddMeshFeaturesMaterialMask(new_mfi, 0); + } else { + // Otherwise mesh features use same masking as the source mesh because + // the material attribute is still present in the split meshes. + // Note that this masking can be later changed in + // RemoveUnusedMaterials() call below. + for (int mask_index = 0; + mask_index < mesh.NumMeshFeaturesMaterialMasks(mfi); + ++mask_index) { + out_meshes[mi]->AddMeshFeaturesMaterialMask( + new_mfi, mesh.GetMeshFeaturesMaterialMask(mfi, mask_index)); + } + } + } + + // Copy over all features textures to the split mesh. + out_meshes[mi]->GetNonMaterialTextureLibrary().Copy( + mesh.GetNonMaterialTextureLibrary()); + + // Update mesh features texture pointers to the new library. + for (MeshFeaturesIndex mfi(0); mfi < out_meshes[mi]->NumMeshFeatures(); + ++mfi) { + Mesh::UpdateMeshFeaturesTexturePointer( + features_texture_to_index_map, + &out_meshes[mi]->GetNonMaterialTextureLibrary(), + &out_meshes[mi]->GetMeshFeatures(mfi)); + } + + // This will remove any mesh features that may not be be actually used + // by this |out_meshes[mi]| (e.g. because corresponding material indices + // were not present in this split mesh). This also removes any unused + // features textures from the non-material texture library. + DRACO_RETURN_IF_ERROR( + MeshUtils::RemoveUnusedMeshFeatures(out_meshes[mi].get())); + } + + // Remove unused materials after we remove mesh features because some of + // the mesh features may have referenced old material indices. + if (preserve_materials_) { + out_meshes[mi]->RemoveUnusedMaterials(remove_unused_material_indices_); + } + + // Copy structural metadata from input mesh to each of the output meshes. + out_meshes[mi]->GetStructuralMetadata().Copy(mesh.GetStructuralMetadata()); + } + return std::move(out_meshes); +} + +StatusOr MeshSplitter::SplitMeshToComponents( + const Mesh &mesh, const MeshConnectedComponents &connected_components) { + // Create the sub-meshes. + const int num_out_meshes = connected_components.NumConnectedComponents(); + MeshSplitterInternal splitter_internal; + typename MeshSplitterInternal::WorkData work_data; + work_data.builders.resize(num_out_meshes); + work_data.num_sub_mesh_elements.resize(num_out_meshes, 0); + work_data.att_id_map.resize(mesh.num_attributes(), -1); + for (int mi = 0; mi < num_out_meshes; ++mi) { + const int num_faces = connected_components.NumConnectedComponentFaces(mi); + work_data.num_sub_mesh_elements[mi] = num_faces; + splitter_internal.InitializeBuilder(mi, num_faces, mesh, -1, &work_data); + } + + // Go over all faces of the input mesh and add them to the appropriate + // sub-mesh. + for (int mi = 0; mi < num_out_meshes; ++mi) { + for (int cfi = 0; cfi < connected_components.NumConnectedComponentFaces(mi); + ++cfi) { + const FaceIndex fi( + connected_components.GetConnectedComponent(mi).faces[cfi]); + const FaceIndex target_fi(cfi); + AddElementToBuilder(mi, fi, target_fi, mesh, &work_data); + } + } + DRACO_ASSIGN_OR_RETURN(auto out_meshes, + splitter_internal.BuildMeshes(mesh, &work_data)); + return FinalizeMeshes(mesh, work_data, std::move(out_meshes)); +} + +} // namespace draco +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/mesh/mesh_splitter.h b/contrib/draco/src/draco/mesh/mesh_splitter.h new file mode 100644 index 000000000..bf5cd9794 --- /dev/null +++ b/contrib/draco/src/draco/mesh/mesh_splitter.h @@ -0,0 +1,109 @@ +// Copyright 2017 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_MESH_MESH_SPLITTER_H_ +#define DRACO_MESH_MESH_SPLITTER_H_ + +#include +#include + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/status_or.h" +#include "draco/mesh/mesh.h" +#include "draco/mesh/mesh_connected_components.h" +#include "draco/mesh/triangle_soup_mesh_builder.h" + +namespace draco { + +// Class that can be used to split a single mesh into multiple sub-meshes +// according to specified criteria. +class MeshSplitter { + public: + typedef std::vector> MeshVector; + MeshSplitter(); + + // Sets a flag that tells the splitter to preserve all materials on the input + // mesh during mesh splitting. When set, the materials used on sub-meshes are + // going to be copied over. Any redundant materials on sub-meshes are going to + // be deleted but material indices may still be preserved depending on the + // SetRemoveUnusedMaterialIndices() flag. + // Default = false. + void SetPreserveMaterials(bool flag) { preserve_materials_ = flag; } + + // Sets a flag that tells the splitter to delete any unused material indices + // on the generated sub-meshes. This option is currently used only when + // SetPreserveMaterials() was set to true. If this option is set to false, the + // material indices of the MATERIAL attribute will be the same as in the + // source mesh. If the flag is true, then the unused material indices will be + // removed and they may no longer correspond to the source mesh. Note that + // when this flag is false, any unused materials would be replaced with empty + // (default) materials. + // Default = true. + void SetRemoveUnusedMaterialIndices(bool flag) { + remove_unused_material_indices_ = flag; + } + + // Sets a flag that tells the splitter to preserve all mesh features on the + // input mesh during mesh splitting. When set, the mesh features used on + // sub-meshes are going to be copied over. Any redundant mesh features on + // sub-meshes are going to be deleted. + // Default = false. + void SetPreserveMeshFeatures(bool flag) { preserve_mesh_features_ = flag; } + + // Splits the input |mesh| according to attribute values stored in the + // specified attribute. If the |mesh| contains faces, the attribute values + // need to be defined per-face, that is, all points attached to a single face + // must share the same attribute value. Meshes without faces are treated as + // point clouds and the attribute values can be defined per-point. Each + // attribute value (AttributeValueIndex) is mapped to a single output mesh. If + // an AttributeValueIndex is unused, no mesh is created for the given value. + StatusOr SplitMesh(const Mesh &mesh, uint32_t split_attribute_id); + + // Splits the input |mesh| into separate components defined in + // |connected_components|. That is, all faces associated with a given + // component index will be stored in the same mesh. The number of generated + // meshes will correspond to |connected_components.NumConnectedComponents()|. + StatusOr SplitMeshToComponents( + const Mesh &mesh, const MeshConnectedComponents &connected_components); + + private: + struct WorkData { + // Map between attribute ids of the input and output meshes. + std::vector att_id_map; + std::vector num_sub_mesh_elements; + bool split_by_materials = false; + }; + + template + StatusOr SplitMeshInternal(const Mesh &mesh, + int split_attribute_id); + + StatusOr FinalizeMeshes(const Mesh &mesh, + const WorkData &work_data, + MeshVector out_meshes) const; + + bool preserve_materials_; + bool remove_unused_material_indices_; + bool preserve_mesh_features_; + + template + friend class MeshSplitterInternal; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_MESH_MESH_SPLITTER_H_ diff --git a/contrib/draco/src/draco/javascript/emscripten/draco_animation_encoder_glue_wrapper.cc b/contrib/draco/src/draco/mesh/mesh_splitter_test.cc similarity index 51% rename from contrib/draco/src/draco/javascript/emscripten/draco_animation_encoder_glue_wrapper.cc rename to contrib/draco/src/draco/mesh/mesh_splitter_test.cc index 29e7ed3ba..7432c4736 100644 --- a/contrib/draco/src/draco/javascript/emscripten/draco_animation_encoder_glue_wrapper.cc +++ b/contrib/draco/src/draco/mesh/mesh_splitter_test.cc @@ -12,14 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. // -// This file is used by emscripten's WebIDL Binder. -// http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/WebIDL-Binder.html -#include "draco/attributes/geometry_attribute.h" -#include "draco/attributes/point_attribute.h" -#include "draco/compression/encode.h" -#include "draco/javascript/emscripten/animation_encoder_webidl_wrapper.h" -#include "draco/mesh/mesh.h" -#include "draco/point_cloud/point_cloud.h" +#include "draco/mesh/mesh_splitter.h" -// glue_animation_encoder.cpp is generated by Makefile.emcc build_glue target. -#include "glue_animation_encoder.cpp" +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/core/vector_d.h" +#include "draco/io/mesh_io.h" +#include "draco/mesh/mesh_misc_functions.h" + +namespace {} // namespace +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/mesh/mesh_stripifier.h b/contrib/draco/src/draco/mesh/mesh_stripifier.h index 262e3c792..8e8d8d9f2 100644 --- a/contrib/draco/src/draco/mesh/mesh_stripifier.h +++ b/contrib/draco/src/draco/mesh/mesh_stripifier.h @@ -71,8 +71,6 @@ class MeshStripifier { mesh_ = &mesh; num_strips_ = 0; num_encoded_faces_ = 0; - // TODO(ostava): We may be able to avoid computing the corner table if we - // already have it stored somewhere. corner_table_ = CreateCornerTableFromPositionAttribute(mesh_); if (corner_table_ == nullptr) { return false; diff --git a/contrib/draco/src/draco/mesh/mesh_test.cc b/contrib/draco/src/draco/mesh/mesh_test.cc new file mode 100644 index 000000000..7cc046a7e --- /dev/null +++ b/contrib/draco/src/draco/mesh/mesh_test.cc @@ -0,0 +1,644 @@ +// Copyright 2018 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/mesh/mesh.h" + +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/compression/draco_compression_options.h" +#include "draco/material/material_utils.h" +#include "draco/mesh/mesh_are_equivalent.h" +#include "draco/mesh/mesh_features.h" +#include "draco/mesh/mesh_utils.h" +#include "draco/mesh/triangle_soup_mesh_builder.h" +#endif // DRACO_TRANSCODER_SUPPORTED + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED +// Tests naming of a mesh. +TEST(MeshTest, MeshName) { + draco::Mesh mesh; + ASSERT_TRUE(mesh.GetName().empty()); + mesh.SetName("Bob"); + ASSERT_EQ(mesh.GetName(), "Bob"); +} + +// Tests copying of a mesh. +TEST(MeshTest, MeshCopy) { + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + draco::Mesh mesh_copy; + mesh_copy.Copy(*mesh); + draco::MeshAreEquivalent eq; + ASSERT_TRUE(eq(*mesh, mesh_copy)); +} + +// Tests that we can copy a mesh to a different mesh that already contains some +// data. +TEST(MeshTest, MeshCopyToExistingMesh) { + const std::unique_ptr mesh_0 = + draco::ReadMeshFromTestFile("cube_att.obj"); + const std::unique_ptr mesh_1 = + draco::ReadMeshFromTestFile("test_nm.obj"); + ASSERT_NE(mesh_0, nullptr); + ASSERT_NE(mesh_1, nullptr); + draco::MeshAreEquivalent eq; + ASSERT_FALSE(eq(*mesh_0, *mesh_1)); + + mesh_1->Copy(*mesh_0); + ASSERT_TRUE(eq(*mesh_0, *mesh_1)); +} + +// Tests that we can remove unused materials from a mesh. +TEST(MeshTest, RemoveUnusedMaterials) { + // Input mesh has 29 materials defined in the source file but only 7 are + // actually used. + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("mat_test.obj"); + ASSERT_NE(mesh, nullptr); + + const draco::PointAttribute *const mat_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::MATERIAL); + ASSERT_NE(mat_att, nullptr); + ASSERT_EQ(mat_att->size(), 29); + + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), mat_att->size()); + + // Get materials on all faces. + std::vector face_materials(mesh->num_faces(), + nullptr); + for (draco::FaceIndex fi(0); fi < mesh->num_faces(); ++fi) { + uint32_t mat_index = 0; + mat_att->GetMappedValue(mesh->face(fi)[0], &mat_index); + face_materials[fi.value()] = + mesh->GetMaterialLibrary().GetMaterial(mat_index); + } + + mesh->RemoveUnusedMaterials(); + + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 7); + + // Ensure the material attribute contains material indices in the valid range. + for (draco::AttributeValueIndex avi(0); avi < mat_att->size(); ++avi) { + uint32_t mat_index = 0; + mat_att->GetValue(avi, &mat_index); + ASSERT_LT(mat_index, mesh->GetMaterialLibrary().NumMaterials()); + } + + // Ensure all materials are still the same for all faces. + for (draco::FaceIndex fi(0); fi < mesh->num_faces(); ++fi) { + uint32_t mat_index = 0; + mat_att->GetMappedValue(mesh->face(fi)[0], &mat_index); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(mat_index), + face_materials[fi.value()]); + } +} + +TEST(MeshTest, RemoveUnusedMaterialsOnPointClud) { + // Input mesh has 29 materials defined in the source file but only 7 are + // actually used. Same as above test but we remove all faces and treat the + // model as a point cloud. + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("mat_test.obj"); + ASSERT_NE(mesh, nullptr); + + // Make it a point cloud. + mesh->SetNumFaces(0); + + const draco::PointAttribute *const mat_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::MATERIAL); + ASSERT_NE(mat_att, nullptr); + ASSERT_EQ(mat_att->size(), 29); + + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), mat_att->size()); + + // Get materials on all points. + std::vector point_materials(mesh->num_points(), + nullptr); + for (draco::PointIndex pi(0); pi < mesh->num_points(); ++pi) { + uint32_t mat_index = 0; + mat_att->GetMappedValue(pi, &mat_index); + point_materials[pi.value()] = + mesh->GetMaterialLibrary().GetMaterial(mat_index); + } + + mesh->RemoveUnusedMaterials(); + + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 7); + + // Ensure the material attribute contains material indices in the valid range. + for (draco::AttributeValueIndex avi(0); avi < mat_att->size(); ++avi) { + uint32_t mat_index = 0; + mat_att->GetValue(avi, &mat_index); + ASSERT_LT(mat_index, mesh->GetMaterialLibrary().NumMaterials()); + } + + // Ensure all materials are still the same for all points. + for (draco::PointIndex pi(0); pi < mesh->num_points(); ++pi) { + uint32_t mat_index = 0; + mat_att->GetMappedValue(pi, &mat_index); + ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(mat_index), + point_materials[pi.value()]); + } +} + +TEST(MeshTest, RemoveUnusedMaterialsNoIndices) { + // The same as above but we actually want to remove only materials and not + // material indices. Therefore we should end up with the same number of + // materials as source but all unused materials should be "default". + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("mat_test.obj"); + ASSERT_NE(mesh, nullptr); + + const draco::PointAttribute *const mat_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::MATERIAL); + ASSERT_NE(mat_att, nullptr); + ASSERT_EQ(mat_att->size(), 29); + + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), mat_att->size()); + + // Do not remove unused material indices. + mesh->RemoveUnusedMaterials(false); + + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 29); + + // Gether which materials were actually used and check that all remaining + // materials are "default". + std::vector is_mat_used(mesh->GetMaterialLibrary().NumMaterials(), + false); + for (draco::AttributeValueIndex avi(0); avi < mat_att->size(); ++avi) { + uint32_t mat_index = 0; + mat_att->GetValue(avi, &mat_index); + is_mat_used[mat_index] = true; + } + + for (int mi = 0; mi < mesh->GetMaterialLibrary().NumMaterials(); ++mi) { + if (!is_mat_used[mi]) { + ASSERT_TRUE(draco::MaterialUtils::AreMaterialsEquivalent( + *mesh->GetMaterialLibrary().GetMaterial(mi), draco::Material())); + } + } +} + +TEST(MeshTest, TestAddNewAttributeWithConnectivity) { + // Tests that we can add new attributes with arbitrary connectivity to an + // existing mesh. + + // Create a simple quad. See corner indices of the quad on the figure below: + // + // *-------* + // |2\3 5| + // | \ | + // | \ | + // | \ | + // | \4| + // |0 1\| + // *-------* + // + draco::TriangleSoupMeshBuilder mb; + mb.Start(2); + mb.AddAttribute(draco::GeometryAttribute::POSITION, 3, draco::DT_FLOAT32); + mb.SetAttributeValuesForFace( + 0, draco::FaceIndex(0), draco::Vector3f(0, 0, 0).data(), + draco::Vector3f(1, 0, 0).data(), draco::Vector3f(1, 1, 0).data()); + mb.SetAttributeValuesForFace( + 0, draco::FaceIndex(1), draco::Vector3f(1, 1, 0).data(), + draco::Vector3f(1, 0, 0).data(), draco::Vector3f(1, 1, 1).data()); + std::unique_ptr mesh = mb.Finalize(); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->num_points(), 4); + ASSERT_EQ(mesh->GetNamedAttribute(draco::GeometryAttribute::POSITION)->size(), + 4); + + // Create a simple attribute that has a constant value on every corner. + std::unique_ptr pa(new draco::PointAttribute()); + pa->Init(draco::GeometryAttribute::GENERIC, 1 /*One components*/, + draco::DT_UINT8, false, 1); + uint8_t val = 10; + pa->SetAttributeValue(draco::AttributeValueIndex(0), &val); + + // Map all corners to the same value. + draco::IndexTypeVector + corner_to_point(6, draco::AttributeValueIndex(0)); + + // Adding this attribute to the mesh should not increase the number of points. + const int new_att_id_0 = + mesh->AddAttributeWithConnectivity(std::move(pa), corner_to_point); + + ASSERT_EQ(mesh->num_attributes(), 2); + ASSERT_EQ(mesh->num_points(), 4); + + const draco::PointAttribute *const new_att_0 = mesh->attribute(new_att_id_0); + ASSERT_NE(new_att_0, nullptr); + + // All points of the mesh should be mapped to the same attribute value. + for (draco::PointIndex pi(0); pi < mesh->num_points(); ++pi) { + uint8_t att_val = 0; + new_att_0->GetMappedValue(pi, &att_val); + ASSERT_EQ(att_val, 10); + } + + // Add a new attribute with two values and different connectivity. + pa = std::unique_ptr(new draco::PointAttribute()); + pa->Init(draco::GeometryAttribute::GENERIC, 1 /*One components*/, + draco::DT_UINT8, false, 2); + val = 11; + pa->SetAttributeValue(draco::AttributeValueIndex(0), &val); + val = 12; + pa->SetAttributeValue(draco::AttributeValueIndex(1), &val); + + // Map all corners to the value index 0 except for corner 1 that is mapped to + // value index 1. This should result in a new point being created on either + // corner 1 or corner 4 (see figure at the beginning of this test). + corner_to_point.assign(6, draco::AttributeValueIndex(0)); + corner_to_point[draco::CornerIndex(1)] = draco::AttributeValueIndex(1); + + const int new_att_id_1 = + mesh->AddAttributeWithConnectivity(std::move(pa), corner_to_point); + + ASSERT_EQ(mesh->num_attributes(), 3); + + // One new point should have been created by adding the new attribute. + ASSERT_EQ(mesh->num_points(), 5); + + const draco::PointAttribute *const new_att_1 = mesh->attribute(new_att_id_1); + ASSERT_NE(new_att_1, nullptr); + ASSERT_TRUE(mesh->CornerToPointId(1) == draco::PointIndex(4) || + mesh->CornerToPointId(4) == draco::PointIndex(4)); + + new_att_1->GetMappedValue(mesh->CornerToPointId(1), &val); + ASSERT_EQ(val, 12); + + new_att_1->GetMappedValue(mesh->CornerToPointId(4), &val); + ASSERT_EQ(val, 11); + + // Ensure the attribute values of the remaining attributes are well defined + // on the new point. + draco::Vector3f pos; + mesh->attribute(0)->GetMappedValue(draco::PointIndex(4), &pos[0]); + ASSERT_EQ(pos, draco::Vector3f(1, 0, 0)); + + new_att_0->GetMappedValue(draco::PointIndex(4), &val); + ASSERT_EQ(val, 10); + + new_att_0->GetMappedValue(mesh->CornerToPointId(1), &val); + ASSERT_EQ(val, 10); + new_att_0->GetMappedValue(mesh->CornerToPointId(4), &val); + ASSERT_EQ(val, 10); +} + +TEST(MeshTest, TestAddNewAttributeWithConnectivityWithIsolatedVertices) { + // Tests that we can add a new attribute with connectivity to a mesh that + // contains isolated vertices. + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("isolated_vertices.ply"); + ASSERT_NE(mesh, nullptr); + const draco::PointAttribute *const pos_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::POSITION); + ASSERT_NE(pos_att, nullptr); + ASSERT_TRUE(pos_att->is_mapping_identity()); + ASSERT_EQ(pos_att->size(), 5); + ASSERT_EQ(mesh->num_points(), 5); + ASSERT_EQ(mesh->num_faces(), 2); + + // Add a new attribute with two values (one for each face). + auto pa = std::unique_ptr(new draco::PointAttribute()); + pa->Init(draco::GeometryAttribute::GENERIC, 1 /*One component*/, + draco::DT_UINT8, false, 2); + uint8_t val = 11; + pa->SetAttributeValue(draco::AttributeValueIndex(0), &val); + val = 12; + pa->SetAttributeValue(draco::AttributeValueIndex(1), &val); + + draco::IndexTypeVector + corner_to_point(6, draco::AttributeValueIndex(0)); + // All corners on the second face are mapped to the value 1. + for (draco::CornerIndex ci(3); ci < 6; ++ci) { + corner_to_point[ci] = draco::AttributeValueIndex(1); + } + + const draco::PointAttribute *const pa_raw = pa.get(); + mesh->AddAttributeWithConnectivity(std::move(pa), corner_to_point); + + // Two new point should have been added. + ASSERT_EQ(mesh->num_points(), 7); + + for (draco::PointIndex pi(0); pi < mesh->num_points(); ++pi) { + ASSERT_NE(pa_raw->mapped_index(pi), draco::kInvalidAttributeValueIndex); + ASSERT_NE(pos_att->mapped_index(pi), draco::kInvalidAttributeValueIndex); + } +} + +TEST(MeshTest, TestAddPerVertexAttribute) { + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + + ASSERT_NE(mesh, nullptr); + const draco::PointAttribute *const pos_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::POSITION); + ASSERT_NE(pos_att, nullptr); + + // The input mesh should have 8 spatial vertices. + ASSERT_EQ(pos_att->size(), 8); + + // Add a new scalar attribute where each value corresponds to the position + // value index (vertex). + std::unique_ptr pa(new draco::PointAttribute()); + pa->Init(draco::GeometryAttribute::GENERIC, /* scalar */ 1, draco::DT_FLOAT32, + false, /* one value per position value */ 8); + + // Set the value for the new attribute. + for (draco::AttributeValueIndex avi(0); avi < 8; ++avi) { + const float att_value = avi.value(); + pa->SetAttributeValue(avi, &att_value); + } + + // Add the attribute to the existing mesh. + const int new_att_id = mesh->AddPerVertexAttribute(std::move(pa)); + ASSERT_NE(new_att_id, -1); + + // Make sure all the attribute values are set correctly for every point of the + // mesh. + for (draco::PointIndex pi(0); pi < mesh->num_points(); ++pi) { + const draco::AttributeValueIndex pos_avi = pos_att->mapped_index(pi); + const draco::AttributeValueIndex new_att_avi = + mesh->attribute(new_att_id)->mapped_index(pi); + ASSERT_EQ(pos_avi, new_att_avi); + + float new_att_value; + mesh->attribute(new_att_id)->GetValue(new_att_avi, &new_att_value); + ASSERT_EQ(new_att_value, new_att_avi.value()); + } +} + +TEST(MeshTest, TestRemovalOfIsolatedPoints) { + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("isolated_vertices.ply"); + + draco::Mesh mesh_copy; + mesh_copy.Copy(*mesh); + + ASSERT_EQ(mesh_copy.num_points(), 5); + mesh_copy.RemoveIsolatedPoints(); + ASSERT_EQ(mesh_copy.num_points(), 4); + + draco::MeshAreEquivalent eq; + ASSERT_TRUE(eq(*mesh, mesh_copy)); +} + +TEST(MeshTest, TestCompressionSettings) { + // Tests compression settings of a mesh. + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + // Check that compression is disabled and compression settings are default. + ASSERT_FALSE(mesh->IsCompressionEnabled()); + const draco::DracoCompressionOptions default_compression_options; + ASSERT_EQ(mesh->GetCompressionOptions(), default_compression_options); + + // Check that compression options can be set without enabling compression. + draco::DracoCompressionOptions compression_options; + compression_options.quantization_bits_normal = 12; + mesh->SetCompressionOptions(compression_options); + ASSERT_EQ(mesh->GetCompressionOptions(), compression_options); + ASSERT_FALSE(mesh->IsCompressionEnabled()); + + // Check that compression can be enabled. + mesh->SetCompressionEnabled(true); + ASSERT_TRUE(mesh->IsCompressionEnabled()); + + // Check that individual compression options can be updated. + mesh->GetCompressionOptions().compression_level++; + mesh->GetCompressionOptions().compression_level--; + + // Check that compression settings can be copied. + draco::Mesh mesh_copy; + mesh_copy.Copy(*mesh); + ASSERT_TRUE(mesh_copy.IsCompressionEnabled()); + ASSERT_EQ(mesh_copy.GetCompressionOptions(), compression_options); +} + +// Tests adding and removing of mesh features to a mesh. +TEST(MeshTest, TestMeshFeatures) { + // Create a mesh with two feature ID sets. + draco::Mesh mesh; + ASSERT_EQ(mesh.NumMeshFeatures(), 0); + std::unique_ptr oceans(new draco::MeshFeatures()); + std::unique_ptr continents(new draco::MeshFeatures()); + oceans->SetLabel("oceans"); + continents->SetLabel("continents"); + const draco::MeshFeaturesIndex index_0 = + mesh.AddMeshFeatures(std::move(oceans)); + const draco::MeshFeaturesIndex index_1 = + mesh.AddMeshFeatures(std::move(continents)); + ASSERT_EQ(index_0, draco::MeshFeaturesIndex(0)); + ASSERT_EQ(index_1, draco::MeshFeaturesIndex(1)); + + // Check that the mesh has two feature ID sets. + ASSERT_EQ(mesh.NumMeshFeatures(), 2); + ASSERT_EQ(mesh.GetMeshFeatures(index_0).GetLabel(), "oceans"); + ASSERT_EQ(mesh.GetMeshFeatures(index_1).GetLabel(), "continents"); + + // Remove one feature ID set and check the remaining feature ID set. + mesh.RemoveMeshFeatures(draco::MeshFeaturesIndex(1)); + ASSERT_EQ(mesh.NumMeshFeatures(), 1); + ASSERT_EQ(mesh.GetMeshFeatures(draco::MeshFeaturesIndex(0)).GetLabel(), + "oceans"); + + // Remove the remaining feature ID set and check that no sets remain. + mesh.RemoveMeshFeatures(draco::MeshFeaturesIndex(0)); + ASSERT_EQ(mesh.NumMeshFeatures(), 0); +} + +// Tests copying of a mesh with feature ID sets. +TEST(MeshTest, MeshCopyWithMeshFeatures) { + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + // Add two textures to the non-material texture library of the mesh. + std::unique_ptr texture0(new draco::Texture()); + std::unique_ptr texture1(new draco::Texture()); + texture0->Resize(128, 128); + texture1->Resize(256, 256); + texture0->FillImage(draco::RGBA(100, 0, 0, 0)); + texture1->FillImage(draco::RGBA(200, 0, 0, 0)); + draco::TextureLibrary &library = mesh->GetNonMaterialTextureLibrary(); + library.PushTexture(std::move(texture0)); + library.PushTexture(std::move(texture1)); + + // Add feature ID set referring to an attribute. + const draco::MeshFeaturesIndex index_0 = mesh->AddMeshFeatures( + std::unique_ptr(new draco::MeshFeatures())); + mesh->GetMeshFeatures(index_0).SetLabel("planet"); + mesh->GetMeshFeatures(index_0).SetFeatureCount(2); + mesh->GetMeshFeatures(index_0).SetAttributeIndex(1); + + // Add feature ID set referring to texture at index 0. + const draco::MeshFeaturesIndex index_1 = mesh->AddMeshFeatures( + std::unique_ptr(new draco::MeshFeatures())); + mesh->GetMeshFeatures(index_1).SetLabel("continents"); + mesh->GetMeshFeatures(index_1).SetFeatureCount(7); + mesh->GetMeshFeatures(index_1).GetTextureMap().SetTexture( + library.GetTexture(0)); + + // Add feature ID set referring to a texture at index 1. + const draco::MeshFeaturesIndex index_2 = mesh->AddMeshFeatures( + std::unique_ptr(new draco::MeshFeatures())); + mesh->GetMeshFeatures(index_2).SetLabel("oceans"); + mesh->GetMeshFeatures(index_2).SetFeatureCount(5); + mesh->GetMeshFeatures(index_2).GetTextureMap().SetTexture( + library.GetTexture(1)); + + // Check mesh feature ID set texture pointers. + ASSERT_EQ(library.NumTextures(), 2); + ASSERT_EQ(mesh->NumMeshFeatures(), 3); + ASSERT_EQ(mesh->GetMeshFeatures(index_0).GetTextureMap().texture(), nullptr); + ASSERT_EQ(mesh->GetMeshFeatures(index_1).GetTextureMap().texture(), + library.GetTexture(0)); + ASSERT_EQ(mesh->GetMeshFeatures(index_2).GetTextureMap().texture(), + library.GetTexture(1)); + + // Copy the mesh. + draco::Mesh mesh_copy; + mesh_copy.Copy(*mesh); + + // Check that the meshes are equivalent. + draco::MeshAreEquivalent eq; + ASSERT_TRUE(eq(*mesh, mesh_copy)); + + // Also check that the texture pointers have been updated correctly. + const draco::TextureLibrary &library_copy = + mesh_copy.GetNonMaterialTextureLibrary(); + ASSERT_EQ(library_copy.NumTextures(), 2); + ASSERT_EQ(mesh_copy.NumMeshFeatures(), 3); + ASSERT_EQ(mesh_copy.GetMeshFeatures(index_0).GetTextureMap().texture(), + nullptr); + ASSERT_EQ(mesh_copy.GetMeshFeatures(index_1).GetTextureMap().texture(), + library_copy.GetTexture(0)); + ASSERT_EQ(mesh_copy.GetMeshFeatures(index_2).GetTextureMap().texture(), + library_copy.GetTexture(1)); +} + +// Tests copying of a mesh with structural metadata. +TEST(MeshTest, TestCopyWithStructuralMetadata) { + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + // Add structural metadata to the mesh. + draco::PropertyTable::Schema schema; + schema.json.SetString("Data"); + mesh->GetStructuralMetadata().SetPropertyTableSchema(schema); + + // Copy the mesh. + draco::Mesh copy; + copy.Copy(*mesh); + + // Check that the structural metadata has been copied. + ASSERT_EQ( + copy.GetStructuralMetadata().GetPropertyTableSchema().json.GetString(), + "Data"); +} + +// Tests removing of unused materials for a mesh with mesh features. +TEST(MeshTest, RemoveUnusedMaterialsWithMeshFeatures) { + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("BoxesMeta/glTF/BoxesMeta.gltf"); + ASSERT_NE(mesh, nullptr); + + // Input has five mesh features, two associated with material 0 and three with + // material 1. + ASSERT_EQ(mesh->NumMeshFeatures(), 5); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(0), 0), + 0); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(1), 0), + 0); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(2), 0), + 1); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(3), 0), + 1); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(4), 0), + 1); + + // Remove material 0. + draco::PointAttribute *mat_att = mesh->attribute( + mesh->GetNamedAttributeId(draco::GeometryAttribute::MATERIAL)); + // Map mat value 0 to 1. + uint32_t new_mat_index = 1; + mat_att->SetAttributeValue(draco::AttributeValueIndex(0), &new_mat_index); + + // This should not do anything because we still have the material 0 referenced + // by mesh features 0 and 1. + mesh->RemoveUnusedMaterials(); + + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 2); + ASSERT_EQ(mesh->NumMeshFeatures(), 5); + + // Now remove unused mesh features (should be 0 and 1). + DRACO_ASSERT_OK(draco::MeshUtils::RemoveUnusedMeshFeatures(mesh.get())); + + ASSERT_EQ(mesh->NumMeshFeatures(), 3); + // All remaining mesh features should be still mapped to material 1. + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(0), 0), + 1); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(1), 0), + 1); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(2), 0), + 1); + + // Now remove the unused materials (0). + mesh->RemoveUnusedMaterials(); + + // Only one material should be remaining and all the mesh features should now + // be mapped to material 0. + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(0), 0), + 0); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(1), 0), + 0); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(2), 0), + 0); +} +#endif // DRACO_TRANSCODER_SUPPORTED + +// Test bounding box. +TEST(MeshTest, TestMeshBoundingBox) { + const draco::Vector3f max_pt(1, 1, 1); + const draco::Vector3f min_pt(0, 0, 0); + + const std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr) << "Failed in Loading: " + << "cube_att.obj"; + const draco::BoundingBox bounding_box = mesh->ComputeBoundingBox(); + + EXPECT_EQ(max_pt[0], bounding_box.GetMaxPoint()[0]); + EXPECT_EQ(max_pt[1], bounding_box.GetMaxPoint()[1]); + EXPECT_EQ(max_pt[2], bounding_box.GetMaxPoint()[2]); + + EXPECT_EQ(min_pt[0], bounding_box.GetMinPoint()[0]); + EXPECT_EQ(min_pt[1], bounding_box.GetMinPoint()[1]); + EXPECT_EQ(min_pt[2], bounding_box.GetMinPoint()[2]); +} + +} // namespace diff --git a/contrib/draco/src/draco/mesh/mesh_utils.cc b/contrib/draco/src/draco/mesh/mesh_utils.cc new file mode 100644 index 000000000..0fbe366c1 --- /dev/null +++ b/contrib/draco/src/draco/mesh/mesh_utils.cc @@ -0,0 +1,492 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/mesh/mesh_utils.h" + +#include +#include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/attributes/attribute_quantization_transform.h" +#include "draco/core/quantization_utils.h" + +namespace draco { + +void MeshUtils::TransformMesh(const Eigen::Matrix4d &transform, Mesh *mesh) { + // Transform positions. + PointAttribute *pos_att = + mesh->attribute(mesh->GetNamedAttributeId(GeometryAttribute::POSITION)); + for (AttributeValueIndex avi(0); avi < pos_att->size(); ++avi) { + Vector3f pos_val; + pos_att->GetValue(avi, &pos_val[0]); + Eigen::Vector4d transformed_val(pos_val[0], pos_val[1], pos_val[2], 1); + transformed_val = transform * transformed_val; + pos_val = + Vector3f(transformed_val[0], transformed_val[1], transformed_val[2]); + pos_att->SetAttributeValue(avi, &pos_val[0]); + } + + // Transform normals and tangents. + PointAttribute *normal_att = nullptr; + PointAttribute *tangent_att = nullptr; + if (mesh->NumNamedAttributes(GeometryAttribute::NORMAL) > 0) { + normal_att = + mesh->attribute(mesh->GetNamedAttributeId(GeometryAttribute::NORMAL)); + } + if (mesh->NumNamedAttributes(GeometryAttribute::TANGENT) > 0) { + tangent_att = + mesh->attribute(mesh->GetNamedAttributeId(GeometryAttribute::TANGENT)); + } + + if (normal_att || tangent_att) { + // Use inverse-transpose matrix to transform normals and tangents. + Eigen::Matrix3d it_transform = transform.block<3, 3>(0, 0); + + it_transform = it_transform.inverse().transpose(); + + if (normal_att) { + TransformNormalizedAttribute(it_transform, normal_att); + } + if (tangent_att) { + TransformNormalizedAttribute(it_transform, tangent_att); + } + } +} + +namespace { + +// Merges entries from |src_metadata| to |dst_metadata|. Any metadata entries +// with the same names are left unchanged. +void MergeMetadataInternal(const Metadata &src_metadata, + Metadata *dst_metadata) { + const auto &src_entries = src_metadata.entries(); + const auto &dst_entries = dst_metadata->entries(); + for (const auto &it : src_entries) { + if (dst_entries.find(it.first) != dst_entries.end()) { + // Source entry already exists in the target metadata. + continue; + } + // Copy over the entry (entries don't store the data type so binary copy + // is ok). + dst_metadata->AddEntryBinary(it.first, it.second.data()); + } + + // Merge any sub-metadata. + const auto &src_sub_metadata = src_metadata.sub_metadatas(); + const auto &dst_sub_metadata = dst_metadata->sub_metadatas(); + for (const auto &it : src_sub_metadata) { + if (dst_sub_metadata.find(it.first) == dst_sub_metadata.end()) { + // Source sub-metadata doesn't exists in the target metadata, copy it + // over. + std::unique_ptr sub_metadata(new Metadata(*it.second)); + dst_metadata->AddSubMetadata(it.first, std::move(sub_metadata)); + continue; + } + // Merge entries on the sub-metadata. + MergeMetadataInternal(*it.second, dst_metadata->sub_metadata(it.first)); + } +} + +} // namespace + +void MeshUtils::MergeMetadata(const Mesh &src_mesh, Mesh *dst_mesh) { + const auto *src_metadata = src_mesh.GetMetadata(); + if (src_metadata == nullptr) { + return; // Nothing to merge. + } + if (dst_mesh->GetMetadata() == nullptr) { + // Create new metadata for the |dst_mesh|. We do not copy the metadata + // directly because some of the underlying attribute metadata may need to + // be remapped to the format used by |dst_mesh| (e.g. unique ids of the + // attributes may have changed or some attributes may be missing on the + // |dst_mesh|). + std::unique_ptr new_metadata(new GeometryMetadata()); + dst_mesh->AddMetadata(std::move(new_metadata)); + } + auto *dst_metadata = dst_mesh->metadata(); + + // First go over all entries of the geometry part of |src_metadata|. + MergeMetadataInternal(*src_metadata, dst_metadata); + + // Go over attribute metadata. Merges only metadata for attributes that exist + // both on the source and target meshes. Attribute unique ids are remapped + // if needed. + for (int att_type_i = 0; + att_type_i < GeometryAttribute::NAMED_ATTRIBUTES_COUNT; ++att_type_i) { + const GeometryAttribute::Type att_type = + static_cast(att_type_i); + // TODO(ostava): Handle case when the number of attributes of a given type + // does not match. + if (src_mesh.NumNamedAttributes(att_type) != + dst_mesh->NumNamedAttributes(att_type)) { + continue; + } + for (int j = 0; j < src_mesh.NumNamedAttributes(att_type); ++j) { + // First check if we have a metadata for this attribute. + const PointAttribute *const src_att = + src_mesh.GetNamedAttribute(att_type, j); + const auto *src_metadata = + src_mesh.GetMetadata()->GetAttributeMetadataByUniqueId( + src_att->unique_id()); + if (src_metadata == nullptr) { + // No metadata at the source, ignore the attribute. + continue; + } + // Find target attribute corresponding to the source. + const PointAttribute *const dst_att = + dst_mesh->GetNamedAttribute(att_type, j); + if (dst_att == nullptr) { + // No corresponding attribute found, ignore the source metadata. + continue; + } + auto *dst_metadata = + dst_mesh->metadata()->attribute_metadata(dst_att->unique_id()); + if (dst_metadata == nullptr) { + // Copy over the metadata (with remapped attribute unique id). + std::unique_ptr new_metadata( + new AttributeMetadata(*src_metadata)); + new_metadata->set_att_unique_id(dst_att->unique_id()); + dst_mesh->metadata()->AddAttributeMetadata(std::move(new_metadata)); + continue; + } + // Merge metadata entries. + MergeMetadataInternal(*src_metadata, dst_metadata); + } + } +} + +Status MeshUtils::RemoveUnusedMeshFeatures(Mesh *mesh) { + // Unused mesh features are features that are not used by any face / vertex + // of the |mesh|. Currently, each mesh feature can be "masked" for specific + // materials, in which case we need to check whether the mask materials + // are present in the |mesh|. If not, we can remove the mesh features from the + // mesh. + const PointAttribute *const mat_att = + mesh->GetNamedAttribute(GeometryAttribute::MATERIAL); + // Find which materials are used. + std::unordered_set used_materials; + if (mat_att == nullptr) { + // Only material with index 0 is assumed to be used. + used_materials.insert(0); + } else { + for (AttributeValueIndex avi(0); avi < mat_att->size(); ++avi) { + uint32_t mat_index = 0; + mat_att->GetValue(avi, &mat_index); + used_materials.insert(mat_index); + } + } + + std::vector unused_mesh_features; + for (MeshFeaturesIndex mfi(0); mfi < mesh->NumMeshFeatures(); ++mfi) { + bool is_used = false; + if (mesh->NumMeshFeaturesMaterialMasks(mfi) == 0) { + is_used = true; + } else { + for (int mask_i = 0; mask_i < mesh->NumMeshFeaturesMaterialMasks(mfi); + ++mask_i) { + const int material_index = + mesh->GetMeshFeaturesMaterialMask(mfi, mask_i); + if (used_materials.count(material_index)) { + is_used = true; + break; + } + } + } + if (!is_used) { + unused_mesh_features.push_back(mfi); + } + } + + // Remove the unused mesh features (from back). + for (auto it = unused_mesh_features.rbegin(); + it != unused_mesh_features.rend(); ++it) { + const MeshFeaturesIndex mfi = *it; + mesh->RemoveMeshFeatures(mfi); + } + + // Remove all features textures that are not used anymore. + + // First find which textures are referenced by the mesh features. + std::unordered_set used_textures; + for (MeshFeaturesIndex mfi(0); mfi < mesh->NumMeshFeatures(); ++mfi) { + const Texture *const texture = + mesh->GetMeshFeatures(mfi).GetTextureMap().texture(); + if (texture) { + used_textures.insert(texture); + } + } + + if (!used_textures.empty() && + mesh->GetNonMaterialTextureLibrary().NumTextures() == 0) { + return ErrorStatus( + "Trying to remove mesh features textures that are not owned by the " + "mesh."); + } + + // Remove all unreferenced textures from the non-material texture library. + for (int ti = mesh->GetNonMaterialTextureLibrary().NumTextures() - 1; ti >= 0; + --ti) { + const Texture *const texture = + mesh->GetNonMaterialTextureLibrary().GetTexture(ti); + if (used_textures.count(texture) == 0) { + mesh->GetNonMaterialTextureLibrary().RemoveTexture(ti); + } + } + return OkStatus(); +} + +bool MeshUtils::FlipTextureUvValues(bool flip_u, bool flip_v, + PointAttribute *att) { + if (att->attribute_type() != GeometryAttribute::TEX_COORD) { + return false; + } + if (att->data_type() != DataType::DT_FLOAT32) { + return false; + } + if (att->num_components() != 2) { + return false; + } + + std::array value; + for (AttributeValueIndex avi(0); avi < att->size(); ++avi) { + if (!att->GetValue(avi, &value)) { + return false; + } + if (flip_u) { + value[0] = 1.0 - value[0]; + } + if (flip_v) { + value[1] = 1.0 - value[1]; + } + att->SetAttributeValue(avi, value.data()); + } + return true; +} + +// TODO(fgalligan): Change att_id to be of type const PointAttribute &. +int MeshUtils::CountDegenerateFaces(const Mesh &mesh, int att_id) { + const PointAttribute *const att = mesh.attribute(att_id); + if (att == nullptr) { + return -1; + } + const int num_components = att->num_components(); + switch (num_components) { + case 2: + return MeshUtils::CountDegenerateFaces(mesh, *att); + case 3: + return MeshUtils::CountDegenerateFaces(mesh, *att); + case 4: + return MeshUtils::CountDegenerateFaces(mesh, *att); + default: + break; + } + return -1; +} + +StatusOr MeshUtils::FindLowestTextureQuantization( + const Mesh &mesh, const PointAttribute &pos_att, int pos_quantization_bits, + const PointAttribute &tex_att, int tex_target_quantization_bits) { + if (tex_target_quantization_bits < 0 || tex_target_quantization_bits >= 30) { + return Status(Status::DRACO_ERROR, + "Target texture quantization is out of range."); + } + // The target quantization is no quantization, so return 0. + if (tex_target_quantization_bits == 0) { + return 0; + } + const uint32_t pos_max_quantized_value = (1 << (pos_quantization_bits)) - 1; + AttributeQuantizationTransform pos_transform; + if (!pos_transform.ComputeParameters(pos_att, pos_quantization_bits)) { + return Status(Status::DRACO_ERROR, + "Failed computing position quantization parameters."); + } + + // Get all degenerate faces for positions. If the model already has + // degenerate faces for positions, but valid faces for texture coordinates, + // those will not count as new degenerate faces for texture coordinates, + // because the faces would not have been rendered anyway. + const std::vector pos_degenerate_faces_sorted = + MeshUtils::ListDegenerateQuantizedFaces( + mesh, pos_att, pos_transform.range(), pos_max_quantized_value, false); + + // Initialize return value to zero signifying that it could not find a + // quantization that did not cause any new degenerate faces. + int lowest_quantization_bits = 0; + int min_quantization_bits = tex_target_quantization_bits; + int max_quantization_bits = 29; + while (true) { + const int curr_quantization_bits = + min_quantization_bits + + (max_quantization_bits - min_quantization_bits) / 2; + AttributeQuantizationTransform transform; + if (!transform.ComputeParameters(tex_att, curr_quantization_bits)) { + return Status(Status::DRACO_ERROR, + "Failed computing texture quantization parameters."); + } + + const uint32_t max_quantized_value = (1 << (curr_quantization_bits)) - 1; + + // Get only new degenerate faces for texture coordinates. If the model + // already has degenerate faces for texture coordinates, we don't want to + // take into account those faces in the source, because those faces would + // not have been rendered correctly anyway. + const std::vector tex_degenerate_faces_sorted = + MeshUtils::ListDegenerateQuantizedFaces( + mesh, tex_att, transform.range(), max_quantized_value, true); + + if (tex_degenerate_faces_sorted.size() <= + pos_degenerate_faces_sorted.size()) { + if (std::includes(pos_degenerate_faces_sorted.begin(), + pos_degenerate_faces_sorted.end(), + tex_degenerate_faces_sorted.begin(), + tex_degenerate_faces_sorted.end())) { + // Degenerate texture coordinate faces are a subset of position + // degenerate faces. + lowest_quantization_bits = curr_quantization_bits; + } + } + + if (lowest_quantization_bits == curr_quantization_bits) { + // The lowest quantization is the current quantization, see if lower + // quantization is possible. + max_quantization_bits = curr_quantization_bits - 1; + } else { + min_quantization_bits = curr_quantization_bits + 1; + } + if (min_quantization_bits > max_quantization_bits) { + break; + } + } + return lowest_quantization_bits; +} + +void MeshUtils::TransformNormalizedAttribute(const Eigen::Matrix3d &transform, + PointAttribute *att) { + for (AttributeValueIndex avi(0); avi < att->size(); ++avi) { + // Store up to 4 component values. + Vector4f val(0, 0, 0, 1); + att->GetValue(avi, &val); + // Ignore the last component during transformation. + Eigen::Vector3d transformed_val(val[0], val[1], val[2]); + transformed_val = transform * transformed_val; + transformed_val = transformed_val.normalized(); + // Last component is passed to the transformed value. + val = Vector4f(transformed_val[0], transformed_val[1], transformed_val[2], + val[3]); + + // Set the value to the attribute. Note that in case the attribute is using + // fewer than 4 components, the 4th component is going to be ignored. + att->SetAttributeValue(avi, &val[0]); + } +} + +template +int MeshUtils::CountDegenerateFaces(const Mesh &mesh, + const PointAttribute &att) { + if (att.data_type() != DataType::DT_FLOAT32) { + return -1; + } + std::array values; + int degenerate_values = 0; + for (FaceIndex fi(0); fi < mesh.num_faces(); ++fi) { + const auto &face = mesh.face(fi); + for (int c = 0; c < 3; ++c) { + att.GetMappedValue(face[c], &values[c][0]); + } + if (values[0] == values[1] || values[0] == values[2] || + values[1] == values[2]) { + degenerate_values++; + } + } + return degenerate_values; +} + +std::vector MeshUtils::ListDegenerateQuantizedFaces( + const Mesh &mesh, const PointAttribute &att, float range, + uint32_t max_quantized_value, bool quantized_degenerate_only) { + const int num_components = att.num_components(); + switch (num_components) { + case 2: + return MeshUtils::ListDegenerateQuantizedFaces>( + mesh, att, range, max_quantized_value, quantized_degenerate_only); + case 3: + return MeshUtils::ListDegenerateQuantizedFaces>( + mesh, att, range, max_quantized_value, quantized_degenerate_only); + case 4: + return MeshUtils::ListDegenerateQuantizedFaces>( + mesh, att, range, max_quantized_value, quantized_degenerate_only); + default: + break; + } + return std::vector(); +} + +template +std::vector MeshUtils::ListDegenerateQuantizedFaces( + const Mesh &mesh, const PointAttribute &att, float range, + uint32_t max_quantized_value, bool quantized_degenerate_only) { + std::array values; + std::array quantized_values; + + Quantizer quantizer; + quantizer.Init(range, max_quantized_value); + std::vector degenerate_faces; + + for (FaceIndex fi(0); fi < mesh.num_faces(); ++fi) { + const auto &face = mesh.face(fi); + for (int c = 0; c < 3; ++c) { + att.GetMappedValue(face[c], &values[c][0]); + for (int i = 0; i < att_components_t::dimension; ++i) { + quantized_values[c][i] = quantizer.QuantizeFloat(values[c][i]); + } + } + + if (quantized_degenerate_only && + (values[0] == values[1] || values[0] == values[2] || + values[1] == values[2])) { + continue; + } + if (quantized_values[0] == quantized_values[1] || + quantized_values[0] == quantized_values[2] || + quantized_values[1] == quantized_values[2]) { + degenerate_faces.push_back(fi); + } + } + return degenerate_faces; +} + +bool MeshUtils::HasAutoGeneratedTangents(const Mesh &mesh) { + const int tangent_att_id = + mesh.GetNamedAttributeId(draco::GeometryAttribute::TANGENT); + if (tangent_att_id == -1) { + return false; + } + const auto metadata = mesh.GetAttributeMetadataByAttributeId(tangent_att_id); + if (metadata) { + int is_auto_generated = 0; + if (metadata->GetEntryInt("auto_generated", &is_auto_generated) && + is_auto_generated == 1) { + return true; + } + } + return false; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/mesh/mesh_utils.h b/contrib/draco/src/draco/mesh/mesh_utils.h new file mode 100644 index 000000000..e17dfd8ed --- /dev/null +++ b/contrib/draco/src/draco/mesh/mesh_utils.h @@ -0,0 +1,102 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_MESH_MESH_UTILS_H_ +#define DRACO_MESH_MESH_UTILS_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "Eigen/Geometry" +#include "draco/core/status_or.h" +#include "draco/mesh/mesh.h" + +namespace draco { + +// Helper class containing various utilities operating on draco::Mesh. +// TODO(ostava): Move scattered functions in this folder here (e.g. corner table +// construction). +class MeshUtils { + public: + // Transforms |mesh| using the |transform| matrix. The mesh is transformed + // in-place. + static void TransformMesh(const Eigen::Matrix4d &transform, Mesh *mesh); + + // Merges metadata from |src_mesh| to |dst_mesh|. Any metadata with the same + // names are left unchanged. + static void MergeMetadata(const Mesh &src_mesh, Mesh *dst_mesh); + + // Removes unused MeshFeatures from |mesh|. If the |mesh| contains any mesh + // feature textures, the textures must be owned by the |mesh| otherwise an + // error is returned. + static Status RemoveUnusedMeshFeatures(Mesh *mesh); + + // Flips the UV values of |att|. + static bool FlipTextureUvValues(bool flip_u, bool flip_v, + PointAttribute *att); + + // Counts the number of degenerate faces in |mesh| for attribute |att_id|. + // Returns < 0 if counting of degenerate faces is not supported for |att_id|. + static int CountDegenerateFaces(const Mesh &mesh, int att_id); + + // Searches for the lowest texture quantization bits for |tex_att| that does + // not introduce any new texture coordinate degenerate faces. The range for + // the search is |tex_target_quantization_bits| - 29, inclusive. The function + // does not count texture coordinate degenerate faces already in the source. + // Nor does it count any new texture coordinate degenerate faces that are a + // subset of new position degenerate faces created from the quantization of + // |pos_att| using |pos_quantization_bits|. Returns the lowest quantization + // bits within the specified range or zero signifying that it could not find a + // quantization that did not cause any new degenerate faces. + static StatusOr FindLowestTextureQuantization( + const Mesh &mesh, const PointAttribute &pos_att, + int pos_quantization_bits, const PointAttribute &tex_att, + int tex_target_quantization_bits); + + // Helper function that checks whether a mesh has auto-generated tangents. + // See go/tangents_and_draco_simplifier. + static bool HasAutoGeneratedTangents(const Mesh &mesh); + + private: + static void TransformNormalizedAttribute(const Eigen::Matrix3d &transform, + PointAttribute *att); + + template + static int CountDegenerateFaces(const Mesh &mesh, const PointAttribute &att); + + // Returns a sorted list of degenerate faces for |att|. |att| must use |mesh| + // for its connectivity. |range| and |max_quantized_value| are the values + // passed into the quantizer. |quantized_degenerate_only|, is true will only + // include degenerate faces caused by the quantization. Otherwise all + // degenerate faces will be included, those made by the quantization and those + // already in the source. + static std::vector ListDegenerateQuantizedFaces( + const Mesh &mesh, const PointAttribute &att, float range, + uint32_t max_quantized_value, bool quantized_degenerate_only); + + // Returns a sorted list of degenerate faces for |att|. |att_components_t| is + // the component count for |att| as a VectorD. E.g. Vector2f, Vector3f, or + // Vector4f. |quantized_components_t| is the quantized component count for + // |att| as a VectorD. E.g. VectorD, VectorD, or + // VectorD. + template + static std::vector ListDegenerateQuantizedFaces( + const Mesh &mesh, const PointAttribute &att, float range, + uint32_t max_quantized_value, bool quantized_degenerate_only); +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_MESH_MESH_UTILS_H_ diff --git a/contrib/draco/src/draco/mesh/mesh_utils_test.cc b/contrib/draco/src/draco/mesh/mesh_utils_test.cc new file mode 100644 index 000000000..022669cb0 --- /dev/null +++ b/contrib/draco/src/draco/mesh/mesh_utils_test.cc @@ -0,0 +1,391 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/mesh/mesh_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace { + +// Compare normal vector rotated by |angle| around the x-axis. +void CompareRotatedNormals(const draco::Mesh &mesh_0, const draco::Mesh &mesh_1, + float angle) { + const draco::PointAttribute *const norm_att_0 = + mesh_0.GetNamedAttribute(draco::GeometryAttribute::NORMAL); + const draco::PointAttribute *const norm_att_1 = + mesh_1.GetNamedAttribute(draco::GeometryAttribute::NORMAL); + ASSERT_EQ(norm_att_0->size(), norm_att_1->size()); + for (draco::AttributeValueIndex avi(0); avi < norm_att_0->size(); ++avi) { + Eigen::Vector3f norm_0, norm_1; + norm_att_0->GetValue(avi, norm_0.data()); + norm_att_1->GetValue(avi, norm_1.data()); + + // Project the normals into yz plane + norm_0[0] = 0.f; + norm_1[0] = 0.f; + + if (norm_0.squaredNorm() < 1e-6f) { + // Normal pointing towards X. Make sure the rotated normal is about the + // same. + ASSERT_NEAR(norm_1.squaredNorm(), 0.f, 1e-6f); + continue; + } + + // Ensure the angle between the normals is as expected. + norm_0.normalize(); + norm_1.normalize(); + const float norm_angle = + std::atan2(norm_0.cross(norm_1).norm(), norm_0.dot(norm_1)); + ASSERT_NEAR(std::abs(norm_angle), angle, 1e-6f); + } +} + +TEST(MeshUtilsTest, TestTransform) { + auto mesh = draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + draco::Mesh transformed_mesh; + transformed_mesh.Copy(*mesh); + Eigen::Matrix4d transform = Eigen::Matrix4d::Identity(); + draco::MeshUtils::TransformMesh(transform, &transformed_mesh); + + // Rotate the mesh by 45 deg around the x-axis. + transform.block<3, 3>(0, 0) = + Eigen::Quaterniond( + Eigen::AngleAxisd(M_PI / 4.f, Eigen::Vector3d::UnitX())) + .normalized() + .toRotationMatrix(); + draco::MeshUtils::TransformMesh(transform, &transformed_mesh); + CompareRotatedNormals(*mesh, transformed_mesh, M_PI / 4.f); + + // Now rotate the cube back. + transform.block<3, 3>(0, 0) = + Eigen::Quaterniond( + Eigen::AngleAxisd(-M_PI / 4.f, Eigen::Vector3d::UnitX())) + .normalized() + .toRotationMatrix(); + + draco::MeshUtils::TransformMesh(transform, &transformed_mesh); + CompareRotatedNormals(*mesh, transformed_mesh, 0.f); +} + +TEST(MeshUtilsTest, TestTextureUvFlips) { + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + ASSERT_NE(mesh, nullptr); + + // Check that FlipTextureUvValues() only works on texture coordinates. + draco::PointAttribute *att = mesh->attribute(0); + ASSERT_EQ(att->attribute_type(), draco::GeometryAttribute::POSITION); + ASSERT_FALSE(draco::MeshUtils::FlipTextureUvValues(false, true, att)); + + att = mesh->attribute(1); + ASSERT_EQ(att->attribute_type(), draco::GeometryAttribute::TEX_COORD); + + // Get the values and flip the V values. + std::vector> check_uv_values; + check_uv_values.resize(att->size()); + for (draco::AttributeValueIndex avi(0); avi < att->size(); ++avi) { + att->GetValue(avi, &check_uv_values[avi.value()]); + check_uv_values[avi.value()][1] = 1.0 - check_uv_values[avi.value()][1]; + } + + ASSERT_TRUE(draco::MeshUtils::FlipTextureUvValues(false, true, att)); + + std::array value; + for (draco::AttributeValueIndex avi(0); avi < att->size(); ++avi) { + att->GetValue(avi, &value); + ASSERT_EQ(value[0], check_uv_values[avi.value()][0]); + ASSERT_EQ(value[1], check_uv_values[avi.value()][1]); + } + + // Flip the U values. + for (int i = 0; i < check_uv_values.size(); ++i) { + check_uv_values[i][0] = 1.0 - check_uv_values[i][0]; + } + + ASSERT_TRUE(draco::MeshUtils::FlipTextureUvValues(true, false, att)); + + for (draco::AttributeValueIndex avi(0); avi < att->size(); ++avi) { + att->GetValue(avi, &value); + ASSERT_EQ(value[0], check_uv_values[avi.value()][0]); + ASSERT_EQ(value[1], check_uv_values[avi.value()][1]); + } +} + +// Tests counting degenerate values for positions and texture coordinates for +// both scene and mesh. +TEST(MeshUtilsTest, CountDegenerateValuesLantern) { + int degenerate_positions_scene = 0; + int degenerate_tex_coords_scene = 0; + std::unique_ptr scene = + draco::ReadSceneFromTestFile("Lantern/glTF/Lantern.gltf"); + ASSERT_NE(scene, nullptr); + + for (int mgi = 0; mgi < scene->NumMeshGroups(); ++mgi) { + const draco::MeshGroup *const mesh_group = + scene->GetMeshGroup(draco::MeshGroupIndex(mgi)); + ASSERT_NE(mesh_group, nullptr); + + for (int mi = 0; mi < mesh_group->NumMeshInstances(); ++mi) { + const draco::MeshIndex mesh_index = + mesh_group->GetMeshInstance(mi).mesh_index; + const draco::Mesh &m = scene->GetMesh(mesh_index); + + for (int i = 0; i < m.num_attributes(); ++i) { + const draco::PointAttribute *const att = m.attribute(i); + ASSERT_NE(att, nullptr); + + if (att->attribute_type() == draco::GeometryAttribute::Type::POSITION) { + degenerate_positions_scene += + draco::MeshUtils::CountDegenerateFaces(m, i); + } else if (att->attribute_type() == + draco::GeometryAttribute::Type::TEX_COORD) { + degenerate_tex_coords_scene += + draco::MeshUtils::CountDegenerateFaces(m, i); + } + } + } + } + EXPECT_EQ(degenerate_positions_scene, 0); + EXPECT_EQ(degenerate_tex_coords_scene, 2); + + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("Lantern/glTF/Lantern.gltf"); + ASSERT_NE(mesh, nullptr); + for (int i = 0; i < mesh->num_attributes(); ++i) { + const draco::PointAttribute *const att = mesh->attribute(i); + ASSERT_NE(att, nullptr); + if (att->attribute_type() == draco::GeometryAttribute::Type::POSITION) { + EXPECT_EQ(draco::MeshUtils::CountDegenerateFaces(*mesh, i), + degenerate_positions_scene); + } else if (att->attribute_type() == + draco::GeometryAttribute::Type::TEX_COORD) { + EXPECT_EQ(draco::MeshUtils::CountDegenerateFaces(*mesh, i), + degenerate_tex_coords_scene); + } + } +} + +// Tests finding the lowest quantization bits for the texture coordinate in a +// mesh. +TEST(MeshUtilsTest, FindLowsetTextureQuantizationLanternMesh) { + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("Lantern/glTF/Lantern.gltf"); + ASSERT_NE(mesh, nullptr); + + const int pos_quantization_bits = 11; + const draco::PointAttribute *const pos_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::Type::POSITION, 0); + ASSERT_NE(pos_att, nullptr); + + const draco::PointAttribute *const tex_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::Type::TEX_COORD, 0); + ASSERT_NE(tex_att, nullptr); + + // Tests target no quantization returns no quantization. + const int target_no_quantization_bits = 0; + DRACO_ASSIGN_OR_ASSERT(const int no_quantization_bits, + draco::MeshUtils::FindLowestTextureQuantization( + *mesh, *pos_att, pos_quantization_bits, *tex_att, + target_no_quantization_bits)); + ASSERT_EQ(no_quantization_bits, 0); + + // Test failures. + const int out_of_range_low = -1; + const auto statusor_low = draco::MeshUtils::FindLowestTextureQuantization( + *mesh, *pos_att, pos_quantization_bits, *tex_att, out_of_range_low); + ASSERT_FALSE(statusor_low.ok()); + + const int out_of_range_high = 30; + const auto statusor_high = draco::MeshUtils::FindLowestTextureQuantization( + *mesh, *pos_att, pos_quantization_bits, *tex_att, out_of_range_high); + ASSERT_FALSE(statusor_high.ok()); + + // Tests finding the lowest quantization bits for the texture coordinate. + const int target_bits = 6; + DRACO_ASSIGN_OR_ASSERT( + const int lowest_bits, + draco::MeshUtils::FindLowestTextureQuantization( + *mesh, *pos_att, pos_quantization_bits, *tex_att, target_bits)); + ASSERT_EQ(lowest_bits, 14); +} + +// Tests finding the lowest quantization bits for the texture coordinates for +// the three meshes in the scene. +TEST(MeshUtilsTest, FindLowsetTextureQuantizationLanternScene) { + std::unique_ptr scene = + draco::ReadSceneFromTestFile("Lantern/glTF/Lantern.gltf"); + ASSERT_NE(scene, nullptr); + + const std::vector expected_mesh_quantization_bits{11, 8, 14}; + for (int mi = 0; mi < scene->NumMeshes(); ++mi) { + const draco::Mesh &mesh = scene->GetMesh(draco::MeshIndex(mi)); + + const int pos_quantization_bits = 11; + const draco::PointAttribute *const pos_att = + mesh.GetNamedAttribute(draco::GeometryAttribute::Type::POSITION, 0); + ASSERT_NE(pos_att, nullptr); + + const draco::PointAttribute *const tex_att = + mesh.GetNamedAttribute(draco::GeometryAttribute::Type::TEX_COORD, 0); + ASSERT_NE(tex_att, nullptr); + + const int target_bits = 8; + DRACO_ASSIGN_OR_ASSERT( + const int lowest_bits, + draco::MeshUtils::FindLowestTextureQuantization( + mesh, *pos_att, pos_quantization_bits, *tex_att, target_bits)); + ASSERT_EQ(lowest_bits, expected_mesh_quantization_bits[mi]); + } +} + +TEST(MeshUtilsTest, CheckAutoGeneratedTangents) { + // Test verifies that MeshUtils::HasAutoGeneratedTangents works as intended. + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("sphere_no_tangents.gltf"); + ASSERT_NE(mesh, nullptr); + + ASSERT_TRUE(draco::MeshUtils::HasAutoGeneratedTangents(*mesh)); +} + +TEST(MeshUtilsTest, CheckMergeMetadata) { + // Test verifies that we can merge metadata using MeshUtils::MergeMetadata(). + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("sphere_no_tangents.gltf"); + ASSERT_NE(mesh, nullptr); + + std::unique_ptr other_mesh = + draco::ReadMeshFromTestFile("cube_att.obj"); + + ASSERT_NE(mesh->GetMetadata(), nullptr); + // One attribute metadata (for the tangent attribute) and no other entries. + ASSERT_EQ(mesh->GetMetadata()->attribute_metadatas().size(), 1); + ASSERT_EQ(mesh->GetMetadata()->num_entries(), 0); + + // No metadata at the other attribute. + ASSERT_EQ(other_mesh->GetMetadata(), nullptr); + + // First try to merge |other_mesh| metadata to |mesh|. This shouldn't do + // anything. + draco::MeshUtils::MergeMetadata(*other_mesh, mesh.get()); + ASSERT_EQ(mesh->GetMetadata()->attribute_metadatas().size(), 1); + ASSERT_EQ(mesh->GetMetadata()->num_entries(), 0); + + // Merge |mesh| metadata to |other_mesh|. This will create empty metadata but + // not any attribute metadata because |other_mesh| doesn't have the tangent + // attribute. + draco::MeshUtils::MergeMetadata(*mesh, other_mesh.get()); + ASSERT_NE(other_mesh->GetMetadata(), nullptr); + ASSERT_EQ(other_mesh->GetMetadata()->attribute_metadatas().size(), 0); + ASSERT_EQ(other_mesh->GetMetadata()->num_entries(), 0); + ASSERT_FALSE(draco::MeshUtils::HasAutoGeneratedTangents(*other_mesh)); + + // Add dummy tangent attribute to the |other_mesh|. + std::unique_ptr tang_att(new draco::PointAttribute()); + draco::PointAttribute *const tang_att_ptr = tang_att.get(); + tang_att->set_attribute_type(draco::GeometryAttribute::TANGENT); + other_mesh->AddAttribute(std::move(tang_att)); + + // Merge |mesh| metadata to |other_mesh|. This time the tangent metadata + // should be copied over. + draco::MeshUtils::MergeMetadata(*mesh, other_mesh.get()); + ASSERT_NE(other_mesh->GetMetadata(), nullptr); + ASSERT_EQ(other_mesh->GetMetadata()->attribute_metadatas().size(), 1); + ASSERT_EQ(other_mesh->GetMetadata()->num_entries(), 0); + ASSERT_NE(other_mesh->GetMetadata()->GetAttributeMetadataByUniqueId( + tang_att_ptr->unique_id()), + nullptr); + ASSERT_TRUE(draco::MeshUtils::HasAutoGeneratedTangents(*other_mesh)); + + // Now add some entries to the geometry metadata and merge again. + mesh->metadata()->AddEntryInt("test_int_0", 0); + mesh->metadata()->AddEntryInt("test_int_1", 1); + mesh->metadata()->AddEntryInt("test_int_shared", 2); + other_mesh->metadata()->AddEntryInt("test_int_shared", 3); + + // "test_int_0" and "test_int_1" should be copied over while + // "test_entry_shared" should stay unchanged. + draco::MeshUtils::MergeMetadata(*mesh, other_mesh.get()); + ASSERT_NE(other_mesh->GetMetadata(), nullptr); + // Attribute metadata should stay unchanged. + ASSERT_EQ(other_mesh->GetMetadata()->attribute_metadatas().size(), 1); + ASSERT_NE(other_mesh->GetMetadata()->GetAttributeMetadataByUniqueId( + tang_att_ptr->unique_id()), + nullptr); + ASSERT_EQ(other_mesh->GetMetadata() + ->GetAttributeMetadataByUniqueId(tang_att_ptr->unique_id()) + ->num_entries(), + 1); + + // Check the geometry metadata entries. + ASSERT_EQ(other_mesh->GetMetadata()->num_entries(), 3); + int metadata_value; + ASSERT_TRUE( + other_mesh->GetMetadata()->GetEntryInt("test_int_0", &metadata_value)); + ASSERT_EQ(metadata_value, 0); + ASSERT_TRUE( + other_mesh->GetMetadata()->GetEntryInt("test_int_1", &metadata_value)); + ASSERT_EQ(metadata_value, 1); + + // The shared entry should have an unchanged value. + ASSERT_TRUE(other_mesh->GetMetadata()->GetEntryInt("test_int_shared", + &metadata_value)); + ASSERT_EQ(metadata_value, 3); +} + +TEST(MeshUtilsTest, RemoveUnusedMeshFeatures) { + // Test verifies that MeshUtils::RemoveUnusedMeshFeatures works as intended. + std::unique_ptr mesh = + draco::ReadMeshFromTestFile("BoxesMeta/glTF/BoxesMeta.gltf"); + ASSERT_NE(mesh, nullptr); + + // The input mesh should have five mesh features and two features textures. + ASSERT_EQ(mesh->NumMeshFeatures(), 5); + ASSERT_EQ(mesh->GetNonMaterialTextureLibrary().NumTextures(), 2); + + // All of those features and textures should be used so calling the method + // below shouldn't do anything. + draco::MeshUtils::RemoveUnusedMeshFeatures(mesh.get()); + ASSERT_EQ(mesh->NumMeshFeatures(), 5); + ASSERT_EQ(mesh->GetNonMaterialTextureLibrary().NumTextures(), 2); + + // Now remove material 1 that is mapped to first two mesh features. + draco::PointAttribute *mat_att = mesh->attribute( + mesh->GetNamedAttributeId(draco::GeometryAttribute::MATERIAL)); + + // This basically remaps all faces from material 1 to material 0. + uint32_t mat_index = 0; + mat_att->SetAttributeValue(draco::AttributeValueIndex(1), &mat_index); + + // Try to remove the mesh features again. + draco::MeshUtils::RemoveUnusedMeshFeatures(mesh.get()); + + // Three of the mesh features should have been removed as well as one mesh + // features texture. + ASSERT_EQ(mesh->NumMeshFeatures(), 2); + ASSERT_EQ(mesh->GetNonMaterialTextureLibrary().NumTextures(), 1); + + // Ensure the remaining mesh features are mapped to the correct material. + for (draco::MeshFeaturesIndex mfi(0); mfi < mesh->NumMeshFeatures(); ++mfi) { + ASSERT_EQ(mesh->NumMeshFeaturesMaterialMasks(mfi), 1); + ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(mfi, 0), 0); + } +} + +} // namespace + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.cc b/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.cc index 60b0c50b8..2af94a052 100644 --- a/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.cc +++ b/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.cc @@ -23,11 +23,23 @@ void TriangleSoupMeshBuilder::Start(int num_faces) { attribute_element_types_.clear(); } +#ifdef DRACO_TRANSCODER_SUPPORTED +void TriangleSoupMeshBuilder::SetName(const std::string &name) { + mesh_->SetName(name); +} +#endif // DRACO_TRANSCODER_SUPPORTED + int TriangleSoupMeshBuilder::AddAttribute( GeometryAttribute::Type attribute_type, int8_t num_components, DataType data_type) { + return AddAttribute(attribute_type, num_components, data_type, false); +} + +int TriangleSoupMeshBuilder::AddAttribute( + GeometryAttribute::Type attribute_type, int8_t num_components, + DataType data_type, bool normalized) { GeometryAttribute va; - va.Init(attribute_type, nullptr, num_components, data_type, false, + va.Init(attribute_type, nullptr, num_components, data_type, normalized, DataTypeLength(data_type) * num_components, 0); attribute_element_types_.push_back(-1); return mesh_->AddAttribute(va, true, mesh_->num_points()); @@ -41,8 +53,6 @@ void TriangleSoupMeshBuilder::SetAttributeValuesForFace( att->SetAttributeValue(AttributeValueIndex(start_index), corner_value_0); att->SetAttributeValue(AttributeValueIndex(start_index + 1), corner_value_1); att->SetAttributeValue(AttributeValueIndex(start_index + 2), corner_value_2); - // TODO(ostava): The below code should be called only for one attribute. - // It will work OK even for multiple attributes, but it's redundant. mesh_->SetFace(face_id, {{PointIndex(start_index), PointIndex(start_index + 1), PointIndex(start_index + 2)}}); diff --git a/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.h b/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.h index 89466e1d8..503fe84c5 100644 --- a/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.h +++ b/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder.h @@ -15,7 +15,14 @@ #ifndef DRACO_MESH_TRIANGLE_SOUP_MESH_BUILDER_H_ #define DRACO_MESH_TRIANGLE_SOUP_MESH_BUILDER_H_ +#include +#include + #include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/status.h" +#endif #include "draco/mesh/mesh.h" namespace draco { @@ -25,15 +32,25 @@ namespace draco { // deduplicated. class TriangleSoupMeshBuilder { public: + // Index type of the inserted element. + typedef FaceIndex ElementIndex; + // Starts mesh building for a given number of faces. // TODO(ostava): Currently it's necessary to select the correct number of // faces upfront. This should be generalized, but it will require us to // rewrite our attribute resizing functions. void Start(int num_faces); +#ifdef DRACO_TRANSCODER_SUPPORTED + // Sets mesh name. + void SetName(const std::string &name); +#endif // DRACO_TRANSCODER_SUPPORTED + // Adds an empty attribute to the mesh. Returns the new attribute's id. int AddAttribute(GeometryAttribute::Type attribute_type, int8_t num_components, DataType data_type); + int AddAttribute(GeometryAttribute::Type attribute_type, + int8_t num_components, DataType data_type, bool normalized); // Sets values for a given attribute on all corners of a given face. void SetAttributeValuesForFace(int att_id, FaceIndex face_id, @@ -41,12 +58,34 @@ class TriangleSoupMeshBuilder { const void *corner_value_1, const void *corner_value_2); +#ifdef DRACO_TRANSCODER_SUPPORTED + // Converts input values of type T into internal representation used by + // |att_id|. Each input value needs to have |input_num_components| entries. + template + Status ConvertAndSetAttributeValuesForFace(int att_id, FaceIndex face_id, + int input_num_components, + const T *corner_value_0, + const T *corner_value_1, + const T *corner_value_2); +#endif + // Sets value for a per-face attribute. If all faces of a given attribute are // set with this method, the attribute will be marked as per-face, otherwise // it will be marked as per-corner attribute. void SetPerFaceAttributeValueForFace(int att_id, FaceIndex face_id, const void *value); + // Add metadata. + void AddMetadata(std::unique_ptr metadata) { + mesh_->AddMetadata(std::move(metadata)); + } + + // Add metadata for an attribute. + void AddAttributeMetadata(int32_t att_id, + std::unique_ptr metadata) { + mesh_->AddAttributeMetadata(att_id, std::move(metadata)); + } + // Finalizes the mesh or returns nullptr on error. // Once this function is called, the builder becomes invalid and cannot be // used until the method Start() is called again. @@ -58,6 +97,30 @@ class TriangleSoupMeshBuilder { std::unique_ptr mesh_; }; +#ifdef DRACO_TRANSCODER_SUPPORTED +template +Status TriangleSoupMeshBuilder::ConvertAndSetAttributeValuesForFace( + int att_id, FaceIndex face_id, int input_num_components, + const T *corner_value_0, const T *corner_value_1, const T *corner_value_2) { + const int start_index = 3 * face_id.value(); + PointAttribute *const att = mesh_->attribute(att_id); + DRACO_RETURN_IF_ERROR( + att->ConvertAndSetAttributeValue(AttributeValueIndex(start_index + 0), + input_num_components, corner_value_0)); + DRACO_RETURN_IF_ERROR( + att->ConvertAndSetAttributeValue(AttributeValueIndex(start_index + 1), + input_num_components, corner_value_1)); + DRACO_RETURN_IF_ERROR( + att->ConvertAndSetAttributeValue(AttributeValueIndex(start_index + 2), + input_num_components, corner_value_2)); + mesh_->SetFace(face_id, + {{PointIndex(start_index), PointIndex(start_index + 1), + PointIndex(start_index + 2)}}); + attribute_element_types_[att_id] = MESH_CORNER_ATTRIBUTE; + return OkStatus(); +} +#endif // DRACO_TRANSCODER_SUPPORTED + } // namespace draco #endif // DRACO_MESH_TRIANGLE_SOUP_MESH_BUILDER_H_ diff --git a/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder_test.cc b/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder_test.cc index 171f8fe24..b23641760 100644 --- a/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder_test.cc +++ b/contrib/draco/src/draco/mesh/triangle_soup_mesh_builder_test.cc @@ -14,7 +14,11 @@ // #include "draco/mesh/triangle_soup_mesh_builder.h" +#include +#include + #include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" #include "draco/core/vector_d.h" namespace draco { @@ -26,6 +30,9 @@ TEST_F(TriangleSoupMeshBuilderTest, CubeTest) { // of the provided triangle soup data. TriangleSoupMeshBuilder mb; mb.Start(12); +#ifdef DRACO_TRANSCODER_SUPPORTED + mb.SetName("Cube"); +#endif const int pos_att_id = mb.AddAttribute(GeometryAttribute::POSITION, 3, DT_FLOAT32); // clang-format off @@ -92,6 +99,9 @@ TEST_F(TriangleSoupMeshBuilderTest, CubeTest) { std::unique_ptr mesh = mb.Finalize(); ASSERT_NE(mesh, nullptr) << "Failed to build the cube mesh."; +#ifdef DRACO_TRANSCODER_SUPPORTED + EXPECT_EQ(mesh->GetName(), "Cube"); +#endif EXPECT_EQ(mesh->num_points(), 8) << "Unexpected number of vertices."; EXPECT_EQ(mesh->num_faces(), 12) << "Unexpected number of faces."; } @@ -139,7 +149,7 @@ TEST_F(TriangleSoupMeshBuilderTest, TestPerFaceAttribs) { Vector3f(0.f, 1.f, 0.f).data(), Vector3f(1.f, 1.f, 0.f).data(), Vector3f(0.f, 1.f, 1.f).data()); - mb.SetPerFaceAttributeValueForFace(gen_att_id, FaceIndex(4), &bool_false);; + mb.SetPerFaceAttributeValueForFace(gen_att_id, FaceIndex(4), &bool_false); mb.SetAttributeValuesForFace(pos_att_id, FaceIndex(5), Vector3f(0.f, 1.f, 1.f).data(), @@ -189,9 +199,69 @@ TEST_F(TriangleSoupMeshBuilderTest, TestPerFaceAttribs) { std::unique_ptr mesh = mb.Finalize(); ASSERT_NE(mesh, nullptr) << "Failed to build the cube mesh."; +#ifdef DRACO_TRANSCODER_SUPPORTED + EXPECT_TRUE(mesh->GetName().empty()); +#endif EXPECT_EQ(mesh->num_faces(), 12) << "Unexpected number of faces."; EXPECT_EQ(mesh->GetAttributeElementType(gen_att_id), MESH_FACE_ATTRIBUTE) << "Unexpected attribute element type."; } +#ifdef DRACO_TRANSCODER_SUPPORTED +TEST_F(TriangleSoupMeshBuilderTest, NormalizedColor) { + // This tests, verifies that the mesh builder constructs a valid model with + // normalized integer colors using floating points as input. + TriangleSoupMeshBuilder mb; + mb.Start(2); + const int pos_att_id = + mb.AddAttribute(GeometryAttribute::POSITION, 3, DT_FLOAT32); + const int color_att_id = + mb.AddAttribute(GeometryAttribute::COLOR, 3, DT_UINT8, true); + + mb.SetAttributeValuesForFace( + pos_att_id, FaceIndex(0), Vector3f(0.f, 0.f, 0.f).data(), + Vector3f(1.f, 0.f, 0.f).data(), Vector3f(0.f, 1.f, 0.f).data()); + DRACO_ASSERT_OK(mb.ConvertAndSetAttributeValuesForFace( + color_att_id, FaceIndex(0), 4, Vector4f(0.f, 0.f, 0.f, 1.f).data(), + Vector4f(1.f, 1.f, 1.f, 1.f).data(), + Vector4f(0.5f, 0.5f, 0.5f, 1.f).data())); + mb.SetAttributeValuesForFace( + pos_att_id, FaceIndex(1), Vector3f(0.f, 1.f, 0.f).data(), + Vector3f(1.f, 0.f, 0.f).data(), Vector3f(1.f, 1.f, 0.f).data()); + + DRACO_ASSERT_OK(mb.ConvertAndSetAttributeValuesForFace( + color_att_id, FaceIndex(1), 4, Vector4f(0.5f, 0.5f, 0.5f, 1.f).data(), + Vector4f(1.f, 1.f, 1.f, 1.f).data(), + Vector4f(0.25f, 0.0f, 1.f, 1.f).data())); + + std::unique_ptr mesh = mb.Finalize(); + ASSERT_NE(mesh, nullptr) << "Failed to build the test mesh."; + + EXPECT_EQ(mesh->num_points(), 4) << "Unexpected number of vertices."; + EXPECT_EQ(mesh->num_faces(), 2) << "Unexpected number of faces."; + + const auto *col_att = + mesh->GetNamedAttribute(draco::GeometryAttribute::COLOR); + ASSERT_NE(col_att, nullptr) << "Missing color attribute."; + ASSERT_EQ(col_att->size(), 4); + + // All colors should be in range 0-255. + uint8_t max_val = 0, min_val = 255; + for (draco::AttributeValueIndex avi(0); avi < col_att->size(); ++avi) { + VectorD cval; + col_att->GetValue(avi, &cval); + const uint8_t max = cval.MaxCoeff(); + const uint8_t min = cval.MinCoeff(); + if (max > max_val) { + max_val = max; + } + if (min < min_val) { + min_val = min; + } + } + ASSERT_EQ(max_val, 255); + ASSERT_EQ(min_val, 0); +} +#endif + } // namespace draco diff --git a/contrib/draco/src/draco/metadata/geometry_metadata.cc b/contrib/draco/src/draco/metadata/geometry_metadata.cc index b83898140..b6a882c0b 100644 --- a/contrib/draco/src/draco/metadata/geometry_metadata.cc +++ b/contrib/draco/src/draco/metadata/geometry_metadata.cc @@ -18,6 +18,19 @@ namespace draco { +AttributeMetadata::AttributeMetadata(const AttributeMetadata &metadata) + : Metadata(metadata) { + att_unique_id_ = metadata.att_unique_id_; +} + +GeometryMetadata::GeometryMetadata(const GeometryMetadata &metadata) + : Metadata(metadata) { + for (size_t i = 0; i < metadata.att_metadatas_.size(); ++i) { + att_metadatas_.push_back(std::unique_ptr( + new AttributeMetadata(*metadata.att_metadatas_[i]))); + } +} + const AttributeMetadata *GeometryMetadata::GetAttributeMetadataByStringEntry( const std::string &entry_name, const std::string &entry_value) const { for (auto &&att_metadata : att_metadatas_) { @@ -35,7 +48,7 @@ const AttributeMetadata *GeometryMetadata::GetAttributeMetadataByStringEntry( bool GeometryMetadata::AddAttributeMetadata( std::unique_ptr att_metadata) { - if (!att_metadata.get()) { + if (!att_metadata) { return false; } att_metadatas_.push_back(std::move(att_metadata)); diff --git a/contrib/draco/src/draco/metadata/geometry_metadata.h b/contrib/draco/src/draco/metadata/geometry_metadata.h index ec7ecb9ee..531bdef25 100644 --- a/contrib/draco/src/draco/metadata/geometry_metadata.h +++ b/contrib/draco/src/draco/metadata/geometry_metadata.h @@ -25,6 +25,7 @@ namespace draco { class AttributeMetadata : public Metadata { public: AttributeMetadata() : att_unique_id_(0) {} + AttributeMetadata(const AttributeMetadata &metadata); explicit AttributeMetadata(const Metadata &metadata) : Metadata(metadata), att_unique_id_(0) {} @@ -57,6 +58,7 @@ struct AttributeMetadataHasher { class GeometryMetadata : public Metadata { public: GeometryMetadata() {} + GeometryMetadata(const GeometryMetadata &metadata); explicit GeometryMetadata(const Metadata &metadata) : Metadata(metadata) {} const AttributeMetadata *GetAttributeMetadataByStringEntry( diff --git a/contrib/draco/src/draco/metadata/metadata.cc b/contrib/draco/src/draco/metadata/metadata.cc index 9141907ed..51b4e93a3 100644 --- a/contrib/draco/src/draco/metadata/metadata.cc +++ b/contrib/draco/src/draco/metadata/metadata.cc @@ -122,6 +122,14 @@ const Metadata *Metadata::GetSubMetadata(const std::string &name) const { return sub_ptr->second.get(); } +Metadata *Metadata::sub_metadata(const std::string &name) { + auto sub_ptr = sub_metadatas_.find(name); + if (sub_ptr == sub_metadatas_.end()) { + return nullptr; + } + return sub_ptr->second.get(); +} + void Metadata::RemoveEntry(const std::string &name) { // Actually just remove "name", no need to check if it exists. auto entry_ptr = entries_.find(name); diff --git a/contrib/draco/src/draco/metadata/metadata.h b/contrib/draco/src/draco/metadata/metadata.h index 56d05e46a..12c1ba974 100644 --- a/contrib/draco/src/draco/metadata/metadata.h +++ b/contrib/draco/src/draco/metadata/metadata.h @@ -147,6 +147,7 @@ class Metadata { bool AddSubMetadata(const std::string &name, std::unique_ptr sub_metadata); const Metadata *GetSubMetadata(const std::string &name) const; + Metadata *sub_metadata(const std::string &name); void RemoveEntry(const std::string &name); diff --git a/contrib/draco/src/draco/metadata/metadata_decoder.cc b/contrib/draco/src/draco/metadata/metadata_decoder.cc index a8e66f854..6468e3207 100644 --- a/contrib/draco/src/draco/metadata/metadata_decoder.cc +++ b/contrib/draco/src/draco/metadata/metadata_decoder.cc @@ -59,18 +59,25 @@ bool MetadataDecoder::DecodeGeometryMetadata(DecoderBuffer *in_buffer, } bool MetadataDecoder::DecodeMetadata(Metadata *metadata) { - struct MetadataPair { + // Limit metadata nesting depth to avoid stack overflow in destructor. + constexpr int kMaxSubmetadataLevel = 1000; + + struct MetadataTuple { Metadata *parent_metadata; Metadata *decoded_metadata; + int level; }; - std::vector metadata_stack; - metadata_stack.push_back({nullptr, metadata}); + std::vector metadata_stack; + metadata_stack.push_back({nullptr, metadata, 0}); while (!metadata_stack.empty()) { - const MetadataPair mp = metadata_stack.back(); + const MetadataTuple mp = metadata_stack.back(); metadata_stack.pop_back(); metadata = mp.decoded_metadata; if (mp.parent_metadata != nullptr) { + if (mp.level > kMaxSubmetadataLevel) { + return false; + } std::string sub_metadata_name; if (!DecodeName(&sub_metadata_name)) { return false; @@ -105,7 +112,8 @@ bool MetadataDecoder::DecodeMetadata(Metadata *metadata) { return false; } for (uint32_t i = 0; i < num_sub_metadata; ++i) { - metadata_stack.push_back({metadata, nullptr}); + metadata_stack.push_back( + {metadata, nullptr, mp.parent_metadata ? mp.level + 1 : mp.level}); } } return true; @@ -123,6 +131,9 @@ bool MetadataDecoder::DecodeEntry(Metadata *metadata) { if (data_size == 0) { return false; } + if (data_size > buffer_->remaining_size()) { + return false; + } std::vector entry_value(data_size); if (!buffer_->Decode(&entry_value[0], data_size)) { return false; diff --git a/contrib/draco/src/draco/metadata/metadata_test.cc b/contrib/draco/src/draco/metadata/metadata_test.cc index cf7ae6eee..03104e03e 100644 --- a/contrib/draco/src/draco/metadata/metadata_test.cc +++ b/contrib/draco/src/draco/metadata/metadata_test.cc @@ -104,12 +104,16 @@ TEST_F(MetadataTest, TestNestedMetadata) { sub_metadata->AddEntryInt("int", 100); metadata.AddSubMetadata("sub0", std::move(sub_metadata)); - const auto sub_metadata_ptr = metadata.GetSubMetadata("sub0"); + const auto sub_metadata_ptr = metadata.sub_metadata("sub0"); ASSERT_NE(sub_metadata_ptr, nullptr); int32_t int_value = 0; ASSERT_TRUE(sub_metadata_ptr->GetEntryInt("int", &int_value)); ASSERT_EQ(int_value, 100); + + sub_metadata_ptr->AddEntryInt("new_entry", 20); + ASSERT_TRUE(sub_metadata_ptr->GetEntryInt("new_entry", &int_value)); + ASSERT_EQ(int_value, 20); } TEST_F(MetadataTest, TestHardCopyMetadata) { diff --git a/contrib/draco/src/draco/metadata/property_table.cc b/contrib/draco/src/draco/metadata/property_table.cc new file mode 100644 index 000000000..c6a5fd984 --- /dev/null +++ b/contrib/draco/src/draco/metadata/property_table.cc @@ -0,0 +1,183 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/metadata/property_table.h" + +#include +#include +#include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +bool PropertyTable::Schema::Object::operator==(const Object& other) const { + if (type_ != other.type_ || name_ != other.name_) { + return false; + } + switch (type_) { + case OBJECT: + if (objects_.size() != other.objects_.size()) { + return false; + } + for (int i = 0; i < objects_.size(); ++i) { + if (objects_[i] != other.objects_[i]) { + return false; + } + } + break; + case ARRAY: + if (array_.size() != other.array_.size()) { + return false; + } + for (int i = 0; i < array_.size(); ++i) { + if (array_[i] != other.array_[i]) { + return false; + } + } + break; + case STRING: + return string_ == other.string_; + case INTEGER: + return integer_ == other.integer_; + case BOOLEAN: + return boolean_ == other.boolean_; + } + return true; +} + +void PropertyTable::Schema::Object::Copy(const Object& src) { + name_ = src.name_; + type_ = src.type_; + objects_.reserve(src.objects_.size()); + for (const Object& obj : src.objects_) { + objects_.emplace_back(); + objects_.back().Copy(obj); + } + array_.reserve(src.array_.size()); + for (const Object& obj : src.array_) { + array_.emplace_back(); + array_.back().Copy(obj); + } + string_ = src.string_; + integer_ = src.integer_; + boolean_ = src.boolean_; +} + +PropertyTable::Property::Property() {} + +bool PropertyTable::Property::Data::operator==(const Data& other) const { + return data == other.data && target == other.target; +} + +bool PropertyTable::Property::Offsets::operator==(const Offsets& other) const { + return data == other.data && type == other.type; +} + +bool PropertyTable::Property::operator==(const Property& other) const { + return name_ == other.name_ && data_ == other.data_ && + array_offsets_ == other.array_offsets_ && + string_offsets_ == other.string_offsets_; +} + +void PropertyTable::Property::Copy(const Property& src) { + name_ = src.name_; + data_ = src.data_; + array_offsets_ = src.array_offsets_; + string_offsets_ = src.string_offsets_; +} + +void PropertyTable::Property::SetName(const std::string& name) { name_ = name; } +const std::string& PropertyTable::Property::GetName() const { return name_; } + +PropertyTable::Property::Data& PropertyTable::Property::GetData() { + return data_; +} +const PropertyTable::Property::Data& PropertyTable::Property::GetData() const { + return data_; +} + +const PropertyTable::Property::Offsets& +PropertyTable::Property::GetArrayOffsets() const { + return array_offsets_; +} +PropertyTable::Property::Offsets& PropertyTable::Property::GetArrayOffsets() { + return array_offsets_; +} + +const PropertyTable::Property::Offsets& +PropertyTable::Property::GetStringOffsets() const { + return string_offsets_; +} +PropertyTable::Property::Offsets& PropertyTable::Property::GetStringOffsets() { + return string_offsets_; +} + +PropertyTable::PropertyTable() : count_(0) {} + +bool PropertyTable::operator==(const PropertyTable& other) const { + if (name_ != other.name_ || class_ != other.class_ || + count_ != other.count_ || + properties_.size() != other.properties_.size()) { + return false; + } + for (int i = 0; i < properties_.size(); ++i) { + if (*properties_[i] != *other.properties_[i]) { + return false; + } + } + return true; +} + +void PropertyTable::Copy(const PropertyTable& src) { + name_ = src.name_; + class_ = src.class_; + count_ = src.count_; + properties_.clear(); + properties_.reserve(src.properties_.size()); + for (int i = 0; i < src.properties_.size(); ++i) { + std::unique_ptr property(new Property()); + property->Copy(src.GetProperty(i)); + properties_.push_back(std::move(property)); + } +} + +void PropertyTable::SetName(const std::string& value) { name_ = value; } +const std::string& PropertyTable::GetName() const { return name_; } + +void PropertyTable::SetClass(const std::string& value) { class_ = value; } +const std::string& PropertyTable::GetClass() const { return class_; } + +void PropertyTable::SetCount(int count) { count_ = count; } +int PropertyTable::GetCount() const { return count_; } + +int PropertyTable::AddProperty(std::unique_ptr property) { + properties_.push_back(std::move(property)); + return properties_.size() - 1; +} +int PropertyTable::NumProperties() const { return properties_.size(); } +const PropertyTable::Property& PropertyTable::GetProperty(int index) const { + return *properties_[index]; +} +PropertyTable::Property& PropertyTable::GetProperty(int index) { + return *properties_[index]; +} +void PropertyTable::RemoveProperty(int index) { + properties_.erase(properties_.begin() + index); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/metadata/property_table.h b/contrib/draco/src/draco/metadata/property_table.h new file mode 100644 index 000000000..41efb0163 --- /dev/null +++ b/contrib/draco/src/draco/metadata/property_table.h @@ -0,0 +1,243 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_METADATA_PROPERTY_TABLE_H_ +#define DRACO_METADATA_PROPERTY_TABLE_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include +#include +#include + +namespace draco { + +// Describes a property table as defined in the EXT_structural_metadata glTF +// extension, including property table schema and table properties (columns). +class PropertyTable { + public: + // Describes property table schema in the form of a JSON object. + struct Schema { + // JSON object of the schema. + // TODO(vytyaz): Consider using a third_party/json library. Currently there + // is a conflict between Filament's assert_invariant() macro and JSON + // library's assert_invariant() method that causes compile errors in Draco + // visualization library. + class Object { + public: + enum Type { OBJECT, ARRAY, STRING, INTEGER, BOOLEAN }; + + // Constructors. + Object() : Object("") {} + explicit Object(const std::string& name) + : name_(name), type_(OBJECT), integer_(0), boolean_(false) {} + Object(const std::string& name, const std::string& value) : Object(name) { + SetString(value); + } + Object(const std::string& name, const char* value) : Object(name) { + SetString(value); + } + Object(const std::string& name, int value) : Object(name) { + SetInteger(value); + } + Object(const std::string& name, bool value) : Object(name) { + SetBoolean(value); + } + + // Methods for comparing two objects. + bool operator==(const Object& other) const; + bool operator!=(const Object& other) const { return !(*this == other); } + + // Method for copying the object. + void Copy(const Object& src); + + // Methods for getting object name and type. + const std::string& GetName() const { return name_; } + Type GetType() const { return type_; } + + // Methods for getting object value. + const std::vector& GetObjects() const { return objects_; } + const std::vector& GetArray() const { return array_; } + const std::string& GetString() const { return string_; } + int GetInteger() const { return integer_; } + bool GetBoolean() const { return boolean_; } + + // Methods for setting object value. + std::vector& SetObjects() { + type_ = OBJECT; + return objects_; + } + std::vector& SetArray() { + type_ = ARRAY; + return array_; + } + void SetString(const std::string& value) { + type_ = STRING; + string_ = value; + } + void SetInteger(int value) { + type_ = INTEGER; + integer_ = value; + } + void SetBoolean(bool value) { + type_ = BOOLEAN; + boolean_ = value; + } + + private: + std::string name_; + Type type_; + std::vector objects_; + std::vector array_; + std::string string_; + int integer_; + bool boolean_; + }; + + // Valid schema top-level JSON object name is "schema". + Schema() : json("schema") {} + + // Methods for comparing two schemas. + bool operator==(const Schema& other) const { return json == other.json; } + bool operator!=(const Schema& other) const { return !(*this == other); } + + // Valid schema top-level JSON object is required to have child objects. + bool Empty() const { return json.GetObjects().empty(); } + + // Top-level JSON object of the schema. + Object json; + }; + + // Describes a property (column) of a property table. + class Property { + public: + // Describes glTF buffer view data. + struct Data { + // Methods for comparing two data objects. + bool operator==(const Data& other) const; + bool operator!=(const Data& other) const { return !(*this == other); } + + // Buffer view data. + std::vector data; + + // Data target corresponds to the target property of the glTF bufferView + // object and classifies the type or nature of the data. + int target = 0; + }; + + // Describes offsets of the entries in property data when the data + // represents an array of strings or an array of variable-length number + // arrays. + struct Offsets { + // Methods for comparing two offsets. + bool operator==(const Offsets& other) const; + bool operator!=(const Offsets& other) const { return !(*this == other); } + + // Data containing the offset entries. + Data data; + + // Data type of the offset entries. + std::string type; + }; + + // Creates an empty property. + Property(); + + // Methods for comparing two properties. + bool operator==(const Property& other) const; + bool operator!=(const Property& other) const { return !(*this == other); } + + // Copies all data from |src| property. + void Copy(const Property& src); + + // Name of this property. + void SetName(const std::string& name); + const std::string& GetName() const; + + // Property data stores one table column worth of data. For example, when + // the data of type UINT8 is [11, 22] then the property values are 11 and 22 + // for the first and second table rows. See EXT_structural_metadata glTF + // extension documentation for more details. + Data& GetData(); + const Data& GetData() const; + + // Array offsets are used when property data contains a variable-length + // number arrays. For example, when the data is [0, 1, 2, 3, 4] and the + // array offsets are [0, 2, 5] for a two-row table, then the property value + // arrays are [0, 1] and [2, 3, 4] for the first and second table rows, + // respectively. See EXT_structural_metadata glTF extension documentation + // for more details. + const Offsets& GetArrayOffsets() const; + Offsets& GetArrayOffsets(); + + // String offsets are used when property data contains strings. For example, + // when the data is "SeaLand" and the array offsets are [0, 3, 7] for a + // two-row table, then the property strings are "Sea" and "Land" for the + // first and second table rows, respectively. See EXT_structural_metadata + // glTF extension documentation for more details. + const Offsets& GetStringOffsets() const; + Offsets& GetStringOffsets(); + + private: + std::string name_; + Data data_; + Offsets array_offsets_; + Offsets string_offsets_; + // TODO(vytyaz): Support property value modifiers min, max, offset, scale. + }; + + // Creates an empty property table. + PropertyTable(); + + // Methods for comparing two property tables. + bool operator==(const PropertyTable& other) const; + bool operator!=(const PropertyTable& other) const { + return !(*this == other); + } + + // Copies all data from |src| property table. + void Copy(const PropertyTable& src); + + // Name of this property table. + void SetName(const std::string& value); + const std::string& GetName() const; + + // Class of this property table. + void SetClass(const std::string& value); + const std::string& GetClass() const; + + // Number of rows in this property table. + void SetCount(int count); + int GetCount() const; + + // Table properties (columns). + int AddProperty(std::unique_ptr property); + int NumProperties() const; + const Property& GetProperty(int index) const; + Property& GetProperty(int index); + void RemoveProperty(int index); + + private: + std::string name_; + std::string class_; + int count_; + std::vector> properties_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_METADATA_PROPERTY_TABLE_H_ diff --git a/contrib/draco/src/draco/metadata/property_table_test.cc b/contrib/draco/src/draco/metadata/property_table_test.cc new file mode 100644 index 000000000..4d5ee2d2c --- /dev/null +++ b/contrib/draco/src/draco/metadata/property_table_test.cc @@ -0,0 +1,624 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/metadata/property_table.h" + +#include +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +TEST(PropertyTableTest, TestPropertyDataDefaults) { + // Test construction of an empty property data. + draco::PropertyTable::Property::Data data; + ASSERT_TRUE(data.data.empty()); + ASSERT_EQ(data.target, 0); +} + +TEST(PropertyTableTest, TestPropertyDefaults) { + // Test construction of an empty property table property. + draco::PropertyTable::Property property; + ASSERT_TRUE(property.GetName().empty()); + ASSERT_TRUE(property.GetData().data.empty()); + { + const auto &offsets = property.GetArrayOffsets(); + ASSERT_TRUE(offsets.type.empty()); + ASSERT_TRUE(offsets.data.data.empty()); + ASSERT_EQ(offsets.data.target, 0); + } + { + const auto &offsets = property.GetStringOffsets(); + ASSERT_TRUE(offsets.type.empty()); + ASSERT_TRUE(offsets.data.data.empty()); + ASSERT_EQ(offsets.data.target, 0); + } +} + +TEST(PropertyTableTest, TestPropertyTableDefaults) { + // Test construction of an empty property table. + draco::PropertyTable table; + ASSERT_TRUE(table.GetName().empty()); + ASSERT_TRUE(table.GetClass().empty()); + ASSERT_EQ(table.GetCount(), 0); + ASSERT_EQ(table.NumProperties(), 0); +} + +TEST(PropertyTableTest, TestSchemaDefaults) { + // Test construction of an empty property table schema. + draco::PropertyTable::Schema schema; + ASSERT_TRUE(schema.Empty()); + ASSERT_EQ(schema.json.GetName(), "schema"); + ASSERT_EQ(schema.json.GetType(), + draco::PropertyTable::Schema::Object::OBJECT); + ASSERT_TRUE(schema.json.GetObjects().empty()); + ASSERT_TRUE(schema.json.GetArray().empty()); + ASSERT_TRUE(schema.json.GetString().empty()); + ASSERT_EQ(schema.json.GetInteger(), 0); + ASSERT_FALSE(schema.json.GetBoolean()); +} + +TEST(PropertyTableTest, TestSchemaObjectDefaultConstructor) { + // Test construction of an empty property table schema object. + draco::PropertyTable::Schema::Object object; + ASSERT_TRUE(object.GetName().empty()); + ASSERT_EQ(object.GetType(), draco::PropertyTable::Schema::Object::OBJECT); + ASSERT_TRUE(object.GetObjects().empty()); + ASSERT_TRUE(object.GetArray().empty()); + ASSERT_TRUE(object.GetString().empty()); + ASSERT_EQ(object.GetInteger(), 0); + ASSERT_FALSE(object.GetBoolean()); +} + +TEST(PropertyTableTest, TestSchemaObjectNamedConstructor) { + // Test construction of a named property table schema object. + draco::PropertyTable::Schema::Object object("Flexible Demeanour"); + ASSERT_EQ(object.GetName(), "Flexible Demeanour"); + ASSERT_EQ(object.GetType(), draco::PropertyTable::Schema::Object::OBJECT); + ASSERT_TRUE(object.GetObjects().empty()); +} + +TEST(PropertyTableTest, TestSchemaObjectStringConstructor) { + // Test construction of property table schema object storing a string. + draco::PropertyTable::Schema::Object object("Flexible Demeanour", "GCU"); + ASSERT_EQ(object.GetName(), "Flexible Demeanour"); + ASSERT_EQ(object.GetType(), draco::PropertyTable::Schema::Object::STRING); + ASSERT_EQ(object.GetString(), "GCU"); +} + +TEST(PropertyTableTest, TestSchemaObjectIntegerConstructor) { + // Test construction of property table schema object storing an integer. + draco::PropertyTable::Schema::Object object("Flexible Demeanour", 12); + ASSERT_EQ(object.GetName(), "Flexible Demeanour"); + ASSERT_EQ(object.GetType(), draco::PropertyTable::Schema::Object::INTEGER); + ASSERT_EQ(object.GetInteger(), 12); +} + +TEST(PropertyTableTest, TestSchemaObjectBooleanConstructor) { + // Test construction of property table schema object storing a boolean. + draco::PropertyTable::Schema::Object object("Flexible Demeanour", true); + ASSERT_EQ(object.GetName(), "Flexible Demeanour"); + ASSERT_EQ(object.GetType(), draco::PropertyTable::Schema::Object::BOOLEAN); + ASSERT_TRUE(object.GetBoolean()); +} + +TEST(PropertyTableTest, TestSchemaObjectSettersAndGetters) { + // Test value setters and getters of property table schema object. + typedef draco::PropertyTable::Schema::Object Object; + Object object; + ASSERT_EQ(object.GetType(), Object::OBJECT); + + object.SetArray().push_back(Object("entry", 12)); + ASSERT_EQ(object.GetType(), Object::ARRAY); + ASSERT_EQ(object.GetArray().size(), 1); + ASSERT_EQ(object.GetArray()[0].GetName(), "entry"); + ASSERT_EQ(object.GetArray()[0].GetInteger(), 12); + + object.SetObjects().push_back(Object("object", 9)); + ASSERT_EQ(object.GetType(), Object::OBJECT); + ASSERT_EQ(object.GetObjects().size(), 1); + ASSERT_EQ(object.GetObjects()[0].GetName(), "object"); + ASSERT_EQ(object.GetObjects()[0].GetInteger(), 9); + + object.SetString("matter"); + ASSERT_EQ(object.GetType(), Object::STRING); + ASSERT_EQ(object.GetString(), "matter"); + + object.SetInteger(5); + ASSERT_EQ(object.GetType(), Object::INTEGER); + ASSERT_EQ(object.GetInteger(), 5); + + object.SetBoolean(true); + ASSERT_EQ(object.GetType(), Object::BOOLEAN); + ASSERT_EQ(object.GetBoolean(), true); +} + +TEST(PropertyTableTest, TestSchemaCompare) { + typedef draco::PropertyTable::Schema Schema; + // Test comparison of two schema objects. + { + // Compare the same empty schema object. + Schema a; + ASSERT_TRUE(a == a); + ASSERT_FALSE(a != a); + } + { + // Compare two empty schema objects. + Schema a; + Schema b; + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two schema objects with different JSON objects. + Schema a; + Schema b; + a.json.SetBoolean(true); + b.json.SetBoolean(false); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } +} + +TEST(PropertyTableTest, TestSchemaObjectCompare) { + // Test comparison of two schema JSON objects. + typedef draco::PropertyTable::Schema::Object Object; + { + // Compare the same object. + Object a; + ASSERT_TRUE(a == a); + ASSERT_FALSE(a != a); + } + { + // Compare two default objects. + Object a; + Object b; + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two objects with different names. + Object a("one"); + Object b("two"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two objects with different types. + Object a; + Object b; + a.SetInteger(1); + b.SetString("one"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two identical string-type objects. + Object a; + Object b; + a.SetString("one"); + b.SetString("one"); + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two different string-type objects. + Object a; + Object b; + a.SetString("one"); + b.SetString("two"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two identical integer-type objects. + Object a; + Object b; + a.SetInteger(1); + b.SetInteger(1); + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two different integer-type objects. + Object a; + Object b; + a.SetInteger(1); + b.SetInteger(2); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two identical boolean-type objects. + Object a; + Object b; + a.SetBoolean(true); + b.SetBoolean(true); + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two different boolean-type objects. + Object a; + Object b; + a.SetBoolean(true); + b.SetBoolean(false); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two identical object-type objects. + Object a; + Object b; + a.SetObjects().emplace_back("one"); + b.SetObjects().emplace_back("one"); + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two different object-type objects. + Object a; + Object b; + a.SetObjects().emplace_back("one"); + b.SetObjects().emplace_back("two"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two object-type objects with different counts. + Object a; + Object b; + a.SetObjects().emplace_back("one"); + b.SetObjects().emplace_back("one"); + b.SetObjects().emplace_back("two"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two identical array-type objects. + Object a; + Object b; + a.SetArray().emplace_back("", 1); + b.SetArray().emplace_back("", 1); + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two different array-type objects. + Object a; + Object b; + a.SetArray().emplace_back("", 1); + b.SetArray().emplace_back("", 2); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two array-type objects with different counts. + Object a; + Object b; + a.SetArray().emplace_back("", 1); + b.SetArray().emplace_back("", 1); + b.SetArray().emplace_back("", 2); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } +} + +TEST(PropertyTableTest, TestPropertySettersAndGetters) { + // Test setter and getter methods of the property table property. + draco::PropertyTable::Property property; + property.SetName("Unfortunate Conflict Of Evidence"); + property.GetData().data.push_back(2); + + // Check that property members can be accessed via getters. + ASSERT_EQ(property.GetName(), "Unfortunate Conflict Of Evidence"); + ASSERT_EQ(property.GetData().data.size(), 1); + ASSERT_EQ(property.GetData().data[0], 2); +} + +TEST(PropertyTableTest, TestPropertyTableSettersAndGetters) { + // Test setter and getter methods of the property table. + draco::PropertyTable table; + table.SetName("Just Read The Instructions"); + table.SetClass("General Contact Unit"); + table.SetCount(456); + { + std::unique_ptr property( + new draco::PropertyTable::Property()); + property->SetName("Determinist"); + ASSERT_EQ(table.AddProperty(std::move(property)), 0); + } + { + std::unique_ptr property( + new draco::PropertyTable::Property()); + property->SetName("Revisionist"); + ASSERT_EQ(table.AddProperty(std::move(property)), 1); + } + + // Check that property table members can be accessed via getters. + ASSERT_EQ(table.GetName(), "Just Read The Instructions"); + ASSERT_EQ(table.GetClass(), "General Contact Unit"); + ASSERT_EQ(table.GetCount(), 456); + ASSERT_EQ(table.NumProperties(), 2); + ASSERT_EQ(table.GetProperty(0).GetName(), "Determinist"); + ASSERT_EQ(table.GetProperty(1).GetName(), "Revisionist"); + + // Check that proeprties can be removed. + table.RemoveProperty(0); + ASSERT_EQ(table.NumProperties(), 1); + ASSERT_EQ(table.GetProperty(0).GetName(), "Revisionist"); + table.RemoveProperty(0); + ASSERT_EQ(table.NumProperties(), 0); +} + +TEST(PropertyTableTest, TestPropertyCopy) { + // Test that property table property can be copied. + draco::PropertyTable::Property property; + property.SetName("Unfortunate Conflict Of Evidence"); + property.GetData().data.push_back(2); + + // Make a copy. + draco::PropertyTable::Property copy; + copy.Copy(property); + + // Check the copy. + ASSERT_EQ(copy.GetName(), "Unfortunate Conflict Of Evidence"); + ASSERT_EQ(copy.GetData().data.size(), 1); + ASSERT_EQ(copy.GetData().data[0], 2); +} + +TEST(PropertyTableTest, TestPropertyTableCopy) { + // Test that property table can be copied. + draco::PropertyTable table; + table.SetName("Just Read The Instructions"); + table.SetClass("General Contact Unit"); + table.SetCount(456); + { + std::unique_ptr property( + new draco::PropertyTable::Property()); + property->SetName("Determinist"); + table.AddProperty(std::move(property)); + } + { + std::unique_ptr property( + new draco::PropertyTable::Property()); + property->SetName("Revisionist"); + table.AddProperty(std::move(property)); + } + + // Make a copy. + draco::PropertyTable copy; + copy.Copy(table); + + // Check the copy. + ASSERT_EQ(copy.GetName(), "Just Read The Instructions"); + ASSERT_EQ(copy.GetClass(), "General Contact Unit"); + ASSERT_EQ(copy.GetCount(), 456); + ASSERT_EQ(copy.NumProperties(), 2); + ASSERT_EQ(copy.GetProperty(0).GetName(), "Determinist"); + ASSERT_EQ(copy.GetProperty(1).GetName(), "Revisionist"); +} + +TEST(PropertyTableTest, TestPropertyDataCompare) { + // Test comparison of two property data objects. + typedef draco::PropertyTable::Property::Data Data; + { + // Compare the same data object. + Data a; + ASSERT_TRUE(a == a); + ASSERT_FALSE(a != a); + } + { + // Compare two default data objects. + Data a; + Data b; + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two data objects with different targets. + Data a; + Data b; + a.target = 1; + b.target = 2; + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two data objects with different data vectors. + Data a; + Data b; + a.data = {1}; + a.data = {2}; + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } +} + +TEST(PropertyTableTest, TestPropertyOffsets) { + // Test comparison of two property offsets. + typedef draco::PropertyTable::Property::Offsets Offsets; + { + // Compare the same offsets object. + Offsets a; + ASSERT_TRUE(a == a); + ASSERT_FALSE(a != a); + } + { + // Compare two default offsets objects. + Offsets a; + Offsets b; + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two offsets objects with different types. + Offsets a; + Offsets b; + a.type = 1; + b.type = 2; + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two offsets objects with different data objects. + Offsets a; + Offsets b; + a.data.target = 1; + b.data.target = 2; + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } +} + +TEST(PropertyTableTest, TestPropertyCompare) { + // Test comparison of two properties. + typedef draco::PropertyTable::Property Property; + { + // Compare the same property object. + Property a; + ASSERT_TRUE(a == a); + ASSERT_FALSE(a != a); + } + { + // Compare two default property objects. + Property a; + Property b; + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two property objects with different names. + Property a; + Property b; + a.SetName("one"); + b.SetName("two"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two property objects with different data. + Property a; + Property b; + a.GetData().target = 1; + b.GetData().target = 2; + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two property objects with different array offsets. + Property a; + Property b; + a.GetArrayOffsets().data.target = 1; + b.GetArrayOffsets().data.target = 2; + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two property objects with different string offsets. + Property a; + Property b; + a.GetStringOffsets().data.target = 1; + b.GetStringOffsets().data.target = 2; + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } +} + +TEST(PropertyTableTest, TestPropertyTableCompare) { + // Test comparison of two property tables. + typedef draco::PropertyTable PropertyTable; + typedef draco::PropertyTable::Property Property; + { + // Compare the same property table object. + PropertyTable a; + ASSERT_TRUE(a == a); + ASSERT_FALSE(a != a); + } + { + // Compare two default property tables. + PropertyTable a; + PropertyTable b; + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two property tables with different names. + PropertyTable a; + PropertyTable b; + a.SetName("one"); + b.SetName("two"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two property tables with different classes. + PropertyTable a; + PropertyTable b; + a.SetClass("one"); + b.SetClass("two"); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two property tables with different counts. + PropertyTable a; + PropertyTable b; + a.SetCount(1); + b.SetCount(2); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two property tables with identical properties. + PropertyTable a; + PropertyTable b; + a.AddProperty(std::unique_ptr(new Property)); + b.AddProperty(std::unique_ptr(new Property)); + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two property tables with different number of properties. + PropertyTable a; + PropertyTable b; + a.AddProperty(std::unique_ptr(new Property)); + b.AddProperty(std::unique_ptr(new Property)); + b.AddProperty(std::unique_ptr(new Property)); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two property tables with different properties. + PropertyTable a; + PropertyTable b; + std::unique_ptr p1(new Property); + std::unique_ptr p2(new Property); + p1->SetName("one"); + p2->SetName("two"); + a.AddProperty(std::move(p1)); + b.AddProperty(std::move(p2)); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/contrib/draco/src/draco/metadata/structural_metadata.cc b/contrib/draco/src/draco/metadata/structural_metadata.cc new file mode 100644 index 000000000..48fff2b25 --- /dev/null +++ b/contrib/draco/src/draco/metadata/structural_metadata.cc @@ -0,0 +1,74 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/metadata/structural_metadata.h" + +#include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +StructuralMetadata::StructuralMetadata() {} + +bool StructuralMetadata::operator==(const StructuralMetadata &other) const { + return property_table_schema_ == other.property_table_schema_ && + property_tables_ == other.property_tables_; +} + +void StructuralMetadata::Copy(const StructuralMetadata &src) { + property_table_schema_.json.Copy(src.property_table_schema_.json); + property_tables_.resize(src.property_tables_.size()); + for (int i = 0; i < property_tables_.size(); ++i) { + property_tables_[i] = std::unique_ptr(new PropertyTable()); + property_tables_[i]->Copy(*src.property_tables_[i]); + } +} + +void StructuralMetadata::SetPropertyTableSchema( + const PropertyTable::Schema &schema) { + property_table_schema_ = schema; +} + +const PropertyTable::Schema &StructuralMetadata::GetPropertyTableSchema() + const { + return property_table_schema_; +} + +int StructuralMetadata::AddPropertyTable( + std::unique_ptr property_table) { + property_tables_.push_back(std::move(property_table)); + return property_tables_.size() - 1; +} + +int StructuralMetadata::NumPropertyTables() const { + return property_tables_.size(); +} + +const PropertyTable &StructuralMetadata::GetPropertyTable(int index) const { + return *property_tables_[index]; +} + +PropertyTable &StructuralMetadata::GetPropertyTable(int index) { + return *property_tables_[index]; +} + +void StructuralMetadata::RemovePropertyTable(int index) { + property_tables_.erase(property_tables_.begin() + index); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/metadata/structural_metadata.h b/contrib/draco/src/draco/metadata/structural_metadata.h new file mode 100644 index 000000000..24756c70c --- /dev/null +++ b/contrib/draco/src/draco/metadata/structural_metadata.h @@ -0,0 +1,64 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_METADATA_STRUCTURAL_METADATA_H_ +#define DRACO_METADATA_STRUCTURAL_METADATA_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include +#include +#include + +#include "draco/metadata/property_table.h" + +namespace draco { + +// Holds data associated with EXT_structural_metadata glTF extension. +class StructuralMetadata { + public: + StructuralMetadata(); + + // Methods for comparing two structural metadata objects. + bool operator==(const StructuralMetadata &other) const; + bool operator!=(const StructuralMetadata &other) const { + return !(*this == other); + } + + // Copies |src| structural metadata into this object. + void Copy(const StructuralMetadata &src); + + // Property table schema. + void SetPropertyTableSchema(const PropertyTable::Schema &schema); + const PropertyTable::Schema &GetPropertyTableSchema() const; + + // Property tables. + int AddPropertyTable(std::unique_ptr property_table); + int NumPropertyTables() const; + const PropertyTable &GetPropertyTable(int index) const; + PropertyTable &GetPropertyTable(int index); + void RemovePropertyTable(int index); + + private: + // Property table schema and property tables. + PropertyTable::Schema property_table_schema_; + std::vector> property_tables_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_METADATA_STRUCTURAL_METADATA_H_ diff --git a/contrib/draco/src/draco/metadata/structural_metadata_test.cc b/contrib/draco/src/draco/metadata/structural_metadata_test.cc new file mode 100644 index 000000000..d0429a568 --- /dev/null +++ b/contrib/draco/src/draco/metadata/structural_metadata_test.cc @@ -0,0 +1,170 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/metadata/structural_metadata.h" + +#include +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +TEST(StructuralMetadataTest, TestCopy) { + // Tests copying of structural metadata. + draco::StructuralMetadata structural_metadata; + + // Add property table schema to structural metadata. + draco::PropertyTable::Schema schema; + schema.json.SetString("Culture"); + structural_metadata.SetPropertyTableSchema(schema); + + // Add property table to structural metadata. + std::unique_ptr table(new draco::PropertyTable()); + table->SetName("Just Read The Instructions"); + table->SetClass("General Contact Unit"); + table->SetCount(456); + { + std::unique_ptr property( + new draco::PropertyTable::Property()); + property->SetName("Determinist"); + table->AddProperty(std::move(property)); + } + { + std::unique_ptr property( + new draco::PropertyTable::Property()); + property->SetName("Revisionist"); + table->AddProperty(std::move(property)); + } + ASSERT_EQ(structural_metadata.AddPropertyTable(std::move(table)), 0); + + // Copy the structural metadata. + draco::StructuralMetadata copy; + copy.Copy(structural_metadata); + + // Check that the structural metadata property table schema has been copied. + ASSERT_EQ(copy.GetPropertyTableSchema().json.GetString(), "Culture"); + + // Check that the structural metadata property table has been copied. + ASSERT_EQ(copy.NumPropertyTables(), 1); + ASSERT_EQ(copy.GetPropertyTable(0).GetName(), "Just Read The Instructions"); + ASSERT_EQ(copy.GetPropertyTable(0).GetClass(), "General Contact Unit"); + ASSERT_EQ(copy.GetPropertyTable(0).GetCount(), 456); + ASSERT_EQ(copy.GetPropertyTable(0).NumProperties(), 2); + ASSERT_EQ(copy.GetPropertyTable(0).GetProperty(0).GetName(), "Determinist"); + ASSERT_EQ(copy.GetPropertyTable(0).GetProperty(1).GetName(), "Revisionist"); +} + +TEST(StructuralMetadataTest, TestPropertyTables) { + // Tests adding and removing of property tables to structural metadata. + draco::StructuralMetadata structural_metadata; + + // Check that property tables can be added. + { + std::unique_ptr table(new draco::PropertyTable()); + table->SetName("Just Read The Instructions"); + ASSERT_EQ(structural_metadata.AddPropertyTable(std::move(table)), 0); + } + { + std::unique_ptr table(new draco::PropertyTable()); + table->SetName("So Much For Subtlety"); + ASSERT_EQ(structural_metadata.AddPropertyTable(std::move(table)), 1); + } + { + std::unique_ptr table(new draco::PropertyTable()); + table->SetName("Of Course I Still Love You"); + ASSERT_EQ(structural_metadata.AddPropertyTable(std::move(table)), 2); + } + draco::StructuralMetadata &sm = structural_metadata; + + // Check that the property tables can be removed. + ASSERT_EQ(sm.NumPropertyTables(), 3); + ASSERT_EQ(sm.GetPropertyTable(0).GetName(), "Just Read The Instructions"); + ASSERT_EQ(sm.GetPropertyTable(1).GetName(), "So Much For Subtlety"); + ASSERT_EQ(sm.GetPropertyTable(2).GetName(), "Of Course I Still Love You"); + + sm.RemovePropertyTable(1); + ASSERT_EQ(sm.NumPropertyTables(), 2); + ASSERT_EQ(sm.GetPropertyTable(0).GetName(), "Just Read The Instructions"); + ASSERT_EQ(sm.GetPropertyTable(1).GetName(), "Of Course I Still Love You"); + + sm.RemovePropertyTable(1); + ASSERT_EQ(sm.NumPropertyTables(), 1); + ASSERT_EQ(sm.GetPropertyTable(0).GetName(), "Just Read The Instructions"); + + sm.RemovePropertyTable(0); + ASSERT_EQ(sm.NumPropertyTables(), 0); +} + +TEST(StructuralMetadataTest, TestCompare) { + // Test comparison of two structural metadata objects. + typedef draco::PropertyTable PropertyTable; + { + // Compare the same structural metadata object. + draco::StructuralMetadata a; + ASSERT_TRUE(a == a); + ASSERT_FALSE(a != a); + } + { + // Compare two identical structural metadata objects. + draco::StructuralMetadata a; + draco::StructuralMetadata b; + ASSERT_TRUE(a == b); + ASSERT_FALSE(a != b); + } + { + // Compare two structural metadata objects with different schemas. + draco::StructuralMetadata a; + draco::StructuralMetadata b; + PropertyTable::Schema s1; + PropertyTable::Schema s2; + s1.json.SetString("one"); + s2.json.SetString("two"); + a.SetPropertyTableSchema(s1); + b.SetPropertyTableSchema(s2); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two objects with different number of proeprty tables. + draco::StructuralMetadata a; + draco::StructuralMetadata b; + a.AddPropertyTable(std::unique_ptr(new PropertyTable())); + b.AddPropertyTable(std::unique_ptr(new PropertyTable())); + b.AddPropertyTable(std::unique_ptr(new PropertyTable())); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } + { + // Compare two objects with different proeprty tables. + draco::StructuralMetadata a; + draco::StructuralMetadata b; + auto p1 = std::unique_ptr(new PropertyTable()); + auto p2 = std::unique_ptr(new PropertyTable()); + p1->SetName("one"); + p2->SetName("two"); + a.AddPropertyTable(std::move(p1)); + b.AddPropertyTable(std::move(p2)); + ASSERT_FALSE(a == b); + ASSERT_TRUE(a != b); + } +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/contrib/draco/src/draco/point_cloud/point_cloud.cc b/contrib/draco/src/draco/point_cloud/point_cloud.cc index a9f9ea2af..039c4f201 100644 --- a/contrib/draco/src/draco/point_cloud/point_cloud.cc +++ b/contrib/draco/src/draco/point_cloud/point_cloud.cc @@ -16,11 +16,41 @@ #include #include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/attributes/point_attribute.h" +#endif namespace draco { PointCloud::PointCloud() : num_points_(0) {} +#ifdef DRACO_TRANSCODER_SUPPORTED +void PointCloud::Copy(const PointCloud &src) { + num_points_ = src.num_points_; + for (int i = 0; i < GeometryAttribute::NAMED_ATTRIBUTES_COUNT; ++i) { + named_attribute_index_[i] = src.named_attribute_index_[i]; + } + attributes_.resize(src.attributes_.size()); + for (int i = 0; i < src.attributes_.size(); ++i) { + attributes_[i] = std::unique_ptr(new PointAttribute()); + attributes_[i]->CopyFrom(*src.attributes_[i]); + } + CopyMetadata(src); +} + +void PointCloud::CopyMetadata(const PointCloud &src) { + if (src.metadata_ == nullptr) { + metadata_ = nullptr; + } else { + // Copy base metadata. + const GeometryMetadata *const metadata = src.metadata_.get(); + metadata_.reset(new GeometryMetadata(*metadata)); + } +} +#endif + int32_t PointCloud::NumNamedAttributes(GeometryAttribute::Type type) const { if (type == GeometryAttribute::INVALID || type >= GeometryAttribute::NAMED_ATTRIBUTES_COUNT) { @@ -253,11 +283,16 @@ bool PointCloud::DeduplicateAttributeValues() { } #endif -// TODO(xiaoxumeng): Consider to cash the BBox. +// TODO(b/199760503): Consider to cache the BBox. BoundingBox PointCloud::ComputeBoundingBox() const { BoundingBox bounding_box; auto pc_att = GetNamedAttribute(GeometryAttribute::POSITION); - // TODO(xiaoxumeng): Make the BoundingBox a template type, it may not be easy + if (pc_att == nullptr) { + // Return default invalid bounding box. + return bounding_box; + } + + // TODO(b/199760503): Make the BoundingBox a template type, it may not be easy // because PointCloud is not a template. // Or simply add some preconditioning here to make sure the position attribute // is valid, because the current code works only if the position attribute is diff --git a/contrib/draco/src/draco/point_cloud/point_cloud.h b/contrib/draco/src/draco/point_cloud/point_cloud.h index d11bd47a3..17d0bc392 100644 --- a/contrib/draco/src/draco/point_cloud/point_cloud.h +++ b/contrib/draco/src/draco/point_cloud/point_cloud.h @@ -31,6 +31,11 @@ class PointCloud { PointCloud(); virtual ~PointCloud() = default; +#ifdef DRACO_TRANSCODER_SUPPORTED + // Copies all data from the |src| point cloud. + void Copy(const PointCloud &src); +#endif + // Returns the number of named attributes of a given type. int32_t NumNamedAttributes(GeometryAttribute::Type type) const; @@ -185,6 +190,11 @@ class PointCloud { void set_num_points(PointIndex::ValueType num) { num_points_ = num; } protected: +#ifdef DRACO_TRANSCODER_SUPPORTED + // Copies metadata from the |src| point cloud. + void CopyMetadata(const PointCloud &src); +#endif + #ifdef DRACO_ATTRIBUTE_INDICES_DEDUPLICATION_SUPPORTED // Applies id mapping of deduplicated points (called by DeduplicatePointIds). virtual void ApplyPointIdDeduplication( diff --git a/contrib/draco/src/draco/point_cloud/point_cloud_builder.cc b/contrib/draco/src/draco/point_cloud/point_cloud_builder.cc index 431ae505f..90ec37962 100644 --- a/contrib/draco/src/draco/point_cloud/point_cloud_builder.cc +++ b/contrib/draco/src/draco/point_cloud/point_cloud_builder.cc @@ -14,6 +14,8 @@ // #include "draco/point_cloud/point_cloud_builder.h" +#include + namespace draco { PointCloudBuilder::PointCloudBuilder() {} diff --git a/contrib/draco/src/draco/point_cloud/point_cloud_builder.h b/contrib/draco/src/draco/point_cloud/point_cloud_builder.h index cf55a728b..512b0f71f 100644 --- a/contrib/draco/src/draco/point_cloud/point_cloud_builder.h +++ b/contrib/draco/src/draco/point_cloud/point_cloud_builder.h @@ -15,6 +15,8 @@ #ifndef DRACO_POINT_CLOUD_POINT_CLOUD_BUILDER_H_ #define DRACO_POINT_CLOUD_POINT_CLOUD_BUILDER_H_ +#include + #include "draco/point_cloud/point_cloud.h" namespace draco { @@ -37,6 +39,9 @@ namespace draco { class PointCloudBuilder { public: + // Index type of the inserted element. + typedef PointIndex ElementIndex; + PointCloudBuilder(); // Starts collecting point cloud data. @@ -71,6 +76,12 @@ class PointCloudBuilder { // used until the method Start() is called again. std::unique_ptr Finalize(bool deduplicate_points); + // Add metadata for an attribute. + void AddAttributeMetadata(int32_t att_id, + std::unique_ptr metadata) { + point_cloud_->AddAttributeMetadata(att_id, std::move(metadata)); + } + private: std::unique_ptr point_cloud_; }; diff --git a/contrib/draco/src/draco/point_cloud/point_cloud_test.cc b/contrib/draco/src/draco/point_cloud/point_cloud_test.cc index 4e9460370..1cd780db2 100644 --- a/contrib/draco/src/draco/point_cloud/point_cloud_test.cc +++ b/contrib/draco/src/draco/point_cloud/point_cloud_test.cc @@ -14,6 +14,9 @@ // #include "draco/point_cloud/point_cloud.h" +#include +#include + #include "draco/core/draco_test_base.h" #include "draco/core/draco_test_utils.h" #include "draco/metadata/geometry_metadata.h" @@ -25,6 +28,57 @@ class PointCloudTest : public ::testing::Test { PointCloudTest() {} }; +#ifdef DRACO_TRANSCODER_SUPPORTED +TEST_F(PointCloudTest, PointCloudCopy) { + // Tests that we can copy a point cloud. + std::unique_ptr pc = + draco::ReadPointCloudFromTestFile("pc_kd_color.drc"); + ASSERT_NE(pc, nullptr); + + // Add metadata to the point cloud. + std::unique_ptr metadata( + new draco::GeometryMetadata()); + metadata->AddEntryInt("speed", 1050); + metadata->AddEntryString("code", "YT-1300f"); + + // Add attribute metadata. + std::unique_ptr a_metadata( + new draco::AttributeMetadata()); + a_metadata->set_att_unique_id(pc->attribute(0)->unique_id()); + a_metadata->AddEntryInt("attribute_test", 3); + metadata->AddAttributeMetadata(std::move(a_metadata)); + pc->AddMetadata(std::move(metadata)); + + // Create a copy of the point cloud. + draco::PointCloud pc_copy; + pc_copy.Copy(*pc); + + // Check the point cloud data. + ASSERT_EQ(pc->num_points(), pc_copy.num_points()); + ASSERT_EQ(pc->num_attributes(), pc_copy.num_attributes()); + for (int i = 0; i < pc->num_attributes(); ++i) { + ASSERT_EQ(pc->attribute(i)->attribute_type(), + pc_copy.attribute(i)->attribute_type()); + } + + // Check the point cloud metadata. + int speed; + std::string code; + ASSERT_NE(pc->GetMetadata(), nullptr); + ASSERT_TRUE(pc->GetMetadata()->GetEntryInt("speed", &speed)); + ASSERT_TRUE(pc->GetMetadata()->GetEntryString("code", &code)); + ASSERT_EQ(speed, 1050); + ASSERT_EQ(code, "YT-1300f"); + + const auto *const att_metadata_copy = + pc->GetMetadata()->GetAttributeMetadataByUniqueId(0); + ASSERT_NE(att_metadata_copy, nullptr); + int att_test; + ASSERT_TRUE(att_metadata_copy->GetEntryInt("attribute_test", &att_test)); + ASSERT_EQ(att_test, 3); +} +#endif + TEST_F(PointCloudTest, TestAttributeDeletion) { draco::PointCloud pc; // Test whether we can correctly delete an attribute from a point cloud. diff --git a/contrib/draco/src/draco/scene/instance_array.cc b/contrib/draco/src/draco/scene/instance_array.cc new file mode 100644 index 000000000..bcd49996a --- /dev/null +++ b/contrib/draco/src/draco/scene/instance_array.cc @@ -0,0 +1,45 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/instance_array.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include + +namespace draco { + +void InstanceArray::Copy(const InstanceArray &other) { + instances_.resize(other.instances_.size()); + for (int i = 0; i < instances_.size(); i++) { + instances_[i].trs.Copy(other.instances_[i].trs); + } +} + +Status InstanceArray::AddInstance(const Instance &instance) { + // Check that the |instance.trs| does not have the transformation matrix set, + // because the EXT_mesh_gpu_instancing glTF extension dictates that only the + // individual TRS vectors are stored. + if (instance.trs.MatrixSet()) { + return ErrorStatus("Instance must have no matrix set."); + } + + // Move the |instance| to the end of the instances vector. + instances_.push_back(instance); + return OkStatus(); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/scene/instance_array.h b/contrib/draco/src/draco/scene/instance_array.h new file mode 100644 index 000000000..fb1fbde1e --- /dev/null +++ b/contrib/draco/src/draco/scene/instance_array.h @@ -0,0 +1,61 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_SCENE_INSTANCE_ARRAY_H_ +#define DRACO_SCENE_INSTANCE_ARRAY_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/core/vector_d.h" +#include "draco/scene/trs_matrix.h" + +namespace draco { + +// Describes a mesh group instancing array that includes TRS transformation +// for multiple instance positions and possibly other custom instance attributes +// (not yet supported), following the EXT_mesh_gpu_instancing glTF extension. +class InstanceArray { + public: + struct Instance { + // Translation, rotation, and scale vectors. + TrsMatrix trs; + // TODO(vytyaz): Support custom instance attributes, e.g., _ID, _COLOR, etc. + }; + + InstanceArray() = default; + + void Copy(const InstanceArray &other); + + // Adds one |instance| into this mesh group instance array where the + // |instance.trs| may have optional translation, rotation, and scale set. + Status AddInstance(const Instance &instance); + + // Returns the count of instances in this mesh group instance array. + int NumInstances() const { return instances_.size(); } + + // Returns an instance from this mesh group instance array. + const Instance &GetInstance(int index) const { return instances_[index]; } + + private: + std::vector instances_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_SCENE_INSTANCE_ARRAY_H_ diff --git a/contrib/draco/src/draco/scene/instance_array_test.cc b/contrib/draco/src/draco/scene/instance_array_test.cc new file mode 100644 index 000000000..d3802f901 --- /dev/null +++ b/contrib/draco/src/draco/scene/instance_array_test.cc @@ -0,0 +1,179 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/instance_array.h" + +#include +#include + +#include "draco/core/constants.h" +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +TEST(InstanceArrayTest, TestInstance) { + // Test construction of an empty draco::InstanceArray::Instance struct. + const draco::InstanceArray::Instance instance; + ASSERT_FALSE(instance.trs.TranslationSet()); + ASSERT_FALSE(instance.trs.RotationSet()); + ASSERT_FALSE(instance.trs.ScaleSet()); + ASSERT_FALSE(instance.trs.MatrixSet()); +} + +TEST(InstanceArrayTest, TestDefaults) { + // Test construction of an empty draco::InstanceArray object. + const draco::InstanceArray array; + ASSERT_EQ(array.NumInstances(), 0); +} + +TEST(InstanceArrayTest, TestAddInstance) { + // Test population of draco::InstanceArray object with instances. + draco::InstanceArray array; + + // Create an instance and set its transformation TRS vectors. + const Eigen::Vector3d translation_0(1.0, 2.0, 3.0); + const Eigen::Quaterniond rotation_0(4.0, 5.0, 6.0, 7.0); + const Eigen::Vector3d scale_0(8.0, 9.0, 10.0); + draco::InstanceArray::Instance instance_0; + instance_0.trs.SetTranslation(translation_0); + instance_0.trs.SetRotation(rotation_0); + instance_0.trs.SetScale(scale_0); + + // Create another instance. + const Eigen::Vector3d translation_1(1.1, 2.1, 3.1); + const Eigen::Quaterniond rotation_1(4.1, 5.1, 6.1, 7.1); + const Eigen::Vector3d scale_1(8.1, 9.1, 10.1); + draco::InstanceArray::Instance instance_1; + instance_1.trs.SetTranslation(translation_1); + instance_1.trs.SetRotation(rotation_1); + instance_1.trs.SetScale(scale_1); + + // Add two instances to instance array. + DRACO_ASSERT_OK(array.AddInstance(instance_0)); + DRACO_ASSERT_OK(array.AddInstance(instance_1)); + + // Check that the instances have been added. + ASSERT_EQ(array.NumInstances(), 2); + + // Check transformation of the first instance. + const draco::TrsMatrix &trs_0 = array.GetInstance(0).trs; + ASSERT_TRUE(trs_0.TranslationSet()); + ASSERT_TRUE(trs_0.RotationSet()); + ASSERT_TRUE(trs_0.ScaleSet()); + ASSERT_FALSE(trs_0.MatrixSet()); + ASSERT_EQ(trs_0.Translation().value(), translation_0); + ASSERT_EQ(trs_0.Rotation().value(), rotation_0); + ASSERT_EQ(trs_0.Scale().value(), scale_0); + + // Check transformation of the second instance. + const draco::TrsMatrix &trs_1 = array.GetInstance(1).trs; + ASSERT_TRUE(trs_1.TranslationSet()); + ASSERT_TRUE(trs_1.RotationSet()); + ASSERT_TRUE(trs_1.ScaleSet()); + ASSERT_FALSE(trs_1.MatrixSet()); + ASSERT_EQ(trs_1.Translation().value(), translation_1); + ASSERT_EQ(trs_1.Rotation().value(), rotation_1); + ASSERT_EQ(trs_1.Scale().value(), scale_1); +} + +TEST(InstanceArrayTest, TestAddInstanceWithoutTransform) { + // Test that instance without any transformation can be added. + draco::InstanceArray array; + + // Do not set any transformation. + draco::InstanceArray::Instance instance; + + // Check that such instance can be added. + DRACO_ASSERT_OK(array.AddInstance(instance)); +} + +TEST(InstanceArrayTest, TestAddInstanceWithoutScale) { + // Test that instance without scale can be added. + draco::InstanceArray array; + + // Set only instance translation and rotation. + draco::InstanceArray::Instance instance; + instance.trs.SetTranslation(Eigen::Vector3d(1.0, 2.0, 3.0)); + instance.trs.SetRotation(Eigen::Quaterniond(4.0, 5.0, 6.0, 7.0)); + + // Check that such instance can be added. + DRACO_ASSERT_OK(array.AddInstance(instance)); +} + +TEST(InstanceArrayTest, TestAddInstanceWithMatrixFails) { + // Test that instance without scale cannot be added. + draco::InstanceArray array; + + // Set TRS vectors, as well as the matrix. + draco::InstanceArray::Instance instance; + instance.trs.SetTranslation(Eigen::Vector3d(1.0, 2.0, 3.0)); + instance.trs.SetRotation(Eigen::Quaterniond(4.0, 5.0, 6.0, 7.0)); + instance.trs.SetScale(Eigen::Vector3d(8.0, 9.0, 10.0)); + // clang-format off + Eigen::Matrix4d matrix; + matrix << 1.0, 2.0, 3.0, 4.0, + 5.0, 6.0, 7.0, 8.0, + 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0; + // clang-format on + instance.trs.SetMatrix(matrix); + + // Check that such instance cannot be added. + const draco::Status status = array.AddInstance(instance); + ASSERT_FALSE(status.ok()); + ASSERT_EQ(status.error_msg_string(), "Instance must have no matrix set."); +} + +TEST(InstanceArrayTest, TestCopy) { + // Test copying of draco::InstanceArray object. + draco::InstanceArray array; + + // Create an instance and set its transformation TRS vectors. + const Eigen::Vector3d translation_0(1.0, 2.0, 3.0); + const Eigen::Quaterniond rotation_0(4.0, 5.0, 6.0, 7.0); + const Eigen::Vector3d scale_0(8.0, 9.0, 10.0); + draco::InstanceArray::Instance instance_0; + instance_0.trs.SetTranslation(translation_0); + instance_0.trs.SetRotation(rotation_0); + instance_0.trs.SetScale(scale_0); + + // Create another instance. + const Eigen::Vector3d translation_1(1.1, 2.1, 3.1); + const Eigen::Quaterniond rotation_1(4.1, 5.1, 6.1, 7.1); + const Eigen::Vector3d scale_1(8.1, 9.1, 10.1); + draco::InstanceArray::Instance instance_1; + instance_1.trs.SetTranslation(translation_1); + instance_1.trs.SetRotation(rotation_1); + instance_1.trs.SetScale(scale_1); + + // Add two instances to the instance array. + DRACO_ASSERT_OK(array.AddInstance(instance_0)); + DRACO_ASSERT_OK(array.AddInstance(instance_1)); + + // Create a copy of the populated instance array object. + draco::InstanceArray copy; + copy.Copy(array); + + // Check that the instances have been copied. + ASSERT_EQ(copy.NumInstances(), 2); + ASSERT_EQ(copy.GetInstance(0).trs, instance_0.trs); + ASSERT_EQ(copy.GetInstance(1).trs, instance_1.trs); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/contrib/draco/src/draco/scene/light.cc b/contrib/draco/src/draco/scene/light.cc new file mode 100644 index 000000000..1fef98c0c --- /dev/null +++ b/contrib/draco/src/draco/scene/light.cc @@ -0,0 +1,45 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/light.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include + +#include "draco/core/constants.h" + +namespace draco { + +Light::Light() + : color_(1.0f, 1.0f, 1.0f), + intensity_(1.0), + type_(POINT), + range_(std::numeric_limits::max()), // Infinity. + inner_cone_angle_(0.0), + outer_cone_angle_(DRACO_PI / 4.0) {} + +void Light::Copy(const Light &light) { + name_ = light.name_; + color_ = light.color_; + intensity_ = light.intensity_; + type_ = light.type_; + range_ = light.range_; + inner_cone_angle_ = light.inner_cone_angle_; + outer_cone_angle_ = light.outer_cone_angle_; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/scene/light.h b/contrib/draco/src/draco/scene/light.h new file mode 100644 index 000000000..5ff0d4a6b --- /dev/null +++ b/contrib/draco/src/draco/scene/light.h @@ -0,0 +1,81 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_SCENE_LIGHT_H_ +#define DRACO_SCENE_LIGHT_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +#include "draco/core/vector_d.h" + +namespace draco { + +// Describes a light in a scene according to the KHR_lights_punctual extension. +class Light { + public: + enum Type { DIRECTIONAL, POINT, SPOT }; + + Light(); + + void Copy(const Light &light); + + // Name. + void SetName(const std::string &name) { name_ = name; } + const std::string &GetName() const { return name_; } + + // Color. + void SetColor(const Vector3f &color) { color_ = color; } + const Vector3f &GetColor() const { return color_; } + + // Intensity. + void SetIntensity(double intensity) { intensity_ = intensity; } + double GetIntensity() const { return intensity_; } + + // Type. + void SetType(Type type) { type_ = type; } + Type GetType() const { return type_; } + + // Range. + void SetRange(double range) { range_ = range; } + double GetRange() const { return range_; } + + // Inner cone angle. + void SetInnerConeAngle(double angle) { inner_cone_angle_ = angle; } + double GetInnerConeAngle() const { return inner_cone_angle_; } + + // Outer cone angle. + void SetOuterConeAngle(double angle) { outer_cone_angle_ = angle; } + double GetOuterConeAngle() const { return outer_cone_angle_; } + + private: + std::string name_; + Vector3f color_; + double intensity_; + Type type_; + + // The range is only applicable to lights with Type::POINT or Type::SPOT. + double range_; + + // The cone angles are only applicable to lights with Type::SPOT. + double inner_cone_angle_; + double outer_cone_angle_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_SCENE_LIGHT_H_ diff --git a/contrib/draco/src/draco/scene/light_test.cc b/contrib/draco/src/draco/scene/light_test.cc new file mode 100644 index 000000000..bc24a14ad --- /dev/null +++ b/contrib/draco/src/draco/scene/light_test.cc @@ -0,0 +1,64 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/light.h" + +#include + +#include "draco/core/constants.h" +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +TEST(LightTest, TestDefaults) { + // Text constructing draco::Light object with default properties. + const draco::Light light; + ASSERT_EQ(light.GetName(), ""); + ASSERT_EQ(light.GetColor(), draco::Vector3f(1.0f, 1.0f, 1.0f)); + ASSERT_EQ(light.GetIntensity(), 1.0); + ASSERT_EQ(light.GetType(), draco::Light::POINT); + ASSERT_EQ(light.GetRange(), std::numeric_limits::max()); + ASSERT_EQ(light.GetInnerConeAngle(), 0.0); + ASSERT_EQ(light.GetOuterConeAngle(), DRACO_PI / 4.0); +} + +TEST(LightTest, TestCopy) { + // Test copying of draco::Light object. + draco::Light light; + light.SetName("The Star of Earendil"); + light.SetColor(draco::Vector3f(0.90, 0.97, 1.00)); + light.SetIntensity(5.0); + light.SetType(draco::Light::SPOT); + light.SetRange(1000.0); + light.SetInnerConeAngle(DRACO_PI / 8.0); + light.SetOuterConeAngle(DRACO_PI / 2.0); + + // Create a copy of the initialized light and check all properties. + draco::Light copy; + copy.Copy(light); + ASSERT_EQ(copy.GetName(), "The Star of Earendil"); + ASSERT_EQ(copy.GetColor(), draco::Vector3f(0.90, 0.97, 1.00)); + ASSERT_EQ(copy.GetIntensity(), 5.0); + ASSERT_EQ(copy.GetType(), draco::Light::SPOT); + ASSERT_EQ(copy.GetRange(), 1000.0); + ASSERT_EQ(copy.GetInnerConeAngle(), DRACO_PI / 8.0); + ASSERT_EQ(copy.GetOuterConeAngle(), DRACO_PI / 2.0); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/contrib/draco/src/draco/scene/mesh_group.h b/contrib/draco/src/draco/scene/mesh_group.h new file mode 100644 index 000000000..852318f16 --- /dev/null +++ b/contrib/draco/src/draco/scene/mesh_group.h @@ -0,0 +1,138 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_SCENE_MESH_GROUP_H_ +#define DRACO_SCENE_MESH_GROUP_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/core/macros.h" +#include "draco/scene/scene_indices.h" + +namespace draco { + +// This class is used to hold ordered mesh instances that refer to one or more +// base meshes, materials, and materials variants mappings. +class MeshGroup { + public: + // Stores a mapping from material index to materials variant indices. Each + // mesh instance may have multiple such mappings associated with it. See glTF + // extension KHR_materials_variants for more details. + struct MaterialsVariantsMapping { + MaterialsVariantsMapping() = delete; + MaterialsVariantsMapping(int material, const std::vector &variants) + : material(material), variants(variants) {} + bool operator==(const MaterialsVariantsMapping &other) const { + if (material != other.material) { + return false; + } + if (variants != other.variants) { + return false; + } + return true; + } + bool operator!=(const MaterialsVariantsMapping &other) const { + return !(*this == other); + } + int material; + std::vector variants; + }; + + // Describes mesh instance stored in a mesh group, including base mesh index, + // material index, and materials variants mappings. + struct MeshInstance { + MeshInstance() = delete; + MeshInstance(MeshIndex mesh_index, int material_index) + : MeshInstance(mesh_index, material_index, {}) {} + MeshInstance(MeshIndex mesh_index, int material_index, + const std::vector + &materials_variants_mappings) + : mesh_index(mesh_index), + material_index(material_index), + materials_variants_mappings(materials_variants_mappings) {} + bool operator==(const MeshInstance &other) const { + if (mesh_index != other.mesh_index) { + return false; + } + if (material_index != other.material_index) { + return false; + } + if (materials_variants_mappings.size() != + other.materials_variants_mappings.size()) { + return false; + } + if (materials_variants_mappings != other.materials_variants_mappings) { + return false; + } + return true; + } + bool operator!=(const MeshInstance &other) const { + return !(*this == other); + } + MeshIndex mesh_index; + int material_index; + std::vector materials_variants_mappings; + }; + + MeshGroup() {} + + void Copy(const MeshGroup &mg) { + name_ = mg.name_; + mesh_instances_ = mg.mesh_instances_; + } + + const std::string &GetName() const { return name_; } + void SetName(const std::string &name) { name_ = name; } + + void AddMeshInstance(const MeshInstance &instance) { + mesh_instances_.push_back(instance); + } + + void SetMeshInstance(int index, const MeshInstance &instance) { + mesh_instances_[index] = instance; + } + + const MeshInstance &GetMeshInstance(int index) const { + return mesh_instances_[index]; + } + + MeshInstance &GetMeshInstance(int index) { return mesh_instances_[index]; } + + int NumMeshInstances() const { return mesh_instances_.size(); } + + // Removes all mesh instances referring to base mesh at |mesh_index|. + void RemoveMeshInstances(MeshIndex mesh_index) { + int i = 0; + while (i != mesh_instances_.size()) { + if (mesh_instances_[i].mesh_index == mesh_index) { + mesh_instances_.erase(mesh_instances_.begin() + i); + } else { + i++; + } + } + } + + private: + std::string name_; + std::vector mesh_instances_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_SCENE_MESH_GROUP_H_ diff --git a/contrib/draco/src/draco/scene/mesh_group_test.cc b/contrib/draco/src/draco/scene/mesh_group_test.cc new file mode 100644 index 000000000..76f1bf33e --- /dev/null +++ b/contrib/draco/src/draco/scene/mesh_group_test.cc @@ -0,0 +1,196 @@ +// Copyright 2020 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/mesh_group.h" + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/scene/scene_indices.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +using draco::MeshGroup; +using draco::MeshIndex; + +// Test helper method generates materials variants mappings based on a |seed|. +std::vector MakeMappings(int seed) { + MeshGroup::MaterialsVariantsMapping a(10 * seed + 0, {seed + 0, seed + 1}); + MeshGroup::MaterialsVariantsMapping b(10 * seed + 1, {seed + 2, seed + 3}); + return {a, b}; +} + +TEST(MeshGroupTest, TestMeshInstanceTwoArgumentConstructor) { + MeshGroup::MeshInstance instance(MeshIndex(2), 3); + ASSERT_EQ(instance.mesh_index, MeshIndex(2)); + ASSERT_EQ(instance.material_index, 3); + ASSERT_EQ(instance.materials_variants_mappings.size(), 0); +} + +TEST(MeshGroupTest, TestMeshInstanceThreeArgumentConstructor) { + const auto mappings = MakeMappings(4); + MeshGroup::MeshInstance instance(MeshIndex(2), 3, mappings); + ASSERT_EQ(instance.mesh_index, MeshIndex(2)); + ASSERT_EQ(instance.material_index, 3); + ASSERT_EQ(instance.materials_variants_mappings, mappings); +} + +TEST(MeshGroupTest, TestMeshInstanceEqualsOperator) { + MeshGroup::MeshInstance instance_a(MeshIndex(2), 3, MakeMappings(4)); + MeshGroup::MeshInstance instance_b(MeshIndex(2), 3, MakeMappings(4)); + ASSERT_TRUE(instance_a == instance_b); + ASSERT_FALSE(instance_a != instance_b); + + MeshGroup::MeshInstance instance_c(MeshIndex(1), 3, MakeMappings(4)); + MeshGroup::MeshInstance instance_d(MeshIndex(2), 1, MakeMappings(4)); + MeshGroup::MeshInstance instance_e(MeshIndex(2), 3, MakeMappings(1)); + ASSERT_FALSE(instance_a == instance_c); + ASSERT_FALSE(instance_a == instance_d); + ASSERT_FALSE(instance_a == instance_e); + ASSERT_TRUE(instance_a != instance_c); + ASSERT_TRUE(instance_a != instance_d); + ASSERT_TRUE(instance_a != instance_e); +} + +TEST(MeshGroupTest, TestRemoveMeshInstanceWithNoOccurrences) { + // Test that no mesh instances are removed from mesh group when removing the + // instances by the base mesh index that is not in the mesh group. + + // Create test mesh group. + MeshGroup mesh_group; + mesh_group.AddMeshInstance({MeshIndex(1), 0, {}}); + mesh_group.AddMeshInstance({MeshIndex(3), 0, {}}); + + // Try to remove mesh that is not in the mesh group. + mesh_group.RemoveMeshInstances(MeshIndex(2)); + + // Check result. + ASSERT_EQ(mesh_group.NumMeshInstances(), 2); + ASSERT_EQ(mesh_group.GetMeshInstance(0).mesh_index, MeshIndex(1)); + ASSERT_EQ(mesh_group.GetMeshInstance(1).mesh_index, MeshIndex(3)); +} + +TEST(MeshGroupTest, TestRemoveTheOnlyMeshInstance) { + // Test that the only mesh instance can be removed from mesh group. + + // Create test mesh group. + MeshGroup mesh_group; + MeshGroup::MaterialsVariantsMapping mapping(70, {0, 1}); + mesh_group.AddMeshInstance({MeshIndex(7), 70, {mapping}}); + + // Remove a mesh instance. + mesh_group.RemoveMeshInstances(MeshIndex(7)); + + // Check result. + ASSERT_EQ(mesh_group.NumMeshInstances(), 0); +} + +TEST(MeshGroupTest, TestRemoveOneMeshInstances) { + // Test a mesh instance can be removed from mesh group. + + // Create test mesh group. + MeshGroup mesh_group; + mesh_group.AddMeshInstance({MeshIndex(1), 0, {}}); + mesh_group.AddMeshInstance({MeshIndex(3), 0, {}}); + mesh_group.AddMeshInstance({MeshIndex(5), 0, {}}); + mesh_group.AddMeshInstance({MeshIndex(7), 0, {}}); + + // Remove a mesh. + mesh_group.RemoveMeshInstances(MeshIndex(3)); + + // Check result. + ASSERT_EQ(mesh_group.NumMeshInstances(), 3); + ASSERT_EQ(mesh_group.GetMeshInstance(0).mesh_index, MeshIndex(1)); + ASSERT_EQ(mesh_group.GetMeshInstance(1).mesh_index, MeshIndex(5)); + ASSERT_EQ(mesh_group.GetMeshInstance(2).mesh_index, MeshIndex(7)); +} + +TEST(MeshGroupTest, TestRemoveThreeMeshInstances) { + // Test that multiple mesh instances can be removed from a mesh group. + + // Create test mesh group. + MeshGroup mesh_group; + mesh_group.AddMeshInstance({MeshIndex(1), 10, MakeMappings(1)}); + mesh_group.AddMeshInstance({MeshIndex(3), 30, MakeMappings(3)}); + mesh_group.AddMeshInstance({MeshIndex(5), 50, MakeMappings(5)}); + mesh_group.AddMeshInstance({MeshIndex(1), 10, MakeMappings(1)}); + mesh_group.AddMeshInstance({MeshIndex(7), 70, MakeMappings(7)}); + mesh_group.AddMeshInstance({MeshIndex(1), 10, MakeMappings(1)}); + + // Remove mesh instances. + mesh_group.RemoveMeshInstances(MeshIndex(1)); + + // Check result. + ASSERT_EQ(mesh_group.NumMeshInstances(), 3); + const MeshGroup::MeshInstance mi0 = mesh_group.GetMeshInstance(0); + const MeshGroup::MeshInstance mi1 = mesh_group.GetMeshInstance(1); + const MeshGroup::MeshInstance mi2 = mesh_group.GetMeshInstance(2); + ASSERT_EQ(mi0.mesh_index, MeshIndex(3)); + ASSERT_EQ(mi1.mesh_index, MeshIndex(5)); + ASSERT_EQ(mi2.mesh_index, MeshIndex(7)); + ASSERT_EQ(mi0.material_index, 30); + ASSERT_EQ(mi1.material_index, 50); + ASSERT_EQ(mi2.material_index, 70); + ASSERT_EQ(mi0.materials_variants_mappings, MakeMappings(3)); + ASSERT_EQ(mi1.materials_variants_mappings, MakeMappings(5)); + ASSERT_EQ(mi2.materials_variants_mappings, MakeMappings(7)); +} + +TEST(MeshGroupTest, TestCopy) { + // Tests that a mesh group can be copied. + + // Create test mesh group. + MeshGroup mesh_group; + mesh_group.SetName("Mesh-1-3-5-7"); + mesh_group.AddMeshInstance({MeshIndex(1), 10, MakeMappings(1)}); + mesh_group.AddMeshInstance({MeshIndex(3), 30, MakeMappings(3)}); + mesh_group.AddMeshInstance({MeshIndex(5), 50, MakeMappings(5)}); + mesh_group.AddMeshInstance({MeshIndex(7), 70, MakeMappings(7)}); + + // Verify source MeshGroup. + ASSERT_EQ(mesh_group.GetName(), "Mesh-1-3-5-7"); + ASSERT_EQ(mesh_group.NumMeshInstances(), 4); + const MeshGroup::MeshInstance mi0 = mesh_group.GetMeshInstance(0); + const MeshGroup::MeshInstance mi1 = mesh_group.GetMeshInstance(1); + const MeshGroup::MeshInstance mi2 = mesh_group.GetMeshInstance(2); + const MeshGroup::MeshInstance mi3 = mesh_group.GetMeshInstance(3); + ASSERT_EQ(mi0.mesh_index, MeshIndex(1)); + ASSERT_EQ(mi1.mesh_index, MeshIndex(3)); + ASSERT_EQ(mi2.mesh_index, MeshIndex(5)); + ASSERT_EQ(mi3.mesh_index, MeshIndex(7)); + ASSERT_EQ(mi0.material_index, 10); + ASSERT_EQ(mi1.material_index, 30); + ASSERT_EQ(mi2.material_index, 50); + ASSERT_EQ(mi3.material_index, 70); + ASSERT_EQ(mi0.materials_variants_mappings, MakeMappings(1)); + ASSERT_EQ(mi1.materials_variants_mappings, MakeMappings(3)); + ASSERT_EQ(mi2.materials_variants_mappings, MakeMappings(5)); + ASSERT_EQ(mi3.materials_variants_mappings, MakeMappings(7)); + + MeshGroup copy; + copy.Copy(mesh_group); + + // Verify that Copy worked. + ASSERT_EQ(mesh_group.GetName(), copy.GetName()); + ASSERT_EQ(mesh_group.NumMeshInstances(), copy.NumMeshInstances()); + ASSERT_EQ(mesh_group.GetMeshInstance(0), copy.GetMeshInstance(0)); + ASSERT_EQ(mesh_group.GetMeshInstance(1), copy.GetMeshInstance(1)); + ASSERT_EQ(mesh_group.GetMeshInstance(2), copy.GetMeshInstance(2)); + ASSERT_EQ(mesh_group.GetMeshInstance(3), copy.GetMeshInstance(3)); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/contrib/draco/src/draco/scene/scene.cc b/contrib/draco/src/draco/scene/scene.cc new file mode 100644 index 000000000..9ad574835 --- /dev/null +++ b/contrib/draco/src/draco/scene/scene.cc @@ -0,0 +1,174 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/scene.h" + +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/macros.h" +#include "draco/scene/scene_indices.h" + +namespace draco { + +void Scene::Copy(const Scene &s) { + meshes_.resize(s.meshes_.size()); + for (MeshIndex i(0); i < meshes_.size(); ++i) { + meshes_[i] = std::unique_ptr(new Mesh()); + meshes_[i]->Copy(*s.meshes_[i]); + } + + mesh_groups_.resize(s.mesh_groups_.size()); + for (MeshGroupIndex i(0); i < mesh_groups_.size(); ++i) { + mesh_groups_[i] = std::unique_ptr(new MeshGroup()); + mesh_groups_[i]->Copy(*s.mesh_groups_[i]); + } + + nodes_.resize(s.nodes_.size()); + for (SceneNodeIndex i(0); i < nodes_.size(); ++i) { + nodes_[i] = std::unique_ptr(new SceneNode()); + nodes_[i]->Copy(*s.nodes_[i]); + } + + root_node_indices_ = s.root_node_indices_; + + animations_.resize(s.animations_.size()); + for (AnimationIndex i(0); i < animations_.size(); ++i) { + animations_[i] = std::unique_ptr(new Animation()); + animations_[i]->Copy(*s.animations_[i]); + } + + skins_.resize(s.skins_.size()); + for (SkinIndex i(0); i < skins_.size(); ++i) { + skins_[i] = std::unique_ptr(new Skin()); + skins_[i]->Copy(*s.skins_[i]); + } + + lights_.resize(s.lights_.size()); + for (LightIndex i(0); i < lights_.size(); ++i) { + lights_[i] = std::unique_ptr(new Light()); + lights_[i]->Copy(*s.lights_[i]); + } + + instance_arrays_.resize(s.instance_arrays_.size()); + for (InstanceArrayIndex i(0); i < instance_arrays_.size(); ++i) { + instance_arrays_[i] = std::unique_ptr(new InstanceArray()); + instance_arrays_[i]->Copy(*s.instance_arrays_[i]); + } + + material_library_.Copy(s.material_library_); + +#ifdef DRACO_TRANSCODER_SUPPORTED + // Copy non-material textures. + non_material_texture_library_.Copy(s.non_material_texture_library_); + + // Update pointers to non-material textures in mesh feature ID sets of all + // scene meshes. + if (non_material_texture_library_.NumTextures() != 0) { + const auto texture_to_index_map = + s.non_material_texture_library_.ComputeTextureToIndexMap(); + for (MeshIndex i(0); i < NumMeshes(); ++i) { + for (MeshFeaturesIndex j(0); j < GetMesh(i).NumMeshFeatures(); ++j) { + Mesh::UpdateMeshFeaturesTexturePointer(texture_to_index_map, + &non_material_texture_library_, + &GetMesh(i).GetMeshFeatures(j)); + } + } + } +#endif // DRACO_TRANSCODER_SUPPORTED + + // Copy structural metadata. + structural_metadata_.Copy(s.structural_metadata_); +} + +Status Scene::RemoveMesh(MeshIndex index) { + // Remove base mesh at |index| from |meshes_| and corresponding material index + // from |mesh_material_indices_|. + const int new_num_meshes = meshes_.size() - 1; + for (MeshIndex i(index); i < new_num_meshes; i++) { + meshes_[i] = std::move(meshes_[i + 1]); + } + meshes_.resize(new_num_meshes); + + // Remove references to removed base mesh and corresponding materials from + // mesh groups, and update references to remaining base meshes in mesh groups. + for (MeshGroupIndex mgi(0); mgi < NumMeshGroups(); ++mgi) { + MeshGroup *const mesh_group = GetMeshGroup(mgi); + if (!mesh_group) { + return Status(Status::DRACO_ERROR, "MeshGroup is null."); + } + mesh_group->RemoveMeshInstances(index); + for (int i = 0; i < mesh_group->NumMeshInstances(); ++i) { + MeshGroup::MeshInstance &mesh_instance = mesh_group->GetMeshInstance(i); + if (mesh_instance.mesh_index > index && + mesh_instance.mesh_index != kInvalidMeshIndex) { + mesh_instance.mesh_index--; + } + } + } + return OkStatus(); +} + +Status Scene::RemoveMeshGroup(MeshGroupIndex index) { + // Remove mesh group at |index| from |mesh_groups_| vector. + const int new_num_mesh_groups = mesh_groups_.size() - 1; + for (MeshGroupIndex i(index); i < new_num_mesh_groups; i++) { + mesh_groups_[i] = std::move(mesh_groups_[i + 1]); + } + mesh_groups_.resize(new_num_mesh_groups); + + // Invalidate references to removed mesh group in scene nodes, and update + // references to remaining mesh groups in scene nodes. + for (SceneNodeIndex sni(0); sni < NumNodes(); ++sni) { + SceneNode *node = GetNode(sni); + if (!node) { + return Status(Status::DRACO_ERROR, "Node is null."); + } + const MeshGroupIndex mgi = node->GetMeshGroupIndex(); + if (mgi == index) { + // TODO(vytyaz): Remove the node if possible, e.g., when node has no + // geometry, no child nodes, no skins, no lights, and no mesh group + // instance arrays. + node->SetMeshGroupIndex(kInvalidMeshGroupIndex); + } else if (mgi > index && mgi != kInvalidMeshGroupIndex) { + node->SetMeshGroupIndex(mgi - 1); + } + } + return OkStatus(); +} + +Status Scene::RemoveMaterial(int index) { + if (index < 0 || index >= material_library_.NumMaterials()) { + return Status(Status::DRACO_ERROR, "Material index is out of range."); + } + material_library_.RemoveMaterial(index); + + // Update material indices of mesh instances. + for (MeshGroupIndex mgi(0); mgi < NumMeshGroups(); ++mgi) { + MeshGroup *const mesh_group = GetMeshGroup(mgi); + for (int i = 0; i < mesh_group->NumMeshInstances(); i++) { + MeshGroup::MeshInstance &mesh_instance = mesh_group->GetMeshInstance(i); + if (mesh_instance.material_index > index) { + mesh_instance.material_index--; + } else if (mesh_instance.material_index == index) { + return Status(Status::DRACO_ERROR, "Removed material has references."); + } + } + } + return OkStatus(); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/scene/scene.h b/contrib/draco/src/draco/scene/scene.h new file mode 100644 index 000000000..3c76ead7a --- /dev/null +++ b/contrib/draco/src/draco/scene/scene.h @@ -0,0 +1,258 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_SCENE_SCENE_H_ +#define DRACO_SCENE_SCENE_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +#include "draco/animation/animation.h" +#include "draco/animation/skin.h" +#include "draco/mesh/mesh.h" +#include "draco/metadata/structural_metadata.h" +#include "draco/scene/instance_array.h" +#include "draco/scene/light.h" +#include "draco/scene/mesh_group.h" +#include "draco/scene/scene_indices.h" +#include "draco/scene/scene_node.h" + +namespace draco { + +// Class used to hold all of the geometry to create a scene. A scene is +// comprised of one or more meshes, one or more scene nodes, one or more +// mesh groups, and a material library. The meshes are defined in their +// local space. A mesh group is a list of meshes. The scene nodes create +// a scene hierarchy to transform meshes in their local space into scene space. +// The material library contains all of the materials and textures used by the +// meshes in this scene. +class Scene { + public: + Scene() {} + + void Copy(const Scene &s); + + // Adds a Draco |mesh| to the scene. Returns the index to the stored mesh or + // |kInvalidMeshIndex| if the mesh is a nullptr. + MeshIndex AddMesh(std::unique_ptr mesh) { + if (mesh == nullptr) { + return kInvalidMeshIndex; + } + meshes_.push_back(std::move(mesh)); + return MeshIndex(meshes_.size() - 1); + } + + // Removes base mesh and corresponding material at |index|, removes references + // to removed base mesh and corresponding materials from mesh groups, and + // updates references to remaining base meshes in mesh groups. + Status RemoveMesh(MeshIndex index); + + // Returns the number of meshes in a scene before instancing is applied. + int NumMeshes() const { return meshes_.size(); } + + // Returns a mesh in the scene before instancing is applied. The mesh + // coordinates are local to the mesh. + Mesh &GetMesh(MeshIndex index) { return *meshes_[index]; } + const Mesh &GetMesh(MeshIndex index) const { return *meshes_[index]; } + + // Creates a mesh group and returns the index to the mesh group. + MeshGroupIndex AddMeshGroup() { + std::unique_ptr mesh(new MeshGroup()); + mesh_groups_.push_back(std::move(mesh)); + return MeshGroupIndex(mesh_groups_.size() - 1); + } + + // Removes mesh group at |index|, invalidates references to removed mesh group + // in scene nodes, and updates references to remaining mesh groups in scene + // nodes. + Status RemoveMeshGroup(MeshGroupIndex index); + + // Removes unused material at |index| and updates references to materials at + // indices greater than |index|. Returns error status when |index| is out of + // valid range and when material at |index| is used in the scene. + Status RemoveMaterial(int index); + + // Returns the number of mesh groups in a scene. + int NumMeshGroups() const { return mesh_groups_.size(); } + + // Returns a mesh group in the scene. + MeshGroup *GetMeshGroup(MeshGroupIndex index) { + return mesh_groups_[index].get(); + } + const MeshGroup *GetMeshGroup(MeshGroupIndex index) const { + return mesh_groups_[index].get(); + } + + // Creates a scene node and returns the index to the node. + SceneNodeIndex AddNode() { + std::unique_ptr node(new SceneNode()); + nodes_.push_back(std::move(node)); + return SceneNodeIndex(nodes_.size() - 1); + } + + // Returns the number of nodes in a scene. + int NumNodes() const { return nodes_.size(); } + + // Returns a node in the scene. + SceneNode *GetNode(SceneNodeIndex index) { return nodes_[index].get(); } + const SceneNode *GetNode(SceneNodeIndex index) const { + return nodes_[index].get(); + } + + // Either allocates new nodes or removes existing nodes that are beyond + // |num_nodes|. + void ResizeNodes(int num_nodes) { + const size_t old_num_nodes = nodes_.size(); + nodes_.resize(num_nodes); + for (SceneNodeIndex i(old_num_nodes); i < num_nodes; ++i) { + nodes_[i].reset(new SceneNode()); + } + } + + // Returns the number of root node indices in a scene. + int NumRootNodes() const { return root_node_indices_.size(); } + SceneNodeIndex GetRootNodeIndex(int i) const { return root_node_indices_[i]; } + const std::vector &GetRootNodeIndices() const { + return root_node_indices_; + } + void AddRootNodeIndex(SceneNodeIndex index) { + root_node_indices_.push_back(index); + } + void SetRootNodeIndex(int i, SceneNodeIndex index) { + root_node_indices_[i] = index; + } + void RemoveAllRootNodeIndices() { root_node_indices_.clear(); } + + const MaterialLibrary &GetMaterialLibrary() const { + return material_library_; + } + MaterialLibrary &GetMaterialLibrary() { return material_library_; } + + // Library that contains non-material textures. + const TextureLibrary &GetNonMaterialTextureLibrary() const { + return non_material_texture_library_; + } + TextureLibrary &GetNonMaterialTextureLibrary() { + return non_material_texture_library_; + } + + // Structural metadata. + const StructuralMetadata &GetStructuralMetadata() const { + return structural_metadata_; + } + StructuralMetadata &GetStructuralMetadata() { return structural_metadata_; } + + // Creates an animation and returns the index to the animation. + AnimationIndex AddAnimation() { + std::unique_ptr animation(new Animation()); + animations_.push_back(std::move(animation)); + return AnimationIndex(animations_.size() - 1); + } + + // Returns the number of animations in a scene. + int NumAnimations() const { return animations_.size(); } + + // Returns an animation in the scene. + Animation *GetAnimation(AnimationIndex index) { + return animations_[index].get(); + } + const Animation *GetAnimation(AnimationIndex index) const { + return animations_[index].get(); + } + + // Creates a skin and returns the index to the skin. + SkinIndex AddSkin() { + std::unique_ptr skin(new Skin()); + skins_.push_back(std::move(skin)); + return SkinIndex(skins_.size() - 1); + } + + // Returns the number of skins in a scene. + int NumSkins() const { return skins_.size(); } + + // Returns a skin in the scene. + Skin *GetSkin(SkinIndex index) { return skins_[index].get(); } + const Skin *GetSkin(SkinIndex index) const { return skins_[index].get(); } + + // Creates a light and returns the index to the light. + LightIndex AddLight() { + std::unique_ptr light(new Light()); + lights_.push_back(std::move(light)); + return LightIndex(lights_.size() - 1); + } + + // Returns the number of lights in a scene. + int NumLights() const { return lights_.size(); } + + // Returns a light in the scene. + Light *GetLight(LightIndex index) { return lights_[index].get(); } + const Light *GetLight(LightIndex index) const { return lights_[index].get(); } + + // Creates a mesh group instance array and returns the index to it. This array + // is used for storing the attributes of the EXT_mesh_gpu_instancing glTF + // extension. + InstanceArrayIndex AddInstanceArray() { + std::unique_ptr array(new InstanceArray()); + instance_arrays_.push_back(std::move(array)); + return InstanceArrayIndex(instance_arrays_.size() - 1); + } + + // Returns the number of mesh group instance arrays in a scene. + int NumInstanceArrays() const { return instance_arrays_.size(); } + + // Returns a mesh group instance array in the scene. + InstanceArray *GetInstanceArray(InstanceArrayIndex index) { + return instance_arrays_[index].get(); + } + const InstanceArray *GetInstanceArray(InstanceArrayIndex index) const { + return instance_arrays_[index].get(); + } + + private: + IndexTypeVector> meshes_; + IndexTypeVector> mesh_groups_; + IndexTypeVector> nodes_; + std::vector root_node_indices_; + IndexTypeVector> animations_; + IndexTypeVector> skins_; + + // The lights will be written to the output scene but not used for internal + // rendering in Draco, e.g, while computing distortion metric. + IndexTypeVector> lights_; + + // The mesh group instance array information will be written to the output + // scene but not processed by Draco simplifier modules. + IndexTypeVector> + instance_arrays_; + + // Materials used by this scene. + MaterialLibrary material_library_; + + // Texture library for storing non-material textures used by this scene, e.g., + // textures containing mesh feature IDs of EXT_mesh_features glTF extension. + // Note that scene meshes contain pointers to non-material textures. It is + // responsibility of class user to update these pointers when updating the + // textures. See Scene::Copy() for example. + TextureLibrary non_material_texture_library_; + + // Structural metadata defined by the EXT_structural_metadata glTF extension. + StructuralMetadata structural_metadata_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_SCENE_SCENE_H_ diff --git a/contrib/draco/src/draco/scene/scene_are_equivalent.cc b/contrib/draco/src/draco/scene/scene_are_equivalent.cc new file mode 100644 index 000000000..7d0663e08 --- /dev/null +++ b/contrib/draco/src/draco/scene/scene_are_equivalent.cc @@ -0,0 +1,109 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/scene_are_equivalent.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/mesh/mesh_are_equivalent.h" + +namespace draco { + +bool SceneAreEquivalent::operator()(const Scene &scene0, const Scene &scene1) { + // Check scene component sizes. + if (scene0.NumAnimations() != scene1.NumAnimations()) { + return false; + } + if (scene0.NumMeshGroups() != scene1.NumMeshGroups()) { + return false; + } + if (scene0.NumSkins() != scene1.NumSkins()) { + return false; + } + + // Check equivalence of each mesh. + if (scene0.NumMeshes() != scene1.NumMeshes()) { + return false; + } + for (MeshIndex i(0); i < scene0.NumMeshes(); i++) { + if (!AreEquivalent(scene0.GetMesh(i), scene1.GetMesh(i))) { + return false; + } + } + + // Check eqiuvalence of each node. + if (scene0.NumNodes() != scene1.NumNodes()) { + return false; + } + for (SceneNodeIndex i(0); i < scene0.NumNodes(); i++) { + if (!AreEquivalent(*scene0.GetNode(i), *scene1.GetNode(i))) { + return false; + } + } + + // Check non-material texture library sizes. + if (scene0.GetNonMaterialTextureLibrary().NumTextures() != + scene1.GetNonMaterialTextureLibrary().NumTextures()) { + return false; + } + + // TODO(vytyaz): Check remaining scene properties like animations and skins. + return true; +} + +bool SceneAreEquivalent::AreEquivalent(const Mesh &mesh0, const Mesh &mesh1) { + MeshAreEquivalent eq; + return eq(mesh0, mesh1); +} + +bool SceneAreEquivalent::AreEquivalent(const SceneNode &node0, + const SceneNode &node1) { + // Check equivalence of node indices. + if (node0.GetMeshGroupIndex() != node1.GetMeshGroupIndex()) { + return false; + } + if (node0.GetSkinIndex() != node1.GetSkinIndex()) { + return false; + } + + // Check equivalence of node transformations. + if (node0.GetTrsMatrix().ComputeTransformationMatrix() != + node1.GetTrsMatrix().ComputeTransformationMatrix()) { + return false; + } + + // Check equivalence of node children. + if (node0.NumChildren() != node1.NumChildren()) { + return false; + } + for (int i = 0; i < node0.NumChildren(); i++) { + if (node0.Child(i) != node1.Child(i)) { + return false; + } + } + + // Check equivalence of node parents. + if (node0.NumParents() != node1.NumParents()) { + return false; + } + for (int i = 0; i < node0.NumParents(); i++) { + if (node0.Parent(i) != node1.Parent(i)) { + return false; + } + } + return true; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/scene/scene_are_equivalent.h b/contrib/draco/src/draco/scene/scene_are_equivalent.h new file mode 100644 index 000000000..b309c0338 --- /dev/null +++ b/contrib/draco/src/draco/scene/scene_are_equivalent.h @@ -0,0 +1,42 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef DRACO_SCENE_SCENE_ARE_EQUIVALENT_H_ +#define DRACO_SCENE_SCENE_ARE_EQUIVALENT_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/scene/scene.h" + +namespace draco { + +// A functor to compare two scenes for equivalency up to permutation of mesh +// vertices. +class SceneAreEquivalent { + public: + // Returns true if both scenes are equivalent up to permutation of + // the internal order of mesh vertices. This includes all attributes. + bool operator()(const Scene &scene0, const Scene &scene1); + + private: + static bool AreEquivalent(const Mesh &mesh0, const Mesh &mesh1); + static bool AreEquivalent(const SceneNode &node0, const SceneNode &node1); +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_SCENE_SCENE_ARE_EQUIVALENT_H_ diff --git a/contrib/draco/src/draco/scene/scene_are_equivalent_test.cc b/contrib/draco/src/draco/scene/scene_are_equivalent_test.cc new file mode 100644 index 000000000..3a9edc6c3 --- /dev/null +++ b/contrib/draco/src/draco/scene/scene_are_equivalent_test.cc @@ -0,0 +1,86 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/scene_are_equivalent.h" + +#include +#include +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/io/scene_io.h" +#include "draco/scene/scene.h" + +namespace draco { + +#ifdef DRACO_TRANSCODER_SUPPORTED +class SceneAreEquivalentTest : public ::testing::Test {}; + +TEST_F(SceneAreEquivalentTest, TestOnIndenticalScenes) { + const std::string file_name = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + const std::unique_ptr scene(ReadSceneFromTestFile(file_name)); + ASSERT_NE(scene, nullptr) << "Failed to load test scene: " << file_name; + + // Add mesh feature ID set to a scene mesh. + std::unique_ptr mesh_features(new MeshFeatures()); + scene->GetMesh(MeshIndex(2)).AddMeshFeatures(std::move(mesh_features)); + + SceneAreEquivalent equiv; + ASSERT_TRUE(equiv(*scene, *scene)); +} + +TEST_F(SceneAreEquivalentTest, TestOnDifferentScenes) { + const std::string file_name0 = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + const std::string file_name1 = "Lantern/glTF/Lantern.gltf"; + const std::unique_ptr scene0(ReadSceneFromTestFile(file_name0)); + const std::unique_ptr scene1(ReadSceneFromTestFile(file_name1)); + ASSERT_NE(scene0, nullptr) << "Failed to load test scene: " << file_name0; + ASSERT_NE(scene1, nullptr) << "Failed to load test scene: " << file_name1; + SceneAreEquivalent equiv; + ASSERT_FALSE(equiv(*scene0, *scene1)); +} + +TEST_F(SceneAreEquivalentTest, TestMeshFeatures) { + const std::string file_name = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + const std::unique_ptr scene0(ReadSceneFromTestFile(file_name)); + const std::unique_ptr scene1(ReadSceneFromTestFile(file_name)); + ASSERT_NE(scene0, nullptr); + ASSERT_NE(scene1, nullptr); + + // Add identical mesh feature ID sets to mesh at index 0. + Mesh &mesh0 = scene0->GetMesh(MeshIndex(0)); + Mesh &mesh1 = scene1->GetMesh(MeshIndex(0)); + mesh0.AddMeshFeatures(std::unique_ptr(new MeshFeatures())); + mesh1.AddMeshFeatures(std::unique_ptr(new MeshFeatures())); + + // Empty feature sets should match. + SceneAreEquivalent equiv; + ASSERT_TRUE(equiv(*scene0, *scene1)); + + // Make mesh features different and check that the meshes are not equivalent. + mesh0.GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(5); + mesh1.GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(6); + ASSERT_FALSE(equiv(*scene0, *scene1)); + + // Make mesh features identical and check that the meshes are equivalent. + mesh0.GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(1); + mesh1.GetMeshFeatures(MeshFeaturesIndex(0)).SetFeatureCount(1); + ASSERT_TRUE(equiv(*scene0, *scene1)); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace draco diff --git a/contrib/draco/src/draco/scene/scene_indices.h b/contrib/draco/src/draco/scene/scene_indices.h new file mode 100644 index 000000000..7b57e3e4a --- /dev/null +++ b/contrib/draco/src/draco/scene/scene_indices.h @@ -0,0 +1,72 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifdef DRACO_TRANSCODER_SUPPORTED +#ifndef DRACO_SCENE_SCENE_INDICES_H_ +#define DRACO_SCENE_SCENE_INDICES_H_ + +#include + +#include + +#include "draco/core/draco_index_type.h" + +namespace draco { + +// Index of a mesh in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, MeshIndex) + +// Index of a mesh instance in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, MeshInstanceIndex) + +// Index of a mesh group in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, MeshGroupIndex) + +// Index of a node in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, SceneNodeIndex) + +// Index of an animation in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, AnimationIndex) + +// Index of a skin in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, SkinIndex) + +// Index of a light in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, LightIndex) + +// Index of a mesh group GPU instancing in a scene. +DEFINE_NEW_DRACO_INDEX_TYPE(uint32_t, InstanceArrayIndex) + +// Constants denoting invalid indices. +static constexpr MeshIndex kInvalidMeshIndex( + std::numeric_limits::max()); +static constexpr MeshInstanceIndex kInvalidMeshInstanceIndex( + std::numeric_limits::max()); +static constexpr MeshGroupIndex kInvalidMeshGroupIndex( + std::numeric_limits::max()); +static constexpr SceneNodeIndex kInvalidSceneNodeIndex( + std::numeric_limits::max()); +static constexpr AnimationIndex kInvalidAnimationIndex( + std::numeric_limits::max()); +static constexpr SkinIndex kInvalidSkinIndex( + std::numeric_limits::max()); +static constexpr LightIndex kInvalidLightIndex( + std::numeric_limits::max()); +static constexpr InstanceArrayIndex kInvalidInstanceArrayIndex( + std::numeric_limits::max()); + +} // namespace draco + +#endif // DRACO_SCENE_SCENE_INDICES_H_ +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/scene/scene_node.h b/contrib/draco/src/draco/scene/scene_node.h new file mode 100644 index 000000000..6cfe04e2e --- /dev/null +++ b/contrib/draco/src/draco/scene/scene_node.h @@ -0,0 +1,105 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifdef DRACO_TRANSCODER_SUPPORTED +#ifndef DRACO_SCENE_SCENE_NODE_H_ +#define DRACO_SCENE_SCENE_NODE_H_ + +#include "draco/scene/scene_indices.h" +#include "draco/scene/trs_matrix.h" + +namespace draco { + +// This class is used to create a scene hierarchy from meshes in their local +// space transformed into scene space. +class SceneNode { + public: + SceneNode() + : mesh_group_index_(-1), + skin_index_(-1), + light_index_(-1), + instance_array_index_(-1) {} + + void Copy(const SceneNode &sn) { + name_ = sn.name_; + trs_matrix_.Copy(sn.trs_matrix_); + mesh_group_index_ = sn.mesh_group_index_; + skin_index_ = sn.skin_index_; + parents_ = sn.parents_; + children_ = sn.children_; + light_index_ = sn.light_index_; + instance_array_index_ = sn.instance_array_index_; + } + + // Sets a name. + void SetName(const std::string &name) { name_ = name; } + + // Returns the name. + const std::string &GetName() const { return name_; } + + // Set transformation from mesh local space to scene space. + void SetTrsMatrix(const TrsMatrix &trsm) { trs_matrix_.Copy(trsm); } + const TrsMatrix &GetTrsMatrix() const { return trs_matrix_; } + + // Set the index to the mesh group in the scene. + void SetMeshGroupIndex(MeshGroupIndex index) { mesh_group_index_ = index; } + MeshGroupIndex GetMeshGroupIndex() const { return mesh_group_index_; } + + // Set the index to the skin in the scene. + void SetSkinIndex(SkinIndex index) { skin_index_ = index; } + SkinIndex GetSkinIndex() const { return skin_index_; } + + // Set the index to the light in the scene. + void SetLightIndex(LightIndex index) { light_index_ = index; } + LightIndex GetLightIndex() const { return light_index_; } + + // Set the index to the mesh group instance array in the scene. Note that + // according to EXT_mesh_gpu_instancing glTF extension there is no defined + // behavior for a node with instance array and without a mesh group. + void SetInstanceArrayIndex(InstanceArrayIndex index) { + instance_array_index_ = index; + } + InstanceArrayIndex GetInstanceArrayIndex() const { + return instance_array_index_; + } + + // Functions to set and get zero or more parent nodes of this node. + SceneNodeIndex Parent(int index) const { return parents_[index]; } + const std::vector &Parents() const { return parents_; } + void AddParentIndex(SceneNodeIndex index) { parents_.push_back(index); } + int NumParents() const { return parents_.size(); } + void RemoveAllParents() { parents_.clear(); } + + // Functions to set and get zero or more child nodes of this node. + SceneNodeIndex Child(int index) const { return children_[index]; } + const std::vector &Children() const { return children_; } + void AddChildIndex(SceneNodeIndex index) { children_.push_back(index); } + int NumChildren() const { return children_.size(); } + void RemoveAllChildren() { children_.clear(); } + + private: + std::string name_; + TrsMatrix trs_matrix_; + draco::MeshGroupIndex mesh_group_index_; + draco::SkinIndex skin_index_; + std::vector parents_; + std::vector children_; + LightIndex light_index_; + InstanceArrayIndex instance_array_index_; +}; + +} // namespace draco + +#endif // DRACO_SCENE_SCENE_NODE_H_ +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/scene/scene_test.cc b/contrib/draco/src/draco/scene/scene_test.cc new file mode 100644 index 000000000..d639614c7 --- /dev/null +++ b/contrib/draco/src/draco/scene/scene_test.cc @@ -0,0 +1,295 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/scene.h" + +#include +#include +#include + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/core/status.h" +#include "draco/mesh/mesh_are_equivalent.h" +#include "draco/scene/scene_are_equivalent.h" +#include "draco/scene/scene_indices.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +// Helper method for adding mesh group GPU instancing to the milk truck scene. +draco::Status AddGpuInstancingToMilkTruck(draco::Scene *scene) { + // Create an instance and set its transformation TRS vectors. + draco::InstanceArray::Instance instance_0; + instance_0.trs.SetTranslation(Eigen::Vector3d(1.0, 2.0, 3.0)); + instance_0.trs.SetRotation(Eigen::Quaterniond(4.0, 5.0, 6.0, 7.0)); + instance_0.trs.SetScale(Eigen::Vector3d(8.0, 9.0, 10.0)); + + // Create another instance. + draco::InstanceArray::Instance instance_1; + instance_1.trs.SetTranslation(Eigen::Vector3d(1.1, 2.1, 3.1)); + instance_1.trs.SetRotation(Eigen::Quaterniond(4.1, 5.1, 6.1, 7.1)); + instance_1.trs.SetScale(Eigen::Vector3d(8.1, 9.1, 10.1)); + + // Add an empty GPU instancing object to the scene. + const draco::InstanceArrayIndex index = scene->AddInstanceArray(); + draco::InstanceArray *gpu_instancing = scene->GetInstanceArray(index); + + // Add two instances to the GPU instancing object stored in the scene. + DRACO_RETURN_IF_ERROR(gpu_instancing->AddInstance(instance_0)); + DRACO_RETURN_IF_ERROR(gpu_instancing->AddInstance(instance_1)); + + // Assign the GPU instancing object to two mesh groups in two scene nodes. + scene->GetNode(draco::SceneNodeIndex(2))->SetInstanceArrayIndex(index); + scene->GetNode(draco::SceneNodeIndex(4))->SetInstanceArrayIndex(index); + + return draco::OkStatus(); +} + +TEST(SceneTest, TestCopy) { + // Test copying of scene data. + auto src_scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(src_scene, nullptr); + + // Add GPU instancing to the scene for testing. + DRACO_ASSERT_OK(AddGpuInstancingToMilkTruck(src_scene.get())); + ASSERT_EQ(src_scene->NumInstanceArrays(), 1); + ASSERT_EQ(src_scene->NumNodes(), 5); + ASSERT_EQ( + src_scene->GetNode(draco::SceneNodeIndex(0))->GetInstanceArrayIndex(), + draco::kInvalidInstanceArrayIndex); + ASSERT_EQ( + src_scene->GetNode(draco::SceneNodeIndex(1))->GetInstanceArrayIndex(), + draco::kInvalidInstanceArrayIndex); + ASSERT_EQ( + src_scene->GetNode(draco::SceneNodeIndex(2))->GetInstanceArrayIndex(), + draco::InstanceArrayIndex(0)); + ASSERT_EQ( + src_scene->GetNode(draco::SceneNodeIndex(3))->GetInstanceArrayIndex(), + draco::kInvalidInstanceArrayIndex); + ASSERT_EQ( + src_scene->GetNode(draco::SceneNodeIndex(4))->GetInstanceArrayIndex(), + draco::InstanceArrayIndex(0)); + + // Make a copy of the scene. + draco::Scene dst_scene; + dst_scene.Copy(*src_scene); + + ASSERT_EQ(src_scene->NumMeshes(), dst_scene.NumMeshes()); + ASSERT_EQ(src_scene->NumMeshGroups(), dst_scene.NumMeshGroups()); + ASSERT_EQ(src_scene->NumNodes(), dst_scene.NumNodes()); + ASSERT_EQ(src_scene->NumAnimations(), dst_scene.NumAnimations()); + ASSERT_EQ(src_scene->NumSkins(), dst_scene.NumSkins()); + ASSERT_EQ(src_scene->NumLights(), dst_scene.NumLights()); + ASSERT_EQ(src_scene->NumInstanceArrays(), dst_scene.NumInstanceArrays()); + + for (draco::MeshIndex i(0); i < src_scene->NumMeshes(); ++i) { + draco::MeshAreEquivalent eq; + ASSERT_TRUE(eq(src_scene->GetMesh(i), dst_scene.GetMesh(i))); + } + for (draco::MeshGroupIndex i(0); i < src_scene->NumMeshGroups(); ++i) { + ASSERT_EQ(src_scene->GetMeshGroup(i)->NumMeshInstances(), + dst_scene.GetMeshGroup(i)->NumMeshInstances()); + for (int j = 0; j < src_scene->GetMeshGroup(i)->NumMeshInstances(); ++j) { + ASSERT_EQ(src_scene->GetMeshGroup(i)->GetMeshInstance(j).mesh_index, + dst_scene.GetMeshGroup(i)->GetMeshInstance(j).mesh_index); + ASSERT_EQ(src_scene->GetMeshGroup(i)->GetMeshInstance(j).material_index, + dst_scene.GetMeshGroup(i)->GetMeshInstance(j).material_index); + ASSERT_EQ(src_scene->GetMeshGroup(i) + ->GetMeshInstance(j) + .materials_variants_mappings.size(), + dst_scene.GetMeshGroup(i) + ->GetMeshInstance(j) + .materials_variants_mappings.size()); + } + } + for (draco::SceneNodeIndex i(0); i < src_scene->NumNodes(); ++i) { + ASSERT_EQ(src_scene->GetNode(i)->NumParents(), + dst_scene.GetNode(i)->NumParents()); + for (int j = 0; j < src_scene->GetNode(i)->NumParents(); ++j) { + ASSERT_EQ(src_scene->GetNode(i)->Parent(j), + dst_scene.GetNode(i)->Parent(j)); + } + ASSERT_EQ(src_scene->GetNode(i)->NumChildren(), + dst_scene.GetNode(i)->NumChildren()); + for (int j = 0; j < src_scene->GetNode(i)->NumChildren(); ++j) { + ASSERT_EQ(src_scene->GetNode(i)->Child(j), + dst_scene.GetNode(i)->Child(j)); + } + ASSERT_EQ(src_scene->GetNode(i)->GetMeshGroupIndex(), + dst_scene.GetNode(i)->GetMeshGroupIndex()); + ASSERT_EQ(src_scene->GetNode(i)->GetSkinIndex(), + dst_scene.GetNode(i)->GetSkinIndex()); + ASSERT_EQ(src_scene->GetNode(i)->GetLightIndex(), + dst_scene.GetNode(i)->GetLightIndex()); + ASSERT_EQ(src_scene->GetNode(i)->GetInstanceArrayIndex(), + dst_scene.GetNode(i)->GetInstanceArrayIndex()); + } +} + +TEST(SceneTest, TestRemoveMesh) { + // Test that a base mesh can be removed from scene. + auto src_scene_ptr = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(src_scene_ptr, nullptr); + const draco::Scene &src_scene = *src_scene_ptr; + + // Copy scene. + draco::Scene dst_scene; + dst_scene.Copy(src_scene); + ASSERT_EQ(dst_scene.NumMeshes(), 4); + draco::MeshAreEquivalent eq; + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(0)), + src_scene.GetMesh(draco::MeshIndex(0)))); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(1)), + src_scene.GetMesh(draco::MeshIndex(1)))); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(2)), + src_scene.GetMesh(draco::MeshIndex(2)))); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(3)), + src_scene.GetMesh(draco::MeshIndex(3)))); + + // Remove base mesh from scene. + dst_scene.RemoveMesh(draco::MeshIndex(2)); + ASSERT_EQ(dst_scene.NumMeshes(), 3); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(0)), + src_scene.GetMesh(draco::MeshIndex(0)))); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(1)), + src_scene.GetMesh(draco::MeshIndex(1)))); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(2)), + src_scene.GetMesh(draco::MeshIndex(3)))); + + // Remove another base mesh from scene. + dst_scene.RemoveMesh(draco::MeshIndex(1)); + ASSERT_EQ(dst_scene.NumMeshes(), 2); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(0)), + src_scene.GetMesh(draco::MeshIndex(0)))); + ASSERT_TRUE(eq(dst_scene.GetMesh(draco::MeshIndex(1)), + src_scene.GetMesh(draco::MeshIndex(3)))); +} + +TEST(SceneTest, TestRemoveMeshGroup) { + // Test that a mesh group can be removed from scene. + auto src_scene_ptr = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(src_scene_ptr, nullptr); + const draco::Scene &src_scene = *src_scene_ptr; + + // Copy scene. + draco::Scene dst_scene; + dst_scene.Copy(src_scene); + ASSERT_EQ(dst_scene.NumMeshGroups(), 2); + ASSERT_EQ(dst_scene.NumNodes(), 5); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(0))->GetMeshGroupIndex(), + draco::MeshGroupIndex(0)); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(2))->GetMeshGroupIndex(), + draco::MeshGroupIndex(1)); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(4))->GetMeshGroupIndex(), + draco::MeshGroupIndex(1)); + + // Remove mesh group from scene. + dst_scene.RemoveMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(dst_scene.NumMeshGroups(), 1); + ASSERT_EQ(dst_scene.NumNodes(), 5); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(0))->GetMeshGroupIndex(), + draco::kInvalidMeshGroupIndex); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(2))->GetMeshGroupIndex(), + draco::MeshGroupIndex(0)); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(4))->GetMeshGroupIndex(), + draco::MeshGroupIndex(0)); + + // Remove another mesh group from scene. + dst_scene.RemoveMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(dst_scene.NumMeshGroups(), 0); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(0))->GetMeshGroupIndex(), + draco::kInvalidMeshGroupIndex); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(2))->GetMeshGroupIndex(), + draco::kInvalidMeshGroupIndex); + ASSERT_EQ(dst_scene.GetNode(draco::SceneNodeIndex(4))->GetMeshGroupIndex(), + draco::kInvalidMeshGroupIndex); +} + +void CheckMeshMaterials(const draco::Scene &scene, + const std::vector &expected_material_indices) { + ASSERT_EQ(scene.NumMeshes(), expected_material_indices.size()); + std::vector scene_material_indices; + for (draco::MeshGroupIndex i(0); i < scene.NumMeshGroups(); i++) { + const auto mg = scene.GetMeshGroup(i); + for (int mi = 0; mi < mg->NumMeshInstances(); ++mi) { + scene_material_indices.push_back(mg->GetMeshInstance(mi).material_index); + } + } + ASSERT_EQ(scene_material_indices, expected_material_indices); +} + +TEST(SceneTest, TestRemoveMaterial) { + // Test that materials can be removed from a scene. + auto src_scene_ptr = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(src_scene_ptr, nullptr); + const draco::Scene &src_scene = *src_scene_ptr; + ASSERT_EQ(src_scene.GetMaterialLibrary().NumMaterials(), 4); + CheckMeshMaterials(src_scene, {0, 1, 2, 3}); + + // Copy scene. + draco::Scene dst_scene; + dst_scene.Copy(src_scene); + + // Check that referenced material cannot be removed from the scene. + ASSERT_FALSE(dst_scene.RemoveMaterial(2).ok()); + + // Copy scene again, since failed material removal corrupts the scene. + dst_scene.Copy(src_scene); + + // Remove base mesh from scene. Material at index 2 becomes unreferenced. + DRACO_ASSERT_OK(dst_scene.RemoveMesh(draco::MeshIndex(2))); + ASSERT_EQ(dst_scene.GetMaterialLibrary().NumMaterials(), 4); + CheckMeshMaterials(dst_scene, {0, 1, 3}); + + // Check that unreferenced material can be removed from the scene. + DRACO_ASSERT_OK(dst_scene.RemoveMaterial(2)); + ASSERT_EQ(dst_scene.GetMaterialLibrary().NumMaterials(), 3); + CheckMeshMaterials(dst_scene, {0, 1, 2}); + + // Check that material cannot be removed when material index is out of range. + ASSERT_FALSE(dst_scene.RemoveMaterial(-1).ok()); + ASSERT_FALSE(dst_scene.RemoveMaterial(3).ok()); +} + +TEST(SceneTest, TestCopyWithStructuralMetadata) { + // Tests copying of a scene with structural metadata. + auto scene_ptr = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene_ptr, nullptr); + draco::Scene &scene = *scene_ptr; + + // Add structural metadata to the scene. + draco::PropertyTable::Schema schema; + schema.json.SetString("Data"); + scene.GetStructuralMetadata().SetPropertyTableSchema(schema); + + // Copy the scene. + draco::Scene copy; + copy.Copy(scene); + + // Check that the structural metadata has been copied. + ASSERT_EQ( + copy.GetStructuralMetadata().GetPropertyTableSchema().json.GetString(), + "Data"); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/contrib/draco/src/draco/scene/scene_utils.cc b/contrib/draco/src/draco/scene/scene_utils.cc new file mode 100644 index 000000000..a7bf1dcb9 --- /dev/null +++ b/contrib/draco/src/draco/scene/scene_utils.cc @@ -0,0 +1,962 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "draco/scene/scene_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include +#include +#include +#include + +#include "draco/core/draco_index_type_vector.h" +#include "draco/core/hash_utils.h" +#include "draco/core/vector_d.h" +#include "draco/mesh/mesh_splitter.h" +#include "draco/mesh/mesh_utils.h" +#include "draco/scene/scene_indices.h" +#include "draco/texture/texture_utils.h" + +namespace draco { + +IndexTypeVector +SceneUtils::ComputeAllInstances(const Scene &scene) { + IndexTypeVector instances; + + // Traverse the scene assuming multiple root nodes. + const Eigen::Matrix4d transform = Eigen::Matrix4d::Identity(); + + struct Node { + const SceneNodeIndex scene_node_index; + Eigen::Matrix4d transform; + }; + std::vector nodes; + nodes.reserve(scene.NumRootNodes()); + for (int i = 0; i < scene.NumRootNodes(); ++i) { + nodes.push_back({scene.GetRootNodeIndex(i), transform}); + } + + while (!nodes.empty()) { + const Node node = nodes.back(); + nodes.pop_back(); + const SceneNode &scene_node = *scene.GetNode(node.scene_node_index); + const Eigen::Matrix4d combined_transform = + node.transform * + scene_node.GetTrsMatrix().ComputeTransformationMatrix(); + + // Create instances from node meshes. + const MeshGroupIndex mesh_group_index = scene_node.GetMeshGroupIndex(); + if (mesh_group_index != kInvalidMeshGroupIndex) { + const MeshGroup &mesh_group = *scene.GetMeshGroup(mesh_group_index); + for (int i = 0; i < mesh_group.NumMeshInstances(); i++) { + const MeshIndex mesh_index = mesh_group.GetMeshInstance(i).mesh_index; + if (mesh_index != kInvalidMeshIndex) { + instances.push_back( + {mesh_index, node.scene_node_index, i, combined_transform}); + } + } + } + + // Traverse children nodes. + for (int i = 0; i < scene_node.NumChildren(); i++) { + nodes.push_back({scene_node.Child(i), combined_transform}); + } + } + return instances; +} + +Eigen::Matrix4d SceneUtils::ComputeGlobalNodeTransform(const Scene &scene, + SceneNodeIndex index) { + Eigen::Matrix4d transform = Eigen::Matrix4d::Identity(); + while (index != kInvalidSceneNodeIndex) { + const SceneNode *const node = scene.GetNode(index); + transform = node->GetTrsMatrix().ComputeTransformationMatrix() * transform; + index = node->NumParents() == 1 ? node->Parent(0) : kInvalidSceneNodeIndex; + } + return transform; +} + +IndexTypeVector SceneUtils::NumMeshInstances( + const Scene &scene) { + const auto instances = ComputeAllInstances(scene); + IndexTypeVector num_mesh_instances(scene.NumMeshes(), 0); + for (MeshInstanceIndex i(0); i < instances.size(); i++) { + const MeshInstance &instance = instances[i]; + num_mesh_instances[instance.mesh_index]++; + } + return num_mesh_instances; +} + +int SceneUtils::GetMeshInstanceMaterialIndex(const Scene &scene, + const MeshInstance &instance) { + const auto *const node = scene.GetNode(instance.scene_node_index); + return scene.GetMeshGroup(node->GetMeshGroupIndex()) + ->GetMeshInstance(instance.mesh_group_mesh_index) + .material_index; +} + +int SceneUtils::NumFacesOnBaseMeshes(const Scene &scene) { + int num_faces = 0; + for (MeshIndex i(0); i < scene.NumMeshes(); ++i) { + num_faces += scene.GetMesh(i).num_faces(); + } + return num_faces; +} + +int SceneUtils::NumFacesOnInstancedMeshes(const Scene &scene) { + const auto instances = ComputeAllInstances(scene); + int num_faces = 0; + for (MeshInstanceIndex i(0); i < instances.size(); i++) { + const MeshInstance &instance = instances[i]; + num_faces += scene.GetMesh(instance.mesh_index).num_faces(); + } + return num_faces; +} + +int SceneUtils::NumPointsOnBaseMeshes(const Scene &scene) { + int num_points = 0; + for (MeshIndex i(0); i < scene.NumMeshes(); ++i) { + num_points += scene.GetMesh(i).num_points(); + } + return num_points; +} + +int SceneUtils::NumPointsOnInstancedMeshes(const Scene &scene) { + const auto instances = ComputeAllInstances(scene); + int num_points = 0; + for (MeshInstanceIndex i(0); i < instances.size(); i++) { + const MeshInstance &instance = instances[i]; + num_points += scene.GetMesh(instance.mesh_index).num_points(); + } + return num_points; +} + +int SceneUtils::NumAttEntriesOnBaseMeshes(const Scene &scene, + GeometryAttribute::Type att_type) { + int num_att_entries = 0; + for (MeshIndex i(0); i < scene.NumMeshes(); ++i) { + const Mesh &mesh = scene.GetMesh(i); + const PointAttribute *att = mesh.GetNamedAttribute(att_type); + if (att != nullptr) { + num_att_entries += att->size(); + } + } + return num_att_entries; +} + +int SceneUtils::NumAttEntriesOnInstancedMeshes( + const Scene &scene, GeometryAttribute::Type att_type) { + const auto instances = ComputeAllInstances(scene); + int num_att_entries = 0; + for (MeshInstanceIndex i(0); i < instances.size(); i++) { + const MeshInstance &instance = instances[i]; + const Mesh &mesh = scene.GetMesh(instance.mesh_index); + const PointAttribute *att = mesh.GetNamedAttribute(att_type); + if (att != nullptr) { + num_att_entries += att->size(); + } + } + return num_att_entries; +} + +BoundingBox SceneUtils::ComputeBoundingBox(const Scene &scene) { + // Compute bounding box that includes all scene mesh instances. + const auto instances = ComputeAllInstances(scene); + BoundingBox scene_bbox; + for (MeshInstanceIndex i(0); i < instances.size(); i++) { + const MeshInstance &instance = instances[i]; + const BoundingBox mesh_bbox = + ComputeMeshInstanceBoundingBox(scene, instance); + scene_bbox.Update(mesh_bbox); + } + return scene_bbox; +} + +BoundingBox SceneUtils::ComputeMeshInstanceBoundingBox( + const Scene &scene, const MeshInstance &instance) { + const Mesh &mesh = scene.GetMesh(instance.mesh_index); + BoundingBox mesh_bbox; + auto pc_att = mesh.GetNamedAttribute(GeometryAttribute::POSITION); + Eigen::Vector4d position; + position[3] = 1.0; + for (AttributeValueIndex i(0); i < pc_att->size(); ++i) { + pc_att->ConvertValue(i, &position[0]); + const Eigen::Vector4d transformed = instance.transform * position; + mesh_bbox.Update({static_cast(transformed[0]), + static_cast(transformed[1]), + static_cast(transformed[2])}); + } + return mesh_bbox; +} + +namespace { + +// Updates texture pointers in mesh features of |mesh| to texture pointers +// stored in |new_texture_library|. |texture_to_index_map| stores texture +// indices of the old texture pointers within |mesh|. +void UpdateMeshFeaturesTexturesOnMesh( + const std::unordered_map &texture_to_index_map, + TextureLibrary *new_texture_library, Mesh *mesh) { + for (MeshFeaturesIndex mfi(0); mfi < mesh->NumMeshFeatures(); ++mfi) { + mesh->UpdateMeshFeaturesTexturePointer( + texture_to_index_map, new_texture_library, &mesh->GetMeshFeatures(mfi)); + } +} + +} // namespace + +StatusOr> SceneUtils::MeshToScene( + std::unique_ptr mesh) { + const size_t num_mesh_materials = mesh->GetMaterialLibrary().NumMaterials(); + std::unique_ptr scene(new Scene()); + if (num_mesh_materials > 0) { + scene->GetMaterialLibrary().Copy(mesh->GetMaterialLibrary()); + mesh->GetMaterialLibrary().Clear(); + } else { + // Create a default material for the scene. + scene->GetMaterialLibrary().MutableMaterial(0); + } + + // Copy mesh feature textures. + scene->GetNonMaterialTextureLibrary().Copy( + mesh->GetNonMaterialTextureLibrary()); + + const auto old_texture_to_index_map = + mesh->GetNonMaterialTextureLibrary().ComputeTextureToIndexMap(); + + const SceneNodeIndex scene_node_index = scene->AddNode(); + SceneNode *const scene_node = scene->GetNode(scene_node_index); + const MeshGroupIndex mesh_group_index = scene->AddMeshGroup(); + MeshGroup *const mesh_group = scene->GetMeshGroup(mesh_group_index); + + if (num_mesh_materials <= 1) { + const MeshIndex mesh_index = scene->AddMesh(std::move(mesh)); + if (mesh_index == kInvalidMeshIndex) { + // No idea whether this can happen. It's not covered by any unit test. + return Status(Status::DRACO_ERROR, "Could not add Draco mesh to scene."); + } + mesh_group->AddMeshInstance({mesh_index, 0, {}}); + + UpdateMeshFeaturesTexturesOnMesh(old_texture_to_index_map, + &scene->GetNonMaterialTextureLibrary(), + &scene->GetMesh(mesh_index)); + + } else { + const int32_t mat_att_id = + mesh->GetNamedAttributeId(GeometryAttribute::MATERIAL); + if (mat_att_id == -1) { + // Probably dead code, not covered by any unit test. + return Status(Status::DRACO_ERROR, + "Internal error in MeshToScene: " + "GetNamedAttributeId(MATERIAL) returned -1"); + } + const PointAttribute *const mat_att = + mesh->GetNamedAttribute(GeometryAttribute::MATERIAL); + if (mat_att == nullptr) { + // Probably dead code, not covered by any unit test. + return Status(Status::DRACO_ERROR, + "Internal error in MeshToScene: " + "GetNamedAttribute(MATERIAL) returned nullptr"); + } + + MeshSplitter splitter; + DRACO_ASSIGN_OR_RETURN(MeshSplitter::MeshVector split_meshes, + splitter.SplitMesh(*mesh, mat_att_id)); + // Note: cannot clear mesh here, since mat_att points into it. + for (size_t i = 0; i < split_meshes.size(); ++i) { + if (split_meshes[i] == nullptr) { + // Probably dead code, not covered by any unit test. + continue; + } + const MeshIndex mesh_index = scene->AddMesh(std::move(split_meshes[i])); + if (mesh_index == kInvalidMeshIndex) { + // No idea whether this can happen. It's not covered by any unit test. + return Status(Status::DRACO_ERROR, + "Could not add Draco mesh to scene."); + } + + int material_index = 0; + mat_att->GetValue(AttributeValueIndex(i), &material_index); + mesh_group->AddMeshInstance({mesh_index, material_index, {}}); + + // Copy over mesh features that were associated with the |material_index|. + Mesh &scene_mesh = scene->GetMesh(mesh_index); + Mesh::CopyMeshFeaturesForMaterial(*mesh, &scene_mesh, material_index); + UpdateMeshFeaturesTexturesOnMesh(old_texture_to_index_map, + &scene->GetNonMaterialTextureLibrary(), + &scene_mesh); + } + } + + scene_node->SetMeshGroupIndex(mesh_group_index); + scene->AddRootNodeIndex(scene_node_index); + return scene; +} + +void SceneUtils::PrintInfo(const Scene &input, const Scene &simplified, + bool verbose) { + struct Printer { + Printer(const Scene &input, const Scene &simplified) + : input(input), simplified(simplified), print_instanced_info(false) { + // Info about the instanced meshes is printed if some of the meshes have + // multiple instances and also if the number of base meshes has changed. + auto input_instances = SceneUtils::NumMeshInstances(input); + auto simplified_instances = SceneUtils::NumMeshInstances(simplified); + if (input_instances.size() != simplified_instances.size()) { + print_instanced_info = true; + return; + } + for (MeshIndex i(0); i < input_instances.size(); i++) { + if (input_instances[i] != 1 || simplified_instances[i] != 1) { + print_instanced_info = true; + return; + } + } + } + + void PrintInfoHeader() const { + printf("\n"); + printf("%21s | geometry: base", ""); + if (print_instanced_info) { + printf(" instanced"); + } + printf("\n"); + } + + void PrintInfoRow(const std::string &label, int count_input_base, + int count_input_instanced, int count_simplified_base, + int count_simplified_instanced) const { + // Do not clutter the printout with empty info. + if (count_input_base == 0 && count_input_instanced == 0) { + return; + } + printf(" ----------------------------------------------"); + if (print_instanced_info) { + printf("-------------"); + } + printf("\n"); + printf("%21s | input: %12d", label.c_str(), count_input_base); + if (print_instanced_info) { + printf(" %12d", count_input_instanced); + } + printf("\n"); + printf("%21s | simplified: %12d", "", count_simplified_base); + if (print_instanced_info) { + printf(" %12d", count_simplified_instanced); + } + printf("\n"); + } + + void PrintAttInfoRow(const std::string &label, const draco::Scene &input, + const draco::Scene &simplified, + draco::GeometryAttribute::Type att_type) const { + PrintInfoRow(label, NumAttEntriesOnBaseMeshes(input, att_type), + NumAttEntriesOnInstancedMeshes(input, att_type), + NumAttEntriesOnBaseMeshes(simplified, att_type), + NumAttEntriesOnInstancedMeshes(simplified, att_type)); + } + + const Scene &input; + const Scene &simplified; + bool print_instanced_info; + }; + + // Print information about input and simplified scenes. + const Printer printer(input, simplified); + printer.PrintInfoHeader(); + if (verbose) { + const int num_meshes_input_base = input.NumMeshes(); + const int num_meshes_simplified_base = simplified.NumMeshes(); + const int num_meshes_input_instanced = ComputeAllInstances(input).size(); + const int num_meshes_simplified_instanced = + ComputeAllInstances(simplified).size(); + printer.PrintInfoRow("Number of meshes", num_meshes_input_base, + num_meshes_input_instanced, num_meshes_simplified_base, + num_meshes_simplified_instanced); + } + printer.PrintInfoRow("Number of faces", NumFacesOnBaseMeshes(input), + NumFacesOnInstancedMeshes(input), + NumFacesOnBaseMeshes(simplified), + NumFacesOnInstancedMeshes(simplified)); + if (verbose) { + printer.PrintInfoRow("Number of points", NumPointsOnBaseMeshes(input), + NumPointsOnInstancedMeshes(input), + NumPointsOnBaseMeshes(simplified), + NumPointsOnInstancedMeshes(simplified)); + printer.PrintAttInfoRow("Number of positions", input, simplified, + draco::GeometryAttribute::POSITION); + printer.PrintAttInfoRow("Number of normals", input, simplified, + draco::GeometryAttribute::NORMAL); + printer.PrintAttInfoRow("Number of colors", input, simplified, + draco::GeometryAttribute::COLOR); + printer.PrintInfoRow("Number of materials", + input.GetMaterialLibrary().NumMaterials(), + simplified.GetMaterialLibrary().NumMaterials(), + input.GetMaterialLibrary().NumMaterials(), + simplified.GetMaterialLibrary().NumMaterials()); + } +} + +StatusOr> SceneUtils::InstantiateMesh( + const Scene &scene, const MeshInstance &instance) { + // Check if the |scene| has base mesh corresponding to mesh |instance|. + if (scene.NumMeshes() <= instance.mesh_index.value()) { + Status(Status::DRACO_ERROR, "Scene has no corresponding base mesh."); + } + + // Check that mesh has valid positions. + const Mesh &base_mesh = scene.GetMesh(instance.mesh_index); + const int32_t pos_id = + base_mesh.GetNamedAttributeId(GeometryAttribute::POSITION); + const PointAttribute *const pos_att = base_mesh.attribute(pos_id); + if (pos_att == nullptr) { + return Status(Status::DRACO_ERROR, "Mesh has no positions."); + } + if (pos_att->data_type() != DT_FLOAT32 || pos_att->num_components() != 3) { + return Status(Status::DRACO_ERROR, "Mesh has invalid positions."); + } + + // Copy the base mesh from |scene|. + std::unique_ptr mesh(new Mesh()); + mesh->Copy(base_mesh); + + // Apply transformation to mesh unless transformation is identity. + if (instance.transform != Eigen::Matrix4d::Identity()) { + MeshUtils::TransformMesh(instance.transform, mesh.get()); + } + return mesh; +} + +namespace { + +// Helper class for deleting unused nodes from the scene. +class SceneUnusedNodeRemover { + public: + // Removes unused nodes from the |scene|. + void RemoveUnusedNodes(Scene *scene) { + // Finds all unused nodes and initializes |node_map_| that maps old node + // indices to new node indices. + const int num_unused_nodes = FindUnusedNodes(*scene); + if (num_unused_nodes == 0) { + return; // All nodes are used. + } + + // Update indices of all scene elements accounting for nodes that are going + // to be removed from the scene. + UpdateNodeIndices(scene); + RemoveUnusedNodesFromScene(scene); + } + + private: + // Returns the number of unused nodes. + int FindUnusedNodes(const Scene &scene) { + // First all nodes are considered unused (mapped to invalid index). + // Initially if a node is used, we just map it to its own index. The final + // mapping will be updated once we know all used nodes. + node_map_.resize(scene.NumNodes(), kInvalidSceneNodeIndex); + for (SceneNodeIndex sni(0); sni < scene.NumNodes(); ++sni) { + // If the scene node has a valid mesh group, mark it as used. + if (scene.GetNode(sni)->GetMeshGroupIndex() != kInvalidMeshGroupIndex) { + node_map_[sni] = sni; + } + } + + // Preserve nodes used by animations. + for (AnimationIndex i(0); i < scene.NumAnimations(); i++) { + const Animation &animation = *scene.GetAnimation(i); + for (int channel_i = 0; channel_i < animation.NumChannels(); + channel_i++) { + const SceneNodeIndex node_index( + animation.GetChannel(channel_i)->target_index); + node_map_[node_index] = node_index; + } + } + for (SkinIndex i(0); i < scene.NumSkins(); i++) { + const Skin &skin = *scene.GetSkin(i); + for (int j = 0; j < skin.NumJoints(); j++) { + const SceneNodeIndex node_index = skin.GetJoint(j); + node_map_[node_index] = node_index; + } + const SceneNodeIndex root_index = skin.GetJointRoot(); + if (root_index != kInvalidSceneNodeIndex) { + node_map_[root_index] = root_index; + } + } + + // Ensure that "unused" nodes with used child nodes are marked as used + // (a node can't be deleted as long as it has a used child node). + for (int r = 0; r < scene.NumRootNodes(); ++r) { + UpdateUsedNodesFromSceneGraph(scene, scene.GetRootNodeIndex(r)); + } + + // All used / unused nodes are known. Find new indices for all scene nodes. + int num_valid_nodes = 0; + for (SceneNodeIndex sni(0); sni < scene.NumNodes(); ++sni) { + if (node_map_[sni] != kInvalidSceneNodeIndex) { + node_map_[sni] = SceneNodeIndex(num_valid_nodes++); + } + } + // Return the number of nodes that were unused. + return scene.NumNodes() - num_valid_nodes; + } + + // Recursively traverse node |sni| and mark it as used as long as it has a + // used child node. The function returns true when |sni| is a used node. + bool UpdateUsedNodesFromSceneGraph(const Scene &scene, SceneNodeIndex sni) { + const auto &node = scene.GetNode(sni); + bool is_any_child_node_used = false; + for (int c = 0; c < node->NumChildren(); ++c) { + const SceneNodeIndex cni = node->Child(c); + // Check if the child node is used. + const bool is_c_used = UpdateUsedNodesFromSceneGraph(scene, cni); + if (is_c_used) { + is_any_child_node_used = true; + } + } + if (is_any_child_node_used) { + // The node must be used even if it was previously marked as unused. + node_map_[sni] = sni; + } + // Returns whether this node is used or not. + return node_map_[sni] != kInvalidSceneNodeIndex; + } + + // Remaps existing node indices at various scene elements to new node indices + // defined by |node_map_|. + void UpdateNodeIndices(Scene *scene) const { + // Update node indices on child / parent nodes. + std::vector indices; + for (SceneNodeIndex sni(0); sni < scene->NumNodes(); ++sni) { + indices = scene->GetNode(sni)->Children(); + scene->GetNode(sni)->RemoveAllChildren(); + for (int j = 0; j < indices.size(); ++j) { + const SceneNodeIndex new_sni = node_map_[indices[j]]; + if (new_sni != kInvalidSceneNodeIndex) { + scene->GetNode(sni)->AddChildIndex(new_sni); + } + } + indices = scene->GetNode(sni)->Parents(); + scene->GetNode(sni)->RemoveAllParents(); + for (int j = 0; j < indices.size(); ++j) { + const SceneNodeIndex new_sni = node_map_[indices[j]]; + if (new_sni != kInvalidSceneNodeIndex) { + scene->GetNode(sni)->AddParentIndex(new_sni); + } + } + } + + // Update root node indices. + indices = scene->GetRootNodeIndices(); + scene->RemoveAllRootNodeIndices(); + for (int ri = 0; ri < indices.size(); ++ri) { + const SceneNodeIndex new_rni = node_map_[indices[ri]]; + if (new_rni != kInvalidSceneNodeIndex) { + scene->AddRootNodeIndex(new_rni); + } + } + + // Update node indices used by animations. + for (AnimationIndex i(0); i < scene->NumAnimations(); i++) { + Animation &animation = *scene->GetAnimation(i); + for (int i = 0; i < animation.NumChannels(); i++) { + const SceneNodeIndex node_index(animation.GetChannel(i)->target_index); + animation.GetChannel(i)->target_index = node_map_[node_index].value(); + } + } + for (SkinIndex i(0); i < scene->NumSkins(); i++) { + Skin &skin = *scene->GetSkin(i); + for (int j = 0; j < skin.NumJoints(); j++) { + const SceneNodeIndex node_index = skin.GetJoint(j); + skin.GetJoint(j) = node_map_[node_index]; + } + const SceneNodeIndex root_index = skin.GetJointRoot(); + if (root_index != kInvalidSceneNodeIndex) { + skin.SetJointRoot(node_map_[root_index]); + } + } + } + + // Removes all unused nodes from the scene. + void RemoveUnusedNodesFromScene(Scene *scene) const { + int num_valid_nodes = 0; + // Copy over nodes to their new position in the nodes array. + for (SceneNodeIndex sni(0); sni < scene->NumNodes(); ++sni) { + const SceneNodeIndex new_sni = node_map_[sni]; + if (new_sni == kInvalidSceneNodeIndex) { + continue; + } + num_valid_nodes++; + if (sni != new_sni) { + // Copy over the |sni| node to the new location (|new_sni| is lower than + // |sni|). + scene->GetNode(new_sni)->Copy(*scene->GetNode(sni)); + } + } + // Resize the nodes in the scene to account for the unused ones. This will + // delete all unused nodes. + scene->ResizeNodes(num_valid_nodes); + } + + IndexTypeVector node_map_; +}; + +} // namespace + +void SceneUtils::Cleanup(Scene *scene) { Cleanup(scene, CleanupOptions()); } + +void SceneUtils::Cleanup(Scene *scene, const CleanupOptions &options) { + // Remove invalid mesh indices from mesh groups. + if (options.remove_invalid_mesh_instances) { + for (MeshGroupIndex i(0); i < scene->NumMeshGroups(); i++) { + scene->GetMeshGroup(i)->RemoveMeshInstances(kInvalidMeshIndex); + } + } + + // Find references to mesh groups. + std::vector is_mesh_group_referenced(scene->NumMeshGroups(), false); + for (SceneNodeIndex i(0); i < scene->NumNodes(); i++) { + const SceneNode &node = *scene->GetNode(i); + const MeshGroupIndex mesh_group_index = node.GetMeshGroupIndex(); + if (mesh_group_index != kInvalidMeshGroupIndex) { + is_mesh_group_referenced[mesh_group_index.value()] = true; + } + } + + // Find references to base meshes from referenced mesh groups and find mesh + // groups that have no valid references to base meshes. + std::vector is_base_mesh_referenced(scene->NumMeshes(), false); + std::vector is_mesh_group_empty(scene->NumMeshGroups(), false); + for (MeshGroupIndex i(0); i < scene->NumMeshGroups(); i++) { + if (!is_mesh_group_referenced[i.value()]) { + continue; + } + const MeshGroup &mesh_group = *scene->GetMeshGroup(i); + bool mesh_group_is_empty = true; + for (int j = 0; j < mesh_group.NumMeshInstances(); j++) { + const MeshIndex mesh_index = mesh_group.GetMeshInstance(j).mesh_index; + mesh_group_is_empty = false; + is_base_mesh_referenced[mesh_index.value()] = true; + } + if (mesh_group_is_empty) { + is_mesh_group_empty[i.value()] = true; + } + } + + if (options.remove_unused_meshes) { + // Remove base meshes with no references to them. + for (int i = scene->NumMeshes() - 1; i >= 0; i--) { + const MeshIndex mi(i); + if (!is_base_mesh_referenced[mi.value()]) { + scene->RemoveMesh(mi); + } + } + } + + if (options.remove_unused_mesh_groups) { + // Remove empty mesh groups with no geometry or no references to them. + for (int i = scene->NumMeshGroups() - 1; i >= 0; i--) { + const MeshGroupIndex mgi(i); + if (is_mesh_group_empty[mgi.value()] || + !is_mesh_group_referenced[mgi.value()]) { + scene->RemoveMeshGroup(mgi); + } + } + } + + // Find materials that reference a texture. + MaterialLibrary &material_library = scene->GetMaterialLibrary(); + std::vector materials_with_textures(material_library.NumMaterials(), + false); + for (int i = 0; i < material_library.NumMaterials(); ++i) { + if (material_library.GetMaterial(i)->NumTextureMaps() > 0) { + materials_with_textures[i] = true; + } + } + + // Maps material index to a set of meshes that use that material. + std::vector> material_meshes( + material_library.NumMaterials()); + + // Maps mesh index to a set of materials used by that mesh. + IndexTypeVector> mesh_materials( + scene->NumMeshes()); + + // Maps mesh index to a set of tex coord indices referenced by materials. + IndexTypeVector> tex_coord_referenced( + scene->NumMeshes()); + + // Populate the maps that will be used to remove unused texture coordinates. + for (int mgi = 0; mgi < scene->NumMeshGroups(); ++mgi) { + const MeshGroup *const mesh_group = + scene->GetMeshGroup(MeshGroupIndex(mgi)); + for (int mi = 0; mi < mesh_group->NumMeshInstances(); ++mi) { + const MeshIndex mesh_index = mesh_group->GetMeshInstance(mi).mesh_index; + const int material_index = mesh_group->GetMeshInstance(mi).material_index; + if (material_index == -1) { + continue; + } + + // Populate mesh-material mapping. + material_meshes[material_index].insert(mesh_index); + mesh_materials[mesh_index].insert(material_index); + + // Populate texture coordinate indices referenced by material textures. + const auto material = material_library.GetMaterial(material_index); + for (int i = 0; i < material->NumTextureMaps(); i++) { + const TextureMap *const texture_map = material->GetTextureMapByIndex(i); + const int tex_coord_index = texture_map->tex_coord_index(); + tex_coord_referenced[mesh_index].insert(tex_coord_index); + } + } + } + + // From each mesh, remove texture coordinate attributes that are not + // referenced by any materials and decrement texture coordinate indices in + // texture maps of the mesh materials accordingly. + if (options.remove_unused_tex_coords) { + for (MeshIndex mi(0); mi < scene->NumMeshes(); ++mi) { + // Do not remove unreferenced texture coordinates when the mesh materials + // are used by any other meshes to avoid corrupting those other meshes. + // TODO(vytyaz): Consider removing this limitation. + bool remove_tex_coord = true; + for (const int material_index : mesh_materials[mi]) { + if (material_meshes[material_index].size() != 1) { + // Materials of this mesh are used by other meshes. + remove_tex_coord = false; + break; + } + } + if (!remove_tex_coord) { + continue; + } + + // Remove unreferenced texture coordinate sets from this mesh. + Mesh &mesh = scene->GetMesh(mi); + const int tex_coord_count = + mesh.NumNamedAttributes(GeometryAttribute::TEX_COORD); + for (int tci = tex_coord_count - 1; tci >= 0; tci--) { + if (tex_coord_referenced[mi].count(tci) != 0) { + // Texture coordinate set is referenced. + continue; + } + mesh.DeleteAttribute( + mesh.GetNamedAttributeId(GeometryAttribute::TEX_COORD, tci)); + + // Decrement texture coordinate indices in all materials of this mesh. + for (const int material_index : mesh_materials[mi]) { + auto material = material_library.MutableMaterial(material_index); + for (int i = 0; i < material->NumTextureMaps(); i++) { + auto texture_map = material->GetTextureMapByIndex(i); + // Decrement the indices that are greater than the removed index. + if (texture_map->tex_coord_index() > tci) { + texture_map->SetProperties(texture_map->type(), + texture_map->tex_coord_index() - 1); + } + } + } + } + } + } + + if (options.remove_unused_materials) { + // Remove materials that are not used by any mesh. + for (int i = material_library.NumMaterials() - 1; i >= 0; --i) { + if (material_meshes[i].empty()) { + // Material |i| is not used. + scene->RemoveMaterial(i); + } + } + } + + if (options.remove_unused_nodes) { + SceneUnusedNodeRemover node_remover; + node_remover.RemoveUnusedNodes(scene); + } +} + +void SceneUtils::RemoveMeshInstances(const std::vector &instances, + Scene *scene) { + // Remove mesh instances from the scene. + for (const SceneUtils::MeshInstance &instance : instances) { + const MeshGroupIndex mgi = + scene->GetNode(instance.scene_node_index)->GetMeshGroupIndex(); + + // Create a new mesh group with removed instance (we can't just delete the + // instance from the mesh group directly, because the same mesh group may + // be used by multiple scene nodes). + const MeshGroupIndex new_mesh_group_index = scene->AddMeshGroup(); + MeshGroup &new_mesh_group = *scene->GetMeshGroup(new_mesh_group_index); + + new_mesh_group.Copy(*scene->GetMeshGroup(mgi)); + new_mesh_group.RemoveMeshInstances(instance.mesh_index); + + // Assign the new mesh group to the scene node. Unused mesh groups will be + // automatically removed later during a scene cleanup operation. + scene->GetNode(instance.scene_node_index) + ->SetMeshGroupIndex(new_mesh_group_index); + } + + // Remove duplicate mesh groups that may have been created during the instance + // removal process. + DeduplicateMeshGroups(scene); +} + +void SceneUtils::DeduplicateMeshGroups(Scene *scene) { + if (scene->NumMeshGroups() <= 1) { + return; + } + + // Signature of a mesh group used for detecting duplicates. + struct MeshGroupSignature { + const MeshGroupIndex mesh_group_index; + const MeshGroup &mesh_group; + MeshGroupSignature(MeshGroupIndex mgi, const MeshGroup &mesh_group) + : mesh_group_index(mgi), mesh_group(mesh_group) {} + + bool operator==(const MeshGroupSignature &signature) const { + if (mesh_group.GetName() != signature.mesh_group.GetName()) { + return false; + } + if (mesh_group.NumMeshInstances() != + signature.mesh_group.NumMeshInstances()) { + return false; + } + // TODO(ostava): We may consider sorting meshes within a mesh group to + // make the order of meshes irrelevant. This should be done only for + // meshes with opaque materials though, because for transparent + // geometries, the order matters. + for (int i = 0; i < mesh_group.NumMeshInstances(); ++i) { + if (mesh_group.GetMeshInstance(i) != + signature.mesh_group.GetMeshInstance(i)) { + return false; + } + } + return true; + } + struct Hash { + size_t operator()(const MeshGroupSignature &signature) const { + size_t hash = 79; // Magic number. + const MeshGroup &group = signature.mesh_group; + hash = HashCombine(group.GetName(), hash); + hash = HashCombine(group.NumMeshInstances(), hash); + for (int i = 0; i < group.NumMeshInstances(); ++i) { + const MeshGroup::MeshInstance &instance = group.GetMeshInstance(i); + hash = HashCombine(instance.mesh_index, hash); + hash = HashCombine(instance.material_index, hash); + hash = HashCombine(instance.materials_variants_mappings.size(), hash); + for (const MeshGroup::MaterialsVariantsMapping &mapping : + instance.materials_variants_mappings) { + hash = HashCombine(mapping.material, hash); + hash = HashCombine(mapping.variants.size(), hash); + for (const int &variant : mapping.variants) { + hash = HashCombine(variant, hash); + } + } + } + return hash; + } + }; + }; + + // Set holding unique mesh groups. + std::unordered_set + unique_mesh_groups; + IndexTypeVector parent_mesh_group( + scene->NumMeshGroups()); + for (MeshGroupIndex mgi(0); mgi < scene->NumMeshGroups(); ++mgi) { + const MeshGroup *mg = scene->GetMeshGroup(mgi); + const MeshGroupSignature signature(mgi, *mg); + auto it = unique_mesh_groups.find(signature); + if (it != unique_mesh_groups.end()) { + parent_mesh_group[mgi] = it->mesh_group_index; + } else { + parent_mesh_group[mgi] = kInvalidMeshGroupIndex; + unique_mesh_groups.insert(signature); + } + } + + // Go over all nodes and update mesh groups if needed. + for (SceneNodeIndex sni(0); sni < scene->NumNodes(); ++sni) { + const MeshGroupIndex mgi = scene->GetNode(sni)->GetMeshGroupIndex(); + if (mgi == kInvalidMeshGroupIndex || + parent_mesh_group[mgi] == kInvalidMeshGroupIndex) { + continue; // Nothing to update. + } + scene->GetNode(sni)->SetMeshGroupIndex(parent_mesh_group[mgi]); + } + + // Remove any unused mesh groups. + Cleanup(scene); +} + +void SceneUtils::SetDracoCompressionOptions( + const DracoCompressionOptions *options, Scene *scene) { + for (MeshIndex i(0); i < scene->NumMeshes(); ++i) { + Mesh &mesh = scene->GetMesh(i); + if (options == nullptr) { + mesh.SetCompressionEnabled(false); + } else { + mesh.SetCompressionEnabled(true); + mesh.SetCompressionOptions(*options); + } + } +} + +bool SceneUtils::IsDracoCompressionEnabled(const Scene &scene) { + for (MeshIndex i(0); i < scene.NumMeshes(); ++i) { + if (scene.GetMesh(i).IsCompressionEnabled()) { + return true; + } + } + return false; +} + +IndexTypeVector +SceneUtils::FindLargestBaseMeshTransforms(const Scene &scene) { + IndexTypeVector transforms( + scene.NumMeshes(), Eigen::Matrix4d::Identity()); + + // In case a mesh has multiple instances we want to use the instance with + // the largest scale. + IndexTypeVector transform_scale(scene.NumMeshes(), 0.f); + + const auto instances = SceneUtils::ComputeAllInstances(scene); + for (MeshInstanceIndex i(0); i < instances.size(); ++i) { + const auto &instance = instances[i]; + + // Compute the scale of the transform. + const Vector3f scale_vec(instance.transform.col(0).norm(), + instance.transform.col(1).norm(), + instance.transform.col(2).norm()); + + // In our framework we support uniform scale only. For now, just take the + // maximum scale across all axes. + // TODO(ostava): Investigate how to properly support non-uniform scaling. + const float max_scale = scale_vec.MaxCoeff(); + + if (transform_scale[instance.mesh_index] < max_scale) { + transform_scale[instance.mesh_index] = max_scale; + transforms[instance.mesh_index] = instance.transform; + } + } + + return transforms; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/scene/scene_utils.h b/contrib/draco/src/draco/scene/scene_utils.h new file mode 100644 index 000000000..5b978c3c5 --- /dev/null +++ b/contrib/draco/src/draco/scene/scene_utils.h @@ -0,0 +1,150 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_SCENE_SCENE_UTILS_H_ +#define DRACO_SCENE_SCENE_UTILS_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/attributes/geometry_attribute.h" +#include "draco/scene/scene.h" + +namespace draco { + +// Helper class containing various utility functions operating on draco::Scene. +class SceneUtils { + public: + // Helper struct holding instanced meshes and their transformations. + struct MeshInstance { + // Index of the parent mesh in the draco::Scene. + MeshIndex mesh_index; + // Index of the node in the draco::Scene. + SceneNodeIndex scene_node_index; + // Index of the mesh in the mesh group. + int mesh_group_mesh_index; + // Transform of the instance from the mesh local space to the global space + // of the scene. + Eigen::Matrix4d transform; + }; + + // Computes all mesh instances in the |scene|. + static IndexTypeVector ComputeAllInstances( + const Scene &scene); + + // Computes global transform matrix of a |scene| node given by its |index|. + static Eigen::Matrix4d ComputeGlobalNodeTransform(const Scene &scene, + SceneNodeIndex index); + + // Returns a vector of mesh instance counts for all base meshes. + static IndexTypeVector NumMeshInstances(const Scene &scene); + + // Returns the material index of the given |instance| or -1 if the mesh + // |instance| has a default material. + static int GetMeshInstanceMaterialIndex(const Scene &scene, + const MeshInstance &instance); + + // Returns the total number of faces on all base meshes of the scene (not + // counting instances). + static int NumFacesOnBaseMeshes(const Scene &scene); + + // Returns the total number of faces on all meshes of the scenes, including + // all instances of the same mesh. + static int NumFacesOnInstancedMeshes(const Scene &scene); + + // Returns the total number of points on all base meshes of the scene (not + // counting instances). + static int NumPointsOnBaseMeshes(const Scene &scene); + + // Returns the total number of points on all meshes of the scenes, including + // all instances of the same mesh. + static int NumPointsOnInstancedMeshes(const Scene &scene); + + // Returns the total number of attribute entries on all base meshes of the + // scene (not counting instances) for the first attribute of |att_type|. + static int NumAttEntriesOnBaseMeshes(const Scene &scene, + GeometryAttribute::Type att_type); + + // Returns the total number of attribute ent on all meshes of the scenes, + // including all instances of the same mesh for the first attribute of + // |att_type|. + static int NumAttEntriesOnInstancedMeshes(const Scene &scene, + GeometryAttribute::Type att_type); + + // Returns the bounding box of the scene. + static BoundingBox ComputeBoundingBox(const Scene &scene); + + // Returns the bounding box of a mesh instance. + static BoundingBox ComputeMeshInstanceBoundingBox( + const Scene &scene, const MeshInstance &instance); + + // Prints info about input and simplified scenes. + static void PrintInfo(const Scene &input, const Scene &simplified, + bool verbose); + + // Converts a draco::Mesh into a draco::Scene. If the passed-in `mesh` has + // multiple materials, the returned scene will contain multiple meshes, one + // for each of the source mesh's materials; if `mesh` has no material, one + // will be created for it. + static StatusOr> MeshToScene( + std::unique_ptr mesh); + + // Creates a mesh according to mesh |instance| in |scene|. Error is returned + // if there is no corresponding base mesh in the |scene| or the base mesh has + // no valid positions. + static StatusOr> InstantiateMesh( + const Scene &scene, const MeshInstance &instance); + + // Cleans up a |scene| by removing unused base meshes, unused and empty mesh + // groups, unused materials, unused texture coordinates and unused scene + // nodes. The actual behavior of the cleanup operation can be controller via + // the user provided |options|. + struct CleanupOptions { + bool remove_invalid_mesh_instances = true; + bool remove_unused_mesh_groups = true; + bool remove_unused_meshes = true; + bool remove_unused_nodes = false; + bool remove_unused_tex_coords = false; + bool remove_unused_materials = true; + }; + static void Cleanup(Scene *scene); + static void Cleanup(Scene *scene, const CleanupOptions &options); + + // Removes mesh |instances| from |scene|. + static void RemoveMeshInstances(const std::vector &instances, + Scene *scene); + + // Removes duplicate mesh groups that have the same name and that contain + // exactly the same meshes and materials. + static void DeduplicateMeshGroups(Scene *scene); + + // Enables geometry compression and sets compression |options| to all meshes + // in the |scene|. If |options| is nullptr then geometry compression is + // disabled for all meshes in the |scene|. + static void SetDracoCompressionOptions(const DracoCompressionOptions *options, + Scene *scene); + + // Returns true if geometry compression is eabled for any of |scene| meshes. + static bool IsDracoCompressionEnabled(const Scene &scene); + + // Returns a single tranformation matrix for each base mesh of the |scene| + // corresponding to the instance with the maximum scale. + static IndexTypeVector + FindLargestBaseMeshTransforms(const Scene &scene); +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_SCENE_SCENE_UTILS_H_ diff --git a/contrib/draco/src/draco/scene/scene_utils_test.cc b/contrib/draco/src/draco/scene/scene_utils_test.cc new file mode 100644 index 000000000..4d6bd731d --- /dev/null +++ b/contrib/draco/src/draco/scene/scene_utils_test.cc @@ -0,0 +1,763 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/scene_utils.h" + +#include +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/bounding_box.h" +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/io/texture_io.h" +#include "draco/scene/scene_indices.h" + +namespace { + +using draco::MeshIndex; +using draco::MeshInstanceIndex; + +void AssertMatrixNear(const Eigen::Matrix4d &a, const Eigen::Matrix4d &b, + float tolerance) { + Eigen::Matrix4d diff = a - b; + ASSERT_NEAR(diff.norm(), 0.f, tolerance) << a << " vs " << b; +} + +// TODO(fgalligan): Re-factor this code with gltf_encoder_test. +void CompareScenes(const draco::Scene *scene0, const draco::Scene *scene1) { + ASSERT_EQ(scene0->NumMeshGroups(), scene1->NumMeshGroups()); + ASSERT_EQ(scene0->NumMeshes(), scene1->NumMeshes()); + ASSERT_EQ(scene0->GetMaterialLibrary().NumMaterials(), + scene1->GetMaterialLibrary().NumMaterials()); + ASSERT_EQ(scene0->NumAnimations(), scene1->NumAnimations()); + ASSERT_EQ(scene0->NumSkins(), scene1->NumSkins()); + for (draco::AnimationIndex i(0); i < scene0->NumAnimations(); ++i) { + const draco::Animation *const animation0 = scene0->GetAnimation(i); + const draco::Animation *const animation1 = scene1->GetAnimation(i); + ASSERT_NE(animation0, nullptr); + ASSERT_NE(animation1, nullptr); + ASSERT_EQ(animation0->NumSamplers(), animation1->NumSamplers()); + ASSERT_EQ(animation0->NumChannels(), animation1->NumChannels()); + ASSERT_EQ(animation0->NumNodeAnimationData(), + animation1->NumNodeAnimationData()); + } +} + +TEST(SceneUtilsTest, TestComputeAllInstances) { + // Tests that we can compute all instances in an input scene along with their + // transformations. + + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 4); + + // Compute mesh instances. + const auto instances = draco::SceneUtils::ComputeAllInstances(*scene); + ASSERT_EQ(instances.size(), 5); + + // Check base mesh indices. + ASSERT_EQ(instances[MeshInstanceIndex(0)].mesh_index, 0); + ASSERT_EQ(instances[MeshInstanceIndex(1)].mesh_index, 1); + ASSERT_EQ(instances[MeshInstanceIndex(2)].mesh_index, 2); + ASSERT_EQ(instances[MeshInstanceIndex(3)].mesh_index, 3); + ASSERT_EQ(instances[MeshInstanceIndex(4)].mesh_index, 3); + + // Check scene node indices. + ASSERT_EQ(instances[MeshInstanceIndex(0)].scene_node_index, 0); + ASSERT_EQ(instances[MeshInstanceIndex(1)].scene_node_index, 0); + ASSERT_EQ(instances[MeshInstanceIndex(2)].scene_node_index, 0); + ASSERT_EQ(instances[MeshInstanceIndex(3)].scene_node_index, 4); + ASSERT_EQ(instances[MeshInstanceIndex(4)].scene_node_index, 2); + + // Check indices of meshes in mesh group. + ASSERT_EQ(instances[MeshInstanceIndex(0)].mesh_group_mesh_index, 0); + ASSERT_EQ(instances[MeshInstanceIndex(1)].mesh_group_mesh_index, 1); + ASSERT_EQ(instances[MeshInstanceIndex(2)].mesh_group_mesh_index, 2); + ASSERT_EQ(instances[MeshInstanceIndex(3)].mesh_group_mesh_index, 0); + ASSERT_EQ(instances[MeshInstanceIndex(4)].mesh_group_mesh_index, 0); + + // The first three instances should have identity transformation. + for (MeshInstanceIndex i(0); i < 3; ++i) { + AssertMatrixNear(instances[i].transform, Eigen::Matrix4d::Identity(), + 1e-6f); + } + + // Fourth and fifth instances are transformed. + Eigen::Matrix4d expected_transform = Eigen::Matrix4d::Identity(); + // Expected translation. + expected_transform(0, 3) = -1.352329969406128; + expected_transform(1, 3) = 0.4277220070362091; + expected_transform(2, 3) = -2.98022992950564e-8; + + // Expected rotation. + Eigen::Matrix4d expected_rotation = Eigen::Matrix4d::Identity(); + expected_rotation.block<3, 3>(0, 0) = + Eigen::Quaterniond(-0.9960774183273317, -0.0, -0.0, 0.08848590403795243) + .normalized() + .toRotationMatrix(); + expected_transform = expected_transform * expected_rotation; + + AssertMatrixNear(instances[MeshInstanceIndex(3)].transform, + expected_transform, 1e-6f); + + // Last instance differs only in the translation part in X axis. + expected_transform(0, 3) = 1.432669997215271; + + AssertMatrixNear(instances[MeshInstanceIndex(4)].transform, + expected_transform, 1e-6f); +} + +TEST(SceneUtilsTest, TestComputeAllInstancesWithShiftedGeometryRoot) { + // Tests that we can compute all instances in an input scene along with their + // transformations. This scene has light and camera nodes before the geometry + // node. + auto scene = draco::ReadSceneFromTestFile( + "SphereWithCircleTexture/sphere_with_circle_texture.gltf"); + ASSERT_NE(scene, nullptr); + + // There is one base mesh. + ASSERT_EQ(scene->NumMeshes(), 1); + + // There is a single mesh instance. + const auto instances = draco::SceneUtils::ComputeAllInstances(*scene); + ASSERT_EQ(instances.size(), 1); + ASSERT_EQ(instances[MeshInstanceIndex(0)].mesh_index, 0); + + // There is no transformation. + AssertMatrixNear(instances[MeshInstanceIndex(0)].transform, + Eigen::Matrix4d::Identity(), 1e-6); +} + +TEST(SceneUtilsTest, TestNumMeshInstances) { + // Tests that we can compute mesh instance counts for all base meshes in an + // input scene. + + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 4); + + const auto num_mesh_instances = draco::SceneUtils::NumMeshInstances(*scene); + ASSERT_EQ(num_mesh_instances.size(), 4); + ASSERT_EQ(num_mesh_instances[draco::MeshIndex(0)], 1); + ASSERT_EQ(num_mesh_instances[draco::MeshIndex(1)], 1); + ASSERT_EQ(num_mesh_instances[draco::MeshIndex(2)], 1); + ASSERT_EQ(num_mesh_instances[draco::MeshIndex(3)], 2); +} + +TEST(SceneUtilsTest, TestNumFacesOnScene) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(draco::SceneUtils::NumFacesOnBaseMeshes(*scene), 2856); + ASSERT_EQ(draco::SceneUtils::NumFacesOnInstancedMeshes(*scene), 3624); +} + +TEST(SceneUtilsTest, TestNumPointsOnScene) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(draco::SceneUtils::NumPointsOnBaseMeshes(*scene), 2978); + ASSERT_EQ(draco::SceneUtils::NumPointsOnInstancedMeshes(*scene), 3564); +} + +TEST(SceneUtilsTest, TestNumPositionsOnScene) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(draco::SceneUtils::NumAttEntriesOnBaseMeshes( + *scene, draco::GeometryAttribute::POSITION), + 1572); + ASSERT_EQ(draco::SceneUtils::NumAttEntriesOnInstancedMeshes( + *scene, draco::GeometryAttribute::POSITION), + 1960); +} + +TEST(SceneUtilsTest, TestNumNormalsOnScene) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(draco::SceneUtils::NumAttEntriesOnBaseMeshes( + *scene, draco::GeometryAttribute::NORMAL), + 1252); + ASSERT_EQ(draco::SceneUtils::NumAttEntriesOnInstancedMeshes( + *scene, draco::GeometryAttribute::NORMAL), + 1612); +} + +TEST(SceneUtilsTest, TestNumColorsOnScene) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(draco::SceneUtils::NumAttEntriesOnBaseMeshes( + *scene, draco::GeometryAttribute::COLOR), + 0); + ASSERT_EQ(draco::SceneUtils::NumAttEntriesOnInstancedMeshes( + *scene, draco::GeometryAttribute::COLOR), + 0); +} + +TEST(SceneUtilsTest, TestComputeBoundingBox) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + const draco::BoundingBox bbox = draco::SceneUtils::ComputeBoundingBox(*scene); + const draco::Vector3f min_point = bbox.GetMinPoint(); + const draco::Vector3f max_point = bbox.GetMaxPoint(); + constexpr float tolerance = 1e-4f; + EXPECT_NEAR(min_point[0], -2.43091, tolerance); + EXPECT_NEAR(min_point[1], +0.00145, tolerance); + EXPECT_NEAR(min_point[2], -1.39600, tolerance); + EXPECT_NEAR(max_point[0], +2.43800, tolerance); + EXPECT_NEAR(max_point[1], +2.58437, tolerance); + EXPECT_NEAR(max_point[2], +1.39600, tolerance); +} + +TEST(SceneUtilsTest, TestComputeMeshInstanceBoundingBox) { + auto scene = draco::ReadSceneFromTestFile( + "SphereWithCircleTexture/sphere_with_circle_texture.gltf"); + ASSERT_NE(scene, nullptr); + const draco::BoundingBox scene_bbox = + draco::SceneUtils::ComputeBoundingBox(*scene); + const auto instances = draco::SceneUtils::ComputeAllInstances(*scene); + ASSERT_EQ(instances.size(), 1); + const draco::BoundingBox mesh_bbox = + draco::SceneUtils::ComputeMeshInstanceBoundingBox( + *scene, instances[draco::MeshInstanceIndex(0)]); + ASSERT_EQ(scene_bbox.GetMinPoint(), mesh_bbox.GetMinPoint()); + ASSERT_EQ(scene_bbox.GetMaxPoint(), mesh_bbox.GetMaxPoint()); +} + +TEST(SceneUtilsTest, TestMeshToSceneZeroMaterials) { + const std::string filename = "cube_att.obj"; + std::unique_ptr mesh = draco::ReadMeshFromTestFile(filename); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 0); + + DRACO_ASSIGN_OR_ASSERT(const std::unique_ptr scene_from_mesh, + draco::SceneUtils::MeshToScene(std::move(mesh))); + ASSERT_NE(scene_from_mesh, nullptr); + ASSERT_EQ(scene_from_mesh->NumMeshes(), 1); + ASSERT_EQ(scene_from_mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(scene_from_mesh->NumMeshGroups(), 1); + const draco::MeshGroup *const mesh_group = + scene_from_mesh->GetMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(mesh_group->NumMeshInstances(), 1); +} + +TEST(SceneUtilsTest, TestMeshToSceneOneMaterial) { + const std::string filename = + "SphereWithCircleTexture/sphere_with_circle_texture.gltf"; + auto scene = draco::ReadSceneFromTestFile(filename); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->GetMaterialLibrary().NumMaterials(), 1); + + std::unique_ptr mesh = draco::ReadMeshFromTestFile(filename); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1); + + DRACO_ASSIGN_OR_ASSERT(const std::unique_ptr scene_from_mesh, + draco::SceneUtils::MeshToScene(std::move(mesh))); + ASSERT_NE(scene_from_mesh, nullptr); + ASSERT_EQ(scene_from_mesh->NumMeshes(), 1); + ASSERT_EQ(scene_from_mesh->GetMaterialLibrary().NumMaterials(), 1); + ASSERT_EQ(scene_from_mesh->NumMeshGroups(), 1); + const draco::MeshGroup *const mesh_group = + scene_from_mesh->GetMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(mesh_group->NumMeshInstances(), 1); + + CompareScenes(scene.get(), scene_from_mesh.get()); +} + +TEST(SceneUtilsTest, TestMeshToSceneMultipleMaterials) { + const std::string filename = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + auto scene = draco::ReadSceneFromTestFile(filename); + ASSERT_NE(scene, nullptr); + + std::unique_ptr mesh = draco::ReadMeshFromTestFile(filename); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 4); + + DRACO_ASSIGN_OR_ASSERT(const std::unique_ptr scene_from_mesh, + draco::SceneUtils::MeshToScene(std::move(mesh))); + ASSERT_NE(scene_from_mesh, nullptr); + ASSERT_EQ(scene_from_mesh->NumMeshes(), 4); + ASSERT_EQ(scene_from_mesh->GetMaterialLibrary().NumMaterials(), 4); + ASSERT_EQ(scene_from_mesh->NumMeshGroups(), 1); + const draco::MeshGroup *const mesh_group = + scene_from_mesh->GetMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(mesh_group->NumMeshInstances(), 4); + + // Unfortunately we can't CompareScenes(scene.get(), scene_from_mesh.get()), + // because scene has two mesh groups and scene_from_mesh has only one. +} + +TEST(SceneUtilsTest, TestMeshToSceneMultipleMeshFeatures) { + const std::string filename = "BoxesMeta/glTF/BoxesMeta.gltf"; + std::unique_ptr scene = draco::ReadSceneFromTestFile(filename); + ASSERT_NE(scene, nullptr); + std::unique_ptr mesh = draco::ReadMeshFromTestFile(filename); + ASSERT_NE(mesh, nullptr); + ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 2); + ASSERT_EQ(mesh->NumMeshFeatures(), 5); + + DRACO_ASSIGN_OR_ASSERT(const std::unique_ptr scene_from_mesh, + draco::SceneUtils::MeshToScene(std::move(mesh))); + ASSERT_NE(scene_from_mesh, nullptr); + ASSERT_EQ(scene_from_mesh->NumMeshes(), 2); + ASSERT_EQ(scene_from_mesh->GetMaterialLibrary().NumMaterials(), 2); + ASSERT_EQ(scene_from_mesh->NumMeshGroups(), 1); + const draco::MeshGroup *const mesh_group = + scene_from_mesh->GetMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(mesh_group->NumMeshInstances(), 2); + + // Meshes of the new scene should have the same properties as meshes loaded + // directly into |scene|. + for (draco::MeshIndex mi(0); mi < scene->NumMeshes(); ++mi) { + ASSERT_EQ(scene->GetMesh(mi).NumMeshFeatures(), + scene_from_mesh->GetMesh(mi).NumMeshFeatures()); + for (draco::MeshFeaturesIndex mfi(0); + mfi < scene->GetMesh(mi).NumMeshFeatures(); ++mfi) { + const auto &scene_mf = scene->GetMesh(mi).GetMeshFeatures(mfi); + const auto &scene_from_mesh_mf = + scene_from_mesh->GetMesh(mi).GetMeshFeatures(mfi); + ASSERT_EQ(scene_mf.GetAttributeIndex(), + scene_from_mesh_mf.GetAttributeIndex()); + ASSERT_EQ(scene_mf.GetPropertyTableIndex(), + scene_from_mesh_mf.GetPropertyTableIndex()); + ASSERT_EQ(scene_mf.GetLabel(), scene_from_mesh_mf.GetLabel()); + ASSERT_EQ(scene_mf.GetNullFeatureId(), + scene_from_mesh_mf.GetNullFeatureId()); + ASSERT_EQ(scene_mf.GetFeatureCount(), + scene_from_mesh_mf.GetFeatureCount()); + ASSERT_EQ(scene_mf.GetTextureChannels(), + scene_from_mesh_mf.GetTextureChannels()); + ASSERT_EQ(scene_mf.GetTextureMap().texture() != nullptr, + scene_from_mesh_mf.GetTextureMap().texture() != nullptr); + } + } +} + +TEST(SceneUtilsTest, TestInstantiateMeshWithIdentityTransformation) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + + // Compute scene mesh instances. + const auto instances = draco::SceneUtils::ComputeAllInstances(*scene); + ASSERT_EQ(instances.size(), 5); + + // Check instantiation of mesh with identity transformation. + const draco::SceneUtils::MeshInstance instance = + instances[MeshInstanceIndex(0)]; + ASSERT_EQ(instance.transform, Eigen::Matrix4d::Identity()); + + // Instantiate this mesh instance. + DRACO_ASSIGN_OR_ASSERT(auto mesh, + draco::SceneUtils::InstantiateMesh(*scene, instance)); + const draco::Mesh &base_mesh = scene->GetMesh(instance.mesh_index); + + // Check that bounding box of the instanced mesh is same as box of base mesh. + const draco::BoundingBox instanced_bbox = mesh->ComputeBoundingBox(); + const draco::BoundingBox base_bbox = base_mesh.ComputeBoundingBox(); + ASSERT_EQ(instanced_bbox.GetMinPoint()[0], base_bbox.GetMinPoint()[0]); + ASSERT_EQ(instanced_bbox.GetMinPoint()[1], base_bbox.GetMinPoint()[1]); + ASSERT_EQ(instanced_bbox.GetMinPoint()[2], base_bbox.GetMinPoint()[2]); + ASSERT_EQ(instanced_bbox.GetMaxPoint()[0], base_bbox.GetMaxPoint()[0]); + ASSERT_EQ(instanced_bbox.GetMaxPoint()[1], base_bbox.GetMaxPoint()[1]); + ASSERT_EQ(instanced_bbox.GetMaxPoint()[2], base_bbox.GetMaxPoint()[2]); +} + +TEST(SceneUtilsTest, TestInstantiateMesh) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + + // Compute scene mesh instances. + const auto instances = draco::SceneUtils::ComputeAllInstances(*scene); + ASSERT_EQ(instances.size(), 5); + + // Check instantiation of mesh with identity transformation. + const draco::SceneUtils::MeshInstance instance = + instances[MeshInstanceIndex(3)]; + ASSERT_NE(instance.transform, Eigen::Matrix4d::Identity()); + + // Instantiate this mesh instance. + DRACO_ASSIGN_OR_ASSERT(auto mesh, + draco::SceneUtils::InstantiateMesh(*scene, instance)); + const draco::Mesh &base_mesh = scene->GetMesh(instance.mesh_index); + + // Check bounding box of the base mesh. + constexpr float tolerance = 1e-4f; + const draco::BoundingBox base_bbox = base_mesh.ComputeBoundingBox(); + EXPECT_NEAR(base_bbox.GetMinPoint()[0], -0.42780, tolerance); + EXPECT_NEAR(base_bbox.GetMinPoint()[1], -0.42780, tolerance); + EXPECT_NEAR(base_bbox.GetMinPoint()[2], -1.05800, tolerance); + EXPECT_NEAR(base_bbox.GetMaxPoint()[0], +0.42780, tolerance); + EXPECT_NEAR(base_bbox.GetMaxPoint()[1], +0.42780, tolerance); + EXPECT_NEAR(base_bbox.GetMaxPoint()[2], +1.05800, tolerance); + + // Check bounding box of the instanced mesh. It should differ. + const draco::BoundingBox instanced_bbox = mesh->ComputeBoundingBox(); + EXPECT_NEAR(instanced_bbox.GetMinPoint()[0], -1.77860, tolerance); + EXPECT_NEAR(instanced_bbox.GetMinPoint()[1], +0.00145, tolerance); + EXPECT_NEAR(instanced_bbox.GetMinPoint()[2], -1.05800, tolerance); + EXPECT_NEAR(instanced_bbox.GetMaxPoint()[0], -0.92606, tolerance); + EXPECT_NEAR(instanced_bbox.GetMaxPoint()[1], +0.85399, tolerance); + EXPECT_NEAR(instanced_bbox.GetMaxPoint()[2], +1.05800, tolerance); +} + +TEST(SceneUtilsTest, TestCleanupEmptyMeshGroup) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 4); + ASSERT_EQ(scene->NumMeshGroups(), 2); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 5); + ASSERT_EQ(scene->GetNode(draco::SceneNodeIndex(0))->GetMeshGroupIndex(), + draco::MeshGroupIndex(0)); + + // Invalidate references to the three truck body parts in mesh group. + draco::MeshGroup &mesh_group = *scene->GetMeshGroup(draco::MeshGroupIndex(0)); + mesh_group.SetMeshInstance(0, {draco::kInvalidMeshIndex, 0}); + mesh_group.SetMeshInstance(1, {draco::kInvalidMeshIndex, 0}); + mesh_group.SetMeshInstance(2, {draco::kInvalidMeshIndex, 0}); + + // Cleanup scene. + draco::SceneUtils::Cleanup(scene.get()); + + // Check cleaned up scene. + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 1); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 2); + ASSERT_EQ(scene->GetNode(draco::SceneNodeIndex(0))->GetMeshGroupIndex(), + draco::kInvalidMeshGroupIndex); +} + +TEST(SceneUtilsTest, TestCleanupUnreferencedMeshGroup) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 4); + ASSERT_EQ(scene->NumMeshGroups(), 2); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 5); + + // Invalidate references to truck axle mesh group. + scene->GetNode(draco::SceneNodeIndex(2)) + ->SetMeshGroupIndex(draco::kInvalidMeshGroupIndex); + scene->GetNode(draco::SceneNodeIndex(4)) + ->SetMeshGroupIndex(draco::kInvalidMeshGroupIndex); + + // Cleanup scene. + draco::SceneUtils::Cleanup(scene.get()); + + // Check cleaned up scene. + ASSERT_EQ(scene->NumMeshes(), 3); + ASSERT_EQ(scene->NumMeshGroups(), 1); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 3); +} + +TEST(SceneUtilsTest, TestCleanupInvalidMeshIndex) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 4); + ASSERT_EQ(scene->NumMeshGroups(), 2); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 5); + ASSERT_EQ(scene->GetNode(draco::SceneNodeIndex(0))->GetMeshGroupIndex(), + draco::MeshGroupIndex(0)); + + // Invalidate references to two truck body parts in mesh group. + draco::MeshGroup &mesh_group = *scene->GetMeshGroup(draco::MeshGroupIndex(0)); + ASSERT_EQ(mesh_group.NumMeshInstances(), 3); + mesh_group.SetMeshInstance(0, {draco::kInvalidMeshIndex, 0}); + mesh_group.SetMeshInstance(2, {draco::kInvalidMeshIndex, 0}); + + // Cleanup scene. + draco::SceneUtils::Cleanup(scene.get()); + + // Check cleaned up scene. + ASSERT_EQ(scene->NumMeshes(), 2); + ASSERT_EQ(scene->NumMeshGroups(), 2); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 3); + ASSERT_EQ(scene->GetMeshGroup(draco::MeshGroupIndex(0))->NumMeshInstances(), + 1); +} + +TEST(SceneUtilsTest, TestCleanupUnusedNodes) { + auto scene = + draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumNodes(), 5); + + draco::SceneUtils::CleanupOptions options; + options.remove_unused_nodes = true; + + // Delete mesh on node 2 and try to remove unused nodes. + // Node 2 is connected to node 1 that has no mesh as well. But node 2 is also + // used in an animation so we don't actually expect anything to be deleted. + scene->GetNode(draco::SceneNodeIndex(2)) + ->SetMeshGroupIndex(draco::kInvalidMeshGroupIndex); + draco::SceneUtils::Cleanup(scene.get(), options); + + ASSERT_EQ(scene->NumNodes(), 5); + + // Now remove the animation channel that used the node and try it again. This + // time, we expect two nodes to be deleted (node 1 and node 2). Node 1 will be + // deleted because it doesn't contain a mesh and all its children are unused. + ASSERT_EQ(scene->GetAnimation(draco::AnimationIndex(0)) + ->GetChannel(0) + ->target_index, + 2); + // Change the mapped node to node 4 (we can't actually remove channel as of + // the time this test was written). + scene->GetAnimation(draco::AnimationIndex(0))->GetChannel(0)->target_index = + 4; + + // Cleanup again. + draco::SceneUtils::Cleanup(scene.get(), options); + ASSERT_EQ(scene->NumNodes(), 3); // Two nodes should be deleted. + + // Ensure all node indices are remapped to the new values. + for (draco::SceneNodeIndex sni(0); sni < scene->NumNodes(); ++sni) { + const auto *node = scene->GetNode(sni); + for (int i = 0; i < node->NumChildren(); ++i) { + ASSERT_LT(node->Child(i).value(), 3); + } + for (int i = 0; i < node->NumParents(); ++i) { + ASSERT_LT(node->Parent(i).value(), 3); + } + } + + // Ensure the animation channels are mapped to the updated node indices (node + // 4 should be new node 2 because two nodes were removed). + ASSERT_EQ(scene->GetAnimation(draco::AnimationIndex(0)) + ->GetChannel(0) + ->target_index, + 2); +} + +TEST(SceneUtilsTest, TestDeduplicateMeshGroups) { + // Input scene has four different mesh groups but only two of them should + // contain unique set of meshes. + auto scene = + draco::ReadSceneFromTestFile("DuplicateMeshes/duplicate_meshes.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 4); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 7); + + draco::SceneUtils::DeduplicateMeshGroups(scene.get()); + + // Check deduplicated scene. + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(scene->NumMeshGroups(), 2); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 7); +} + +TEST(SceneUtilsTest, TestCleanupUnusedTexCoordsNoTextures) { + // The glTF file has two tex coords that are unused because the materials do + // not reference any textures. + auto scene = draco::ReadSceneFromTestFile("UnusedTexCoords/NoTextures.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->GetMesh(draco::MeshIndex(0)) + .NumNamedAttributes(draco::GeometryAttribute::TEX_COORD), + 2); + + // Cleanup scene and check that unused UV are not removed by default. + draco::SceneUtils::Cleanup(scene.get()); + ASSERT_EQ(scene->GetMesh(draco::MeshIndex(0)) + .NumNamedAttributes(draco::GeometryAttribute::TEX_COORD), + 2); + + // Cleanup scene and check that unused UV are removed when requested. + draco::SceneUtils::CleanupOptions options; + options.remove_unused_tex_coords = true; + draco::SceneUtils::Cleanup(scene.get(), options); + ASSERT_EQ(scene->GetMesh(draco::MeshIndex(0)) + .NumNamedAttributes(draco::GeometryAttribute::TEX_COORD), + 0); +} + +TEST(SceneUtilsTest, TestCleanupUnusedTexCoords0NoReferences) { + auto scene = draco::ReadSceneFromTestFile( + "UnusedTexCoords/TexCoord0InvalidTexCoord1Valid.gltf"); + ASSERT_NE(scene, nullptr); + typedef draco::GeometryAttribute Att; + + draco::Mesh &mesh = scene->GetMesh(draco::MeshIndex(0)); + ASSERT_EQ(mesh.NumNamedAttributes(Att::TEX_COORD), 2); + ASSERT_EQ(mesh.GetNamedAttribute(Att::TEX_COORD, 0)->size(), 14); + ASSERT_EQ(mesh.GetNamedAttribute(Att::TEX_COORD, 1)->size(), 4); + auto &ml = scene->GetMaterialLibrary(); + ASSERT_EQ(ml.NumMaterials(), 1); + ASSERT_EQ(ml.GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(ml.GetMaterial(0)->GetTextureMapByIndex(0)->tex_coord_index(), 1); + + // Cleanup unused texture coordinate attributes. + draco::SceneUtils::CleanupOptions options; + options.remove_unused_tex_coords = true; + draco::SceneUtils::Cleanup(scene.get(), options); + + // Check that the unreferenced attribute was removed. + ASSERT_EQ(mesh.NumNamedAttributes(Att::TEX_COORD), 1); + ASSERT_EQ(mesh.GetNamedAttribute(Att::TEX_COORD, 0)->size(), 4); + ASSERT_EQ(ml.NumMaterials(), 1); + ASSERT_EQ(ml.GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(ml.GetMaterial(0)->GetTextureMapByIndex(0)->tex_coord_index(), 0); +} + +TEST(SceneUtilsTest, TestCleanupUnusedTexCoords1NoReferences) { + auto scene = draco::ReadSceneFromTestFile( + "UnusedTexCoords/TexCoord0ValidTexCoord1Invalid.gltf"); + ASSERT_NE(scene, nullptr); + typedef draco::GeometryAttribute Att; + + draco::Mesh &mesh = scene->GetMesh(draco::MeshIndex(0)); + ASSERT_EQ(mesh.NumNamedAttributes(Att::TEX_COORD), 2); + ASSERT_EQ(mesh.GetNamedAttribute(Att::TEX_COORD, 0)->size(), 14); + ASSERT_EQ(mesh.GetNamedAttribute(Att::TEX_COORD, 1)->size(), 4); + auto &ml = scene->GetMaterialLibrary(); + ASSERT_EQ(ml.NumMaterials(), 1); + ASSERT_EQ(ml.GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(ml.GetMaterial(0)->GetTextureMapByIndex(0)->tex_coord_index(), 0); + + // Cleanup unused texture coordinate attributes. + draco::SceneUtils::CleanupOptions options; + options.remove_unused_tex_coords = true; + draco::SceneUtils::Cleanup(scene.get(), options); + + // Check that the unreferenced attribute was removed. + ASSERT_EQ(mesh.NumNamedAttributes(Att::TEX_COORD), 1); + ASSERT_EQ(mesh.GetNamedAttribute(Att::TEX_COORD, 0)->size(), 14); + ASSERT_EQ(ml.NumMaterials(), 1); + ASSERT_EQ(ml.GetMaterial(0)->NumTextureMaps(), 1); + ASSERT_EQ(ml.GetMaterial(0)->GetTextureMapByIndex(0)->tex_coord_index(), 0); +} + +TEST(SceneUtilsTest, TestComputeGlobalNodeTransform) { + // Tests that we can compute global transformation of scene nodes. + + auto scene = draco::ReadSceneFromTestFile("simple_skin.gltf"); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumNodes(), 3); + + // Compute and check global node transforms. + constexpr float kTolerance = 1e-6; + // clang-format off + AssertMatrixNear(draco::SceneUtils::ComputeGlobalNodeTransform( + *scene, draco::SceneNodeIndex(0)), + Eigen::Matrix4d::Identity(), + kTolerance); + AssertMatrixNear(draco::SceneUtils::ComputeGlobalNodeTransform( + *scene, draco::SceneNodeIndex(1)), + Eigen::Matrix4d{{1.0, 0.0, 0.0, 0.0}, + {0.0, 1.0, 0.0, 1.0}, + {0.0, 0.0, 1.0, 0.0}, + {0.0, 0.0, 0.0, 1.0}}, + kTolerance); + AssertMatrixNear(draco::SceneUtils::ComputeGlobalNodeTransform( + *scene, draco::SceneNodeIndex(2)), + Eigen::Matrix4d{{1.0, 0.0, 0.0, 0.0}, + {0.0, 1.0, 0.0, 1.0}, + {0.0, 0.0, 1.0, 0.0}, + {0.0, 0.0, 0.0, 1.0}}, + kTolerance); + // clang-format on +} + +TEST(SceneUtilsTest, TestIsDracoCompressionEnabled) { + // Tests that we can determine whether any of the scene meshes have geometry + // compression enabled. + const std::string file = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + auto scene = draco::ReadSceneFromTestFile(file); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 4); + + // Check that the scene has geometry compression disabled by default. + ASSERT_FALSE(draco::SceneUtils::IsDracoCompressionEnabled(*scene)); + + // Check that geometry compression can be enabled. + scene->GetMesh(MeshIndex(2)).SetCompressionEnabled(true); + ASSERT_TRUE(draco::SceneUtils::IsDracoCompressionEnabled(*scene)); +} + +TEST(SceneUtilsTest, TestSetDracoCompressionOptions) { + // Tests that geometry compression settings can be set for all scene meshes. + const std::string file = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf"; + auto scene = draco::ReadSceneFromTestFile(file); + ASSERT_NE(scene, nullptr); + ASSERT_EQ(scene->NumMeshes(), 4); + + // Check that compression is initially disabled for all scene meshes. + ASSERT_FALSE(scene->GetMesh(MeshIndex(0)).IsCompressionEnabled()); + ASSERT_FALSE(scene->GetMesh(MeshIndex(1)).IsCompressionEnabled()); + ASSERT_FALSE(scene->GetMesh(MeshIndex(2)).IsCompressionEnabled()); + ASSERT_FALSE(scene->GetMesh(MeshIndex(3)).IsCompressionEnabled()); + + // Check that initially all scene meshes have default compression options. + draco::DracoCompressionOptions defaults; + ASSERT_EQ(scene->GetMesh(MeshIndex(0)).GetCompressionOptions(), defaults); + ASSERT_EQ(scene->GetMesh(MeshIndex(1)).GetCompressionOptions(), defaults); + ASSERT_EQ(scene->GetMesh(MeshIndex(2)).GetCompressionOptions(), defaults); + ASSERT_EQ(scene->GetMesh(MeshIndex(3)).GetCompressionOptions(), defaults); + + // Check geometry compression options can be set to all scene meshes and that + // this also enables compression for all scnene meshes. + draco::DracoCompressionOptions options; + options.compression_level = 10; + options.quantization_bits_normal = 12; + draco::SceneUtils::SetDracoCompressionOptions(&options, scene.get()); + ASSERT_TRUE(scene->GetMesh(MeshIndex(0)).IsCompressionEnabled()); + ASSERT_TRUE(scene->GetMesh(MeshIndex(1)).IsCompressionEnabled()); + ASSERT_TRUE(scene->GetMesh(MeshIndex(2)).IsCompressionEnabled()); + ASSERT_TRUE(scene->GetMesh(MeshIndex(3)).IsCompressionEnabled()); + ASSERT_EQ(scene->GetMesh(MeshIndex(0)).GetCompressionOptions(), options); + ASSERT_EQ(scene->GetMesh(MeshIndex(1)).GetCompressionOptions(), options); + ASSERT_EQ(scene->GetMesh(MeshIndex(2)).GetCompressionOptions(), options); + ASSERT_EQ(scene->GetMesh(MeshIndex(3)).GetCompressionOptions(), options); + + // Check that geometry compression can be disabled for all scene meshes. + draco::SceneUtils::SetDracoCompressionOptions(nullptr, scene.get()); + ASSERT_FALSE(scene->GetMesh(MeshIndex(0)).IsCompressionEnabled()); + ASSERT_FALSE(scene->GetMesh(MeshIndex(1)).IsCompressionEnabled()); + ASSERT_FALSE(scene->GetMesh(MeshIndex(2)).IsCompressionEnabled()); + ASSERT_FALSE(scene->GetMesh(MeshIndex(3)).IsCompressionEnabled()); +} + +TEST(SceneUtilsTest, TestFindLargestBaseMeshTransforms) { + // Tests that FindLargestBaseMeshTransforms() works as expected. + auto scene = + draco::ReadSceneFromTestFile("CubeScaledInstances/glTF/cube_att.gltf"); + ASSERT_NE(scene, nullptr); + + // There should be one base mesh with four instances. + ASSERT_EQ(scene->NumMeshes(), 1); + ASSERT_EQ(draco::SceneUtils::ComputeAllInstances(*scene).size(), 4); + + const auto transforms = + draco::SceneUtils::FindLargestBaseMeshTransforms(*scene); + + ASSERT_EQ(transforms.size(), 1); // One transform for the single base mesh. + + // The largest instance should have a uniform scale 4. + const draco::MeshIndex mi(0); + ASSERT_EQ(transforms[mi].diagonal(), Eigen::Vector4d(4, 4, 4, 1)); +} + +} // namespace + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/scene/trs_matrix.cc b/contrib/draco/src/draco/scene/trs_matrix.cc new file mode 100644 index 000000000..6e6dac251 --- /dev/null +++ b/contrib/draco/src/draco/scene/trs_matrix.cc @@ -0,0 +1,102 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/trs_matrix.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +void TrsMatrix::Copy(const TrsMatrix &tm) { + matrix_ = tm.matrix_; + translation_ = tm.translation_; + rotation_ = tm.rotation_; + scale_ = tm.scale_; + matrix_set_ = tm.matrix_set_; + translation_set_ = tm.translation_set_; + rotation_set_ = tm.rotation_set_; + scale_set_ = tm.scale_set_; +} + +Eigen::Matrix4d TrsMatrix::ComputeTransformationMatrix() const { + // Return transformation matrix if it has been set. + if (matrix_set_) { + return matrix_; + } + + // Populate translation matrix. + Eigen::Matrix4d translation_matrix = Eigen::Matrix4d::Identity(); + translation_matrix(0, 3) = translation_[0]; + translation_matrix(1, 3) = translation_[1]; + translation_matrix(2, 3) = translation_[2]; + + // Populate rotation matrix using rotation quaternion. + Eigen::Matrix3d rotation_matrix_3 = rotation_.normalized().toRotationMatrix(); + + // Convert the 3x3 matrix to a 4x4 matrix that can be multiplied with the + // other TRS matrices. + Eigen::Matrix4d rotation_matrix = Eigen::Matrix4d::Identity(); + rotation_matrix.block<3, 3>(0, 0) = rotation_matrix_3; + + // Populate scale matrix. + const Eigen::Matrix4d scale_matrix( + Eigen::Vector4d(scale_.x(), scale_.y(), scale_.z(), 1.0).asDiagonal()); + + // Return transformation matrix computed by combining TRS matrices. + return translation_matrix * rotation_matrix * scale_matrix; +} + +bool TrsMatrix::IsMatrixIdentity() const { + if (!matrix_set_) { + return true; + } + return matrix_ == Eigen::Matrix4d::Identity(); +} + +bool TrsMatrix::IsMatrixTranslationOnly() const { + if (!matrix_set_) { + return false; + } + Eigen::Matrix4d translation_check = matrix_; + translation_check(0, 3) = 0.0; + translation_check(1, 3) = 0.0; + translation_check(2, 3) = 0.0; + return translation_check == Eigen::Matrix4d::Identity(); +} + +bool TrsMatrix::operator==(const TrsMatrix &trs_matrix) const { + if (matrix_set_ != trs_matrix.matrix_set_ || + translation_set_ != trs_matrix.translation_set_ || + rotation_set_ != trs_matrix.rotation_set_ || + scale_set_ != trs_matrix.scale_set_) { + return false; + } + if (matrix_set_ && matrix_ != trs_matrix.matrix_) { + return false; + } + if (translation_set_ && translation_ != trs_matrix.translation_) { + return false; + } + if (rotation_set_ && rotation_ != trs_matrix.rotation_) { + return false; + } + if (scale_set_ && scale_set_ != trs_matrix.scale_set_) { + return false; + } + return true; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/scene/trs_matrix.h b/contrib/draco/src/draco/scene/trs_matrix.h new file mode 100644 index 000000000..6c2ab7388 --- /dev/null +++ b/contrib/draco/src/draco/scene/trs_matrix.h @@ -0,0 +1,124 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_SCENE_TRS_MATRIX_H_ +#define DRACO_SCENE_TRS_MATRIX_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "Eigen/Geometry" +#include "draco/core/status_or.h" + +namespace draco { + +// This class is used to store one or more of a translation, rotation, scale +// vectors or a transformation matrix. +class TrsMatrix { + public: + TrsMatrix() + : matrix_(Eigen::Matrix4d::Identity()), + translation_(0.0, 0.0, 0.0), + rotation_(1.0, 0.0, 0.0, 0.0), // (w, x, y, z) + scale_(1.0, 1.0, 1.0), + matrix_set_(false), + translation_set_(false), + rotation_set_(false), + scale_set_(false) {} + + void Copy(const TrsMatrix &tm); + + void SetMatrix(const Eigen::Matrix4d &matrix) { + matrix_ = matrix; + matrix_set_ = true; + } + bool MatrixSet() const { return matrix_set_; } + StatusOr Matrix() const { + if (!matrix_set_) { + return Status(Status::DRACO_ERROR, "Matrix is not set."); + } + return matrix_; + } + + void SetTranslation(const Eigen::Vector3d &translation) { + translation_ = translation; + translation_set_ = true; + } + bool TranslationSet() const { return translation_set_; } + StatusOr Translation() const { + if (!translation_set_) { + return Status(Status::DRACO_ERROR, "Translation is not set."); + } + return translation_; + } + + void SetRotation(const Eigen::Quaterniond &rotation) { + rotation_ = rotation; + rotation_set_ = true; + } + bool RotationSet() const { return rotation_set_; } + StatusOr Rotation() const { + if (!rotation_set_) { + return Status(Status::DRACO_ERROR, "Rotation is not set."); + } + return rotation_; + } + + void SetScale(const Eigen::Vector3d &scale) { + scale_ = scale; + scale_set_ = true; + } + bool ScaleSet() const { return scale_set_; } + StatusOr Scale() const { + if (!scale_set_) { + return Status(Status::DRACO_ERROR, "Scale is not set."); + } + return scale_; + } + + // Returns true if the matrix is not set or if matrix is set and is equal to + // identity. + bool IsMatrixIdentity() const; + + // Returns true if matrix is set and only the translation elements may differ + // from identity. Returns false if matrix is not set. + bool IsMatrixTranslationOnly() const; + + // Returns transformation matrix if it has been set. Otherwise, computes + // transformation matrix from TRS vectors and returns it. + Eigen::Matrix4d ComputeTransformationMatrix() const; + + // Returns a boolean indicating whether any of the transforms have been set. + // Can be used to check whether this object represents a default transform. + bool TransformSet() const { + return matrix_set_ || translation_set_ || rotation_set_ || scale_set_; + } + + bool operator==(const TrsMatrix &trs_matrix) const; + + private: + Eigen::Matrix4d matrix_; + Eigen::Vector3d translation_; + Eigen::Quaterniond rotation_; + Eigen::Vector3d scale_; + bool matrix_set_; + bool translation_set_; + bool rotation_set_; + bool scale_set_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_SCENE_TRS_MATRIX_H_ diff --git a/contrib/draco/src/draco/scene/trs_matrix_test.cc b/contrib/draco/src/draco/scene/trs_matrix_test.cc new file mode 100644 index 000000000..d7938e974 --- /dev/null +++ b/contrib/draco/src/draco/scene/trs_matrix_test.cc @@ -0,0 +1,79 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/scene/trs_matrix.h" + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" + +namespace { + +#ifdef DRACO_TRANSCODER_SUPPORTED + +TEST(TrsMatrixTest, TestIsMatrixIdentity) { + draco::TrsMatrix trs; + ASSERT_EQ(trs.MatrixSet(), false); + ASSERT_EQ(trs.IsMatrixIdentity(), true); + + // clang-format off + Eigen::Matrix4d matrix; + matrix << 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16; + // clang-format on + trs.SetMatrix(matrix); + ASSERT_EQ(trs.MatrixSet(), true); + ASSERT_EQ(trs.IsMatrixIdentity(), false); + + trs.SetMatrix(Eigen::Matrix4d::Identity()); + ASSERT_EQ(trs.MatrixSet(), true); + ASSERT_EQ(trs.IsMatrixIdentity(), true); +} + +TEST(TrsMatrixTest, TestIsMatrixTranslationOnly) { + draco::TrsMatrix trs; + ASSERT_EQ(trs.MatrixSet(), false); + ASSERT_EQ(trs.IsMatrixTranslationOnly(), false); + + trs.SetMatrix(Eigen::Matrix4d::Identity()); + ASSERT_EQ(trs.MatrixSet(), true); + ASSERT_EQ(trs.IsMatrixTranslationOnly(), true); + + // clang-format off + Eigen::Matrix4d matrix; + matrix << 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16; + // clang-format on + trs.SetMatrix(matrix); + ASSERT_EQ(trs.MatrixSet(), true); + ASSERT_EQ(trs.IsMatrixTranslationOnly(), false); + + // clang-format off + Eigen::Matrix4d translation; + translation << 1, 0, 0, 1, + 0, 1, 0, 2, + 0, 0, 1, 3, + 0, 0, 0, 1; + // clang-format on + trs.SetMatrix(translation); + ASSERT_EQ(trs.MatrixSet(), true); + ASSERT_EQ(trs.IsMatrixTranslationOnly(), true); +} + +#endif // DRACO_TRANSCODER_SUPPORTED + +} // namespace diff --git a/contrib/draco/src/draco/texture/source_image.cc b/contrib/draco/src/draco/texture/source_image.cc new file mode 100644 index 000000000..b4d493250 --- /dev/null +++ b/contrib/draco/src/draco/texture/source_image.cc @@ -0,0 +1,29 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/texture/source_image.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +void SourceImage::Copy(const SourceImage &src) { + mime_type_ = src.mime_type_; + filename_ = src.filename_; + encoded_data_ = src.encoded_data_; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/texture/source_image.h b/contrib/draco/src/draco/texture/source_image.h new file mode 100644 index 000000000..5827918e4 --- /dev/null +++ b/contrib/draco/src/draco/texture/source_image.h @@ -0,0 +1,72 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_TEXTURE_SOURCE_IMAGE_H_ +#define DRACO_TEXTURE_SOURCE_IMAGE_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include + +#include "draco/core/status.h" + +namespace draco { + +// This class is used to hold the encoded and decoded data and characteristics +// for an image. In order for the image to contain "valid" encoded data, either +// the |filename_| must point to a valid image file or the |mime_type_| and +// |encoded_data_| must contain valid image data. +class SourceImage { + public: + SourceImage() {} + + // No copy constructors. + SourceImage(const SourceImage &) = delete; + SourceImage &operator=(const SourceImage &) = delete; + // No move constructors. + SourceImage(SourceImage &&) = delete; + SourceImage &operator=(SourceImage &&) = delete; + + void Copy(const SourceImage &src); + + // Sets the name of the source image file. + void set_filename(const std::string &filename) { filename_ = filename; } + const std::string &filename() const { return filename_; } + + void set_mime_type(const std::string &mime_type) { mime_type_ = mime_type; } + const std::string &mime_type() const { return mime_type_; } + + std::vector &MutableEncodedData() { return encoded_data_; } + const std::vector &encoded_data() const { return encoded_data_; } + + private: + // The filename of the image. This field can be empty as long as |mime_type_| + // and |encoded_data_| is not empty. + std::string filename_; + + // The mimetype of the |encoded_data_|. + std::string mime_type_; + + // The encoded data of the image. This field can be empty as long as + // |filename_| is not empty. + std::vector encoded_data_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_TEXTURE_SOURCE_IMAGE_H_ diff --git a/contrib/draco/src/draco/texture/texture.h b/contrib/draco/src/draco/texture/texture.h new file mode 100644 index 000000000..1d3b6e382 --- /dev/null +++ b/contrib/draco/src/draco/texture/texture.h @@ -0,0 +1,46 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_TEXTURE_TEXTURE_H_ +#define DRACO_TEXTURE_TEXTURE_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +#include "draco/io/image_compression_options.h" +#include "draco/texture/source_image.h" + +namespace draco { + +// Texture class storing the source image data. +class Texture { + public: + void Copy(Texture &other) { source_image_.Copy(other.source_image_); } + + void set_source_image(const SourceImage &image) { source_image_.Copy(image); } + const SourceImage &source_image() const { return source_image_; } + SourceImage &source_image() { return source_image_; } + + private: + // If set this is the image that this texture is based from. + SourceImage source_image_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_TEXTURE_TEXTURE_H_ diff --git a/contrib/draco/src/draco/texture/texture_library.cc b/contrib/draco/src/draco/texture/texture_library.cc new file mode 100644 index 000000000..221ff28d4 --- /dev/null +++ b/contrib/draco/src/draco/texture/texture_library.cc @@ -0,0 +1,61 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/texture/texture_library.h" + +#include + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +void TextureLibrary::Copy(const TextureLibrary &src) { + Clear(); + Append(src); +} + +void TextureLibrary::Append(const TextureLibrary &src) { + const size_t old_num_textures = textures_.size(); + textures_.resize(old_num_textures + src.textures_.size()); + for (int i = 0; i < src.textures_.size(); ++i) { + textures_[old_num_textures + i] = std::unique_ptr(new Texture()); + textures_[old_num_textures + i]->Copy(*src.textures_[i]); + } +} + +void TextureLibrary::Clear() { textures_.clear(); } + +int TextureLibrary::PushTexture(std::unique_ptr texture) { + textures_.push_back(std::move(texture)); + return textures_.size() - 1; +} + +std::unordered_map +TextureLibrary::ComputeTextureToIndexMap() const { + std::unordered_map ret; + for (int i = 0; i < textures_.size(); ++i) { + ret[textures_[i].get()] = i; + } + return ret; +} + +std::unique_ptr TextureLibrary::RemoveTexture(int index) { + std::unique_ptr ret = std::move(textures_[index]); + textures_.erase(textures_.begin() + index); + return ret; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/texture/texture_library.h b/contrib/draco/src/draco/texture/texture_library.h new file mode 100644 index 000000000..a377d8fbc --- /dev/null +++ b/contrib/draco/src/draco/texture/texture_library.h @@ -0,0 +1,67 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_TEXTURE_TEXTURE_LIBRARY_H_ +#define DRACO_TEXTURE_TEXTURE_LIBRARY_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include + +#include "draco/texture/texture.h" + +namespace draco { + +// Container class for storing draco::Texture objects in an indexed list. +class TextureLibrary { + public: + // Copies textures from the source library to this library. Order of the + // copied textures is preserved. + void Copy(const TextureLibrary &src); + + // Appends all textures from the source library to this library. All textures + // are copied over. + void Append(const TextureLibrary &src); + + // Removes all textures from the library. + void Clear(); + + // Pushes a new texture into the library. Returns an index of the newly + // inserted texture. + int PushTexture(std::unique_ptr texture); + + size_t NumTextures() const { return textures_.size(); } + + Texture *GetTexture(int index) { return textures_[index].get(); } + const Texture *GetTexture(int index) const { return textures_[index].get(); } + + // Returns a map from texture pointer to texture index for all textures. + std::unordered_map ComputeTextureToIndexMap() const; + + // Removes and returns a texture from the library. The returned texture can be + // either used by the caller or ignored in which case it would be + // automatically deleted. + std::unique_ptr RemoveTexture(int index); + + private: + std::vector> textures_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_TEXTURE_TEXTURE_LIBRARY_H_ diff --git a/contrib/draco/src/draco/texture/texture_library_test.cc b/contrib/draco/src/draco/texture/texture_library_test.cc new file mode 100644 index 000000000..4d681fdd2 --- /dev/null +++ b/contrib/draco/src/draco/texture/texture_library_test.cc @@ -0,0 +1,22 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/texture/texture_library.h" + +#include + +#include "draco/core/draco_test_utils.h" +#include "draco/io/texture_io.h" + +namespace {} // namespace diff --git a/contrib/draco/src/draco/texture/texture_map.cc b/contrib/draco/src/draco/texture/texture_map.cc new file mode 100644 index 000000000..459d3f600 --- /dev/null +++ b/contrib/draco/src/draco/texture/texture_map.cc @@ -0,0 +1,86 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/texture/texture_map.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +TextureMap::TextureMap() + : type_(TextureMap::GENERIC), + wrapping_mode_(CLAMP_TO_EDGE), + tex_coord_index_(-1), + min_filter_(UNSPECIFIED), + mag_filter_(UNSPECIFIED), + texture_(nullptr) {} + +void TextureMap::Copy(const TextureMap &src) { + type_ = src.type_; + wrapping_mode_ = src.wrapping_mode_; + tex_coord_index_ = src.tex_coord_index_; + min_filter_ = src.min_filter_; + mag_filter_ = src.mag_filter_; + if (src.owned_texture_ == nullptr) { + owned_texture_ = nullptr; + texture_ = src.texture_; + } else { + std::unique_ptr new_texture(new Texture()); + new_texture->Copy(*src.owned_texture_); + owned_texture_ = std::move(new_texture); + texture_ = owned_texture_.get(); + } + texture_transform_.Copy(src.texture_transform_); +} + +void TextureMap::SetProperties(Type type) { + SetProperties(type, WrappingMode(CLAMP_TO_EDGE), 0); +} + +void TextureMap::SetProperties(TextureMap::Type type, int tex_coord_index) { + SetProperties(type, WrappingMode(CLAMP_TO_EDGE), tex_coord_index); +} + +void TextureMap::SetProperties(Type type, WrappingMode wrapping_mode, + int tex_coord_index) { + SetProperties(type, wrapping_mode, tex_coord_index, UNSPECIFIED, UNSPECIFIED); +} + +void TextureMap::SetProperties(Type type, WrappingMode wrapping_mode, + int tex_coord_index, FilterType min_filter, + FilterType mag_filter) { + type_ = type; + wrapping_mode_ = wrapping_mode; + tex_coord_index_ = tex_coord_index; + min_filter_ = min_filter; + mag_filter_ = mag_filter; +} + +void TextureMap::SetTexture(std::unique_ptr texture) { + owned_texture_ = std::move(texture); + texture_ = owned_texture_.get(); +} + +void TextureMap::SetTexture(Texture *texture) { + owned_texture_ = nullptr; + texture_ = texture; +} + +void TextureMap::SetTransform(const TextureTransform &transform) { + texture_transform_.Copy(transform); +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/texture/texture_map.h b/contrib/draco/src/draco/texture/texture_map.h new file mode 100644 index 000000000..f3a95b501 --- /dev/null +++ b/contrib/draco/src/draco/texture/texture_map.h @@ -0,0 +1,175 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_TEXTURE_TEXTURE_MAP_H_ +#define DRACO_TEXTURE_TEXTURE_MAP_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +#include "draco/texture/texture.h" +#include "draco/texture/texture_transform.h" + +namespace draco { + +// Class representing mapping of one texture to a mesh. A texture map +// specifies the mesh attribute that contains texture coordinates used by the +// texture. The class also defines an intended use of the texture as a so called +// mapping type (COLOR, NORMAL_TANGENT_SPACE, etc..). Mapping types are roughly +// based on GLTF 2.0 material spec that describes a metallic-roughness PBR +// material model. More details can be found here: +// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#materials +class TextureMap { + public: + enum Type { + // Generic purpose texture (not GLTF compliant). + GENERIC = 0, + // Color data with optional alpha channel for transparency (GLTF compliant). + COLOR = 1, + // Dedicated texture for storing transparency (not GLTF compliant). + OPACITY = 2, + // Dedicated texture for storing metallic property (not GLTF compliant). + METALLIC = 3, + // Dedicated texture for storing roughness property (not GLTF compliant). + ROUGHNESS = 4, + // Combined texture for storing metallic and roughness properties. + // B == metallic, G == roughness (GLTF compliant). + METALLIC_ROUGHNESS = 5, + // Normal map defined in the object space of the mesh (not GLTF compliant). + NORMAL_OBJECT_SPACE = 6, + // Normal map defined in the tangent space of the mesh (GLTF compliant). + NORMAL_TANGENT_SPACE = 7, + // Precomputed ambient occlusion on the surface (GLTF compliant). + AMBIENT_OCCLUSION = 8, + // Emissive color (GLTF compliant). + EMISSIVE = 9, + // Texture types of glTF material extension KHR_materials_sheen. + SHEEN_COLOR = 10, + SHEEN_ROUGHNESS = 11, + // Texture types of glTF material extension KHR_materials_transmission. + TRANSMISSION = 12, + // Texture types of glTF material extension KHR_materials_clearcoat. + CLEARCOAT = 13, + CLEARCOAT_ROUGHNESS = 14, + CLEARCOAT_NORMAL = 15, + // Texture types of glTF material extension KHR_materials_volume. + THICKNESS = 16, + // Texture types of glTF material extension KHR_materials_specular. + SPECULAR = 17, + SPECULAR_COLOR = 18, + // The number of texture types. + TEXTURE_TYPES_COUNT + }; + + enum AxisWrappingMode { + // Out of bounds access along a texture axis should be clamped to the + // nearest edge (default). + CLAMP_TO_EDGE = 0, + // Texture is repeated along a texture axis in a mirrored pattern. + MIRRORED_REPEAT, + // Texture is repeated along a texture axis (tiled textures). + REPEAT + }; + + struct WrappingMode { + explicit WrappingMode(AxisWrappingMode mode) : WrappingMode(mode, mode) {} + WrappingMode(AxisWrappingMode s, AxisWrappingMode t) : s(s), t(t) {} + AxisWrappingMode s; + AxisWrappingMode t; + }; + + // Filter types are roughly based on glTF 2.0 samplers spec. + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#samplers + enum FilterType { + UNSPECIFIED = 0, + NEAREST, + LINEAR, + NEAREST_MIPMAP_NEAREST, + LINEAR_MIPMAP_NEAREST, + NEAREST_MIPMAP_LINEAR, + LINEAR_MIPMAP_LINEAR + }; + + TextureMap(); + TextureMap(TextureMap &&) = default; + + // Copies texture map data from the |src| texture map to this texture map. + void Copy(const TextureMap &src); + + // Sets the mapping information between the texture and the target mesh. + // |tex_coord_index| is the local index of the texture coordinates that is + // used to map the texture on the mesh. + void SetProperties(Type type); + void SetProperties(Type type, int tex_coord_index); + void SetProperties(Type type, WrappingMode wrapping_mode, + int tex_coord_index); + void SetProperties(Type type, WrappingMode wrapping_mode, int tex_coord_index, + FilterType min_filter, FilterType mag_filter); + + // Set texture and transfer its ownership to the TextureMap object. + // + // Note that this should not be used if this TextureMap is part of a + // MaterialLibrary. For such cases, the TextureMap's texture should refer to + // an entry in the MaterialLibrary's TextureLibrary. + void SetTexture(std::unique_ptr texture); + + // Set texture and without transferring the ownership. The caller needs to + // ensure the texture is valid during the lifetime of the TextureMap object. + void SetTexture(Texture *texture); + + void SetTransform(const TextureTransform &transform); + const TextureTransform &texture_transform() const { + return texture_transform_; + } + + const Texture *texture() const { return texture_; } + Texture *texture() { return texture_; } + Type type() const { return type_; } + WrappingMode wrapping_mode() const { return wrapping_mode_; } + int tex_coord_index() const { return tex_coord_index_; } + FilterType min_filter() const { return min_filter_; } + FilterType mag_filter() const { return mag_filter_; } + + TextureMap &operator=(TextureMap &&) = default; + + private: + Type type_; + WrappingMode wrapping_mode_; + + // Local index of the texture coordinates that is used to map the texture on + // the mesh. For example, |tex_coord_index_ == 0| would correspond to the + // first TEX_COORD attribute of the mesh. + int tex_coord_index_; + + FilterType min_filter_; + FilterType mag_filter_; + + // Used when the texture object is owned by TextureMap, otherwise set to + // nullptr. + std::unique_ptr owned_texture_; + + // Either raw pointer owned by |owned_texture_| or a pointer to a user + // specified texture in case |owned_texture_| is nullptr. + Texture *texture_; + + // Transformation values of the texture map. + TextureTransform texture_transform_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_TEXTURE_TEXTURE_MAP_H_ diff --git a/contrib/draco/src/draco/texture/texture_map_test.cc b/contrib/draco/src/draco/texture/texture_map_test.cc new file mode 100644 index 000000000..e69de29bb diff --git a/contrib/draco/src/draco/texture/texture_transform.cc b/contrib/draco/src/draco/texture/texture_transform.cc new file mode 100644 index 000000000..ccb00d59f --- /dev/null +++ b/contrib/draco/src/draco/texture/texture_transform.cc @@ -0,0 +1,79 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/texture/texture_transform.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED + +namespace draco { + +TextureTransform::TextureTransform() { Clear(); } + +void TextureTransform::Clear() { + offset_ = TextureTransform::GetDefaultOffset(); + rotation_ = TextureTransform::GetDefaultRotation(); + scale_ = TextureTransform::GetDefaultScale(); + tex_coord_ = TextureTransform::GetDefaultTexCoord(); +} + +void TextureTransform::Copy(const TextureTransform &src) { + offset_ = src.offset_; + rotation_ = src.rotation_; + scale_ = src.scale_; + tex_coord_ = src.tex_coord_; +} + +bool TextureTransform::IsDefault(const TextureTransform &tt) { + const TextureTransform defaults; + if (tt == defaults) { + return true; + } + return false; +} + +bool TextureTransform::IsOffsetSet() const { + return offset_ != TextureTransform::GetDefaultOffset(); +} + +bool TextureTransform::IsRotationSet() const { + return rotation_ != TextureTransform::GetDefaultRotation(); +} + +bool TextureTransform::IsScaleSet() const { + return scale_ != TextureTransform::GetDefaultScale(); +} + +bool TextureTransform::IsTexCoordSet() const { + return tex_coord_ != TextureTransform::GetDefaultTexCoord(); +} + +bool TextureTransform::operator==(const TextureTransform &tt) const { + if (tex_coord_ != tt.tex_coord_) { + return false; + } + if (rotation_ != tt.rotation_) { + return false; + } + if (offset_ != tt.offset_) { + return false; + } + if (scale_ != tt.scale_) { + return false; + } + return true; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/texture/texture_transform.h b/contrib/draco/src/draco/texture/texture_transform.h new file mode 100644 index 000000000..b2ec47f2e --- /dev/null +++ b/contrib/draco/src/draco/texture/texture_transform.h @@ -0,0 +1,75 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_TEXTURE_TEXTURE_TRANSFORM_H_ +#define DRACO_TEXTURE_TEXTURE_TRANSFORM_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include + +namespace draco { + +// Class to hold texture transformations. Parameters are based on the glTF 2.0 +// extension KHR_texture_transform: +// https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform +class TextureTransform { + public: + TextureTransform(); + + // Resets the values back to defaults. + void Clear(); + + // Copies texture transform data from the |src| texture transform to this + // texture transform. + void Copy(const TextureTransform &src); + + // Returns true if |tt| contains all default values. + static bool IsDefault(const TextureTransform &tt); + + bool IsOffsetSet() const; + bool IsRotationSet() const; + bool IsScaleSet() const; + bool IsTexCoordSet() const; + + void set_offset(const std::array &offset) { offset_ = offset; } + const std::array &offset() const { return offset_; } + void set_scale(const std::array &scale) { scale_ = scale; } + const std::array &scale() const { return scale_; } + + void set_rotation(double rotation) { rotation_ = rotation; } + double rotation() const { return rotation_; } + void set_tex_coord(int tex_coord) { tex_coord_ = tex_coord; } + int tex_coord() const { return tex_coord_; } + + bool operator==(const TextureTransform &tt) const; + + private: + static std::array GetDefaultOffset() { return {0.0, 0.0}; } + static float GetDefaultRotation() { return 0.0; } + static std::array GetDefaultScale() { return {0.0, 0.0}; } + static int GetDefaultTexCoord() { return -1; } + + std::array offset_; + double rotation_; + std::array scale_; + int tex_coord_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_TEXTURE_TEXTURE_TRANSFORM_H_ diff --git a/contrib/draco/src/draco/texture/texture_transform_test.cc b/contrib/draco/src/draco/texture/texture_transform_test.cc new file mode 100644 index 000000000..e69de29bb diff --git a/contrib/draco/src/draco/texture/texture_utils.cc b/contrib/draco/src/draco/texture/texture_utils.cc new file mode 100644 index 000000000..7598ac780 --- /dev/null +++ b/contrib/draco/src/draco/texture/texture_utils.cc @@ -0,0 +1,144 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/texture/texture_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include +#include +#include +#include +#include +#include +#include + +namespace draco { + +std::string TextureUtils::GetTargetStem(const Texture &texture) { + // Return stem of the source image if there is one. + if (!texture.source_image().filename().empty()) { + const std::string &full_path = texture.source_image().filename(); + std::string folder_path; + std::string filename; + SplitPath(full_path, &folder_path, &filename); + return RemoveFileExtension(filename); + } + + // Return an empty stem. + return ""; +} + +std::string TextureUtils::GetOrGenerateTargetStem(const Texture &texture, + int index, + const std::string &suffix) { + // Return target stem from |texture| if there is one. + const std::string name = GetTargetStem(texture); + if (!name.empty()) { + return name; + } + + // Return target stem generated from |index| and |suffix|. + return "Texture" + std::to_string(index) + suffix; +} + +ImageFormat TextureUtils::GetTargetFormat(const Texture &texture) { + // Return format based on source image mime type. + return GetSourceFormat(texture); +} + +std::string TextureUtils::GetTargetExtension(const Texture &texture) { + return GetExtension(GetTargetFormat(texture)); +} + +ImageFormat TextureUtils::GetSourceFormat(const Texture &texture) { + // Try to get the extension based on source image mime type. + std::string extension = + LowercaseMimeTypeExtension(texture.source_image().mime_type()); + if (extension.empty() && !texture.source_image().filename().empty()) { + // Try to get the extension from the source image filename. + extension = LowercaseFileExtension(texture.source_image().filename()); + } + if (extension.empty()) { + // Default to png. + extension = "png"; + } + return GetFormat(extension); +} + +ImageFormat TextureUtils::GetFormat(const std::string &extension) { + if (extension == "png") { + return ImageFormat::PNG; + } else if (extension == "jpg" || extension == "jpeg") { + return ImageFormat::JPEG; + } else if (extension == "basis" || extension == "ktx2") { + return ImageFormat::BASIS; + } else if (extension == "webp") { + return ImageFormat::WEBP; + } + return ImageFormat::NONE; +} + +std::string TextureUtils::GetExtension(ImageFormat format) { + switch (format) { + case ImageFormat::PNG: + return "png"; + case ImageFormat::JPEG: + return "jpg"; + case ImageFormat::BASIS: + return "ktx2"; + case ImageFormat::WEBP: + return "webp"; + case ImageFormat::NONE: + default: + return ""; + } +} + +int TextureUtils::ComputeRequiredNumChannels( + const Texture &texture, const MaterialLibrary &material_library) { + // TODO(vytyaz): Consider a case where |texture| is not only used in OMR but + // also in other texture map types. + const auto mr_textures = TextureUtils::FindTextures( + TextureMap::METALLIC_ROUGHNESS, &material_library); + if (std::find(mr_textures.begin(), mr_textures.end(), &texture) == + mr_textures.end()) { + // Occlusion-only texture. + return 1; + } + // Occlusion-metallic-roughness texture. + return 3; +} + +std::vector TextureUtils::FindTextures( + const TextureMap::Type texture_type, + const MaterialLibrary *material_library) { + // Find textures with no duplicates. + std::unordered_set textures; + for (int i = 0; i < material_library->NumMaterials(); ++i) { + const TextureMap *const texture_map = + material_library->GetMaterial(i)->GetTextureMapByType(texture_type); + if (texture_map != nullptr && texture_map->texture() != nullptr) { + textures.insert(texture_map->texture()); + } + } + + // Return the textures as a vector. + std::vector result; + result.insert(result.end(), textures.begin(), textures.end()); + return result; +} + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/texture/texture_utils.h b/contrib/draco/src/draco/texture/texture_utils.h new file mode 100644 index 000000000..18d29950a --- /dev/null +++ b/contrib/draco/src/draco/texture/texture_utils.h @@ -0,0 +1,78 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_TEXTURE_TEXTURE_UTILS_H_ +#define DRACO_TEXTURE_TEXTURE_UTILS_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/status.h" +#include "draco/core/status_or.h" +#include "draco/io/file_utils.h" +#include "draco/material/material_library.h" +#include "draco/texture/texture_library.h" +#include "draco/texture/texture_map.h" + +namespace draco { + +// Helper class implementing various utilities operating on draco::Texture. +class TextureUtils { + public: + // Returns |texture| image stem (file basename without extension) based on the + // source image filename or an empty string when source image is not set. + static std::string GetTargetStem(const Texture &texture); + + // Returns |texture| image stem (file basename without extension) based on the + // source image filename or a name generated from |index| and |suffix| like + // "Texture5_BaseColor" when source image is not set. + static std::string GetOrGenerateTargetStem(const Texture &texture, int index, + const std::string &suffix); + + // Returns |texture| format based on compression settings, the source image + // mime type or the source image filename. + static ImageFormat GetTargetFormat(const Texture &texture); + + // Returns |texture| image file extension based on compression settings, the + // source image mime type or the source image filename. + static std::string GetTargetExtension(const Texture &texture); + + // Returns |texture| format based on source image mime type or the source + // image filename. + static ImageFormat GetSourceFormat(const Texture &texture); + + // Returns image format corresponding to a given image file |extension|. NONE + // is returned when |extension| is empty or unknown. + static ImageFormat GetFormat(const std::string &extension); + + // Returns image file extension corresponding to a given image |format|. Empty + // extension is returned when the |format| is NONE. + static std::string GetExtension(ImageFormat format); + + // Returns the number of channels required for encoding a |texture| from a + // given |material_library|, taking into account texture opacity and assuming + // that occlusion and metallic-roughness texture maps may share a texture. + // TODO(vytyaz): Move this and FindTextures() to MaterialLibrary class. + static int ComputeRequiredNumChannels( + const Texture &texture, const MaterialLibrary &material_library); + + static std::vector FindTextures( + const TextureMap::Type texture_type, + const MaterialLibrary *material_library); +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_TEXTURE_TEXTURE_UTILS_H_ diff --git a/contrib/draco/src/draco/texture/texture_utils_test.cc b/contrib/draco/src/draco/texture/texture_utils_test.cc new file mode 100644 index 000000000..45873ea7a --- /dev/null +++ b/contrib/draco/src/draco/texture/texture_utils_test.cc @@ -0,0 +1,163 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/texture/texture_utils.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +#include "draco/core/draco_test_utils.h" +#include "draco/io/texture_io.h" +#include "draco/texture/color_utils.h" + +namespace { + +TEST(TextureUtilsTest, TestGetTargetNameForTextureLoadedFromFile) { + // Tests that correct target stem and format are returned by texture utils for + // texture loaded from image file (stem and format from source file). + std::unique_ptr texture = + draco::ReadTextureFromFile(draco::GetTestFileFullPath("fast.jpg")) + .value(); + ASSERT_NE(texture, nullptr); + ASSERT_EQ(draco::TextureUtils::GetTargetStem(*texture), "fast"); + ASSERT_EQ(draco::TextureUtils::GetTargetExtension(*texture), "jpg"); + ASSERT_EQ(draco::TextureUtils::GetTargetFormat(*texture), + draco::ImageFormat::JPEG); + ASSERT_EQ(draco::TextureUtils::GetOrGenerateTargetStem(*texture, 5, "_Color"), + "fast"); +} + +TEST(TextureUtilsTest, TestGetTargetNameForNewTexture) { + // Tests that correct target stem and format are returned by texture utils for + // a newly created texture (empty stem and PNG image type by default). + std::unique_ptr texture(new draco::Texture()); + ASSERT_NE(texture, nullptr); + ASSERT_EQ(draco::TextureUtils::GetTargetStem(*texture), ""); + ASSERT_EQ(draco::TextureUtils::GetOrGenerateTargetStem(*texture, 5, "_Color"), + "Texture5_Color"); + ASSERT_EQ(draco::TextureUtils::GetTargetExtension(*texture), "png"); + ASSERT_EQ(draco::TextureUtils::GetTargetFormat(*texture), + draco::ImageFormat::PNG); +} + +TEST(TextureUtilsTest, TestGetSourceFormat) { + // Tests that the source format is determined correctly for new textures and + // for textures loaded from file. + std::unique_ptr new_texture(new draco::Texture()); + DRACO_ASSIGN_OR_ASSERT( + std::unique_ptr png_texture, + draco::ReadTextureFromFile(draco::GetTestFileFullPath("test.png"))); + DRACO_ASSIGN_OR_ASSERT( + std::unique_ptr jpg_texture, + draco::ReadTextureFromFile(draco::GetTestFileFullPath("fast.jpg"))); + + // Check source formats. + ASSERT_EQ(draco::TextureUtils::GetSourceFormat(*new_texture), + draco::ImageFormat::PNG); + ASSERT_EQ(draco::TextureUtils::GetSourceFormat(*png_texture), + draco::ImageFormat::PNG); + ASSERT_EQ(draco::TextureUtils::GetSourceFormat(*jpg_texture), + draco::ImageFormat::JPEG); + + // Remove the mime-type from the jpeg texture and ensure the source format is + // still detected properly based on the filename. + jpg_texture->source_image().set_mime_type(""); + ASSERT_EQ(draco::TextureUtils::GetSourceFormat(*jpg_texture), + draco::ImageFormat::JPEG); +} + +TEST(TextureUtilsTest, TestGetFormat) { + typedef draco::ImageFormat ImageFormat; + ASSERT_EQ(draco::TextureUtils::GetFormat("png"), ImageFormat::PNG); + ASSERT_EQ(draco::TextureUtils::GetFormat("jpg"), ImageFormat::JPEG); + ASSERT_EQ(draco::TextureUtils::GetFormat("jpeg"), ImageFormat::JPEG); + ASSERT_EQ(draco::TextureUtils::GetFormat("basis"), ImageFormat::BASIS); + ASSERT_EQ(draco::TextureUtils::GetFormat("ktx2"), ImageFormat::BASIS); + ASSERT_EQ(draco::TextureUtils::GetFormat("webp"), ImageFormat::WEBP); + ASSERT_EQ(draco::TextureUtils::GetFormat(""), ImageFormat::NONE); + ASSERT_EQ(draco::TextureUtils::GetFormat("bmp"), ImageFormat::NONE); +} + +TEST(TextureUtilsTest, TestGetExtension) { + typedef draco::ImageFormat ImageFormat; + ASSERT_EQ(draco::TextureUtils::GetExtension(ImageFormat::PNG), "png"); + ASSERT_EQ(draco::TextureUtils::GetExtension(ImageFormat::JPEG), "jpg"); + ASSERT_EQ(draco::TextureUtils::GetExtension(ImageFormat::BASIS), "ktx2"); + ASSERT_EQ(draco::TextureUtils::GetExtension(ImageFormat::WEBP), "webp"); + ASSERT_EQ(draco::TextureUtils::GetExtension(ImageFormat::NONE), ""); +} + +TEST(TextureUtilsTest, TestComputeRequiredNumChannels) { + // Tests that the number of texture channels can be computed. Material library + // under test is created programmatically. + + // Load textures. + DRACO_ASSIGN_OR_ASSERT( + auto texture0, draco::ReadTextureFromFile( + draco::GetTestFileFullPath("fully_transparent.png"))); + ASSERT_NE(texture0, nullptr); + draco::Texture *texture0_ptr = texture0.get(); + DRACO_ASSIGN_OR_ASSERT( + auto texture1, + draco::ReadTextureFromFile(draco::GetTestFileFullPath("squares.png"))); + ASSERT_NE(texture1, nullptr); + const draco::Texture *texture1_ptr = texture1.get(); + DRACO_ASSIGN_OR_ASSERT( + auto texture2, draco::ReadTextureFromFile( + draco::GetTestFileFullPath("fully_transparent.png"))); + ASSERT_NE(texture2, nullptr); + const draco::Texture *texture2_ptr = texture2.get(); + + // Compute number of channels for occlusion-only texture. + draco::MaterialLibrary library; + draco::Material *const material0 = library.MutableMaterial(0); + material0->SetTextureMap(std::move(texture0), + draco::TextureMap::AMBIENT_OCCLUSION, 0); + ASSERT_EQ( + draco::TextureUtils::ComputeRequiredNumChannels(*texture0_ptr, library), + 1); + + // Compute number of channels for occlusion-only texture with MR present but + // not using the same texture. + draco::Material *const material1 = library.MutableMaterial(1); + material1->SetTextureMap(std::move(texture1), + draco::TextureMap::METALLIC_ROUGHNESS, 0); + ASSERT_EQ( + draco::TextureUtils::ComputeRequiredNumChannels(*texture0_ptr, library), + 1); + + // Compute number of channels for metallic-roughness texture. + ASSERT_EQ( + draco::TextureUtils::ComputeRequiredNumChannels(*texture1_ptr, library), + 3); + + // Compute number of channels texture that is used for occlusin map in one + // material and also shared with metallic-roughness map in another material. + draco::Material *const material2 = library.MutableMaterial(2); + DRACO_ASSERT_OK(material2->SetTextureMap( + texture0_ptr, draco::TextureMap::METALLIC_ROUGHNESS, 0)); + ASSERT_EQ( + draco::TextureUtils::ComputeRequiredNumChannels(*texture0_ptr, library), + 3); + + // Compute number of channels for non-opaque texture. + material0->SetTextureMap(std::move(texture2), draco::TextureMap::COLOR, 0); + ASSERT_EQ( + draco::TextureUtils::ComputeRequiredNumChannels(*texture2_ptr, library), + 4); +} + +} // namespace + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/tools/draco_decoder.cc b/contrib/draco/src/draco/tools/draco_decoder.cc index 610709d62..cf5f18094 100644 --- a/contrib/draco/src/draco/tools/draco_decoder.cc +++ b/contrib/draco/src/draco/tools/draco_decoder.cc @@ -20,6 +20,7 @@ #include "draco/io/obj_encoder.h" #include "draco/io/parser_utils.h" #include "draco/io/ply_encoder.h" +#include "draco/io/stl_encoder.h" namespace { @@ -126,7 +127,6 @@ int main(int argc, char **argv) { } // Save the decoded geometry into a file. - // TODO(fgalligan): Change extension code to look for '.'. const std::string extension = draco::parser::ToLower( options.output.size() >= 4 ? options.output.substr(options.output.size() - 4) @@ -140,7 +140,7 @@ int main(int argc, char **argv) { return -1; } } else { - if (!obj_encoder.EncodeToFile(*pc.get(), options.output)) { + if (!obj_encoder.EncodeToFile(*pc, options.output)) { printf("Failed to store the decoded point cloud as OBJ.\n"); return -1; } @@ -153,13 +153,25 @@ int main(int argc, char **argv) { return -1; } } else { - if (!ply_encoder.EncodeToFile(*pc.get(), options.output)) { + if (!ply_encoder.EncodeToFile(*pc, options.output)) { printf("Failed to store the decoded point cloud as PLY.\n"); return -1; } } + } else if (extension == ".stl") { + draco::StlEncoder stl_encoder; + if (mesh) { + draco::Status s = stl_encoder.EncodeToFile(*mesh, options.output); + if (s.code() != draco::Status::OK) { + printf("Failed to store the decoded mesh as STL.\n"); + return -1; + } + } else { + printf("Can't store a point cloud as STL.\n"); + return -1; + } } else { - printf("Invalid extension of the output file. Use either .ply or .obj.\n"); + printf("Invalid output file extension. Use .obj .ply or .stl.\n"); return -1; } printf("Decoded geometry saved to %s (%" PRId64 " ms to decode)\n", diff --git a/contrib/draco/src/draco/tools/draco_encoder.cc b/contrib/draco/src/draco/tools/draco_encoder.cc index 7e3632d7d..ec639453b 100644 --- a/contrib/draco/src/draco/tools/draco_encoder.cc +++ b/contrib/draco/src/draco/tools/draco_encoder.cc @@ -15,7 +15,9 @@ #include #include +#include "draco/compression/config/compression_shared.h" #include "draco/compression/encode.h" +#include "draco/compression/expert_encode.h" #include "draco/core/cycle_timer.h" #include "draco/io/file_utils.h" #include "draco/io/mesh_io.h" @@ -35,6 +37,7 @@ struct Options { int generic_quantization_bits; bool generic_deleted; int compression_level; + bool preserve_polygons; bool use_metadata; std::string input; std::string output; @@ -50,6 +53,7 @@ Options::Options() generic_quantization_bits(8), generic_deleted(false), compression_level(7), + preserve_polygons(false), use_metadata(false) {} void Usage() { @@ -83,6 +87,11 @@ void Usage() { printf( " --metadata use metadata to encode extra information in " "mesh files.\n"); + // Mesh with polygonal faces loaded from OBJ format is converted to triangular + // mesh and polygon reconstruction information is encoded into a new generic + // attribute. + printf(" -preserve_polygons encode polygon info as an attribute.\n"); + printf( "\nUse negative quantization values to skip the specified attribute\n"); } @@ -138,12 +147,12 @@ void PrintOptions(const draco::PointCloud &pc, const Options &options) { } int EncodePointCloudToFile(const draco::PointCloud &pc, const std::string &file, - draco::Encoder *encoder) { + draco::ExpertEncoder *encoder) { draco::CycleTimer timer; // Encode the geometry. draco::EncoderBuffer buffer; timer.Start(); - const draco::Status status = encoder->EncodePointCloudToBuffer(pc, &buffer); + const draco::Status status = encoder->EncodeToBuffer(&buffer); if (!status.ok()) { printf("Failed to encode the point cloud.\n"); printf("%s\n", status.error_msg()); @@ -162,12 +171,12 @@ int EncodePointCloudToFile(const draco::PointCloud &pc, const std::string &file, } int EncodeMeshToFile(const draco::Mesh &mesh, const std::string &file, - draco::Encoder *encoder) { + draco::ExpertEncoder *encoder) { draco::CycleTimer timer; // Encode the geometry. draco::EncoderBuffer buffer; timer.Start(); - const draco::Status status = encoder->EncodeMeshToBuffer(mesh, &buffer); + const draco::Status status = encoder->EncodeToBuffer(&buffer); if (!status.ok()) { printf("Failed to encode the mesh.\n"); printf("%s\n", status.error_msg()); @@ -249,6 +258,8 @@ int main(int argc, char **argv) { ++i; } else if (!strcmp("--metadata", argv[i])) { options.use_metadata = true; + } else if (!strcmp("-preserve_polygons", argv[i])) { + options.preserve_polygons = true; } } if (argc < 3 || options.input.empty()) { @@ -259,8 +270,10 @@ int main(int argc, char **argv) { std::unique_ptr pc; draco::Mesh *mesh = nullptr; if (!options.is_point_cloud) { - auto maybe_mesh = - draco::ReadMeshFromFile(options.input, options.use_metadata); + draco::Options load_options; + load_options.SetBool("use_metadata", options.use_metadata); + load_options.SetBool("preserve_polygons", options.preserve_polygons); + auto maybe_mesh = draco::ReadMeshFromFile(options.input, load_options); if (!maybe_mesh.ok()) { printf("Failed loading the input mesh: %s.\n", maybe_mesh.status().error_msg()); @@ -350,14 +363,36 @@ int main(int argc, char **argv) { options.output = options.input + ".drc"; } - PrintOptions(*pc.get(), options); + PrintOptions(*pc, options); + + const bool input_is_mesh = mesh && mesh->num_faces() > 0; + + // Convert to ExpertEncoder that allows us to set per-attribute options. + std::unique_ptr expert_encoder; + if (input_is_mesh) { + expert_encoder.reset(new draco::ExpertEncoder(*mesh)); + } else { + expert_encoder.reset(new draco::ExpertEncoder(*pc)); + } + expert_encoder->Reset(encoder.CreateExpertEncoderOptions(*pc)); + + // Check if there is an attribute that stores polygon edges. If so, we disable + // the default prediction scheme for the attribute as it actually makes the + // compression worse. + const int poly_att_id = + pc->GetAttributeIdByMetadataEntry("name", "added_edges"); + if (poly_att_id != -1) { + expert_encoder->SetAttributePredictionScheme( + poly_att_id, draco::PredictionSchemeMethod::PREDICTION_NONE); + } int ret = -1; - const bool input_is_mesh = mesh && mesh->num_faces() > 0; - if (input_is_mesh) - ret = EncodeMeshToFile(*mesh, options.output, &encoder); - else - ret = EncodePointCloudToFile(*pc.get(), options.output, &encoder); + + if (input_is_mesh) { + ret = EncodeMeshToFile(*mesh, options.output, expert_encoder.get()); + } else { + ret = EncodePointCloudToFile(*pc, options.output, expert_encoder.get()); + } if (ret != -1 && options.compression_level < 10) { printf( diff --git a/contrib/draco/src/draco/tools/draco_transcoder.cc b/contrib/draco/src/draco/tools/draco_transcoder.cc new file mode 100644 index 000000000..ad0f27714 --- /dev/null +++ b/contrib/draco/src/draco/tools/draco_transcoder.cc @@ -0,0 +1,130 @@ +// Copyright 2019 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include +#include + +#include "draco/core/cycle_timer.h" +#include "draco/core/status.h" +#include "draco/draco_features.h" +#include "draco/texture/texture_utils.h" +#include "draco/tools/draco_transcoder_lib.h" + +namespace { + +// TODO(fgalligan): Add support for no compression to the transcoder lib. +void Usage() { + // TODO(b/204212351): Revisit using a raw string literal here for readability. + printf("Usage: draco_transcoder [options] -i input -o output\n\n"); + printf("Main options:\n"); + printf(" -h | -? show help.\n"); + printf(" -i input file name.\n"); + printf(" -o output file name.\n"); + printf(" -qp quantization bits for the position attribute, "); + printf("default=11.\n"); + printf(" -qt quantization bits for the texture coordinate "); + printf("attribute, default=10.\n"); + printf(" -qn quantization bits for the normal vector attribute"); + printf(", default=8.\n"); + printf(" -qc quantization bits for the color attribute, "); + printf("default=8.\n"); + printf(" -qtg quantization bits for the tangent attribute, "); + printf("default=8.\n"); + printf(" -qw quantization bits for the weight attribute, "); + printf("default=8.\n"); + printf(" -qg quantization bits for any generic attribute, "); + printf("default=8.\n"); + + printf("\nBoolean options may be negated by prefixing 'no'.\n"); +} + +int StringToInt(const std::string &s) { + char *end; + return strtol(s.c_str(), &end, 10); // NOLINT +} + +bool MatchesBooleanOption(const std::string &option, const std::string &value) { + const std::string opt = "-" + option; + const std::string noopt = "-no" + option; + return value == opt || value == noopt; +} + +draco::Status TranscodeFile( + const draco::DracoTranscoder::FileOptions &file_options, + const draco::DracoTranscodingOptions &transcode_options) { + draco::CycleTimer timer; + timer.Start(); + DRACO_ASSIGN_OR_RETURN(std::unique_ptr dt, + draco::DracoTranscoder::Create(transcode_options)); + + DRACO_RETURN_IF_ERROR(dt->Transcode(file_options)); + timer.Stop(); + printf("Transcode\t%s\t%" PRId64 "\n", file_options.input_filename.c_str(), + timer.GetInMs()); + + return draco::OkStatus(); +} + +} // anonymous namespace + +int main(int argc, char **argv) { + draco::DracoTranscoder::FileOptions file_options; + draco::DracoTranscodingOptions transcode_options; + const int argc_check = argc - 1; + + for (int i = 1; i < argc; ++i) { + if (!strcmp("-h", argv[i]) || !strcmp("-?", argv[i])) { + Usage(); + return 0; + } else if (!strcmp("-i", argv[i]) && i < argc_check) { + file_options.input_filename = argv[++i]; + } else if (!strcmp("-o", argv[i]) && i < argc_check) { + file_options.output_filename = argv[++i]; + } else if (!strcmp("-qp", argv[i]) && i < argc_check) { + transcode_options.geometry.quantization_position.SetQuantizationBits( + StringToInt(argv[++i])); + } else if (!strcmp("-qt", argv[i]) && i < argc_check) { + transcode_options.geometry.quantization_bits_tex_coord = + StringToInt(argv[++i]); + } else if (!strcmp("-qn", argv[i]) && i < argc_check) { + transcode_options.geometry.quantization_bits_normal = + StringToInt(argv[++i]); + } else if (!strcmp("-qc", argv[i]) && i < argc_check) { + transcode_options.geometry.quantization_bits_color = + StringToInt(argv[++i]); + } else if (!strcmp("-qtg", argv[i]) && i < argc_check) { + transcode_options.geometry.quantization_bits_tangent = + StringToInt(argv[++i]); + } else if (!strcmp("-qw", argv[i]) && i < argc_check) { + transcode_options.geometry.quantization_bits_weight = + StringToInt(argv[++i]); + } else if (!strcmp("-qg", argv[i]) && i < argc_check) { + transcode_options.geometry.quantization_bits_generic = + StringToInt(argv[++i]); + } + } + if (argc < 3 || file_options.input_filename.empty() || + file_options.output_filename.empty()) { + Usage(); + return -1; + } + + const draco::Status status = TranscodeFile(file_options, transcode_options); + if (!status.ok()) { + printf("Failed\t%s\t%s\n", file_options.input_filename.c_str(), + status.error_msg()); + return -1; + } + return 0; +} diff --git a/contrib/draco/src/draco/tools/draco_transcoder_lib.cc b/contrib/draco/src/draco/tools/draco_transcoder_lib.cc new file mode 100644 index 000000000..b0bc43843 --- /dev/null +++ b/contrib/draco/src/draco/tools/draco_transcoder_lib.cc @@ -0,0 +1,86 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#include "draco/tools/draco_transcoder_lib.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include "draco/core/status_or.h" +#include "draco/io/file_utils.h" +#include "draco/io/scene_io.h" +#include "draco/scene/scene_utils.h" +#include "draco/texture/texture_utils.h" + +namespace draco { + +DracoTranscoder::DracoTranscoder() {} + +StatusOr> DracoTranscoder::Create( + const DracoTranscodingOptions &options) { + DRACO_RETURN_IF_ERROR(options.geometry.Check()); + std::unique_ptr dt(new DracoTranscoder()); + dt->transcoding_options_ = options; + return dt; +} + +StatusOr> DracoTranscoder::Create( + const DracoCompressionOptions &options) { + DracoTranscodingOptions new_options; + new_options.geometry = options; + return Create(new_options); +} + +Status DracoTranscoder::Transcode(const FileOptions &file_options) { + DRACO_RETURN_IF_ERROR(ReadScene(file_options)); + DRACO_RETURN_IF_ERROR(CompressScene()); + DRACO_RETURN_IF_ERROR(WriteScene(file_options)); + return OkStatus(); +} + +Status DracoTranscoder::ReadScene(const FileOptions &file_options) { + if (file_options.input_filename.empty()) { + return Status(Status::DRACO_ERROR, "Input filename is empty."); + } else if (file_options.output_filename.empty()) { + return Status(Status::DRACO_ERROR, "Output filename is empty."); + } + DRACO_ASSIGN_OR_RETURN(scene_, + ReadSceneFromFile(file_options.input_filename)); + return OkStatus(); +} + +Status DracoTranscoder::WriteScene(const FileOptions &file_options) { + if (!file_options.output_bin_filename.empty() && + !file_options.output_resource_directory.empty()) { + DRACO_RETURN_IF_ERROR(gltf_encoder_.EncodeFile( + *scene_, file_options.output_filename, file_options.output_bin_filename, + file_options.output_resource_directory)); + } else if (!file_options.output_bin_filename.empty()) { + DRACO_RETURN_IF_ERROR( + gltf_encoder_.EncodeFile(*scene_, file_options.output_filename, + file_options.output_bin_filename)); + } else { + DRACO_RETURN_IF_ERROR( + gltf_encoder_.EncodeFile(*scene_, file_options.output_filename)); + } + return OkStatus(); +} + +Status DracoTranscoder::CompressScene() { + // Apply geometry compression settings to all scene meshes. + SceneUtils::SetDracoCompressionOptions(&transcoding_options_.geometry, + scene_.get()); + return OkStatus(); +} + +} // namespace draco +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/tools/draco_transcoder_lib.h b/contrib/draco/src/draco/tools/draco_transcoder_lib.h new file mode 100644 index 000000000..c94d19de7 --- /dev/null +++ b/contrib/draco/src/draco/tools/draco_transcoder_lib.h @@ -0,0 +1,103 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef DRACO_TOOLS_DRACO_TRANSCODER_LIB_H_ +#define DRACO_TOOLS_DRACO_TRANSCODER_LIB_H_ + +#include "draco/draco_features.h" + +#ifdef DRACO_TRANSCODER_SUPPORTED +#include + +#include "draco/compression/draco_compression_options.h" +#include "draco/core/options.h" +#include "draco/io/gltf_encoder.h" +#include "draco/io/image_compression_options.h" + +namespace draco { + +// Struct to hold Draco transcoding options. +struct DracoTranscodingOptions { + DracoTranscodingOptions() {} + + // Options used when geometry compression optimization is disabled. + DracoCompressionOptions geometry; +}; + +// Class that supports input of glTF (and some simple USD) files, encodes +// them with Draco compression, and outputs glTF Draco compressed files. +// +// glTF supported extensions: +// Input and Output: +// KHR_draco_mesh_compression. http://shortn/_L5tPQqdwWf +// KHR_materials_unlit. http://shortn/_3eaDLoIGam +// KHR_texture_transform. http://shortn/_PORWgVTEe8 +// +// glTF unsupported features: +// Input and Output: +// Morph targets. http://shortn/_zE5DLw8a9B +// Sparse accessors. http://shortn/_h3FwbzQl4f +// KHR_lights_punctual. http://shortn/_nzGk80wKtK +// KHR_materials_pbrSpecularGlossiness. http://shortn/_iz0VC6dIKe +// All vendor extensions. +class DracoTranscoder { + public: + struct FileOptions { + std::string input_filename; // Must be non-empty. + std::string output_filename; // Must be non-empty. + std::string output_bin_filename = ""; + std::string output_resource_directory = ""; + }; + + DracoTranscoder(); + + // Creates a DracoTranscoder object. |options| sets the compression options + // used in the Encode function. + static StatusOr> Create( + const DracoTranscodingOptions &options); + + // Deprecated. + // TODO(fgalligan): Remove when function is not being used anymore. + static StatusOr> Create( + const DracoCompressionOptions &options); + + // Encodes the input with Draco compression using the compression options + // passed in the Create function. The recommended use case is to create a + // transcoder once and call Transcode for multiple files. + Status Transcode(const FileOptions &file_options); + + private: + // Read scene from file. + Status ReadScene(const FileOptions &file_options); + + // Write scene to file. + Status WriteScene(const FileOptions &file_options); + + // Apply compression settings to the scene. + Status CompressScene(); + + private: + GltfEncoder gltf_encoder_; + + // The scene being transcoded. + std::unique_ptr scene_; + + // Copy of the transcoding options passed into the Create function. + DracoTranscodingOptions transcoding_options_; +}; + +} // namespace draco + +#endif // DRACO_TRANSCODER_SUPPORTED +#endif // DRACO_TOOLS_DRACO_TRANSCODER_LIB_H_ diff --git a/contrib/draco/src/draco/tools/draco_transcoder_lib_test.cc b/contrib/draco/src/draco/tools/draco_transcoder_lib_test.cc new file mode 100644 index 000000000..a87a158e0 --- /dev/null +++ b/contrib/draco/src/draco/tools/draco_transcoder_lib_test.cc @@ -0,0 +1,172 @@ +// Copyright 2021 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifdef DRACO_TRANSCODER_SUPPORTED + +#include "draco/tools/draco_transcoder_lib.h" + +#include "draco/core/draco_test_base.h" +#include "draco/core/draco_test_utils.h" +#include "draco/io/file_utils.h" + +// Tests encoding a .gltf file with default Draco compression. +TEST(DracoTranscoderTest, DefaultDracoCompression) { + const std::string input_name = "sphere.gltf"; + const std::string input_filename = draco::GetTestFileFullPath(input_name); + const std::string output_filename = + draco::GetTestTempFileFullPath("test.gltf"); + + const draco::DracoTranscodingOptions options; + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr dt, + draco::DracoTranscoder::Create(options)); + + draco::DracoTranscoder::FileOptions file_options; + file_options.input_filename = input_filename; + file_options.output_filename = output_filename; + DRACO_ASSERT_OK(dt->Transcode(file_options)); + + const std::string output_bin_filename = + draco::GetTestTempFileFullPath("test.bin"); + const size_t output_bin_size = draco::GetFileSize(output_bin_filename); + ASSERT_GT(output_bin_size, 0); +} + +// Tests setting the output glTF .bin name. +TEST(DracoTranscoderTest, TestBinName) { + const std::string input_name = "sphere.gltf"; + const std::string input_filename = draco::GetTestFileFullPath(input_name); + const std::string output_filename = + draco::GetTestTempFileFullPath("test.gltf"); + const std::string output_bin_filename = + draco::GetTestTempFileFullPath("different_name.bin"); + + const draco::DracoTranscodingOptions options; + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr dt, + draco::DracoTranscoder::Create(options)); + + draco::DracoTranscoder::FileOptions file_options; + file_options.input_filename = input_filename; + file_options.output_filename = output_filename; + file_options.output_bin_filename = output_bin_filename; + DRACO_ASSERT_OK(dt->Transcode(file_options)); + + const size_t output_bin_size = draco::GetFileSize(output_bin_filename); + ASSERT_GT(output_bin_size, 0); +} + +// Tests setting the output glTF resource directory. +TEST(DracoTranscoderTest, TestResourceDirName) { + const std::string input_name = "sphere.gltf"; + const std::string input_filename = draco::GetTestFileFullPath(input_name); + const std::string output_filename = + draco::GetTestTempFileFullPath("test.gltf"); + const std::string output_bin_filename = + draco::GetTestTempFileFullPath("another_name.bin"); + const std::string output_resource_directory = + draco::GetTestTempFileFullPath("res/other_files"); + + const draco::DracoTranscodingOptions options; + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr dt, + draco::DracoTranscoder::Create(options)); + + draco::DracoTranscoder::FileOptions file_options; + file_options.input_filename = input_filename; + file_options.output_filename = output_filename; + file_options.output_bin_filename = output_bin_filename; + file_options.output_resource_directory = output_resource_directory; + DRACO_ASSERT_OK(dt->Transcode(file_options)); + + const size_t output_bin_size = draco::GetFileSize(output_bin_filename); + ASSERT_GT(output_bin_size, 0); + + const std::string res_dir_png_filename = draco::GetTestTempFileFullPath( + "res/other_files/sphere_Texture0_Normal.png"); + const size_t output_png_size = draco::GetFileSize(res_dir_png_filename); + ASSERT_GT(output_png_size, 0); +} + +// Tests creating one transcoder to encode multiple files. +TEST(DracoTranscoderTest, EncodeMultipleFiles) { + const draco::DracoTranscodingOptions options; + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr dt, + draco::DracoTranscoder::Create(options)); + + draco::DracoTranscoder::FileOptions file_options; + file_options.input_filename = draco::GetTestFileFullPath("sphere.gltf"); + file_options.output_filename = draco::GetTestTempFileFullPath("first.gltf"); + DRACO_ASSERT_OK(dt->Transcode(file_options)); + const size_t first_bin_size = + draco::GetFileSize(draco::GetTestTempFileFullPath("first.bin")); + ASSERT_GT(first_bin_size, 0); + + file_options.input_filename = + draco::GetTestFileFullPath("CesiumMan/glTF/CesiumMan.gltf"); + file_options.output_filename = draco::GetTestTempFileFullPath("second.gltf"); + DRACO_ASSERT_OK(dt->Transcode(file_options)); + const size_t second_bin_size = + draco::GetFileSize(draco::GetTestTempFileFullPath("second.bin")); + ASSERT_GT(second_bin_size, 0); +} + +// Tests using glTF binary as input. +TEST(DracoTranscoderTest, SimpleGlbInput) { + const std::string input_name = "Box/glTF_Binary/Box.glb"; + const std::string input_filename = draco::GetTestFileFullPath(input_name); + const std::string output_filename = + draco::GetTestTempFileFullPath("test.gltf"); + + const draco::DracoTranscodingOptions options; + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr dt, + draco::DracoTranscoder::Create(options)); + + draco::DracoTranscoder::FileOptions file_options; + file_options.input_filename = input_filename; + file_options.output_filename = output_filename; + DRACO_ASSERT_OK(dt->Transcode(file_options)); + + const std::string output_bin_filename = + draco::GetTestTempFileFullPath("test.bin"); + const size_t output_bin_size = draco::GetFileSize(output_bin_filename); + ASSERT_GT(output_bin_size, 0); +} + +// Simple test to check glb input and setting smaller position quantizations +// outputs a smaller file overall. +TEST(DracoTranscoderTest, TestPositionQuantization) { + const std::string input_name = + "KhronosSampleModels/Duck/glTF_Binary/Duck.glb"; + const std::string input_filename = draco::GetTestFileFullPath(input_name); + + draco::DracoTranscodingOptions options; + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr dt, + draco::DracoTranscoder::Create(options)); + + draco::DracoTranscoder::FileOptions file_options; + file_options.input_filename = input_filename; + file_options.output_filename = draco::GetTestTempFileFullPath("first.glb"); + DRACO_ASSERT_OK(dt->Transcode(file_options)); + const size_t first_glb_size = + draco::GetFileSize(draco::GetTestTempFileFullPath("first.glb")); + + options.geometry.quantization_position.SetQuantizationBits(10); + DRACO_ASSIGN_OR_ASSERT(std::unique_ptr dt2, + draco::DracoTranscoder::Create(options)); + file_options.output_filename = draco::GetTestTempFileFullPath("second.glb"); + DRACO_ASSERT_OK(dt2->Transcode(file_options)); + const size_t second_glb_size = + draco::GetFileSize(draco::GetTestTempFileFullPath("second.glb")); + ASSERT_GT(first_glb_size, second_glb_size); +} + +#endif // DRACO_TRANSCODER_SUPPORTED diff --git a/contrib/draco/src/draco/tools/fuzz/build.sh b/contrib/draco/src/draco/tools/fuzz/build.sh index bbeb10591..3e48409fc 100644 --- a/contrib/draco/src/draco/tools/fuzz/build.sh +++ b/contrib/draco/src/draco/tools/fuzz/build.sh @@ -19,7 +19,7 @@ cmake $SRC/draco # The draco_decoder and draco_encoder binaries don't build nicely with OSS-Fuzz # options, so just build the Draco shared libraries. -make -j$(nproc) draco +make -j$(nproc) # build fuzzers for fuzzer in $(find $SRC/draco/src/draco/tools/fuzz -name '*.cc'); do diff --git a/contrib/draco/src/draco/tools/install_test/CMakeLists.txt b/contrib/draco/src/draco/tools/install_test/CMakeLists.txt new file mode 100644 index 000000000..800dc9c9b --- /dev/null +++ b/contrib/draco/src/draco/tools/install_test/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright 2022 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +cmake_minimum_required(VERSION 3.12) +project(install_test C CXX) + +include(GNUInstallDirs) + +find_package(draco REQUIRED CONFIG) + +add_executable(install_check main.cc) +target_link_libraries(install_check PRIVATE draco::draco) + +install(TARGETS install_check DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/contrib/draco/src/draco/tools/install_test/main.cc b/contrib/draco/src/draco/tools/install_test/main.cc new file mode 100644 index 000000000..e76793b64 --- /dev/null +++ b/contrib/draco/src/draco/tools/install_test/main.cc @@ -0,0 +1,44 @@ +// Copyright 2022 The Draco Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// This program is used to test the installed version of Draco. It does just +// enough to confirm that an application using Draco can compile and link +// against an installed version of Draco without errors. It does not perform +// any sort of library tests. + +#include +#include + +#include "draco/core/decoder_buffer.h" + +#if defined DRACO_TRANSCODER_SUPPORTED +#include "draco/scene/scene.h" +#include "draco/scene/scene_utils.h" +#endif + +int main(int /*argc*/, char** /*argv*/) { + std::vector empty_buffer; + draco::DecoderBuffer buffer; + buffer.Init(empty_buffer.data(), empty_buffer.size()); + +#if defined DRACO_TRANSCODER_SUPPORTED + draco::Scene empty_scene; + const int num_meshes = empty_scene.NumMeshes(); + (void)num_meshes; +#endif + + printf("Partial sanity test passed.\n"); + return 0; +} diff --git a/contrib/draco/src/draco/tools/install_test/test.py b/contrib/draco/src/draco/tools/install_test/test.py new file mode 100644 index 000000000..8612e70b5 --- /dev/null +++ b/contrib/draco/src/draco/tools/install_test/test.py @@ -0,0 +1,456 @@ +#!/usr/bin/python3 +# +# Copyright 2022 The Draco Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests installations of the Draco library. + +Builds the library in shared and static configurations on the current host +system, and then confirms that a simple test application can link in both +configurations. +""" + +import argparse +import multiprocessing +import os +import pathlib +import shlex +import shutil +import subprocess +import sys + +# CMake executable. +CMAKE = shutil.which('cmake') + +# List of generators available in the current CMake executable. +CMAKE_AVAILABLE_GENERATORS = [] + +# List of variable defs to be passed through to CMake via its -D argument. +CMAKE_DEFINES = [] + +# CMake builds use the specified generator. +CMAKE_GENERATOR = None + +# Enable the transcoder before running tests (sets DRACO_TRANSCODER_SUPPORTED +# and builds transcoder support dependencies). +ENABLE_TRANSCODER = False + +# The Draco tree that this script uses. +DRACO_SOURCES_PATH = os.path.abspath(os.path.join('..', '..', '..', '..')) + +# Path to this script and the rest of the test project files. +TEST_SOURCES_PATH = os.path.dirname(os.path.abspath(__file__)) + +# The Draco build directories. +DRACO_SHARED_BUILD_PATH = os.path.join(TEST_SOURCES_PATH, '_draco_build_shared') +DRACO_STATIC_BUILD_PATH = os.path.join(TEST_SOURCES_PATH, '_draco_build_static') + +# The Draco install roots. +DRACO_SHARED_INSTALL_PATH = os.path.join(TEST_SOURCES_PATH, + '_draco_install_shared') +DRACO_STATIC_INSTALL_PATH = os.path.join(TEST_SOURCES_PATH, + '_draco_install_static') + +DRACO_SHARED_INSTALL_BIN_PATH = os.path.join(DRACO_SHARED_INSTALL_PATH, 'bin') + +DRACO_SHARED_INSTALL_LIB_PATH = os.path.join(DRACO_SHARED_INSTALL_PATH, 'lib') + +# Argument for -j when using make, or -m when using Visual Studio. Number of +# build jobs. +NUM_PROCESSES = multiprocessing.cpu_count() - 1 + +# The test project build directories. +TEST_SHARED_BUILD_PATH = os.path.join(TEST_SOURCES_PATH, '_test_build_shared') +TEST_STATIC_BUILD_PATH = os.path.join(TEST_SOURCES_PATH, '_test_build_static') + +# The test project install directories. +TEST_SHARED_INSTALL_PATH = os.path.join(TEST_SOURCES_PATH, + '_test_install_shared') +TEST_STATIC_INSTALL_PATH = os.path.join(TEST_SOURCES_PATH, + '_test_install_static') + +# Show configuration and build output. +VERBOSE = False + + +def cmake_get_available_generators(): + """Returns list of generators available in current CMake executable.""" + result = run_process_and_capture_output(f'{CMAKE} --help') + + if result[0] != 0: + raise Exception(f'cmake --help failed, exit code: {result[0]}\n{result[1]}') + + help_text = result[1].splitlines() + generators_start_index = help_text.index('Generators') + 3 + generators_text = help_text[generators_start_index::] + + generators = [] + for gen in generators_text: + gen = gen.split('=')[0].strip().replace('* ', '') + + if gen and gen[0] != '=': + generators.append(gen) + + return generators + + +def cmake_get_generator(): + """Returns the CMake generator from CMakeCache.txt in the current dir.""" + cmake_cache_file_path = os.path.join(os.getcwd(), 'CMakeCache.txt') + cmake_cache_text = '' + with open(cmake_cache_file_path, 'r') as cmake_cache_file: + cmake_cache_text = cmake_cache_file.read() + + if not cmake_cache_text: + raise FileNotFoundError(f'{cmake_cache_file_path} missing or empty.') + + generator = '' + for line in cmake_cache_text.splitlines(): + if line.startswith('CMAKE_GENERATOR:INTERNAL='): + generator = line.split('=')[1] + + return generator + + +def run_process_and_capture_output(cmd, env=None): + """Runs |cmd| as a child process. + + Returns process exit code and output. Streams process output to stdout when + VERBOSE is true. + + Args: + cmd: String containing the command to execute. + env: Optional dict of environment variables. + + Returns: + Tuple of exit code and output. + """ + if not cmd: + raise ValueError('run_process_and_capture_output requires cmd argument.') + + if os.name == 'posix': + # On posix systems subprocess.Popen will treat |cmd| as the program name + # when it is passed as a string. Unconditionally split the command so + # callers don't need to care about this detail. + cmd = shlex.split(cmd) + + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env) + + if VERBOSE: + print('COMMAND output:') + + stdout = '' + for line in iter(proc.stdout.readline, b''): + decoded_line = line.decode('utf-8') + if VERBOSE: + sys.stdout.write(decoded_line) + sys.stdout.flush() + stdout += decoded_line + + # Wait for the process to exit so that the exit code is available. + proc.wait() + return [proc.returncode, stdout] + + +def create_output_directories(): + """Creates the build output directores for the test.""" + pathlib.Path(DRACO_SHARED_BUILD_PATH).mkdir(parents=True, exist_ok=True) + pathlib.Path(DRACO_STATIC_BUILD_PATH).mkdir(parents=True, exist_ok=True) + pathlib.Path(TEST_SHARED_BUILD_PATH).mkdir(parents=True, exist_ok=True) + pathlib.Path(TEST_STATIC_BUILD_PATH).mkdir(parents=True, exist_ok=True) + + +def cleanup(): + """Removes the build output directories from the test.""" + shutil.rmtree(DRACO_SHARED_BUILD_PATH) + shutil.rmtree(DRACO_STATIC_BUILD_PATH) + shutil.rmtree(DRACO_SHARED_INSTALL_PATH) + shutil.rmtree(DRACO_STATIC_INSTALL_PATH) + shutil.rmtree(TEST_SHARED_BUILD_PATH) + shutil.rmtree(TEST_STATIC_BUILD_PATH) + shutil.rmtree(TEST_SHARED_INSTALL_PATH) + shutil.rmtree(TEST_STATIC_INSTALL_PATH) + + +def cmake_configure(source_path, cmake_args=None): + """Configures a CMake build.""" + command = f'{CMAKE} {source_path}' + + if CMAKE_GENERATOR: + if ' ' in CMAKE_GENERATOR: + command += f' -G "{CMAKE_GENERATOR}"' + else: + command += f' -G {CMAKE_GENERATOR}' + + if cmake_args: + for arg in cmake_args: + command += f' {arg}' + + if CMAKE_DEFINES: + for arg in CMAKE_DEFINES: + command += f' -D{arg}' + + if VERBOSE: + print(f'CONFIGURE command:\n{command}') + + result = run_process_and_capture_output(command) + + if result[0] != 0: + raise Exception(f'CONFIGURE failed!\nexit_code: {result[0]}\n{result[1]}') + + +def cmake_build(cmake_args=None, build_args=None): + """Runs a CMake build.""" + command = f'{CMAKE} --build .' + + if cmake_args: + for arg in cmake_args: + command += f' {arg}' + + if not build_args: + build_args = [] + + generator = cmake_get_generator() + if generator.endswith('Makefiles'): + build_args.append(f'-j {NUM_PROCESSES}') + elif generator.startswith('Visual'): + build_args.append(f'-m:{NUM_PROCESSES}') + + if build_args: + command += ' --' + for arg in build_args: + command += f' {arg}' + + if VERBOSE: + print(f'BUILD command:\n{command}') + + result = run_process_and_capture_output(f'{command}') + + if result[0] != 0: + raise Exception(f'BUILD failed!\nexit_code: {result[0]}\n{result[1]}') + + +def run_install_check(install_path): + """Runs the install_check program.""" + cmd = os.path.join(install_path, 'bin', 'install_check') + if VERBOSE: + print(f'RUN command: {cmd}') + + result = run_process_and_capture_output( + cmd, + # On Windows, add location of draco.dll into PATH env var + {'PATH': DRACO_SHARED_INSTALL_BIN_PATH + os.pathsep + os.environ['PATH']}, + ) + if result[0] != 0: + raise Exception( + f'install_check run failed!\nexit_code: {result[0]}\n{result[1]}') + + +def build_and_install_transcoder_dependencies(): + """Builds and installs Draco dependencies for transcoder enabled builds.""" + orig_dir = os.getcwd() + + # The Eigen CMake build in the release Draco has pinned is, to put it mildly, + # user unfriendly. Instead of wasting time trying to integrate it here, just + # shutil.copytree() everything in $eigen_submodule_path to + # $CMAKE_INSTALL_PREFIX/include/Eigen. + # Eigen claims to be header-only, so this should be adequate for Draco's + # needs here. + eigen_submodule_path = os.path.join( + DRACO_SOURCES_PATH, 'third_party', 'eigen', 'Eigen') + + # "Install" Eigen for the shared install root. + eigen_install_path = os.path.join( + DRACO_SHARED_INSTALL_PATH, 'include', 'Eigen') + shutil.copytree(src=eigen_submodule_path, dst=eigen_install_path) + + # "Install" Eigen for the static install root. + eigen_install_path = os.path.join( + DRACO_STATIC_INSTALL_PATH, 'include', 'Eigen') + shutil.copytree(src=eigen_submodule_path, dst=eigen_install_path) + + # Build and install gulrak/filesystem for shared and static configurations. + # Note that this is basically running gulrak/filesystem's CMake build as an + # install script. + fs_submodule_path = os.path.join( + DRACO_SOURCES_PATH, 'third_party', 'filesystem') + + # Install gulrak/filesystem in the shared draco install root. + fs_shared_build = os.path.join(DRACO_SHARED_BUILD_PATH, '_fs') + pathlib.Path(fs_shared_build).mkdir(parents=True, exist_ok=True) + os.chdir(fs_shared_build) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={DRACO_SHARED_INSTALL_PATH}') + cmake_args.append('-DBUILD_SHARED_LIBS=ON') + cmake_args.append('-DGHC_FILESYSTEM_BUILD_TESTING=OFF') + cmake_args.append('-DGHC_FILESYSTEM_BUILD_EXAMPLES=OFF') + cmake_configure(source_path=fs_submodule_path, cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + + # Install gulrak/filesystem in the shared draco install root. + fs_static_build = os.path.join(DRACO_STATIC_BUILD_PATH, '_fs') + pathlib.Path(fs_static_build).mkdir(parents=True, exist_ok=True) + os.chdir(fs_static_build) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={DRACO_SHARED_INSTALL_PATH}') + cmake_args.append('-DBUILD_SHARED_LIBS=OFF') + cmake_args.append('-DGHC_FILESYSTEM_BUILD_TESTING=OFF') + cmake_args.append('-DGHC_FILESYSTEM_BUILD_EXAMPLES=OFF') + cmake_configure(source_path=fs_submodule_path, cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + + # Build and install TinyGLTF for shared and static configurations. + # Note, as above, that this is basically running TinyGLTF's CMake build as an + # install script. + tinygltf_submodule_path = os.path.join( + DRACO_SOURCES_PATH, 'third_party', 'tinygltf') + + # Install TinyGLTF in the shared draco install root. + tinygltf_shared_build = os.path.join(DRACO_SHARED_BUILD_PATH, '_TinyGLTF') + pathlib.Path(tinygltf_shared_build).mkdir(parents=True, exist_ok=True) + os.chdir(tinygltf_shared_build) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={DRACO_SHARED_INSTALL_PATH}') + cmake_args.append('-DTINYGLTF_BUILD_EXAMPLES=OFF') + cmake_configure(source_path=tinygltf_submodule_path, cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + + # Install TinyGLTF in the static draco install root. + tinygltf_static_build = os.path.join(DRACO_STATIC_BUILD_PATH, '_TinyGLTF') + pathlib.Path(tinygltf_static_build).mkdir(parents=True, exist_ok=True) + os.chdir(tinygltf_static_build) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={DRACO_STATIC_INSTALL_PATH}') + cmake_args.append('-DTINYGLTF_BUILD_EXAMPLES=OFF') + cmake_configure(source_path=tinygltf_submodule_path, cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + + os.chdir(orig_dir) + + +def build_and_install_draco(): + """Builds Draco in shared and static configurations.""" + orig_dir = os.getcwd() + + if ENABLE_TRANSCODER: + build_and_install_transcoder_dependencies() + + # Build and install Draco in shared library config for the current host + # machine. + os.chdir(DRACO_SHARED_BUILD_PATH) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={DRACO_SHARED_INSTALL_PATH}') + cmake_args.append('-DBUILD_SHARED_LIBS=ON') + if ENABLE_TRANSCODER: + cmake_args.append('-DDRACO_TRANSCODER_SUPPORTED=ON') + cmake_configure(source_path=DRACO_SOURCES_PATH, cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + + # Build and install Draco in the static config for the current host machine. + os.chdir(DRACO_STATIC_BUILD_PATH) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={DRACO_STATIC_INSTALL_PATH}') + cmake_args.append('-DBUILD_SHARED_LIBS=OFF') + if ENABLE_TRANSCODER: + cmake_args.append('-DDRACO_TRANSCODER_SUPPORTED=ON') + cmake_configure(source_path=DRACO_SOURCES_PATH, cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + + os.chdir(orig_dir) + + +def build_test_project(): + """Builds the test application in shared and static configurations.""" + orig_dir = os.getcwd() + + # Configure the test project against draco shared and build it. + os.chdir(TEST_SHARED_BUILD_PATH) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={TEST_SHARED_INSTALL_PATH}') + cmake_args.append(f'-DCMAKE_PREFIX_PATH={DRACO_SHARED_INSTALL_PATH}') + cmake_args.append(f'-DCMAKE_INSTALL_RPATH={DRACO_SHARED_INSTALL_LIB_PATH}') + cmake_configure(source_path=f'{TEST_SOURCES_PATH}', cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + run_install_check(TEST_SHARED_INSTALL_PATH) + + # Configure the test project against draco static and build it. + os.chdir(TEST_STATIC_BUILD_PATH) + cmake_args = [] + cmake_args.append(f'-DCMAKE_INSTALL_PREFIX={TEST_STATIC_INSTALL_PATH}') + cmake_args.append(f'-DCMAKE_PREFIX_PATH={DRACO_STATIC_INSTALL_PATH}') + cmake_configure(source_path=f'{TEST_SOURCES_PATH}', cmake_args=cmake_args) + cmake_build(cmake_args=['--target install']) + run_install_check(TEST_STATIC_INSTALL_PATH) + + os.chdir(orig_dir) + + +def test_draco_install(): + create_output_directories() + build_and_install_draco() + build_test_project() + cleanup() + + +if __name__ == '__main__': + CMAKE_AVAILABLE_GENERATORS = cmake_get_available_generators() + + parser = argparse.ArgumentParser() + parser.add_argument( + '-G', '--generator', help='CMake builds use the specified generator.') + parser.add_argument( + '-D', '--cmake_define', + action='append', + help='Passes argument through to CMake as a CMake variable via cmake -D.') + parser.add_argument( + '-t', '--with_transcoder', + action='store_true', + help='Run tests with Draco transcoder support enabled.') + parser.add_argument( + '-v', + '--verbose', + action='store_true', + help='Show configuration and build output.') + args = parser.parse_args() + + if args.cmake_define: + CMAKE_DEFINES = args.cmake_define + if args.generator: + CMAKE_GENERATOR = args.generator + if args.verbose: + VERBOSE = True + if args.with_transcoder: + ENABLE_TRANSCODER = True + + if VERBOSE: + print(f'CMAKE={CMAKE}') + print(f'CMAKE_DEFINES={CMAKE_DEFINES}') + print(f'CMAKE_GENERATOR={CMAKE_GENERATOR}') + print(f'CMAKE_AVAILABLE_GENERATORS={CMAKE_AVAILABLE_GENERATORS}') + print(f'ENABLE_TRANSCODER={ENABLE_TRANSCODER}') + print(f'DRACO_SOURCES_PATH={DRACO_SOURCES_PATH}') + print(f'DRACO_SHARED_BUILD_PATH={DRACO_SHARED_BUILD_PATH}') + print(f'DRACO_STATIC_BUILD_PATH={DRACO_STATIC_BUILD_PATH}') + print(f'DRACO_SHARED_INSTALL_PATH={DRACO_SHARED_INSTALL_PATH}') + print(f'DRACO_STATIC_INSTALL_PATH={DRACO_STATIC_INSTALL_PATH}') + print(f'NUM_PROCESSES={NUM_PROCESSES}') + print(f'TEST_SHARED_BUILD_PATH={TEST_SHARED_BUILD_PATH}') + print(f'TEST_STATIC_BUILD_PATH={TEST_STATIC_BUILD_PATH}') + print(f'TEST_SOURCES_PATH={TEST_SOURCES_PATH}') + print(f'VERBOSE={VERBOSE}') + + if CMAKE_GENERATOR and CMAKE_GENERATOR not in CMAKE_AVAILABLE_GENERATORS: + raise ValueError(f'CMake generator unavailable: {CMAKE_GENERATOR}.') + + test_draco_install() From f7f54036f20547b446600476abf1cae902d8148d Mon Sep 17 00:00:00 2001 From: Jackie9527 <80555200+Jackie9527@users.noreply.github.com> Date: Sat, 25 Feb 2023 10:10:03 +0800 Subject: [PATCH 06/49] bugfix the three vertices are collinear when converting a polygon to a triangle. Signed-off-by: Jackie9527 <80555200+Jackie9527@users.noreply.github.com> --- code/PostProcessing/TriangulateProcess.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/code/PostProcessing/TriangulateProcess.cpp b/code/PostProcessing/TriangulateProcess.cpp index 52e760361..8ba6456b7 100644 --- a/code/PostProcessing/TriangulateProcess.cpp +++ b/code/PostProcessing/TriangulateProcess.cpp @@ -468,6 +468,21 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) continue; } + // Skip when three point is in a line + aiVector2D left = *pnt0 - *pnt1; + aiVector2D right = *pnt2 - *pnt1; + + left.Normalize(); + right.Normalize(); + auto mul = left * right; + + // if the angle is 0 or 180 + if (std::abs(mul - 1.f) < ai_epsilon || std::abs(mul + 1.f) < ai_epsilon) { + // skip this ear + ASSIMP_LOG_WARN("Skip a ear, due to its angle is near 0 or 180."); + continue; + } + // and no other point may be contained in this triangle for ( tmp = 0; tmp < max; ++tmp) { From 09dd0d0c2e137ad6bbb8a80782bcee1bb99fa945 Mon Sep 17 00:00:00 2001 From: Jackie9527 <80555200+Jackie9527@users.noreply.github.com> Date: Wed, 1 Mar 2023 15:08:02 +0800 Subject: [PATCH 07/49] Fix build error when building SimpleTexturedDirectx11 with VS2022. Signed-off-by: Jackie9527 <80555200+Jackie9527@users.noreply.github.com> --- samples/SimpleTexturedDirectx11/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/samples/SimpleTexturedDirectx11/CMakeLists.txt b/samples/SimpleTexturedDirectx11/CMakeLists.txt index 007ada3af..b05c3b6ae 100644 --- a/samples/SimpleTexturedDirectx11/CMakeLists.txt +++ b/samples/SimpleTexturedDirectx11/CMakeLists.txt @@ -8,6 +8,9 @@ if ( MSVC ) ADD_DEFINITIONS( -D_SCL_SECURE_NO_WARNINGS ) ADD_DEFINITIONS( -D_CRT_SECURE_NO_WARNINGS ) REMOVE_DEFINITIONS( -DUNICODE -D_UNICODE ) + if ( MSVC_VERSION GREATER_EQUAL 1930 ) + ADD_DEFINITIONS( -D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING=1 ) + endif () endif () ADD_COMPILE_DEFINITIONS(SHADER_PATH="${CMAKE_CURRENT_SOURCE_DIR}/SimpleTexturedDirectx11/") From 1092f0d94e4cf60a28ce96a51334c9f243b997e0 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Wed, 1 Mar 2023 21:40:45 +0100 Subject: [PATCH 08/49] Fix:Use correct encoding --- include/assimp/Base64.hpp | 14 ++- samples/SharedCode/UTFConverter.cpp | 52 --------- samples/SharedCode/UTFConverter.h | 103 ------------------ .../SimpleTexturedDirectx11/CMakeLists.txt | 2 - .../SimpleTexturedDirectx11/main.cpp | 19 +++- samples/SimpleTexturedOpenGL/CMakeLists.txt | 2 - tools/assimp_view/AnimEvaluator.cpp | 6 - tools/assimp_view/AnimEvaluator.h | 2 +- tools/assimp_view/AssetHelper.h | 17 ++- tools/assimp_view/Material.cpp | 22 ++-- tools/assimp_view/assimp_view.cpp | 9 +- tools/assimp_view/assimp_view.h | 6 + 12 files changed, 49 insertions(+), 205 deletions(-) delete mode 100644 samples/SharedCode/UTFConverter.cpp delete mode 100644 samples/SharedCode/UTFConverter.h diff --git a/include/assimp/Base64.hpp b/include/assimp/Base64.hpp index 403723857..10288713c 100644 --- a/include/assimp/Base64.hpp +++ b/include/assimp/Base64.hpp @@ -43,6 +43,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef AI_BASE64_HPP_INC #define AI_BASE64_HPP_INC +#include + #include #include #include @@ -54,35 +56,35 @@ namespace Base64 { /// @param in The UTF-64 buffer. /// @param inLength The size of the buffer /// @param out The encoded ASCII string. -void Encode(const uint8_t *in, size_t inLength, std::string &out); +ASSIMP_API void Encode(const uint8_t *in, size_t inLength, std::string &out); /// @brief Will encode the given character buffer from UTF64 to ASCII. /// @param in A vector, which contains the buffer for encoding. /// @param out The encoded ASCII string. -void Encode(const std::vector& in, std::string &out); +ASSIMP_API void Encode(const std::vector &in, std::string &out); /// @brief Will encode the given character buffer from UTF64 to ASCII. /// @param in A vector, which contains the buffer for encoding. /// @return The encoded ASCII string. -std::string Encode(const std::vector& in); +ASSIMP_API std::string Encode(const std::vector &in); /// @brief Will decode the given character buffer from ASCII to UTF64. /// @param in The ASCII buffer to decode. /// @param inLength The size of the buffer. /// @param out The decoded buffer. /// @return The new buffer size. -size_t Decode(const char *in, size_t inLength, uint8_t *&out); +ASSIMP_API size_t Decode(const char *in, size_t inLength, uint8_t *&out); /// @brief Will decode the given character buffer from ASCII to UTF64. /// @param in The ASCII buffer to decode as a std::string. /// @param out The decoded buffer. /// @return The new buffer size. -size_t Decode(const std::string& in, std::vector& out); +ASSIMP_API size_t Decode(const std::string &in, std::vector &out); /// @brief Will decode the given character buffer from ASCII to UTF64. /// @param in The ASCII string. /// @return The decoded buffer in a vector. -std::vector Decode(const std::string& in); +ASSIMP_API std::vector Decode(const std::string &in); } // namespace Base64 } // namespace Assimp diff --git a/samples/SharedCode/UTFConverter.cpp b/samples/SharedCode/UTFConverter.cpp deleted file mode 100644 index e6c07e946..000000000 --- a/samples/SharedCode/UTFConverter.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* ---------------------------------------------------------------------------- -Open Asset Import Library (assimp) ---------------------------------------------------------------------------- - -Copyright (c) 2006-2020, assimp team - - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following -conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------- -*/ - -#include "UTFConverter.h" - -namespace AssimpSamples { -namespace SharedCode { - -//typename UTFConverter::UTFConverterImpl UTFConverter::impl_; - -} -} diff --git a/samples/SharedCode/UTFConverter.h b/samples/SharedCode/UTFConverter.h deleted file mode 100644 index 34b2293de..000000000 --- a/samples/SharedCode/UTFConverter.h +++ /dev/null @@ -1,103 +0,0 @@ -/* ---------------------------------------------------------------------------- -Open Asset Import Library (assimp) ---------------------------------------------------------------------------- - -Copyright (c) 2006-2020, assimp team - - - -All rights reserved. - -Redistribution and use of this software in source and binary forms, -with or without modification, are permitted provided that the following -conditions are met: - -* Redistributions of source code must retain the above - copyright notice, this list of conditions and the - following disclaimer. - -* Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the - following disclaimer in the documentation and/or other - materials provided with the distribution. - -* Neither the name of the assimp team, nor the names of its - contributors may be used to endorse or promote products - derived from this software without specific prior - written permission of the assimp team. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------------- -*/ - -#ifndef ASSIMP_SAMPLES_SHARED_CODE_UTFCONVERTER_H -#define ASSIMP_SAMPLES_SHARED_CODE_UTFCONVERTER_H - -#include -#include - -#ifdef ASSIMP_USE_HUNTER -#include -#else -#include "../contrib/utf8cpp/source/utf8.h" -#endif - -namespace AssimpSamples { -namespace SharedCode { - -// Used to convert between multibyte and unicode strings. -class UTFConverter { -public: - //utf8::utf16to8(start, end, back_inserter(str)); - - UTFConverter(const char* s) : s_(s), ws_() { - std::vector str; - utf8::utf8to16(s, s + std::strlen(s) + 1, back_inserter(str)); - } - - UTFConverter(const wchar_t* s) : s_(),ws_(s) { - std::vector str; - utf8::utf16to8(s, s + ws_.size() + 1, back_inserter(str)); - } - - UTFConverter(const std::string& s) : s_(s), ws_() { - std::vector str; - utf8::utf8to16(s.c_str(), s.c_str() + s.size() + 1, back_inserter(str)); - } - - UTFConverter(const std::wstring& s) : s_(), ws_(s) { - std::vector str; - utf8::utf16to8(s.c_str(), s.c_str() + ws_.size() + 1, back_inserter(str)); - } - - inline const char* c_str() const { - return s_.c_str(); - } - - inline const std::string& str() const { - return s_; - } - - inline const wchar_t* c_wstr() const { - return ws_.c_str(); - } -private: - std::string s_; - std::wstring ws_; -}; - -} -} - -#endif // ASSIMP_SAMPLES_SHARED_CODE_UTFCONVERTER_H diff --git a/samples/SimpleTexturedDirectx11/CMakeLists.txt b/samples/SimpleTexturedDirectx11/CMakeLists.txt index 007ada3af..f1e03888c 100644 --- a/samples/SimpleTexturedDirectx11/CMakeLists.txt +++ b/samples/SimpleTexturedDirectx11/CMakeLists.txt @@ -33,8 +33,6 @@ ADD_EXECUTABLE( assimp_simpletextureddirectx11 WIN32 #SimpleTexturedDirectx11/VertexShader.hlsl SimpleTexturedDirectx11/main.cpp SimpleTexturedDirectx11/SafeRelease.hpp - ${SAMPLES_SHARED_CODE_DIR}/UTFConverter.cpp - ${SAMPLES_SHARED_CODE_DIR}/UTFConverter.h ) TARGET_USE_COMMON_OUTPUT_DIRECTORY(assimp_simpletextureddirectx11) diff --git a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/main.cpp b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/main.cpp index f7b35024b..7f0d0c84e 100644 --- a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/main.cpp +++ b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/main.cpp @@ -21,8 +21,12 @@ #include #include #include +#ifdef ASSIMP_USE_HUNTER +#include +#else +#include "../contrib/utf8cpp/source/utf8.h" +#endif #include "ModelLoader.h" -#include "UTFConverter.h" #include "SafeRelease.hpp" #ifdef _MSC_VER @@ -33,7 +37,6 @@ #endif // _MSC_VER using namespace DirectX; -using namespace AssimpSamples::SharedCode; #define VERTEX_SHADER_FILE L"VertexShader.hlsl" #define PIXEL_SHADER_FILE L"PixelShader.hlsl" @@ -154,8 +157,14 @@ int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, } // Retrieve the model file path. - g_ModelPath = UTFConverter(argv[1]).str(); + std::wstring filename(argv[1]); + + char *targetStart = new char[filename.size()+1]; + memset(targetStart, '\0', filename.size() + 1); + utf8::utf16to8(filename.c_str(), filename.c_str() + filename.size(), targetStart); + g_ModelPath = targetStart; + delete[] targetStart; free_command_line_allocated_memory(); WNDCLASSEX wc; @@ -511,9 +520,9 @@ void InitPipeline() { ID3DBlob *VS, *PS; if(FAILED(CompileShaderFromFile(SHADER_PATH VERTEX_SHADER_FILE, 0, "main", "vs_4_0", &VS))) - Throwanerror(UTFConverter(L"Failed to compile shader from file " VERTEX_SHADER_FILE).c_str()); + Throwanerror("Failed to compile shader from file"); if(FAILED(CompileShaderFromFile(SHADER_PATH PIXEL_SHADER_FILE, 0, "main", "ps_4_0", &PS))) - Throwanerror(UTFConverter(L"Failed to compile shader from file " PIXEL_SHADER_FILE).c_str()); + Throwanerror("Failed to compile shader from file "); dev->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), nullptr, &pVS); dev->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), nullptr, &pPS); diff --git a/samples/SimpleTexturedOpenGL/CMakeLists.txt b/samples/SimpleTexturedOpenGL/CMakeLists.txt index 1837af033..70837e87c 100644 --- a/samples/SimpleTexturedOpenGL/CMakeLists.txt +++ b/samples/SimpleTexturedOpenGL/CMakeLists.txt @@ -30,8 +30,6 @@ LINK_DIRECTORIES( ADD_EXECUTABLE( assimp_simpletexturedogl WIN32 SimpleTexturedOpenGL/src/model_loading.cpp - ${SAMPLES_SHARED_CODE_DIR}/UTFConverter.cpp - ${SAMPLES_SHARED_CODE_DIR}/UTFConverter.h ) TARGET_USE_COMMON_OUTPUT_DIRECTORY(assimp_simpletexturedogl) diff --git a/tools/assimp_view/AnimEvaluator.cpp b/tools/assimp_view/AnimEvaluator.cpp index ad8e7f5b2..4f8d1bda8 100644 --- a/tools/assimp_view/AnimEvaluator.cpp +++ b/tools/assimp_view/AnimEvaluator.cpp @@ -55,12 +55,6 @@ AnimEvaluator::AnimEvaluator(const aiAnimation *pAnim) : mLastPositions.resize(pAnim->mNumChannels, std::make_tuple(0, 0, 0)); } -// ------------------------------------------------------------------------------------------------ -// Destructor. -AnimEvaluator::~AnimEvaluator() { - // empty -} - // ------------------------------------------------------------------------------------------------ // Evaluates the animation tracks for a given time stamp. void AnimEvaluator::Evaluate(double pTime) { diff --git a/tools/assimp_view/AnimEvaluator.h b/tools/assimp_view/AnimEvaluator.h index 76b22ea8a..aa44ab211 100644 --- a/tools/assimp_view/AnimEvaluator.h +++ b/tools/assimp_view/AnimEvaluator.h @@ -66,7 +66,7 @@ public: AnimEvaluator(const aiAnimation *pAnim); /// @brief The class destructor. - ~AnimEvaluator(); + ~AnimEvaluator() = default; /// @brief Evaluates the animation tracks for a given time stamp. /// The calculated pose can be retrieved as an array of transformation diff --git a/tools/assimp_view/AssetHelper.h b/tools/assimp_view/AssetHelper.h index 1ae469f85..8661d875f 100644 --- a/tools/assimp_view/AssetHelper.h +++ b/tools/assimp_view/AssetHelper.h @@ -77,6 +77,13 @@ public: pcScene = NULL; } + // set the normal set to be used + void SetNormalSet(unsigned int iSet); + + // flip all normal vectors + void FlipNormals(); + void FlipNormalsInt(); + //--------------------------------------------------------------- // default vertex data structure // (even if tangents, bitangents or normals aren't @@ -221,16 +228,8 @@ public: // Specifies the normal set to be used unsigned int iNormalSet; - - // ------------------------------------------------------------------ - // set the normal set to be used - void SetNormalSet(unsigned int iSet); - - // ------------------------------------------------------------------ - // flip all normal vectors - void FlipNormals(); - void FlipNormalsInt(); }; + } // namespace AssimpView #endif // !! IG diff --git a/tools/assimp_view/Material.cpp b/tools/assimp_view/Material.cpp index e3c023bd9..c28231332 100644 --- a/tools/assimp_view/Material.cpp +++ b/tools/assimp_view/Material.cpp @@ -175,33 +175,29 @@ VOID WINAPI FillFunc(D3DXVECTOR4* pOut, pOut->x = pOut->y = 1.0f; pOut->z = 0.0f; } - return; } //------------------------------------------------------------------------------- -int CMaterialManager::UpdateSpecularMaterials() - { - if (g_pcAsset && g_pcAsset->pcScene) - { - for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumMeshes;++i) - { - if (aiShadingMode_Phong == g_pcAsset->apcMeshes[i]->eShadingMode) - { +int CMaterialManager::UpdateSpecularMaterials() { + if (g_pcAsset && g_pcAsset->pcScene) { + for (unsigned int i = 0; i < g_pcAsset->pcScene->mNumMeshes;++i) { + if (aiShadingMode_Phong == g_pcAsset->apcMeshes[i]->eShadingMode) { this->DeleteMaterial(g_pcAsset->apcMeshes[i]); this->CreateMaterial(g_pcAsset->apcMeshes[i],g_pcAsset->pcScene->mMeshes[i]); - } } } - return 1; } + return 1; +} + //------------------------------------------------------------------------------- -int CMaterialManager::SetDefaultTexture(IDirect3DTexture9** p_ppiOut) -{ +int CMaterialManager::SetDefaultTexture(IDirect3DTexture9** p_ppiOut) { if (sDefaultTexture) { sDefaultTexture->AddRef(); *p_ppiOut = sDefaultTexture; return 1; } + if(FAILED(g_piDevice->CreateTexture( 256, 256, diff --git a/tools/assimp_view/assimp_view.cpp b/tools/assimp_view/assimp_view.cpp index c5c48fd93..e00c6e39d 100644 --- a/tools/assimp_view/assimp_view.cpp +++ b/tools/assimp_view/assimp_view.cpp @@ -145,9 +145,7 @@ float g_fLoadTime = 0.0f; // The loader thread loads the asset while the progress dialog displays the // smart progress bar //------------------------------------------------------------------------------- -DWORD WINAPI LoadThreadProc(LPVOID lpParameter) { - UNREFERENCED_PARAMETER(lpParameter); - +DWORD WINAPI LoadThreadProc(LPVOID) { // get current time double fCur = (double)timeGetTime(); @@ -367,7 +365,7 @@ int CalculateBounds(aiNode *piNode, aiVector3D *p_avOut, const aiMatrix4x4 &piMa // The function calculates the boundaries of the mesh and modifies the // global world transformation matrix according to the aset AABB //------------------------------------------------------------------------------- -int ScaleAsset(void) { +int ScaleAsset() { aiVector3D aiVecs[2] = { aiVector3D(1e10f, 1e10f, 1e10f), aiVector3D(-1e10f, -1e10f, -1e10f) }; @@ -521,8 +519,7 @@ int CreateAssetData() { } } else { // create 16 bit index buffer - if (FAILED(g_piDevice->CreateIndexBuffer(2 * -numIndices, + if (FAILED(g_piDevice->CreateIndexBuffer(2 * numIndices, D3DUSAGE_WRITEONLY | dwUsage, D3DFMT_INDEX16, D3DPOOL_DEFAULT, diff --git a/tools/assimp_view/assimp_view.h b/tools/assimp_view/assimp_view.h index cbcee3cac..e67cc9fd0 100644 --- a/tools/assimp_view/assimp_view.h +++ b/tools/assimp_view/assimp_view.h @@ -98,6 +98,12 @@ namespace AssimpView { //------------------------------------------------------------------------------- // Function prototypes //------------------------------------------------------------------------------- +class AssimpVew { +public: + AssimpVew(); + ~AssimpVew(); +}; + int InitD3D(void); int ShutdownD3D(void); int CreateDevice(bool p_bMultiSample, bool p_bSuperSample, bool bHW = true); From 02a427692481e340a051a3e56504748e5a05fb06 Mon Sep 17 00:00:00 2001 From: Jackie9527 <80555200+Jackie9527@users.noreply.github.com> Date: Thu, 2 Mar 2023 09:11:54 +0800 Subject: [PATCH 09/49] upgrade stb_image to v2.28. Signed-off-by: Jackie9527 <80555200+Jackie9527@users.noreply.github.com> --- contrib/stb/stb_image.h | 427 +++++++++++++++++++++++++++++++--------- 1 file changed, 329 insertions(+), 98 deletions(-) diff --git a/contrib/stb/stb_image.h b/contrib/stb/stb_image.h index 8d173c66a..5e807a0a6 100644 --- a/contrib/stb/stb_image.h +++ b/contrib/stb/stb_image.h @@ -1,4 +1,4 @@ -/* stb_image - v2.26 - public domain image loader - http://nothings.org/stb +/* stb_image - v2.28 - public domain image loader - http://nothings.org/stb no warranty implied; use at your own risk Do this: @@ -48,6 +48,8 @@ LICENSE RECENT REVISION HISTORY: + 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes 2.26 (2020-07-13) many minor fixes 2.25 (2020-02-02) fix warnings 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically @@ -89,7 +91,7 @@ RECENT REVISION HISTORY: Jeremy Sawicki (handle all ImageNet JPGs) Optimizations & bugfixes Mikhail Morozov (1-bit BMP) Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) - Arseny Kapoulkine + Arseny Kapoulkine Simon Breuss (16-bit PNM) John-Mark Allen Carmelo J Fdez-Aguera @@ -102,19 +104,21 @@ RECENT REVISION HISTORY: Thomas Ruf Ronny Chevalier github:rlyeh Janez Zemva John Bartholomew Michal Cichon github:romigrou Jonathan Blow Ken Hamada Tero Hanninen github:svdijk - Laurent Gomila Cort Stratton github:snagar + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex Cass Everitt Ryamond Barbiero github:grim210 Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus - Josh Tobin Matthew Gregan github:poppolopoppo + Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo Julian Raschke Gregory Mullen Christian Floisand github:darealshinji Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 - Brad Weinberger Matvey Cherevko [reserved] + Brad Weinberger Matvey Cherevko github:mosra Luca Sas Alexander Veselov Zack Middleton [reserved] Ryan C. Gordon [reserved] [reserved] DO NOT ADD YOUR NAME HERE + Jacko Dirks + To add your name to the credits, pick a random blank space in the middle and fill it. 80% of merge conflicts on stb PRs are due to people adding their name at the end of the credits. @@ -137,7 +141,7 @@ RECENT REVISION HISTORY: // // ... x = width, y = height, n = # 8-bit components per pixel ... // // ... replace '0' with '1'..'4' to force that many components per pixel // // ... but 'n' will always be the number that it would have been if you said 0 -// stbi_image_free(data) +// stbi_image_free(data); // // Standard parameters: // int *x -- outputs image width in pixels @@ -176,6 +180,32 @@ RECENT REVISION HISTORY: // // Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. // +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// // =========================================================================== // // UNICODE: @@ -281,11 +311,10 @@ RECENT REVISION HISTORY: // // iPhone PNG support: // -// By default we convert iphone-formatted PNGs back to RGB, even though -// they are internally encoded differently. You can disable this conversion -// by calling stbi_convert_iphone_png_to_rgb(0), in which case -// you will always just get the native iphone "format" through (which -// is BGR stored in RGB). +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). // // Call stbi_set_unpremultiply_on_load(1) as well to force a divide per // pixel to remove any premultiplied alpha *only* if the image file explicitly @@ -489,6 +518,8 @@ STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); // as above, but only applies to images loaded on the thread that calls the function // this function is only available if your compiler supports thread-local variables; // calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); // ZLIB client - used by PNG, available for other purposes @@ -605,7 +636,7 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const ch #endif #endif -#ifdef _MSC_VER +#if defined(_MSC_VER) || defined(__SYMBIAN32__) typedef unsigned short stbi__uint16; typedef signed short stbi__int16; typedef unsigned int stbi__uint32; @@ -634,7 +665,7 @@ typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; #ifdef STBI_HAS_LROTL #define stbi_lrot(x,y) _lrotl(x,y) #else - #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) #endif #if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) @@ -748,9 +779,12 @@ static int stbi__sse2_available(void) #ifdef STBI_NEON #include -// assume GCC or Clang on ARM targets +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else #define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) #endif +#endif #ifndef STBI_SIMD_ALIGN #define STBI_SIMD_ALIGN(type, name) type name @@ -924,6 +958,7 @@ static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); static int stbi__pnm_test(stbi__context *s); static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); #endif static @@ -998,7 +1033,7 @@ static int stbi__mad3sizes_valid(int a, int b, int c, int add) } // returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) { return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && @@ -1021,7 +1056,7 @@ static void *stbi__malloc_mad3(int a, int b, int c, int add) return stbi__malloc(a*b*c + add); } -#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) { if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; @@ -1029,6 +1064,23 @@ static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) } #endif +// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow. +static int stbi__addints_valid(int a, int b) +{ + if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow + if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0. + return a <= INT_MAX - b; +} + +// returns 1 if the product of two signed shorts is valid, 0 on overflow. +static int stbi__mul2shorts_valid(short a, short b) +{ + if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow + if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid + if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN + return a >= SHRT_MIN / b; +} + // stbi__err - error // stbi__errpf - error returning pointer to float // stbi__errpuc - error returning pointer to unsigned char @@ -1087,9 +1139,8 @@ static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int re ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order ri->num_channels = 0; - #ifndef STBI_NO_JPEG - if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); - #endif + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) #ifndef STBI_NO_PNG if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); #endif @@ -1107,6 +1158,13 @@ static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int re #ifndef STBI_NO_PIC if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif #ifndef STBI_NO_PNM if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); #endif @@ -1262,12 +1320,12 @@ static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, in #ifndef STBI_NO_STDIO -#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); #endif -#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) { return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); @@ -1277,16 +1335,16 @@ STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wch static FILE *stbi__fopen(char const *filename, char const *mode) { FILE *f; -#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) wchar_t wMode[64]; wchar_t wFilename[1024]; - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) return 0; - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) return 0; -#if _MSC_VER >= 1400 +#if defined(_MSC_VER) && _MSC_VER >= 1400 if (0 != _wfopen_s(&f, wFilename, wMode)) f = 0; #else @@ -1662,7 +1720,8 @@ static int stbi__get16le(stbi__context *s) static stbi__uint32 stbi__get32le(stbi__context *s) { stbi__uint32 z = stbi__get16le(s); - return z + (stbi__get16le(s) << 16); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; } #endif @@ -1944,9 +2003,12 @@ static int stbi__build_huffman(stbi__huffman *h, int *count) int i,j,k=0; unsigned int code; // build size list for each symbol (from JPEG spec) - for (i=0; i < 16; ++i) - for (j=0; j < count[i]; ++j) + for (i=0; i < 16; ++i) { + for (j=0; j < count[i]; ++j) { h->size[k++] = (stbi_uc) (i+1); + if(k >= 257) return stbi__err("bad size list","Corrupt JPEG"); + } + } h->size[k] = 0; // compute actual symbols (from jpeg spec) @@ -2071,6 +2133,8 @@ stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) // convert the huffman code to the symbol id c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + if(c < 0 || c >= 256) // symbol id out of bounds! + return -1; STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); // convert the id to a symbol @@ -2089,14 +2153,14 @@ stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n) unsigned int k; int sgn; if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing - sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) k = stbi_lrot(j->code_buffer, n); - if (n < 0 || n >= (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))) return 0; j->code_buffer = k & ~stbi__bmask[n]; k &= stbi__bmask[n]; j->code_bits -= n; - return k + (stbi__jbias[n] & ~sgn); + return k + (stbi__jbias[n] & (sgn - 1)); } // get some unsigned bits @@ -2104,6 +2168,7 @@ stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) { unsigned int k; if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing k = stbi_lrot(j->code_buffer, n); j->code_buffer = k & ~stbi__bmask[n]; k &= stbi__bmask[n]; @@ -2115,6 +2180,7 @@ stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) { unsigned int k; if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing k = j->code_buffer; j->code_buffer <<= 1; --j->code_bits; @@ -2146,14 +2212,16 @@ static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); t = stbi__jpeg_huff_decode(j, hdc); - if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); // 0 all the ac values now so we can do it 32-bits at a time memset(data,0,64*sizeof(data[0])); diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); dc = j->img_comp[b].dc_pred + diff; j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); data[0] = (short) (dc * dequant[0]); // decode AC components, see JPEG spec @@ -2167,6 +2235,7 @@ static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman if (r) { // fast-AC path k += (r >> 4) & 15; // run s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); j->code_buffer <<= s; j->code_bits -= s; // decode into unzigzag'd location @@ -2203,12 +2272,14 @@ static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__ // first scan for DC coefficient, must be first memset(data,0,64*sizeof(data[0])); // 0 all the ac values now t = stbi__jpeg_huff_decode(j, hdc); - if (t == -1) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); dc = j->img_comp[b].dc_pred + diff; j->img_comp[b].dc_pred = dc; - data[0] = (short) (dc << j->succ_low); + if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * (1 << j->succ_low)); } else { // refinement scan for DC coefficient if (stbi__jpeg_get_bit(j)) @@ -2242,10 +2313,11 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__ if (r) { // fast-AC path k += (r >> 4) & 15; // run s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); j->code_buffer <<= s; j->code_bits -= s; zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) ((r >> 8) << shift); + data[zig] = (short) ((r >> 8) * (1 << shift)); } else { int rs = stbi__jpeg_huff_decode(j, hac); if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); @@ -2263,7 +2335,7 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__ } else { k += r; zig = stbi__jpeg_dezigzag[k++]; - data[zig] = (short) (stbi__extend_receive(j,s) << shift); + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); } } } while (k <= j->spec_end); @@ -3062,6 +3134,7 @@ static int stbi__process_marker(stbi__jpeg *z, int m) sizes[i] = stbi__get8(z->s); n += sizes[i]; } + if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values! L -= 17; if (tc == 0) { if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; @@ -3227,6 +3300,13 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan) if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; } + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + // compute interleaved mcu info z->img_h_max = h_max; z->img_v_max = v_max; @@ -3304,6 +3384,28 @@ static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) return 1; } +static int stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +{ + // some JPEGs have junk at end, skip over it but if we find what looks + // like a valid marker, resume there + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + while (x == 255) { // might be a marker + if (stbi__at_eof(j->s)) return STBI__MARKER_none; + x = stbi__get8(j->s); + if (x != 0x00 && x != 0xff) { + // not a stuffed zero or lead-in to another marker, looks + // like an actual marker, return it + return x; + } + // stuffed zero has x=0 now which ends the loop, meaning we go + // back to regular scan loop. + // repeated 0xff keeps trying to read the next byte of the marker. + } + } + return STBI__MARKER_none; +} + // decode image to YCbCr format static int stbi__decode_jpeg_image(stbi__jpeg *j) { @@ -3320,25 +3422,22 @@ static int stbi__decode_jpeg_image(stbi__jpeg *j) if (!stbi__process_scan_header(j)) return 0; if (!stbi__parse_entropy_coded_data(j)) return 0; if (j->marker == STBI__MARKER_none ) { - // handle 0s at the end of image data from IP Kamera 9060 - while (!stbi__at_eof(j->s)) { - int x = stbi__get8(j->s); - if (x == 255) { - j->marker = stbi__get8(j->s); - break; - } - } + j->marker = stbi__skip_jpeg_junk_at_end(j); // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 } + m = stbi__get_marker(j); + if (STBI__RESTART(m)) + m = stbi__get_marker(j); } else if (stbi__DNL(m)) { int Ld = stbi__get16be(j->s); stbi__uint32 NL = stbi__get16be(j->s); if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + m = stbi__get_marker(j); } else { - if (!stbi__process_marker(j, m)) return 0; + if (!stbi__process_marker(j, m)) return 1; + m = stbi__get_marker(j); } - m = stbi__get_marker(j); } if (j->progressive) stbi__jpeg_finish(j); @@ -3782,6 +3881,10 @@ static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp else decode_n = z->s->img_n; + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + // resample and color-convert { int k; @@ -3924,6 +4027,8 @@ static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int re { unsigned char* result; stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); STBI_NOTUSED(ri); j->s = s; stbi__setup_jpeg(j); @@ -3936,6 +4041,8 @@ static int stbi__jpeg_test(stbi__context *s) { int r; stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); j->s = s; stbi__setup_jpeg(j); r = stbi__decode_jpeg_header(j, STBI__SCAN_type); @@ -3960,6 +4067,8 @@ static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) { int result; stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); j->s = s; result = stbi__jpeg_info_raw(j, x, y, comp); STBI_FREE(j); @@ -3979,6 +4088,7 @@ static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) // fast-way is faster to check than jpeg huffman, but slow way is slower #define STBI__ZFAST_BITS 9 // accelerate all cases in default tables #define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet // zlib-style huffman encoding // (jpegs packs from left, zlib from right, so can't share code) @@ -3988,8 +4098,8 @@ typedef struct stbi__uint16 firstcode[16]; int maxcode[17]; stbi__uint16 firstsymbol[16]; - stbi_uc size[288]; - stbi__uint16 value[288]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; } stbi__zhuffman; stbi_inline static int stbi__bitreverse16(int n) @@ -4120,7 +4230,7 @@ static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) if (s >= 16) return -1; // invalid code! // code size is s, so: b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; - if ((unsigned int)b >= sizeof (z->size)) return -1; // some data was corrupt somewhere! + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. a->code_buffer >>= s; a->num_bits -= s; @@ -4201,11 +4311,12 @@ static int stbi__parse_huffman_block(stbi__zbuf *a) a->zout = zout; return 1; } + if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data z -= 257; len = stbi__zlength_base[z]; if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); z = stbi__zhuffman_decode(a, &a->z_distance); - if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data dist = stbi__zdist_base[z]; if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); @@ -4317,7 +4428,7 @@ static int stbi__parse_zlib_header(stbi__zbuf *a) return 1; } -static const stbi_uc stbi__zdefault_length[288] = +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = { 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, @@ -4363,7 +4474,7 @@ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) } else { if (type == 1) { // use fixed code lengths - if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; } else { if (!stbi__compute_huffman_codes(a)) return 0; @@ -4759,6 +4870,7 @@ static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint3 // de-interlacing final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); for (p=0; p < 7; ++p) { int xorig[] = { 0,4,0,2,0,1,0 }; int yorig[] = { 0,0,4,0,2,0,1 }; @@ -4879,19 +4991,46 @@ static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int return 1; } -static int stbi__unpremultiply_on_load = 0; -static int stbi__de_iphone_flag = 0; +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) { - stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; } STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) { - stbi__de_iphone_flag = flag_true_if_should_convert; + stbi__de_iphone_flag_global = flag_true_if_should_convert; } +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + static void stbi__de_iphone(stbi__png *z) { stbi__context *s = z->s; @@ -4941,7 +5080,7 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) { stbi_uc palette[1024], pal_img_n=0; stbi_uc has_trans=0, tc[3]={0}; - stbi__uint16 tc16[3]={0}; + stbi__uint16 tc16[3]; stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; int first=1,k,interlace=0, color=0, is_iphone=0; stbi__context *s = z->s; @@ -4981,14 +5120,13 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (!pal_img_n) { s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); - if (scan == STBI__SCAN_header) return 1; } else { // if paletted, then pal_n is our final components, and // img_n is # components to decompress/filter. s->img_n = 1; if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); - // if SCAN_header, have to scan to see if we have a tRNS } + // even with SCAN_header, have to scan to see if we have a tRNS break; } @@ -5020,6 +5158,8 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); has_trans = 1; + // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. + if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } if (z->depth == 16) { for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is } else { @@ -5032,7 +5172,13 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) case STBI__PNG_TYPE('I','D','A','T'): { if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); - if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if (scan == STBI__SCAN_header) { + // header scan definitely stops at first IDAT + if (pal_img_n) + s->img_n = pal_img_n; + return 1; + } + if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes"); if ((int)(ioff + c.length) < (int)ioff) return 0; if (ioff + c.length > idata_limit) { stbi__uint32 idata_limit_old = idata_limit; @@ -5272,6 +5418,32 @@ typedef struct int extra_read; } stbi__bmp_data; +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) { int hsz; @@ -5299,6 +5471,8 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) if (hsz != 12) { int compress = stbi__get32le(s); if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel stbi__get32le(s); // discard sizeof stbi__get32le(s); // discard hres stbi__get32le(s); // discard vres @@ -5313,17 +5487,7 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) } if (info->bpp == 16 || info->bpp == 32) { if (compress == 0) { - if (info->bpp == 32) { - info->mr = 0xffu << 16; - info->mg = 0xffu << 8; - info->mb = 0xffu << 0; - info->ma = 0xffu << 24; - info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 - } else { - info->mr = 31u << 10; - info->mg = 31u << 5; - info->mb = 31u << 0; - } + stbi__bmp_set_mask_defaults(info, compress); } else if (compress == 3) { info->mr = stbi__get32le(s); info->mg = stbi__get32le(s); @@ -5338,6 +5502,7 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) return stbi__errpuc("bad BMP", "bad BMP"); } } else { + // V4/V5 header int i; if (hsz != 108 && hsz != 124) return stbi__errpuc("bad BMP", "bad BMP"); @@ -5345,6 +5510,8 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) info->mg = stbi__get32le(s); info->mb = stbi__get32le(s); info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); stbi__get32le(s); // discard color space for (i=0; i < 12; ++i) stbi__get32le(s); // discard color space parameters @@ -5394,9 +5561,22 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req psize = (info.offset - info.extra_read - info.hsz) >> 2; } if (psize == 0) { - STBI_ASSERT(info.offset == s->callback_already_read + (int) (s->img_buffer - s->img_buffer_original)); - if (info.offset != s->callback_already_read + (s->img_buffer - s->buffer_start)) { - return stbi__errpuc("bad offset", "Corrupt BMP"); + // accept some number of extra bytes after the header, but if the offset points either to before + // the header ends or implies a large amount of extra data, reject the file as malformed + int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original); + int header_limit = 1024; // max we actually read is below 256 bytes currently. + int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size. + if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) { + return stbi__errpuc("bad header", "Corrupt BMP"); + } + // we established that bytes_read_so_far is positive and sensible. + // the first half of this test rejects offsets that are either too small positives, or + // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn + // ensures the number computed in the second half of the test can't overflow. + if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } else { + stbi__skip(s, info.offset - bytes_read_so_far); } } @@ -6342,6 +6522,7 @@ static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_c // intermediate buffer is RGBA result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); memset(result, 0xff, x*y*4); if (!stbi__pic_load_core(s,x,y,comp, result)) { @@ -6457,6 +6638,7 @@ static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_in static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) { stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); if (!stbi__gif_header(s, g, comp, 1)) { STBI_FREE(g); stbi__rewind( s ); @@ -6766,6 +6948,17 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i } } +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) { if (stbi__gif_test(s)) { @@ -6775,6 +6968,12 @@ static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, stbi_uc *two_back = 0; stbi__gif g; int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + memset(&g, 0, sizeof(g)); if (delays) { *delays = 0; @@ -6791,24 +6990,31 @@ static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, stride = g.w * g.h * 4; if (out) { - void *tmp = (stbi_uc*) STBI_REALLOC( out, layers * stride ); - if (NULL == tmp) { - STBI_FREE(g.out); - STBI_FREE(g.history); - STBI_FREE(g.background); - return stbi__errpuc("outofmem", "Out of memory"); - } + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); else { out = (stbi_uc*) tmp; + out_size = layers * stride; } if (delays) { - *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers ); + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); } } else { out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; if (delays) { *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); } } memcpy( out + ((layers - 1) * stride), u, stride ); @@ -7058,12 +7264,12 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re // Run value = stbi__get8(s); count -= 128; - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } for (z = 0; z < count; ++z) scanline[i++ * 4 + k] = value; } else { // Dump - if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } for (z = 0; z < count; ++z) scanline[i++ * 4 + k] = stbi__get8(s); } @@ -7132,9 +7338,10 @@ static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) info.all_a = 255; p = stbi__bmp_parse_header(s, &info); - stbi__rewind( s ); - if (p == NULL) + if (p == NULL) { + stbi__rewind( s ); return 0; + } if (x) *x = s->img_x; if (y) *y = s->img_y; if (comp) { @@ -7200,8 +7407,8 @@ static int stbi__psd_is16(stbi__context *s) stbi__rewind( s ); return 0; } - (void) stbi__get32be(s); - (void) stbi__get32be(s); + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); depth = stbi__get16be(s); if (depth != 16) { stbi__rewind( s ); @@ -7280,7 +7487,6 @@ static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) // Known limitations: // Does not support comments in the header section // Does not support ASCII image data (formats P2 and P3) -// Does not support 16-bit-per-channel #ifndef STBI_NO_PNM @@ -7301,7 +7507,8 @@ static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req stbi_uc *out; STBI_NOTUSED(ri); - if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) return 0; if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); @@ -7311,15 +7518,22 @@ static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req *y = s->img_y; if (comp) *comp = s->img_n; - if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0)) + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) return stbi__errpuc("too large", "PNM too large"); - out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0); + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); if (!out) return stbi__errpuc("outofmem", "Out of memory"); - stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { + STBI_FREE(out); + return stbi__errpuc("bad PNM", "PNM file truncated"); + } if (req_comp && req_comp != s->img_n) { - out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (ri->bits_per_channel == 16) { + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y); + } else { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + } if (out == NULL) return out; // stbi__convert_format frees input on failure } return out; @@ -7356,6 +7570,8 @@ static int stbi__pnm_getinteger(stbi__context *s, char *c) while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { value = value*10 + (*c - '0'); *c = (char) stbi__get8(s); + if((value > 214748364) || (value == 214748364 && *c > '7')) + return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int"); } return value; @@ -7386,17 +7602,29 @@ static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) stbi__pnm_skip_whitespace(s, &c); *x = stbi__pnm_getinteger(s, &c); // read width + if(*x == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); stbi__pnm_skip_whitespace(s, &c); *y = stbi__pnm_getinteger(s, &c); // read height + if (*y == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); stbi__pnm_skip_whitespace(s, &c); maxv = stbi__pnm_getinteger(s, &c); // read max value - - if (maxv > 255) - return stbi__err("max value > 255", "PPM image not 8-bit"); + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; else - return 1; + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; } #endif @@ -7452,6 +7680,9 @@ static int stbi__is_16_main(stbi__context *s) if (stbi__psd_is16(s)) return 1; #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif return 0; } From 011382424b9bea9984f294a2cf7ec43f378c4e6c Mon Sep 17 00:00:00 2001 From: Urs Hanselmann Date: Fri, 3 Mar 2023 19:31:50 +0100 Subject: [PATCH 10/49] remove debug message from MemoryIOStream (used by public Importer::ReadFileFromMemory method) --- include/assimp/MemoryIOWrapper.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/assimp/MemoryIOWrapper.h b/include/assimp/MemoryIOWrapper.h index 77071e96f..1b986ff1b 100644 --- a/include/assimp/MemoryIOWrapper.h +++ b/include/assimp/MemoryIOWrapper.h @@ -150,7 +150,6 @@ public: // ------------------------------------------------------------------- /// @brief Tests for the existence of a file at the given path. bool Exists(const char* pFile) const override { - printf("Exists\n"); if (0 == strncmp( pFile, AI_MEMORYIO_MAGIC_FILENAME, AI_MEMORYIO_MAGIC_FILENAME_LENGTH ) ) { return true; } From 424f53b4d6055abb582741377f900cbf5b7bdf9a Mon Sep 17 00:00:00 2001 From: Jackie9527 <80555200+Jackie9527@users.noreply.github.com> Date: Sat, 25 Feb 2023 10:08:40 +0800 Subject: [PATCH 11/49] bugfix remove duplicated data. Signed-off-by: Jackie9527 <80555200+Jackie9527@users.noreply.github.com> --- code/AssetLib/FBX/FBXConverter.cpp | 33 ++++++++++++++++----------- code/AssetLib/FBX/FBXDeformer.cpp | 12 ++++++---- code/AssetLib/FBX/FBXDocument.h | 9 ++++---- code/AssetLib/FBX/FBXMeshGeometry.cpp | 7 ++++-- code/AssetLib/FBX/FBXMeshGeometry.h | 9 ++++---- 5 files changed, 43 insertions(+), 27 deletions(-) diff --git a/code/AssetLib/FBX/FBXConverter.cpp b/code/AssetLib/FBX/FBXConverter.cpp index d45919e10..e77dd2fc6 100644 --- a/code/AssetLib/FBX/FBXConverter.cpp +++ b/code/AssetLib/FBX/FBXConverter.cpp @@ -1176,15 +1176,23 @@ unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, c std::vector animMeshes; for (const BlendShape *blendShape : mesh.GetBlendShapes()) { for (const BlendShapeChannel *blendShapeChannel : blendShape->BlendShapeChannels()) { - const std::vector &shapeGeometries = blendShapeChannel->GetShapeGeometries(); - for (size_t i = 0; i < shapeGeometries.size(); i++) { + const auto& shapeGeometries = blendShapeChannel->GetShapeGeometries(); + for (const ShapeGeometry *shapeGeometry : shapeGeometries) { aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh); - const ShapeGeometry *shapeGeometry = shapeGeometries.at(i); - const std::vector &curVertices = shapeGeometry->GetVertices(); - const std::vector &curNormals = shapeGeometry->GetNormals(); - const std::vector &curIndices = shapeGeometry->GetIndices(); + const auto &curVertices = shapeGeometry->GetVertices(); + const auto &curNormals = shapeGeometry->GetNormals(); + const auto &curIndices = shapeGeometry->GetIndices(); //losing channel name if using shapeGeometry->Name() - animMesh->mName.Set(FixAnimMeshName(blendShapeChannel->Name())); + // if blendShapeChannel Name is empty or don't have a ".", add geoMetryName; + auto aniName = FixAnimMeshName(blendShapeChannel->Name()); + auto geoMetryName = FixAnimMeshName(shapeGeometry->Name()); + if (aniName.empty()) { + aniName = geoMetryName; + } + else if (aniName.find('.') == aniName.npos) { + aniName += "." + geoMetryName; + } + animMesh->mName.Set(aniName); for (size_t j = 0; j < curIndices.size(); j++) { const unsigned int curIndex = curIndices.at(j); aiVector3D vertex = curVertices.at(j); @@ -1406,13 +1414,12 @@ unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, co std::vector animMeshes; for (const BlendShape *blendShape : mesh.GetBlendShapes()) { for (const BlendShapeChannel *blendShapeChannel : blendShape->BlendShapeChannels()) { - const std::vector &shapeGeometries = blendShapeChannel->GetShapeGeometries(); - for (size_t i = 0; i < shapeGeometries.size(); i++) { + const auto& shapeGeometries = blendShapeChannel->GetShapeGeometries(); + for (const ShapeGeometry *shapeGeometry : shapeGeometries) { aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh); - const ShapeGeometry *shapeGeometry = shapeGeometries.at(i); - const std::vector &curVertices = shapeGeometry->GetVertices(); - const std::vector &curNormals = shapeGeometry->GetNormals(); - const std::vector &curIndices = shapeGeometry->GetIndices(); + const auto& curVertices = shapeGeometry->GetVertices(); + const auto& curNormals = shapeGeometry->GetNormals(); + const auto& curIndices = shapeGeometry->GetIndices(); animMesh->mName.Set(FixAnimMeshName(shapeGeometry->Name())); for (size_t j = 0; j < curIndices.size(); j++) { unsigned int curIndex = curIndices.at(j); diff --git a/code/AssetLib/FBX/FBXDeformer.cpp b/code/AssetLib/FBX/FBXDeformer.cpp index df134a401..1aab55ea9 100644 --- a/code/AssetLib/FBX/FBXDeformer.cpp +++ b/code/AssetLib/FBX/FBXDeformer.cpp @@ -154,8 +154,10 @@ BlendShape::BlendShape(uint64_t id, const Element& element, const Document& doc, for (const Connection* con : conns) { const BlendShapeChannel* const bspc = ProcessSimpleConnection(*con, false, "BlendShapeChannel -> BlendShape", element); if (bspc) { - blendShapeChannels.push_back(bspc); - continue; + auto pr = blendShapeChannels.insert(bspc); + if (!pr.second) { + FBXImporter::LogWarn("there is the same blendShapeChannel id ", bspc->ID()); + } } } } @@ -179,8 +181,10 @@ BlendShapeChannel::BlendShapeChannel(uint64_t id, const Element& element, const for (const Connection* con : conns) { const ShapeGeometry* const sg = ProcessSimpleConnection(*con, false, "Shape -> BlendShapeChannel", element); if (sg) { - shapeGeometries.push_back(sg); - continue; + auto pr = shapeGeometries.insert(sg); + if (!pr.second) { + FBXImporter::LogWarn("there is the same shapeGeometrie id ", sg->ID()); + } } } } diff --git a/code/AssetLib/FBX/FBXDocument.h b/code/AssetLib/FBX/FBXDocument.h index 8873d65fd..821d4d5cb 100644 --- a/code/AssetLib/FBX/FBXDocument.h +++ b/code/AssetLib/FBX/FBXDocument.h @@ -46,6 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define INCLUDED_AI_FBX_DOCUMENT_H #include +#include #include #include #include "FBXProperties.h" @@ -855,14 +856,14 @@ public: return fullWeights; } - const std::vector& GetShapeGeometries() const { + const std::unordered_set& GetShapeGeometries() const { return shapeGeometries; } private: float percent; WeightArray fullWeights; - std::vector shapeGeometries; + std::unordered_set shapeGeometries; }; /** DOM class for BlendShape deformers */ @@ -872,12 +873,12 @@ public: virtual ~BlendShape(); - const std::vector& BlendShapeChannels() const { + const std::unordered_set& BlendShapeChannels() const { return blendShapeChannels; } private: - std::vector blendShapeChannels; + std::unordered_set blendShapeChannels; }; /** DOM class for skin deformer clusters (aka sub-deformers) */ diff --git a/code/AssetLib/FBX/FBXMeshGeometry.cpp b/code/AssetLib/FBX/FBXMeshGeometry.cpp index ace4ad749..fcbaac169 100644 --- a/code/AssetLib/FBX/FBXMeshGeometry.cpp +++ b/code/AssetLib/FBX/FBXMeshGeometry.cpp @@ -69,13 +69,16 @@ Geometry::Geometry(uint64_t id, const Element& element, const std::string& name, } const BlendShape* const bsp = ProcessSimpleConnection(*con, false, "BlendShape -> Geometry", element); if (bsp) { - blendShapes.push_back(bsp); + auto pr = blendShapes.insert(bsp); + if (!pr.second) { + FBXImporter::LogWarn("there is the same blendShape id ", bsp->ID()); + } } } } // ------------------------------------------------------------------------------------------------ -const std::vector& Geometry::GetBlendShapes() const { +const std::unordered_set& Geometry::GetBlendShapes() const { return blendShapes; } diff --git a/code/AssetLib/FBX/FBXMeshGeometry.h b/code/AssetLib/FBX/FBXMeshGeometry.h index f4a1a2673..3d67ec567 100644 --- a/code/AssetLib/FBX/FBXMeshGeometry.h +++ b/code/AssetLib/FBX/FBXMeshGeometry.h @@ -62,7 +62,7 @@ public: /// @param name The name instance /// @param doc The document instance Geometry( uint64_t id, const Element& element, const std::string& name, const Document& doc ); - + /// @brief The class destructor, default. virtual ~Geometry() = default; @@ -72,11 +72,12 @@ public: /// @brief Get the BlendShape attached to this geometry or nullptr /// @return The blendshape arrays. - const std::vector& GetBlendShapes() const; + const std::unordered_set& GetBlendShapes() const; private: const Skin* skin; - std::vector blendShapes; + std::unordered_set blendShapes; + }; typedef std::vector MatIndexArray; @@ -112,7 +113,7 @@ public: /// @return The binomal vector. const std::vector& GetBinormals() const; - /// @brief Return list of faces - each entry denotes a face and specifies how many vertices it has. + /// @brief Return list of faces - each entry denotes a face and specifies how many vertices it has. /// Vertices are taken from the vertex data arrays in sequential order. /// @return The face indices vector. const std::vector& GetFaceIndexCounts() const; From 74c406dd262e3c92229760683ce814d12116ada5 Mon Sep 17 00:00:00 2001 From: Urs Hanselmann Date: Sat, 4 Mar 2023 14:27:58 +0100 Subject: [PATCH 12/49] add ci script to scan for unexpected printf statements --- .github/workflows/sanitizer.yml | 10 ++++++++++ scripts/scan_printf.sh | 20 ++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100755 scripts/scan_printf.sh diff --git a/.github/workflows/sanitizer.yml b/.github/workflows/sanitizer.yml index 57d6e78f1..b23f4520f 100644 --- a/.github/workflows/sanitizer.yml +++ b/.github/workflows/sanitizer.yml @@ -57,3 +57,13 @@ jobs: - name: test run: cd build/bin && ./unit shell: bash + + job3: + name: printf-sanitizer + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: run scan_printf script + run: ./scripts/scan_printf.sh + shell: bash diff --git a/scripts/scan_printf.sh b/scripts/scan_printf.sh new file mode 100755 index 000000000..b507e6438 --- /dev/null +++ b/scripts/scan_printf.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +PATHS="include code" +FILTER_INCLUDE='\*.{c,cpp,h}' +FILTER_EXCLUDE="{include/assimp/Compiler/pstdint.h,code/AssetLib/M3D/m3d.h}" + +PATTERN='^\s*printf' + +grep \ + --include=\*.{c,cpp,h} \ + --exclude={include/assimp/Compiler/pstdint.h,code/AssetLib/M3D/m3d.h} \ + -rnw include code \ + -e '^\s*printf' + +if [ $? ] +then + echo "Debug statement(s) detected. Please remove, or manually add to exclude filter, if appropriate" + exit 1 +fi + From 1520aff68027047d3c86d64c26f6ee3148af3423 Mon Sep 17 00:00:00 2001 From: Urs Hanselmann Date: Sat, 4 Mar 2023 14:41:10 +0100 Subject: [PATCH 13/49] fix scan_printf script in linux bash --- scripts/scan_printf.sh | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/scripts/scan_printf.sh b/scripts/scan_printf.sh index b507e6438..90ab7c870 100755 --- a/scripts/scan_printf.sh +++ b/scripts/scan_printf.sh @@ -1,14 +1,8 @@ -#!/bin/sh - -PATHS="include code" -FILTER_INCLUDE='\*.{c,cpp,h}' -FILTER_EXCLUDE="{include/assimp/Compiler/pstdint.h,code/AssetLib/M3D/m3d.h}" - -PATTERN='^\s*printf' +#!/bin/bash grep \ --include=\*.{c,cpp,h} \ - --exclude={include/assimp/Compiler/pstdint.h,code/AssetLib/M3D/m3d.h} \ + --exclude={pstdint,m3d}.h \ -rnw include code \ -e '^\s*printf' From 4fa433c8ff9839ac466c4c4bb333d7113db33d78 Mon Sep 17 00:00:00 2001 From: Urs Hanselmann Date: Sat, 4 Mar 2023 14:52:49 +0100 Subject: [PATCH 14/49] improve scan_printf ci script error message --- scripts/scan_printf.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/scan_printf.sh b/scripts/scan_printf.sh index 90ab7c870..a71fab756 100755 --- a/scripts/scan_printf.sh +++ b/scripts/scan_printf.sh @@ -8,7 +8,7 @@ grep \ if [ $? ] then - echo "Debug statement(s) detected. Please remove, or manually add to exclude filter, if appropriate" + echo "Debug statement(s) detected. Please uncomment (using single-line comment), remove, or manually add to exclude filter, if appropriate" exit 1 fi From 2efd48dee2208a255ca4bfa3a821bfd3615e6924 Mon Sep 17 00:00:00 2001 From: Urs Hanselmann Date: Sat, 4 Mar 2023 14:53:48 +0100 Subject: [PATCH 15/49] disable another debug print message --- code/AssetLib/Ogre/OgreXmlSerializer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/AssetLib/Ogre/OgreXmlSerializer.cpp b/code/AssetLib/Ogre/OgreXmlSerializer.cpp index cd9d6dcc2..623f2961e 100644 --- a/code/AssetLib/Ogre/OgreXmlSerializer.cpp +++ b/code/AssetLib/Ogre/OgreXmlSerializer.cpp @@ -490,7 +490,7 @@ bool OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *me OgreXmlSerializer serializer(xmlParser.get()); XmlNode root = xmlParser->getRootNode(); if (std::string(root.name()) != nnSkeleton) { - printf("\nSkeleton is not a valid root: %s\n", root.name()); + // printf("\nSkeleton is not a valid root: %s\n", root.name()); for (auto &a : root.children()) { if (std::string(a.name()) == nnSkeleton) { root = a; From dcb89cf1070ed8cc7676f8f00cf8951906d5b2b6 Mon Sep 17 00:00:00 2001 From: Urs Hanselmann Date: Sat, 4 Mar 2023 15:00:43 +0100 Subject: [PATCH 16/49] fix scan_printf script error code handling --- scripts/scan_printf.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/scan_printf.sh b/scripts/scan_printf.sh index a71fab756..f8c902941 100755 --- a/scripts/scan_printf.sh +++ b/scripts/scan_printf.sh @@ -6,8 +6,7 @@ grep \ -rnw include code \ -e '^\s*printf' -if [ $? ] -then +if [ $? -eq 0 ]; then echo "Debug statement(s) detected. Please uncomment (using single-line comment), remove, or manually add to exclude filter, if appropriate" exit 1 fi From c089f117680c4f99f02b4a1e6771fe348acda2dd Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Mon, 6 Mar 2023 19:56:11 +0100 Subject: [PATCH 17/49] Update utf82utf16. --- include/assimp/types.h | 6 + .../SimpleTexturedDirectx11/main.cpp | 14 +- .../src/model_loading.cpp | 155 ++++++------------ 3 files changed, 64 insertions(+), 111 deletions(-) diff --git a/include/assimp/types.h b/include/assimp/types.h index a41363b8a..be2aad18b 100644 --- a/include/assimp/types.h +++ b/include/assimp/types.h @@ -57,6 +57,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#ifdef ASSIMP_USE_HUNTER +#include +#else +#include "../contrib/utf8cpp/source/utf8.h" +#endif + // Our compile configuration #include diff --git a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/main.cpp b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/main.cpp index 7f0d0c84e..4da5820a1 100644 --- a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/main.cpp +++ b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/main.cpp @@ -13,6 +13,8 @@ // Written by IAS. :) // --------------------------------------------------------------------------- +#include + #include #include #include @@ -21,11 +23,7 @@ #include #include #include -#ifdef ASSIMP_USE_HUNTER -#include -#else -#include "../contrib/utf8cpp/source/utf8.h" -#endif + #include "ModelLoader.h" #include "SafeRelease.hpp" @@ -53,10 +51,10 @@ struct ConstantBuffer { // ------------------------------------------------------------ // Window Variables // ------------------------------------------------------------ -#define SCREEN_WIDTH 800 -#define SCREEN_HEIGHT 600 +static constexpr uint32_t SCREEN_WIDTH = 800; +static constexpr uint32_t SCREEN_HEIGHT = 600; -const char g_szClassName[] = "directxWindowClass"; +constexpr char g_szClassName[] = "directxWindowClass"; static std::string g_ModelPath; diff --git a/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp b/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp index 48066f189..2eb73b403 100644 --- a/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp +++ b/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp @@ -41,15 +41,14 @@ #include #include #include -#include "UTFConverter.h" // The default hard-coded path. Can be overridden by supplying a path through the command line. static std::string modelpath = "../../test/models/OBJ/spider.obj"; -HGLRC hRC=nullptr; // Permanent Rendering Context -HDC hDC=nullptr; // Private GDI Device Context -HWND g_hWnd=nullptr; // Holds Window Handle -HINSTANCE g_hInstance=nullptr; // Holds The Instance Of The Application +HGLRC hRC = nullptr; // Permanent Rendering Context +HDC hDC = nullptr; // Private GDI Device Context +HWND g_hWnd = nullptr; // Holds Window Handle +HINSTANCE g_hInstance = nullptr; // Holds The Instance Of The Application bool keys[256]; // Array used for Keyboard Routine; bool active=TRUE; // Window Active Flag Set To TRUE by Default @@ -69,8 +68,6 @@ GLfloat LightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f }; GLfloat LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f }; GLfloat LightPosition[]= { 0.0f, 0.0f, 15.0f, 1.0f }; - - // the global Assimp scene object const aiScene* g_scene = nullptr; GLuint scene_list = 0; @@ -83,12 +80,8 @@ GLuint* textureIds; // pointer to texture Array // Create an instance of the Importer class Assimp::Importer importer; -using namespace AssimpSamples::SharedCode; - -void createAILogger() -{ - // Change this line to normal if you not want to analyse the import process - //Assimp::Logger::LogSeverity severity = Assimp::Logger::NORMAL; +void createAILogger() { + // Change this line to normal if you not want to analyze the import process Assimp::Logger::LogSeverity severity = Assimp::Logger::VERBOSE; // Create a logger instance for Console Output @@ -101,62 +94,52 @@ void createAILogger() Assimp::DefaultLogger::get()->info("this is my info-call"); } -void destroyAILogger() -{ - // Kill it after the work is done +void destroyAILogger() { Assimp::DefaultLogger::kill(); } -void logInfo(std::string logString) -{ - // Will add message to File with "info" Tag +void logInfo(const std::string &logString) { Assimp::DefaultLogger::get()->info(logString.c_str()); } -void logDebug(const char* logString) -{ - // Will add message to File with "debug" Tag +void logDebug(const char* logString) { Assimp::DefaultLogger::get()->debug(logString); } -bool Import3DFromFile( const std::string& pFile) -{ +bool Import3DFromFile( const std::string &filename) { // Check if file exists - std::ifstream fin(pFile.c_str()); - if(!fin.fail()) - { - fin.close(); + std::ifstream fin(filename.c_str()); + if(fin.fail()) { + std::string message = "Couldn't open file: " + filename; + std::wstring targetMessage; + //utf8::utf8to16(message.c_str(), message.c_str() + message.size(), targetMessage); + ::MessageBox(nullptr, targetMessage.c_str(), L"Error", MB_OK | MB_ICONEXCLAMATION); + logInfo(importer.GetErrorString()); + return false; } - else - { - MessageBox(nullptr, UTFConverter("Couldn't open file: " + pFile).c_wstr() , TEXT("ERROR"), MB_OK | MB_ICONEXCLAMATION); - logInfo( importer.GetErrorString()); - return false; - } - - g_scene = importer.ReadFile(pFile, aiProcessPreset_TargetRealtime_Quality); + + fin.close(); + + g_scene = importer.ReadFile(filename, aiProcessPreset_TargetRealtime_Quality); // If the import failed, report it - if(!g_scene) - { + if (g_scene == nullptr) { logInfo( importer.GetErrorString()); return false; } // Now we can access the file's contents. - logInfo("Import of scene " + pFile + " succeeded."); + logInfo("Import of scene " + filename + " succeeded."); // We're done. Everything will be cleaned up by the importer destructor return true; } // Resize And Initialize The GL Window -void ReSizeGLScene(GLsizei width, GLsizei height) -{ +void ReSizeGLScene(GLsizei width, GLsizei height) { // Prevent A Divide By Zero By - if (height==0) - { + if (height == 0) { // Making Height Equal One height=1; } @@ -174,43 +157,26 @@ void ReSizeGLScene(GLsizei width, GLsizei height) } -std::string getBasePath(const std::string& path) -{ +std::string getBasePath(const std::string& path) { size_t pos = path.find_last_of("\\/"); return (std::string::npos == pos) ? "" : path.substr(0, pos + 1); } -void freeTextureIds() -{ - textureIdMap.clear(); //no need to delete pointers in it manually here. (Pointers point to textureIds deleted in next step) +void freeTextureIds() { + // no need to delete pointers in it manually here. (Pointers point to textureIds deleted in next step) + textureIdMap.clear(); - if (textureIds) - { + if (textureIds) { delete[] textureIds; textureIds = nullptr; } } -int LoadGLTextures(const aiScene* scene) -{ +int LoadGLTextures(const aiScene* scene) { freeTextureIds(); - //ILboolean success; - - /* Before calling ilInit() version should be checked. */ - /*if (ilGetInteger(IL_VERSION_NUM) < IL_VERSION) - { - /// wrong DevIL version /// - std::string err_msg = "Wrong DevIL version. Old devil.dll in system32/SysWow64?"; - char* cErr_msg = (char *) err_msg.c_str(); - abortGLInit(cErr_msg); - return -1; - }*/ - - //ilInit(); /* Initialization of DevIL */ - - if (scene->HasTextures()) return 1; - //abortGLInit("Support for meshes with embedded textures is not implemented"); + if (scene->HasTextures()) + return 1; /* getTexture Filenames and Numb of Textures */ for (unsigned int m=0; mmNumMaterials; m++) @@ -230,14 +196,6 @@ int LoadGLTextures(const aiScene* scene) const size_t numTextures = textureIdMap.size(); - - /* array with DevIL image IDs */ - //ILuint* imageIds = NULL; -// imageIds = new ILuint[numTextures]; - - /* generate DevIL Image IDs */ -// ilGenImages(numTextures, imageIds); /* Generation of numTextures image names */ - /* create and fill array with GL texture ids */ textureIds = new GLuint[numTextures]; glGenTextures(static_cast(numTextures), textureIds); /* Texture name generation */ @@ -248,29 +206,17 @@ int LoadGLTextures(const aiScene* scene) std::string basepath = getBasePath(modelpath); for (size_t i=0; i 1) { std::wstring modelpathW(argv[1]); - modelpath = UTFConverter(modelpathW).str(); + utf8::utf16to8(modelpathW.c_str(), modelpathW.c_str() + modelpathW.size(), back_inserter(modelpath)); } if (!Import3DFromFile(modelpath)) From 8171e041fafea7432b397ef30f7576e0b6156178 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Mon, 6 Mar 2023 20:56:09 +0100 Subject: [PATCH 18/49] Update utf82utf16. --- .../src/model_loading.cpp | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp b/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp index 2eb73b403..89731bd9b 100644 --- a/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp +++ b/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp @@ -233,13 +233,15 @@ int LoadGLTextures(const aiScene* scene) { glPixelStorei( GL_UNPACK_SKIP_PIXELS, 0 ); glPixelStorei( GL_UNPACK_SKIP_ROWS, 0 ); stbi_image_free(data); - } - else - { - /* Error occurred */ + } else { + /* Error occurred */ const std::string message = "Couldn't load Image: " + fileloc; std::wstring targetMessage; - utf8::utf8to16(message.c_str(), message.c_str() + message.size(), back_inserter(targetMessage)); + wchar_t *tmp = new wchar_t[message.size() + 1]; + memset(tmp, L'\0', sizeof(wchar_t) *(message.size() + 1)); + utf8::utf8to16(message.c_str(), message.c_str() + message.size(), tmp); + targetMessage = tmp; + delete [] tmp; MessageBox(nullptr, targetMessage.c_str(), TEXT("ERROR"), MB_OK | MB_ICONEXCLAMATION); } } @@ -535,7 +537,12 @@ GLboolean abortGLInit(const char* abortMessage) KillGLWindow(); const std::string message = abortMessage; std::wstring targetMessage; - utf8::utf8to16(message.c_str(), message.c_str() + message.size(), back_inserter(targetMessage)); + const size_t len = std::strlen(abortMessage) + 1; + wchar_t *tmp = new wchar_t[len]; + memset(tmp, L'\0', len); + utf8::utf8to16(message.c_str(), message.c_str() + message.size(), tmp); + targetMessage = tmp; + delete [] tmp; MessageBox(nullptr, targetMessage.c_str(), TEXT("ERROR"), MB_OK|MB_ICONEXCLAMATION); return FALSE; // quit and return False @@ -587,7 +594,8 @@ BOOL CreateGLWindow(const char* title, int width, int height, int bits, bool ful if (ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL) { // If The Mode Fails, Offer Two Options. Quit Or Run In A Window. - if (MessageBox(nullptr,TEXT("The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?"),TEXT("NeHe GL"),MB_YESNO|MB_ICONEXCLAMATION)==IDYES) + if (MessageBox(nullptr,TEXT("The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?"), + TEXT("NeHe GL"),MB_YESNO|MB_ICONEXCLAMATION)==IDYES) { fullscreen = FALSE; // Select Windowed Mode (Fullscreen = FALSE) } @@ -618,7 +626,7 @@ BOOL CreateGLWindow(const char* title, int width, int height, int bits, bool ful std::wstring targetMessage; utf8::utf8to16(message.c_str(), message.c_str() + message.size(), back_inserter(targetMessage)); - if (nullptr == (g_hWnd=CreateWindowEx(dwExStyle, // Extended Style For The Window + if (nullptr == (g_hWnd = CreateWindowEx(dwExStyle, // Extended Style For The Window TEXT("OpenGL"), // Class Name targetMessage.c_str(), // Window Title WS_CLIPSIBLINGS | // Required Window Style @@ -792,7 +800,11 @@ int WINAPI WinMain( HINSTANCE /*hInstance*/, // The instance if (argv != nullptr && argc > 1) { std::wstring modelpathW(argv[1]); - utf8::utf16to8(modelpathW.c_str(), modelpathW.c_str() + modelpathW.size(), back_inserter(modelpath)); + char *tmp = new char[modelpathW.size() + 1]; + memset(tmp, '\0', modelpathW.size() + 1); + utf8::utf16to8(modelpathW.c_str(), modelpathW.c_str() + modelpathW.size(), tmp); + modelpath = tmp; + delete[]tmp; } if (!Import3DFromFile(modelpath)) @@ -820,7 +832,7 @@ int WINAPI WinMain( HINSTANCE /*hInstance*/, // The instance { if (msg.message==WM_QUIT) { - done=TRUE; + done = TRUE; } else { From 5082c940d0d76871288024ba406864f36bcc8eb8 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Mon, 6 Mar 2023 21:13:40 +0100 Subject: [PATCH 19/49] Fix: Replace back_inserter usage. --- .../SimpleTexturedOpenGL/src/model_loading.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp b/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp index 89731bd9b..7d730a630 100644 --- a/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp +++ b/samples/SimpleTexturedOpenGL/SimpleTexturedOpenGL/src/model_loading.cpp @@ -622,9 +622,12 @@ BOOL CreateGLWindow(const char* title, int width, int height, int bits, bool ful AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); // Adjust Window To True Requested Size - const std::string message = title; - std::wstring targetMessage; - utf8::utf8to16(message.c_str(), message.c_str() + message.size(), back_inserter(targetMessage)); + const size_t len = std::strlen(title) + 1; + wchar_t *tmp = new wchar_t[len]; + memset(tmp, L'\0', sizeof(wchar_t) * len); + utf8::utf8to16(title, title+len, tmp); + std::wstring targetMessage = tmp; + delete[] tmp; if (nullptr == (g_hWnd = CreateWindowEx(dwExStyle, // Extended Style For The Window TEXT("OpenGL"), // Class Name From 44c2785663d38f79614138bfa87d91d0b0ad69c7 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 7 Mar 2023 17:01:08 +0100 Subject: [PATCH 20/49] Make debug message more professional. --- code/AssetLib/Ogre/OgreXmlSerializer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/AssetLib/Ogre/OgreXmlSerializer.cpp b/code/AssetLib/Ogre/OgreXmlSerializer.cpp index 623f2961e..a8faaec34 100644 --- a/code/AssetLib/Ogre/OgreXmlSerializer.cpp +++ b/code/AssetLib/Ogre/OgreXmlSerializer.cpp @@ -490,7 +490,7 @@ bool OgreXmlSerializer::ImportSkeleton(Assimp::IOSystem *pIOHandler, MeshXml *me OgreXmlSerializer serializer(xmlParser.get()); XmlNode root = xmlParser->getRootNode(); if (std::string(root.name()) != nnSkeleton) { - // printf("\nSkeleton is not a valid root: %s\n", root.name()); + ASSIMP_LOG_VERBOSE_DEBUG("nSkeleton is not a valid root: ", root.name(), "."); for (auto &a : root.children()) { if (std::string(a.name()) == nnSkeleton) { root = a; From 069b3ecdf814784f8f80f3858863222184f15172 Mon Sep 17 00:00:00 2001 From: Florian Born Date: Tue, 7 Mar 2023 17:44:48 +0100 Subject: [PATCH 21/49] After Kim's addition to meta data types, use it in the FBX converter --- code/AssetLib/FBX/FBXConverter.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/AssetLib/FBX/FBXConverter.cpp b/code/AssetLib/FBX/FBXConverter.cpp index d45919e10..37654746e 100644 --- a/code/AssetLib/FBX/FBXConverter.cpp +++ b/code/AssetLib/FBX/FBXConverter.cpp @@ -873,8 +873,12 @@ void FBXConverter::SetupNodeMetadata(const Model &model, aiNode &nd) { data->Set(index++, prop.first, interpretedBool->Value()); } else if (const TypedProperty *interpretedInt = prop.second->As>()) { data->Set(index++, prop.first, interpretedInt->Value()); + } else if (const TypedProperty *interpretedUInt = prop.second->As>()) { + data->Set(index++, prop.first, interpretedUInt->Value()); } else if (const TypedProperty *interpretedUint64 = prop.second->As>()) { data->Set(index++, prop.first, interpretedUint64->Value()); + } else if (const TypedProperty *interpretedint64 = prop.second->As>()) { + data->Set(index++, prop.first, interpretedint64->Value()); } else if (const TypedProperty *interpretedFloat = prop.second->As>()) { data->Set(index++, prop.first, interpretedFloat->Value()); } else if (const TypedProperty *interpretedString = prop.second->As>()) { From 4f48348af8f2d0b046788edab8f952af9bf2d52b Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 7 Mar 2023 18:55:18 +0100 Subject: [PATCH 22/49] Fix: Move c++ include to c++ section --- include/assimp/types.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/assimp/types.h b/include/assimp/types.h index be2aad18b..605dc590f 100644 --- a/include/assimp/types.h +++ b/include/assimp/types.h @@ -57,12 +57,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#ifdef ASSIMP_USE_HUNTER -#include -#else -#include "../contrib/utf8cpp/source/utf8.h" -#endif - // Our compile configuration #include @@ -79,6 +73,12 @@ typedef uint32_t ai_uint32; #ifdef __cplusplus +#ifdef ASSIMP_USE_HUNTER +# include +#else +# include "../contrib/utf8cpp/source/utf8.h" +#endif + #include #include // for std::nothrow_t #include // for aiString::Set(const std::string&) From b5345841989a53979260e136c5ca53b181676e13 Mon Sep 17 00:00:00 2001 From: Turo Lamminen Date: Wed, 8 Mar 2023 16:10:40 +0200 Subject: [PATCH 23/49] Improve unit tests which load subdivision models --- test/unit/utACImportExport.cpp | 23 +++++++++++++++++++++++ test/unit/utBlenderImportExport.cpp | 22 ++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/test/unit/utACImportExport.cpp b/test/unit/utACImportExport.cpp index 9615a3a3e..c844603cf 100644 --- a/test/unit/utACImportExport.cpp +++ b/test/unit/utACImportExport.cpp @@ -43,6 +43,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include + using namespace Assimp; @@ -68,6 +70,27 @@ TEST(utACImportExport, importSampleSubdiv) { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/AC/sample_subdiv.ac", aiProcess_ValidateDataStructure); ASSERT_NE(nullptr, scene); + + // check approximate shape by averaging together all vertices + ASSERT_EQ(scene->mNumMeshes, 1u); + aiVector3D vertexAvg(0.0, 0.0, 0.0); + for (unsigned int i = 0; i < scene->mNumMeshes; i++) { + const aiMesh *mesh = scene->mMeshes[i]; + ASSERT_NE(mesh, nullptr); + + ai_real invVertexCount = 1.0 / mesh->mNumVertices; + for (unsigned int j = 0; j < mesh->mNumVertices; j++) { + vertexAvg += mesh->mVertices[j] * invVertexCount; + } + } + + // must not be inf or nan + ASSERT_TRUE(std::isfinite(vertexAvg.x)); + ASSERT_TRUE(std::isfinite(vertexAvg.y)); + ASSERT_TRUE(std::isfinite(vertexAvg.z)); + EXPECT_NEAR(vertexAvg.x, 0.079997420310974121, 0.0001); + EXPECT_NEAR(vertexAvg.y, 0.099498569965362549, 0.0001); + EXPECT_NEAR(vertexAvg.z, -0.10344827175140381, 0.0001); } TEST(utACImportExport, importSphereWithLight) { diff --git a/test/unit/utBlenderImportExport.cpp b/test/unit/utBlenderImportExport.cpp index c9cce72b4..c220b7daa 100644 --- a/test/unit/utBlenderImportExport.cpp +++ b/test/unit/utBlenderImportExport.cpp @@ -45,6 +45,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include using namespace Assimp; @@ -156,6 +157,27 @@ TEST(utBlenderImporter, importSuzanneSubdiv_252) { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/BLEND/SuzanneSubdiv_252.blend", aiProcess_ValidateDataStructure); ASSERT_NE(nullptr, scene); + + // check approximate shape by averaging together all vertices + ASSERT_EQ(scene->mNumMeshes, 1u); + aiVector3D vertexAvg(0.0, 0.0, 0.0); + for (unsigned int i = 0; i < scene->mNumMeshes; i++) { + const aiMesh *mesh = scene->mMeshes[i]; + ASSERT_NE(mesh, nullptr); + + ai_real invVertexCount = 1.0 / mesh->mNumVertices; + for (unsigned int j = 0; j < mesh->mNumVertices; j++) { + vertexAvg += mesh->mVertices[j] * invVertexCount; + } + } + + // must not be inf or nan + ASSERT_TRUE(std::isfinite(vertexAvg.x)); + ASSERT_TRUE(std::isfinite(vertexAvg.y)); + ASSERT_TRUE(std::isfinite(vertexAvg.z)); + EXPECT_NEAR(vertexAvg.x, 6.4022515289252624e-08, 0.0001); + EXPECT_NEAR(vertexAvg.y, 0.060569953173398972, 0.0001); + EXPECT_NEAR(vertexAvg.z, 0.31429031491279602, 0.0001); } TEST(utBlenderImporter, importTexturedCube_ImageGlob_248) { From f3767a4eb21f2f272102d0fe4020d4e4ca3154bc Mon Sep 17 00:00:00 2001 From: Turo Lamminen Date: Wed, 8 Mar 2023 16:18:10 +0200 Subject: [PATCH 24/49] Use unordered_map for subdivision process edge map --- code/Common/Subdivision.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/Common/Subdivision.cpp b/code/Common/Subdivision.cpp index 705ea3fb3..ac4078b47 100644 --- a/code/Common/Subdivision.cpp +++ b/code/Common/Subdivision.cpp @@ -50,6 +50,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include +#include + using namespace Assimp; void mydummy() {} @@ -78,7 +80,7 @@ public: }; typedef std::vector UIntVector; - typedef std::map EdgeMap; + typedef std::unordered_map EdgeMap; // --------------------------------------------------------------------------- // Hashing function to derive an index into an #EdgeMap from two given From c82a6d05b0be9cdd5e16420ff06bd28190508ea7 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Fri, 10 Mar 2023 08:43:12 +0100 Subject: [PATCH 25/49] Code cleanup --- code/CApi/CInterfaceIOWrapper.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/code/CApi/CInterfaceIOWrapper.cpp b/code/CApi/CInterfaceIOWrapper.cpp index 579545ecc..f0e46cd08 100644 --- a/code/CApi/CInterfaceIOWrapper.cpp +++ b/code/CApi/CInterfaceIOWrapper.cpp @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2022, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -47,14 +45,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { +// ------------------------------------------------------------------------------------------------ CIOStreamWrapper::~CIOStreamWrapper() { - /* Various places depend on this destructor to close the file */ - if (mFile) { + // Various places depend on this destructor to close the file + if (mFile != nullptr) { + mIO->mFileSystem->CloseProc(mIO->mFileSystem, mFile); } } -// ................................................................... +// ------------------------------------------------------------------------------------------------ size_t CIOStreamWrapper::Read(void *pvBuffer, size_t pSize, size_t pCount) { @@ -62,7 +62,7 @@ size_t CIOStreamWrapper::Read(void *pvBuffer, return mFile->ReadProc(mFile, (char *)pvBuffer, pSize, pCount); } -// ................................................................... +// ------------------------------------------------------------------------------------------------ size_t CIOStreamWrapper::Write(const void *pvBuffer, size_t pSize, size_t pCount) { @@ -70,23 +70,23 @@ size_t CIOStreamWrapper::Write(const void *pvBuffer, return mFile->WriteProc(mFile, (const char *)pvBuffer, pSize, pCount); } -// ................................................................... +// ------------------------------------------------------------------------------------------------ aiReturn CIOStreamWrapper::Seek(size_t pOffset, aiOrigin pOrigin) { return mFile->SeekProc(mFile, pOffset, pOrigin); } -// ................................................................... +// ------------------------------------------------------------------------------------------------ size_t CIOStreamWrapper::Tell() const { return mFile->TellProc(mFile); } -// ................................................................... +// ------------------------------------------------------------------------------------------------ size_t CIOStreamWrapper::FileSize() const { return mFile->FileSizeProc(mFile); } -// ................................................................... +// ------------------------------------------------------------------------------------------------ void CIOStreamWrapper::Flush() { return mFile->FlushProc(mFile); } From 63dae0a7f24c0bc965f936e5b3b96b8d83a93e5f Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Fri, 10 Mar 2023 08:48:11 +0100 Subject: [PATCH 26/49] Fix: Add security asserts. --- code/CApi/CInterfaceIOWrapper.h | 55 ++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/code/CApi/CInterfaceIOWrapper.h b/code/CApi/CInterfaceIOWrapper.h index 768be3746..28d4c3e75 100644 --- a/code/CApi/CInterfaceIOWrapper.h +++ b/code/CApi/CInterfaceIOWrapper.h @@ -47,48 +47,59 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include namespace Assimp { class CIOSystemWrapper; // ------------------------------------------------------------------------------------------------ -// Custom IOStream implementation for the C-API -class CIOStreamWrapper : public IOStream { +/// @brief Custom IOStream implementation for the C-API- +// ------------------------------------------------------------------------------------------------ +class CIOStreamWrapper final : public IOStream { public: - explicit CIOStreamWrapper(aiFile *pFile, CIOSystemWrapper *io) : - mFile(pFile), - mIO(io) {} - ~CIOStreamWrapper(void); - - size_t Read(void *pvBuffer, size_t pSize, size_t pCount); - size_t Write(const void *pvBuffer, size_t pSize, size_t pCount); - aiReturn Seek(size_t pOffset, aiOrigin pOrigin); - size_t Tell(void) const; - size_t FileSize() const; - void Flush(); + explicit CIOStreamWrapper(aiFile *pFile, CIOSystemWrapper *io); + ~CIOStreamWrapper() override; + size_t Read(void *pvBuffer, size_t pSize, size_t pCount) override; + size_t Write(const void *pvBuffer, size_t pSize, size_t pCount) override; + aiReturn Seek(size_t pOffset, aiOrigin pOrigin) override; + size_t Tell(void) const override; + size_t FileSize() const override; + void Flush() override; private: aiFile *mFile; CIOSystemWrapper *mIO; }; -class CIOSystemWrapper : public IOSystem { +inline CIOStreamWrapper::CIOStreamWrapper(aiFile *pFile, CIOSystemWrapper *io) : + mFile(pFile), + mIO(io) { + ai_assert(io != nullptr); +} + +// ------------------------------------------------------------------------------------------------ +/// @brief Custom IO-System wrapper implementation for the C-API. +// ------------------------------------------------------------------------------------------------ +class CIOSystemWrapper final : public IOSystem { friend class CIOStreamWrapper; public: - explicit CIOSystemWrapper(aiFileIO *pFile) : - mFileSystem(pFile) {} - - bool Exists(const char *pFile) const; - char getOsSeparator() const; - IOStream *Open(const char *pFile, const char *pMode = "rb"); - void Close(IOStream *pFile); + explicit CIOSystemWrapper(aiFileIO *pFile); + ~CIOSystemWrapper() override = default; + bool Exists(const char *pFile) const override; + char getOsSeparator() const override; + IOStream *Open(const char *pFile, const char *pMode = "rb") override; + void Close(IOStream *pFile) override; private: aiFileIO *mFileSystem; }; +inline CIOSystemWrapper::CIOSystemWrapper(aiFileIO *pFile) : mFileSystem(pFile) { + ai_assert(pFile != nullptr); +} + } // namespace Assimp -#endif +#endif // AI_CIOSYSTEM_H_INCLUDED From 2f7882cb8d42e41d5330b154316da3d5617f5769 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Fri, 10 Mar 2023 08:49:58 +0100 Subject: [PATCH 27/49] Update: Small code cleanup --- code/CApi/AssimpCExport.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/CApi/AssimpCExport.cpp b/code/CApi/AssimpCExport.cpp index 5e43958d0..e3d85504d 100644 --- a/code/CApi/AssimpCExport.cpp +++ b/code/CApi/AssimpCExport.cpp @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2022, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -52,7 +50,7 @@ Assimp C export interface. See Exporter.cpp for some notes. #include #include -using namespace Assimp; +namespace Assimp { // ------------------------------------------------------------------------------------------------ ASSIMP_API size_t aiGetExportFormatCount(void) { @@ -141,4 +139,6 @@ ASSIMP_API C_STRUCT void aiReleaseExportBlob(const aiExportDataBlob *pData) { delete pData; } +} // namespace Assimp + #endif // !ASSIMP_BUILD_NO_EXPORT From ffd222334f647f8d65762343a9ebc4ff6247c272 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Fri, 10 Mar 2023 09:00:37 +0100 Subject: [PATCH 28/49] Fix: Remove buggy namespace declaration --- code/CApi/AssimpCExport.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/code/CApi/AssimpCExport.cpp b/code/CApi/AssimpCExport.cpp index e3d85504d..21e40205c 100644 --- a/code/CApi/AssimpCExport.cpp +++ b/code/CApi/AssimpCExport.cpp @@ -50,7 +50,7 @@ Assimp C export interface. See Exporter.cpp for some notes. #include #include -namespace Assimp { +using namespace Assimp; // ------------------------------------------------------------------------------------------------ ASSIMP_API size_t aiGetExportFormatCount(void) { @@ -139,6 +139,4 @@ ASSIMP_API C_STRUCT void aiReleaseExportBlob(const aiExportDataBlob *pData) { delete pData; } -} // namespace Assimp - #endif // !ASSIMP_BUILD_NO_EXPORT From 4b4cb55f22c28af5637819d7b2a3ecffd48fc372 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Lortie Date: Sat, 11 Mar 2023 16:25:04 -0500 Subject: [PATCH 29/49] Fix HL1MDLLoader flattened bone hierarchy. --- code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp | 43 ++++++++++++++++++--- code/AssetLib/MDL/HalfLife/HL1MDLLoader.h | 9 +++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp b/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp index 93d37536c..237ccf3d8 100644 --- a/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp +++ b/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp @@ -472,12 +472,13 @@ void HL1MDLLoader::read_bones() { aiNode *bones_node = new aiNode(AI_MDL_HL1_NODE_BONES); rootnode_children_.push_back(bones_node); - bones_node->mNumChildren = static_cast(header_->numbones); - bones_node->mChildren = new aiNode *[bones_node->mNumChildren]; + + // Store roots bones IDs temporarily. + std::vector roots; // Create bone matrices in local space. for (int i = 0; i < header_->numbones; ++i) { - aiNode *bone_node = temp_bones_[i].node = bones_node->mChildren[i] = new aiNode(unique_bones_names[i]); + aiNode *bone_node = temp_bones_[i].node = new aiNode(unique_bones_names[i]); aiVector3D angles(pbone[i].value[3], pbone[i].value[4], pbone[i].value[5]); temp_bones_[i].absolute_transform = bone_node->mTransformation = @@ -485,9 +486,11 @@ void HL1MDLLoader::read_bones() { aiVector3D(pbone[i].value[0], pbone[i].value[1], pbone[i].value[2])); if (pbone[i].parent == -1) { - bone_node->mParent = scene_->mRootNode; + bone_node->mParent = bones_node; + roots.push_back(i); // This bone has no parent. Add it to the roots list. } else { - bone_node->mParent = bones_node->mChildren[pbone[i].parent]; + bone_node->mParent = temp_bones_[pbone[i].parent].node; + temp_bones_[pbone[i].parent].children.push_back(i); // Add this bone to the parent bone's children list. temp_bones_[i].absolute_transform = temp_bones_[pbone[i].parent].absolute_transform * bone_node->mTransformation; @@ -496,6 +499,36 @@ void HL1MDLLoader::read_bones() { temp_bones_[i].offset_matrix = temp_bones_[i].absolute_transform; temp_bones_[i].offset_matrix.Inverse(); } + + // Create the 'bones' root node that will contain all bone nodes. + bones_node->mNumChildren = static_cast(roots.size()); + bones_node->mChildren = new aiNode *[bones_node->mNumChildren]; + + // Build all bones children hierarchy starting from each root bone. + for (size_t i = 0; i < roots.size(); ++i) + { + const TempBone &root_bone = temp_bones_[roots[i]]; + bones_node->mChildren[i] = root_bone.node; + build_bone_children_hierarchy(root_bone); + } +} + +void HL1MDLLoader::build_bone_children_hierarchy(const TempBone &bone) +{ + if (bone.children.size() > 0) + { + aiNode* bone_node = bone.node; + bone_node->mNumChildren = static_cast(bone.children.size()); + bone_node->mChildren = new aiNode *[bone_node->mNumChildren]; + + // Build each child bone's hierarchy recursively. + for (size_t i = 0; i < bone.children.size(); ++i) + { + const TempBone &child_bone = temp_bones_[bone.children[i]]; + bone_node->mChildren[i] = child_bone.node; + build_bone_children_hierarchy(child_bone); + } + } } // ------------------------------------------------------------------------------------------------ diff --git a/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h b/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h index 0dba5099d..3fc84c1be 100644 --- a/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h +++ b/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h @@ -143,6 +143,14 @@ private: */ static bool get_num_blend_controllers(const int num_blend_animations, int &num_blend_controllers); + /** + * \brief Build a bone's node children hierarchy. + * + * \param[in] bone The bone for which we must build all children hierarchy. + */ + struct TempBone; + void build_bone_children_hierarchy(const TempBone& bone); + /** Output scene to be filled */ aiScene *scene_; @@ -203,6 +211,7 @@ private: aiNode *node; aiMatrix4x4 absolute_transform; aiMatrix4x4 offset_matrix; + std::vector children; // Bone children }; std::vector temp_bones_; From 3c2a42586925115df891df4146181e8d08563e31 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Lortie Date: Sat, 11 Mar 2023 20:46:42 -0500 Subject: [PATCH 30/49] Added a test to validate HL1 MDL bone hierarchy. --- test/models/MDL/MDL (HL1)/multiple_roots.mdl | Bin 0 -> 15372 bytes .../MDL/utMDLImporter_HL1_Nodes.cpp | 67 ++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 test/models/MDL/MDL (HL1)/multiple_roots.mdl diff --git a/test/models/MDL/MDL (HL1)/multiple_roots.mdl b/test/models/MDL/MDL (HL1)/multiple_roots.mdl new file mode 100644 index 0000000000000000000000000000000000000000..f8fc1eed708c8b3f95c5faf7c55e78cb07434771 GIT binary patch literal 15372 zcmeI2dz2LQmB;VAV0cOMRAa!xNq7pj!iYjo|0?kTL_|i6Z=t{$5_AxR0R*Cy7$d#_ zqY)p8R3aF&W{m-c5S-~MV>S#P8A~I&oDg1Z!g82*QH8nQOn!li-v1#!s^OrO=&s*5k zIBU^@1aKf^vSmea2j#MUbx{d)d)T_(HG{Ug__RoM?+M^mqU^~L5>@4l_`YdLbgu^*~+ zXrQC{rR(|HHwW8!+PwDn?~wI8{ZRP>xH3T<)bsy1>IPew&YH6 zRV)3zczAyNJL&M)L9d~4?rgr`9t`}Q(BkGL4f7WK_2TJzJ*D4W8|OAIYP|01BP{e0 zi>anRNaO*m!wu$!ULyLT+5vu`|A*5nldCGZ%95)*xhj&YOLA2wSGVNqo?JbWt7mc@ zlU%)$E0bJ(ldDg1bxp3`$#t;9w6n^}w706Nv^$RMfh+B>q9X0FOP93E>gu%5Zr##O zyLV4}?a?Fcwr9_@-(!wRJMPsh?KzW4yYAaJ?YmE(wDYcA)82b?gI)Kd-EBID?yo)C z2ChqRo%)J32DWkK#{MlvW9oN*&0_4M7N>lT>-dN(d)wcyjJJaGiGEIf#Toc_LkIAY~#vR&aHo{*WT*XPq5C*Vjb5pjqAAf z5bM;`Y3&4~Q)4yberjwF%2mIv`z7So*xUqDtENtI%1Vv7Z&T493 z>T6uN>Ob`0W$3K4eI{5rHlFe|u6*qy*3@-A(6jAR;~LYx6Kv=8?Cv$%Cu>{Nmf*bh ztvKZ;n7RjYb-&W}VZUn9J=MDDUMF>Z)zR}nU5hyn%Ji$>uA7j7M zXl{zNf5mD4igjFT>6p${=aAsM`ieD{<|tSF_C80-Pd{tQPjK#?`W^QCJUQu&Hn2U1 z;5zkHUt{VgIN|t4V{xvT_1?`lz2`^j=@#B=XM{#1@%>5W9lcEd(@J;{!dOa z8q?ea=asKG&QmpkA>--MEf7SjzIVt{|AztSgtER?N{R9`vCCeW} zepoIKYy)r8`x1EE{^`5&TVCjAuADJ4Y~(fSg?{0f3wIUXx^QGz^rhZp}xFK$c z=hx2Im46ma1LNQV*nkau13!eHN3(!th^EEf+wx<9N!3La%Z~K97&itU#x3HeC z?3fh3y>n7{GW_4&vo!y8@V;O4GxZym=7}5PhIrxi2bLDDqi)j=CxzfV*nkau13!dc zKr@eKh^E3VCEEZ@J;6 z0`ZWzA)asMwV2+rZ;dmhB`+0jq@Uq$j|<_4@I&}{Gz(~kXmXw$TUXE02c zAHomOG-!rs<|`*(YwK35*?v0M)&*~OzO%gTd3yfS!s+2MUUT4Aet&u0ec+4H+_`ai z9dSe45N~_;+soT-hclOPa2?oy4SWMXgx`i{9hxDUrklSfB$*k^zfSf)5F>1p4B`v=ai<04fJ!?bjsGy*w>^;-zWfXzKoO+^)9C z;QiG4{fJ#{#0_ynyl&;#U3Jf)zlU*f8`yvid;>p(Ux#KJnjxCzz0z;&d48YIZ~64s z?RhSfGitYdyKd(%Ik#_hsSm$VRUdwn+-`q#C+xuQi{?8yGl94vZiw%^sb%ucnat%_ zxZnw312*st{1E<5G$)`LqRD(uwClXTR8hOP^n1I`AC$(|p4elW=|}Eu-{_gYdt=Xh zH|qYh$F%U55BJO$soSgPw2*j#xFK#FUggxi7V z9;$Gk%nm-i{J8Dg?K+=PyM66|!RFWK|3_FI{<2UVzCzsx2Mi9^;Ga+Ynt_8gZipM= z=3!pF;cR9c9D)tlz&G$i_y)}o%@9pf5&S~;l+RPpQs#`b^Ti)~>N7M7GQXl@N ztUhdo|DA_NcgevZhL+}>&?`i%3Nj)7}!eO5I4kgf8IMF z*9*<};e%Vj25jIP_#ylpnyq@jLeuOit+Mld@3(JpfBv;%voD-dd(L~)!ztuG*Zc0) z(f#gj{daQLyf@t}F5acstOpa%DJC91$m?}DC;a}dRSmsu7W%v%Blov8yW3s?e+kX69k;uUxFK$c*FDvHcb)$3 z_$lMyHvN48Ht-Gn5Plt+ZD@vQno?<)t*dr_sboJ#rP9qU+qUJxm#JI!m&OnUX13@%KGpe zUPGz-=*ay~-$>oF(A*sDdz!c*Zir7j@#TFJ^{oDiy5Oh525jIP_#ym>Xg-Z*h^Bdq zb9~<*JSBq(Zug;iponh-Z0fR*jb>+7;h{)eYZav=U#RYgXWAl6>_v zUln@`4|c#k7*)ldL{zbx^u^1{G13R$f!Pn=kzPbpu^Oa`alRz^s(7pnw2J-Y6EKYQ zm*cHsANeF+meCWeVqY1A@5lf-&MIceRK+-7sHgd=cp@zA@dWu4?*^S3{4Bad@v<^l zK8^3dABOLsdlG&I-68myBwvzzRU9TGtzwOgfnm@cZ54;0riw%56svfWoQm(Df3j6{ zWE6fT$rt+2d{sO}I40mxat7}PzCL5l$_SZ&mxXsGzC+!K_zv~-nRiBpOD%pT$(JNw z6~~Kyu{JVRKFhm7|1(zcR5=?jOa56_@iaLH-$BR4&oH9wOp-52zA74-48zdDRLjZ9 zdH7j6n}(mI-ud_r9bbU&NS%DnDxM)z@b!FXz9jjoI7u$VH*|6_?}pAkkDsN}OYyRD zu3UoeFoVnR9Xh=TKSQT8@H0uiB>Ad%fm~@7&l3-Zq0?Dbaf-~r%hKs=tN1y&8sDMg znO1SKT!o)W^7(gho3DzO$Xu&9L#}~g=wO~ztd}OdES+C#6)%+e_zpApqE(zOU%<~K z`9jBAeiK)!4huaz}040FBB zD$bKT@Un89+-?=Gkvs7n=Kd9{_(i!DKa=DOYoqz9xJ=e!4Rie}?}oYGgP&zi_u*xk z`@Q%MbNw2=!y2r|&#+eO@H0uiB>Aei%06W_%;D=W40C$eDlV5t@UpDMH>~0c*@*A3 zCJ$M~8{|R!Op?!Y7@Mz(x5>9)7`a6r zX7t5ZjXqc%$=G+poxFd-7|7jT83XuHjs93=9*d1NKEd5w8OLLFW!nj+~5D<|){iahl{T<5aAUWUK7-W}7pvoEVPnR*Qg0dOV0Gkd ztTHEIW5(y?a?6;4)se|qWuAwP8Pm9*X&>ifbz~}5nHOMV#)aJ1lu?h>k?B}v&cMcu zi@CEYtFSt9C03cUurXtf++Z2A zu{z>mm3cKbX3Ul4meGjSkp`?XzkrPy*T`zin1|JoFJhH>EjDIc$6ZAGn2*(wCaf|S zU}MH2?k38(9;+h@vC3SGjTuY1pD1GqR!5q#%3Owx87sJ7C}TNRM{d9>b0s!r+{7J2 z88>2e07%~&1rvC6y!8#BHl-?WTdu{!c)tTJ!I#*91UA1vc`td6X~ zD)UZk%vdW=SjOF09k~mu%yrn9@m1Mo8TVjyWIa}y_hMtl{nBC?U&HFieOP5~z$!!J z{MpToi{{O48Z&2p`j6d2kM44z>r4MXLcKof|Bva6cdkI^3UsbO=L&SLK<5f{u0ZDs zbgn?>3UsbO=L&SLK<5hl|5*VZ0r>XiTjS&Te#G^!(PPiO>pJ2&;yV&J5;;6&^G>p17WPp7@>wobgASO zUBBoCMK>zC#iA>^y2$ySE{`ARr2V4!teWF!ZO`$2#}6Doa{Qv>i_S(H^0XZ)Qv^Mq z^>sY$((`=Z^8?S1Jiq86Nj5}cdIi28`F_#&*$F4`nU6;(@PojQ0>2peY>yN9OwxlF z`9b7Ikzb5__RJ~z%-kcn=m$kVD*DBuFM2MR9=i>RJ!HGs8Qn$4(Y5d#RScXca-yQc z=CBXCK%T2*0$m> Hierarchy; + public: /** * @note The following tests require a basic understanding @@ -63,6 +69,49 @@ public: * (Valve Developer Community). */ + // Given a model, verify that the bones nodes hierarchy is correctly formed. + void checkBoneHierarchy() { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "multiple_roots.mdl", aiProcess_ValidateDataStructure); + ASSERT_NE(nullptr, scene); + ASSERT_NE(nullptr, scene->mRootNode); + + const aiNode* node_MDL_root = scene->mRootNode->FindNode(AI_MDL_HL1_NODE_ROOT); + ASSERT_NE(nullptr, node_MDL_root); + + const aiNode *node_MDL_bones = scene->mRootNode->FindNode(AI_MDL_HL1_NODE_BONES); + ASSERT_NE(nullptr, node_MDL_bones); + ASSERT_NE(nullptr, node_MDL_bones->mParent); + ASSERT_EQ(node_MDL_root, node_MDL_bones->mParent); + + const Hierarchy expected_hierarchy = { + { 0, AI_MDL_HL1_NODE_BONES }, + { 1, "root1_bone1" }, + { 2, "root1_bone2" }, + { 3, "root1_bone4" }, + { 3, "root1_bone5" }, + { 2, "root1_bone3" }, + { 3, "root1_bone6" }, + { 1, "root2_bone1" }, + { 2, "root2_bone2" }, + { 2, "root2_bone3" }, + { 3, "root2_bone5" }, + { 2, "root2_bone4" }, + { 3, "root2_bone6" }, + { 1, "root3_bone1" }, + { 2, "root3_bone2" }, + { 2, "root3_bone3" }, + { 2, "root3_bone4" }, + { 3, "root3_bone5" }, + { 4, "root3_bone6" }, + { 4, "root3_bone7" }, + }; + + Hierarchy actual_hierarchy; + flatten_hierarchy(node_MDL_bones, actual_hierarchy); + ASSERT_EQ(expected_hierarchy, actual_hierarchy); + } + /* Given a model with bones that have empty names, verify that all the bones of the imported model have unique and no empty names. @@ -416,8 +465,26 @@ private: EXPECT_NEAR(expected[i][j], actual[i][j], abs_error); } } + + void flatten_hierarchy(const aiNode *node, Hierarchy &hierarchy) + { + flatten_hierarchy(node, hierarchy, 0); + } + + void flatten_hierarchy(const aiNode *node, Hierarchy &hierarchy, unsigned int level) + { + hierarchy.push_back({ level, node->mName.C_Str() }); + for (size_t i = 0; i < node->mNumChildren; ++i) + { + flatten_hierarchy(node->mChildren[i], hierarchy, level + 1); + } + } }; +TEST_F(utMDLImporter_HL1_Nodes, checkBoneHierarchy) { + checkBoneHierarchy(); +} + TEST_F(utMDLImporter_HL1_Nodes, emptyBonesNames) { emptyBonesNames(); } From d500f604903d982c321851d3f108098ab8ff2608 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Lortie Date: Sat, 11 Mar 2023 20:48:17 -0500 Subject: [PATCH 31/49] Adjust emptyBonesNames test. --- .../ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp b/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp index d2984477b..c94fa3f3f 100644 --- a/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp +++ b/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp @@ -143,7 +143,9 @@ public: "Bone_7" }; - expect_named_children(scene, AI_MDL_HL1_NODE_BONES, expected_bones_names); + std::vector actual_bones_names; + get_node_children_names(scene->mRootNode->FindNode(AI_MDL_HL1_NODE_BONES), actual_bones_names); + ASSERT_EQ(expected_bones_names, actual_bones_names); } /* Given a model with bodyparts that have empty names, @@ -479,6 +481,15 @@ private: flatten_hierarchy(node->mChildren[i], hierarchy, level + 1); } } + + void get_node_children_names(const aiNode *node, std::vector &names) + { + for (size_t i = 0; i < node->mNumChildren; ++i) + { + names.push_back(node->mChildren[i]->mName.C_Str()); + get_node_children_names(node->mChildren[i], names); + } + } }; TEST_F(utMDLImporter_HL1_Nodes, checkBoneHierarchy) { From 7bc4c12956a60e563df7cd08b8677f7b8ef8c355 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Lortie Date: Sat, 11 Mar 2023 22:03:29 -0500 Subject: [PATCH 32/49] Simplified HL1 MDL nodes tests. --- .../MDL/utMDLImporter_HL1_Nodes.cpp | 98 ++++++++++++------- 1 file changed, 63 insertions(+), 35 deletions(-) diff --git a/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp b/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp index c94fa3f3f..af6512c87 100644 --- a/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp +++ b/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp @@ -61,6 +61,11 @@ class utMDLImporter_HL1_Nodes : public ::testing::Test { */ typedef std::vector> Hierarchy; + /** + * @note A vector of strings. Used for symplifying syntax. + */ + typedef std::vector StringVector; + public: /** * @note The following tests require a basic understanding @@ -131,7 +136,7 @@ public: const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "unnamed_bones.mdl", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - const std::vector expected_bones_names = { + const StringVector expected_bones_names = { "Bone", "Bone_0", "Bone_1", @@ -143,7 +148,7 @@ public: "Bone_7" }; - std::vector actual_bones_names; + StringVector actual_bones_names; get_node_children_names(scene->mRootNode->FindNode(AI_MDL_HL1_NODE_BONES), actual_bones_names); ASSERT_EQ(expected_bones_names, actual_bones_names); } @@ -167,7 +172,7 @@ public: const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "unnamed_bodyparts.mdl", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - const std::vector expected_bodyparts_names = { + const StringVector expected_bodyparts_names = { "Bodypart", "Bodypart_1", "Bodypart_5", @@ -179,7 +184,10 @@ public: "Bodypart_7" }; - expect_named_children(scene, AI_MDL_HL1_NODE_BODYPARTS, expected_bodyparts_names); + StringVector actual_bodyparts_names; + // Get the bodyparts names "without" the submodels. + get_node_children_names(scene->mRootNode->FindNode(AI_MDL_HL1_NODE_BODYPARTS), actual_bodyparts_names, 0); + ASSERT_EQ(expected_bodyparts_names, actual_bodyparts_names); } /* Given a model with bodyparts that have duplicate names, @@ -201,7 +209,7 @@ public: const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "duplicate_bodyparts.mdl", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - const std::vector expected_bodyparts_names = { + const StringVector expected_bodyparts_names = { "Bodypart", "Bodypart_1", "Bodypart_2", @@ -213,7 +221,10 @@ public: "Bodypart_4" }; - expect_named_children(scene, AI_MDL_HL1_NODE_BODYPARTS, expected_bodyparts_names); + StringVector actual_bodyparts_names; + // Get the bodyparts names "without" the submodels. + get_node_children_names(scene->mRootNode->FindNode(AI_MDL_HL1_NODE_BODYPARTS), actual_bodyparts_names, 0); + ASSERT_EQ(expected_bodyparts_names, actual_bodyparts_names); } /* Given a model with several bodyparts that contains multiple @@ -243,7 +254,7 @@ public: const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "duplicate_submodels.mdl", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - const std::vector> expected_bodypart_sub_models_names = { + const std::vector expected_bodypart_sub_models_names = { { "triangle", "triangle_0", @@ -261,9 +272,13 @@ public: const aiNode *bodyparts_node = scene->mRootNode->FindNode(AI_MDL_HL1_NODE_BODYPARTS); EXPECT_NE(nullptr, bodyparts_node); EXPECT_EQ(3u, bodyparts_node->mNumChildren); - for (unsigned int i = 0; i < bodyparts_node->mNumChildren; ++i) { - expect_named_children(bodyparts_node->mChildren[i], - expected_bodypart_sub_models_names[i]); + + StringVector actual_submodels_names; + for (unsigned int i = 0; i < bodyparts_node->mNumChildren; ++i) + { + actual_submodels_names.clear(); + get_node_children_names(bodyparts_node->mChildren[i], actual_submodels_names); + ASSERT_EQ(expected_bodypart_sub_models_names[i], actual_submodels_names); } } @@ -286,7 +301,7 @@ public: const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "duplicate_sequences.mdl", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - const std::vector expected_sequence_names = { + const StringVector expected_sequence_names = { "idle_1", "idle", "idle_2", @@ -298,7 +313,9 @@ public: "idle_7" }; - expect_named_children(scene, AI_MDL_HL1_NODE_SEQUENCE_INFOS, expected_sequence_names); + StringVector actual_sequence_names; + get_node_children_names(scene->mRootNode->FindNode(AI_MDL_HL1_NODE_SEQUENCE_INFOS), actual_sequence_names); + ASSERT_EQ(expected_sequence_names, actual_sequence_names); } /* Given a model with sequences that have empty names, verify @@ -320,7 +337,7 @@ public: const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "unnamed_sequences.mdl", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - const std::vector expected_sequence_names = { + const StringVector expected_sequence_names = { "Sequence", "Sequence_1", "Sequence_0", @@ -332,7 +349,9 @@ public: "Sequence_6" }; - expect_named_children(scene, AI_MDL_HL1_NODE_SEQUENCE_INFOS, expected_sequence_names); + StringVector actual_sequence_names; + get_node_children_names(scene->mRootNode->FindNode(AI_MDL_HL1_NODE_SEQUENCE_INFOS), actual_sequence_names); + ASSERT_EQ(expected_sequence_names, actual_sequence_names); } /* Given a model with sequence groups that have duplicate names, @@ -355,7 +374,7 @@ public: const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "duplicate_sequence_groups/duplicate_sequence_groups.mdl", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - const std::vector expected_sequence_names = { + const StringVector expected_sequence_names = { "default", "SequenceGroup", "SequenceGroup_1", @@ -368,7 +387,9 @@ public: "SequenceGroup_2" }; - expect_named_children(scene, AI_MDL_HL1_NODE_SEQUENCE_GROUPS, expected_sequence_names); + StringVector actual_sequence_names; + get_node_children_names(scene->mRootNode->FindNode(AI_MDL_HL1_NODE_SEQUENCE_GROUPS), actual_sequence_names); + ASSERT_EQ(expected_sequence_names, actual_sequence_names); } /* Given a model with sequence groups that have empty names, @@ -391,7 +412,7 @@ public: const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "unnamed_sequence_groups/unnamed_sequence_groups.mdl", aiProcess_ValidateDataStructure); EXPECT_NE(nullptr, scene); - const std::vector expected_sequence_names = { + const StringVector expected_sequence_names = { "default", "SequenceGroup", "SequenceGroup_2", @@ -404,7 +425,9 @@ public: "SequenceGroup_4" }; - expect_named_children(scene, AI_MDL_HL1_NODE_SEQUENCE_GROUPS, expected_sequence_names); + StringVector actual_sequence_names; + get_node_children_names(scene->mRootNode->FindNode(AI_MDL_HL1_NODE_SEQUENCE_GROUPS), actual_sequence_names); + ASSERT_EQ(expected_sequence_names, actual_sequence_names); } /* Verify that mOffsetMatrix applies the correct @@ -449,18 +472,6 @@ public: } private: - void expect_named_children(const aiNode *parent_node, const std::vector &expected_names) { - EXPECT_NE(nullptr, parent_node); - EXPECT_EQ(expected_names.size(), parent_node->mNumChildren); - - for (unsigned int i = 0; i < parent_node->mNumChildren; ++i) - EXPECT_EQ(expected_names[i], parent_node->mChildren[i]->mName.C_Str()); - } - - void expect_named_children(const aiScene *scene, const char *node_name, const std::vector &expected_names) { - expect_named_children(scene->mRootNode->FindNode(node_name), expected_names); - } - void expect_equal_matrices(const aiMatrix4x4 &expected, const aiMatrix4x4 &actual, float abs_error) { for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) @@ -468,26 +479,43 @@ private: } } + /** Get a flattened representation of a node's hierarchy. + * \param[in] node The node. + * \param[out] hierarchy The flattened node's hierarchy. + */ void flatten_hierarchy(const aiNode *node, Hierarchy &hierarchy) { - flatten_hierarchy(node, hierarchy, 0); + flatten_hierarchy_impl(node, hierarchy, 0); } - void flatten_hierarchy(const aiNode *node, Hierarchy &hierarchy, unsigned int level) + void flatten_hierarchy_impl(const aiNode *node, Hierarchy &hierarchy, unsigned int level) { hierarchy.push_back({ level, node->mName.C_Str() }); for (size_t i = 0; i < node->mNumChildren; ++i) { - flatten_hierarchy(node->mChildren[i], hierarchy, level + 1); + flatten_hierarchy_impl(node->mChildren[i], hierarchy, level + 1); } } - void get_node_children_names(const aiNode *node, std::vector &names) + /** Get all node's children names beneath max_level. + * \param[in] node The parent node from which to get all children names. + * \param[out] names The list of children names. + * \param[in] max_level If set to -1, all children names will be collected. + */ + void get_node_children_names(const aiNode *node, StringVector &names, const int max_level = -1) + { + get_node_children_names_impl(node, names, 0, max_level); + } + + void get_node_children_names_impl(const aiNode *node, StringVector &names, int level, const int max_level = -1) { for (size_t i = 0; i < node->mNumChildren; ++i) { names.push_back(node->mChildren[i]->mName.C_Str()); - get_node_children_names(node->mChildren[i], names); + if (max_level == -1 || level < max_level) + { + get_node_children_names_impl(node->mChildren[i], names, level + 1, max_level); + } } } }; From 054dacd0687cb2c2bca6fe14b61b8063ec7ba6ac Mon Sep 17 00:00:00 2001 From: Marc-Antoine Lortie Date: Sat, 11 Mar 2023 22:32:48 -0500 Subject: [PATCH 33/49] Improved comments. --- code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp | 5 +++-- test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp b/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp index 237ccf3d8..f3a383e8e 100644 --- a/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp +++ b/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp @@ -470,6 +470,7 @@ void HL1MDLLoader::read_bones() { temp_bones_.resize(header_->numbones); + // Create the main 'bones' node that will contain all MDL root bones. aiNode *bones_node = new aiNode(AI_MDL_HL1_NODE_BONES); rootnode_children_.push_back(bones_node); @@ -500,11 +501,11 @@ void HL1MDLLoader::read_bones() { temp_bones_[i].offset_matrix.Inverse(); } - // Create the 'bones' root node that will contain all bone nodes. + // Allocate memory for each MDL root bone. bones_node->mNumChildren = static_cast(roots.size()); bones_node->mChildren = new aiNode *[bones_node->mNumChildren]; - // Build all bones children hierarchy starting from each root bone. + // Build all bones children hierarchy starting from each MDL root bone. for (size_t i = 0; i < roots.size(); ++i) { const TempBone &root_bone = temp_bones_[roots[i]]; diff --git a/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp b/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp index af6512c87..5a258d0a4 100644 --- a/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp +++ b/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp @@ -81,6 +81,7 @@ public: ASSERT_NE(nullptr, scene); ASSERT_NE(nullptr, scene->mRootNode); + // First, check that "" and "" are linked. const aiNode* node_MDL_root = scene->mRootNode->FindNode(AI_MDL_HL1_NODE_ROOT); ASSERT_NE(nullptr, node_MDL_root); @@ -89,6 +90,7 @@ public: ASSERT_NE(nullptr, node_MDL_bones->mParent); ASSERT_EQ(node_MDL_root, node_MDL_bones->mParent); + // Second, verify "" hierarchy. const Hierarchy expected_hierarchy = { { 0, AI_MDL_HL1_NODE_BONES }, { 1, "root1_bone1" }, From 4c015077b840ddf87d42e70300f51161cd77c415 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Lortie Date: Sun, 12 Mar 2023 13:26:50 -0400 Subject: [PATCH 34/49] Add missing member initializer. --- code/AssetLib/MDL/HalfLife/HL1MDLLoader.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h b/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h index 3fc84c1be..286b6e64c 100644 --- a/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h +++ b/code/AssetLib/MDL/HalfLife/HL1MDLLoader.h @@ -206,7 +206,8 @@ private: TempBone() : node(nullptr), absolute_transform(), - offset_matrix() {} + offset_matrix(), + children() {} aiNode *node; aiMatrix4x4 absolute_transform; From 25ab05eb49c901b54b8f8695dbcc05ab1376ac09 Mon Sep 17 00:00:00 2001 From: Marc-Antoine Lortie Date: Tue, 14 Mar 2023 09:17:39 -0400 Subject: [PATCH 35/49] Replace typedef by using. --- test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp b/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp index 5a258d0a4..712f4da11 100644 --- a/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp +++ b/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp @@ -59,12 +59,12 @@ class utMDLImporter_HL1_Nodes : public ::testing::Test { * @note Represents a flattened node hierarchy where each item is a pair * containing the node level and it's name. */ - typedef std::vector> Hierarchy; + using Hierarchy = std::vector>; /** * @note A vector of strings. Used for symplifying syntax. */ - typedef std::vector StringVector; + using StringVector = std::vector; public: /** From eb3b48e5239fc0dfab9d97ec2224aa65e1712dad Mon Sep 17 00:00:00 2001 From: Marc-Antoine Lortie Date: Tue, 14 Mar 2023 09:21:45 -0400 Subject: [PATCH 36/49] Invert logic in build_bone_children_hierarchy. --- code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp b/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp index f3a383e8e..a8141fcc1 100644 --- a/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp +++ b/code/AssetLib/MDL/HalfLife/HL1MDLLoader.cpp @@ -516,19 +516,19 @@ void HL1MDLLoader::read_bones() { void HL1MDLLoader::build_bone_children_hierarchy(const TempBone &bone) { - if (bone.children.size() > 0) - { - aiNode* bone_node = bone.node; - bone_node->mNumChildren = static_cast(bone.children.size()); - bone_node->mChildren = new aiNode *[bone_node->mNumChildren]; + if (bone.children.empty()) + return; - // Build each child bone's hierarchy recursively. - for (size_t i = 0; i < bone.children.size(); ++i) - { - const TempBone &child_bone = temp_bones_[bone.children[i]]; - bone_node->mChildren[i] = child_bone.node; - build_bone_children_hierarchy(child_bone); - } + aiNode* bone_node = bone.node; + bone_node->mNumChildren = static_cast(bone.children.size()); + bone_node->mChildren = new aiNode *[bone_node->mNumChildren]; + + // Build each child bone's hierarchy recursively. + for (size_t i = 0; i < bone.children.size(); ++i) + { + const TempBone &child_bone = temp_bones_[bone.children[i]]; + bone_node->mChildren[i] = child_bone.node; + build_bone_children_hierarchy(child_bone); } } From 2acfc125c3dfc295136b81050425ac3e23d37654 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 14 Mar 2023 20:03:14 +0100 Subject: [PATCH 37/49] Refactoring: Make GeoUtils reusable --- code/CMakeLists.txt | 7 ++++ code/Common/Subdivision.cpp | 1 + code/Geometry/GeometryUtils.cpp | 40 +++++++++++++++++++ code/Geometry/GeometryUtils.h | 13 ++++++ code/PostProcessing/FindDegenerates.cpp | 38 +----------------- code/PostProcessing/FindDegenerates.h | 14 +++---- code/PostProcessing/FindInstancesProcess.cpp | 4 -- code/PostProcessing/FindInstancesProcess.h | 10 ++--- .../PostProcessing/FindInvalidDataProcess.cpp | 4 -- code/PostProcessing/FindInvalidDataProcess.h | 2 +- code/PostProcessing/FixNormalsStep.cpp | 15 +------ code/PostProcessing/FixNormalsStep.h | 7 ++-- .../GenBoundingBoxesProcess.cpp | 4 -- code/PostProcessing/GenBoundingBoxesProcess.h | 11 ++--- code/PostProcessing/GenFaceNormalsProcess.cpp | 8 ---- code/PostProcessing/GenFaceNormalsProcess.h | 17 ++++---- .../GenVertexNormalsProcess.cpp | 13 +++--- code/PostProcessing/GenVertexNormalsProcess.h | 2 +- 18 files changed, 96 insertions(+), 114 deletions(-) create mode 100644 code/Geometry/GeometryUtils.cpp create mode 100644 code/Geometry/GeometryUtils.h diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index a098f3e85..ba5415fe0 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -218,6 +218,12 @@ SET( CApi_SRCS ) SOURCE_GROUP(CApi FILES ${CApi_SRCS}) +SET(Geometry_SRCS + Geometry/GeometryUtils.h + Geometry/GeometryUtils.cpp +) +SOURCE_GROUP(Geometry FILES ${Geometry_SRCS}) + SET( STEPParser_SRCS AssetLib/STEPParser/STEPFileReader.h AssetLib/STEPParser/STEPFileReader.cpp @@ -1129,6 +1135,7 @@ SET( assimp_src ${Core_SRCS} ${CApi_SRCS} ${Common_SRCS} + ${Geometry_SRCS} ${Logging_SRCS} ${Exporter_SRCS} ${PostProcessing_SRCS} diff --git a/code/Common/Subdivision.cpp b/code/Common/Subdivision.cpp index ac4078b47..3aea5d4c5 100644 --- a/code/Common/Subdivision.cpp +++ b/code/Common/Subdivision.cpp @@ -53,6 +53,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include using namespace Assimp; + void mydummy() {} #ifdef _MSC_VER diff --git a/code/Geometry/GeometryUtils.cpp b/code/Geometry/GeometryUtils.cpp new file mode 100644 index 000000000..9b29af1dd --- /dev/null +++ b/code/Geometry/GeometryUtils.cpp @@ -0,0 +1,40 @@ +#include "GeometryUtils.h" + +#include + +namespace Assimp { + +ai_real GeometryUtils::heron( ai_real a, ai_real b, ai_real c ) { + ai_real s = (a + b + c) / 2; + ai_real area = pow((s * ( s - a ) * ( s - b ) * ( s - c ) ), (ai_real)0.5 ); + return area; +} + +ai_real GeometryUtils::distance3D( const aiVector3D &vA, aiVector3D &vB ) { + const ai_real lx = ( vB.x - vA.x ); + const ai_real ly = ( vB.y - vA.y ); + const ai_real lz = ( vB.z - vA.z ); + ai_real a = lx*lx + ly*ly + lz*lz; + ai_real d = pow( a, (ai_real)0.5 ); + + return d; +} + + + +ai_real GeometryUtils::calculateAreaOfTriangle( const aiFace& face, aiMesh* mesh ) { + ai_real area = 0; + + aiVector3D vA( mesh->mVertices[ face.mIndices[ 0 ] ] ); + aiVector3D vB( mesh->mVertices[ face.mIndices[ 1 ] ] ); + aiVector3D vC( mesh->mVertices[ face.mIndices[ 2 ] ] ); + + ai_real a( distance3D( vA, vB ) ); + ai_real b( distance3D( vB, vC ) ); + ai_real c( distance3D( vC, vA ) ); + area = heron( a, b, c ); + + return area; +} + +} // namespace Assimp diff --git a/code/Geometry/GeometryUtils.h b/code/Geometry/GeometryUtils.h new file mode 100644 index 000000000..2eb96926d --- /dev/null +++ b/code/Geometry/GeometryUtils.h @@ -0,0 +1,13 @@ +#include +#include + +namespace Assimp { + +class GeometryUtils { +public: + static ai_real heron( ai_real a, ai_real b, ai_real c ); + static ai_real distance3D( const aiVector3D &vA, aiVector3D &vB ); + static ai_real calculateAreaOfTriangle( const aiFace& face, aiMesh* mesh ); +}; + +} // namespace Assimp diff --git a/code/PostProcessing/FindDegenerates.cpp b/code/PostProcessing/FindDegenerates.cpp index 344979949..5874c17d2 100644 --- a/code/PostProcessing/FindDegenerates.cpp +++ b/code/PostProcessing/FindDegenerates.cpp @@ -45,6 +45,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ProcessHelper.h" #include "FindDegenerates.h" +#include "Geometry/GeometryUtils.h" #include @@ -63,10 +64,6 @@ FindDegeneratesProcess::FindDegeneratesProcess() : // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -FindDegeneratesProcess::~FindDegeneratesProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool FindDegeneratesProcess::IsActive( unsigned int pFlags) const { @@ -132,37 +129,6 @@ static void updateSceneGraph(aiNode* pNode, const std::unordered_mapmVertices[ face.mIndices[ 0 ] ] ); - aiVector3D vB( mesh->mVertices[ face.mIndices[ 1 ] ] ); - aiVector3D vC( mesh->mVertices[ face.mIndices[ 2 ] ] ); - - ai_real a( distance3D( vA, vB ) ); - ai_real b( distance3D( vB, vC ) ); - ai_real c( distance3D( vC, vA ) ); - area = heron( a, b, c ); - - return area; -} - // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported mesh bool FindDegeneratesProcess::ExecuteOnMesh( aiMesh* mesh) { @@ -218,7 +184,7 @@ bool FindDegeneratesProcess::ExecuteOnMesh( aiMesh* mesh) { if ( mConfigCheckAreaOfTriangle ) { if ( face.mNumIndices == 3 ) { - ai_real area = calculateAreaOfTriangle( face, mesh ); + ai_real area = GeometryUtils::calculateAreaOfTriangle( face, mesh ); if (area < ai_epsilon) { if ( mConfigRemoveDegenerates ) { remove_me[ a ] = true; diff --git a/code/PostProcessing/FindDegenerates.h b/code/PostProcessing/FindDegenerates.h index 6fe1e929b..55a9dd981 100644 --- a/code/PostProcessing/FindDegenerates.h +++ b/code/PostProcessing/FindDegenerates.h @@ -59,7 +59,7 @@ namespace Assimp { class ASSIMP_API FindDegeneratesProcess : public BaseProcess { public: FindDegeneratesProcess(); - ~FindDegeneratesProcess(); + ~FindDegeneratesProcess() = default; // ------------------------------------------------------------------- // Check whether step is active @@ -105,23 +105,19 @@ private: bool mConfigCheckAreaOfTriangle; }; -inline -void FindDegeneratesProcess::EnableInstantRemoval(bool enabled) { +inline void FindDegeneratesProcess::EnableInstantRemoval(bool enabled) { mConfigRemoveDegenerates = enabled; } -inline -bool FindDegeneratesProcess::IsInstantRemoval() const { +inline bool FindDegeneratesProcess::IsInstantRemoval() const { return mConfigRemoveDegenerates; } -inline -void FindDegeneratesProcess::EnableAreaCheck( bool enabled ) { +inline void FindDegeneratesProcess::EnableAreaCheck( bool enabled ) { mConfigCheckAreaOfTriangle = enabled; } -inline -bool FindDegeneratesProcess::isAreaCheckEnabled() const { +inline bool FindDegeneratesProcess::isAreaCheckEnabled() const { return mConfigCheckAreaOfTriangle; } diff --git a/code/PostProcessing/FindInstancesProcess.cpp b/code/PostProcessing/FindInstancesProcess.cpp index 07a0f66db..55974b1c3 100644 --- a/code/PostProcessing/FindInstancesProcess.cpp +++ b/code/PostProcessing/FindInstancesProcess.cpp @@ -58,10 +58,6 @@ FindInstancesProcess::FindInstancesProcess() : configSpeedFlag (false) {} -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -FindInstancesProcess::~FindInstancesProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool FindInstancesProcess::IsActive( unsigned int pFlags) const diff --git a/code/PostProcessing/FindInstancesProcess.h b/code/PostProcessing/FindInstancesProcess.h index b501d88d5..d1daeb1f4 100644 --- a/code/PostProcessing/FindInstancesProcess.h +++ b/code/PostProcessing/FindInstancesProcess.h @@ -107,14 +107,11 @@ inline bool CompareArrays(const aiColor4D* first, const aiColor4D* second, // --------------------------------------------------------------------------- /** @brief A post-processing steps to search for instanced meshes */ -class FindInstancesProcess : public BaseProcess -{ +class FindInstancesProcess : public BaseProcess { public: - FindInstancesProcess(); - ~FindInstancesProcess(); + ~FindInstancesProcess() = default; -public: // ------------------------------------------------------------------- // Check whether step is active in given flags combination bool IsActive( unsigned int pFlags) const; @@ -128,10 +125,9 @@ public: void SetupProperties(const Importer* pImp); private: - bool configSpeedFlag; - }; // ! end class FindInstancesProcess + } // ! end namespace Assimp #endif // !! AI_FINDINSTANCES_H_INC diff --git a/code/PostProcessing/FindInvalidDataProcess.cpp b/code/PostProcessing/FindInvalidDataProcess.cpp index c65208cbd..bb8e365a1 100644 --- a/code/PostProcessing/FindInvalidDataProcess.cpp +++ b/code/PostProcessing/FindInvalidDataProcess.cpp @@ -60,10 +60,6 @@ FindInvalidDataProcess::FindInvalidDataProcess() : // nothing to do here } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -FindInvalidDataProcess::~FindInvalidDataProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool FindInvalidDataProcess::IsActive(unsigned int pFlags) const { diff --git a/code/PostProcessing/FindInvalidDataProcess.h b/code/PostProcessing/FindInvalidDataProcess.h index 5ea895c59..c5c7067a6 100644 --- a/code/PostProcessing/FindInvalidDataProcess.h +++ b/code/PostProcessing/FindInvalidDataProcess.h @@ -65,7 +65,7 @@ namespace Assimp { class ASSIMP_API FindInvalidDataProcess : public BaseProcess { public: FindInvalidDataProcess(); - ~FindInvalidDataProcess(); + ~FindInvalidDataProcess() = default; // ------------------------------------------------------------------- // diff --git a/code/PostProcessing/FixNormalsStep.cpp b/code/PostProcessing/FixNormalsStep.cpp index 3791bd35a..54ac05cc8 100644 --- a/code/PostProcessing/FixNormalsStep.cpp +++ b/code/PostProcessing/FixNormalsStep.cpp @@ -56,26 +56,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; - -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -FixInfacingNormalsProcess::FixInfacingNormalsProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -FixInfacingNormalsProcess::~FixInfacingNormalsProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. -bool FixInfacingNormalsProcess::IsActive( unsigned int pFlags) const -{ +bool FixInfacingNormalsProcess::IsActive( unsigned int pFlags) const { return (pFlags & aiProcess_FixInfacingNormals) != 0; } // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void FixInfacingNormalsProcess::Execute( aiScene* pScene) -{ +void FixInfacingNormalsProcess::Execute( aiScene* pScene) { ASSIMP_LOG_DEBUG("FixInfacingNormalsProcess begin"); bool bHas( false ); diff --git a/code/PostProcessing/FixNormalsStep.h b/code/PostProcessing/FixNormalsStep.h index b7d3ba386..ec546c987 100644 --- a/code/PostProcessing/FixNormalsStep.h +++ b/code/PostProcessing/FixNormalsStep.h @@ -49,8 +49,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. struct aiMesh; -namespace Assimp -{ +namespace Assimp { // --------------------------------------------------------------------------- /** The FixInfacingNormalsProcess tries to determine whether the normal @@ -59,8 +58,8 @@ namespace Assimp */ class FixInfacingNormalsProcess : public BaseProcess { public: - FixInfacingNormalsProcess(); - ~FixInfacingNormalsProcess(); + FixInfacingNormalsProcess() = default; + ~FixInfacingNormalsProcess() = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. diff --git a/code/PostProcessing/GenBoundingBoxesProcess.cpp b/code/PostProcessing/GenBoundingBoxesProcess.cpp index 52a0861e5..ca8e4d6d0 100644 --- a/code/PostProcessing/GenBoundingBoxesProcess.cpp +++ b/code/PostProcessing/GenBoundingBoxesProcess.cpp @@ -48,10 +48,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { -GenBoundingBoxesProcess::GenBoundingBoxesProcess() = default; - -GenBoundingBoxesProcess::~GenBoundingBoxesProcess() = default; - bool GenBoundingBoxesProcess::IsActive(unsigned int pFlags) const { return 0 != ( pFlags & aiProcess_GenBoundingBoxes ); } diff --git a/code/PostProcessing/GenBoundingBoxesProcess.h b/code/PostProcessing/GenBoundingBoxesProcess.h index 0b7591b6d..a880a0638 100644 --- a/code/PostProcessing/GenBoundingBoxesProcess.h +++ b/code/PostProcessing/GenBoundingBoxesProcess.h @@ -19,7 +19,7 @@ conditions are met: copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - +s * Neither the name of the assimp team, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior @@ -54,15 +54,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { -/** Post-processing process to find axis-aligned bounding volumes for amm meshes - * used in a scene +/** + * @brief Post-processing process to find axis-aligned bounding volumes for amm meshes + * used in a scene. */ class ASSIMP_API GenBoundingBoxesProcess : public BaseProcess { public: /// The class constructor. - GenBoundingBoxesProcess(); + GenBoundingBoxesProcess() = default; /// The class destructor. - ~GenBoundingBoxesProcess(); + ~GenBoundingBoxesProcess() = default; /// Will return true, if aiProcess_GenBoundingBoxes is defined. bool IsActive(unsigned int pFlags) const override; /// The execution callback. diff --git a/code/PostProcessing/GenFaceNormalsProcess.cpp b/code/PostProcessing/GenFaceNormalsProcess.cpp index d3520d4b2..1d259ce22 100644 --- a/code/PostProcessing/GenFaceNormalsProcess.cpp +++ b/code/PostProcessing/GenFaceNormalsProcess.cpp @@ -54,14 +54,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -GenFaceNormalsProcess::GenFaceNormalsProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -GenFaceNormalsProcess::~GenFaceNormalsProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool GenFaceNormalsProcess::IsActive(unsigned int pFlags) const { diff --git a/code/PostProcessing/GenFaceNormalsProcess.h b/code/PostProcessing/GenFaceNormalsProcess.h index c2f157e20..68e3fee0b 100644 --- a/code/PostProcessing/GenFaceNormalsProcess.h +++ b/code/PostProcessing/GenFaceNormalsProcess.h @@ -47,20 +47,17 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "Common/BaseProcess.h" #include -namespace Assimp -{ +namespace Assimp { // --------------------------------------------------------------------------- -/** The GenFaceNormalsProcess computes face normals for all faces of all meshes -*/ -class ASSIMP_API_WINONLY GenFaceNormalsProcess : public BaseProcess -{ +/** + * @brief The GenFaceNormalsProcess computes face normals for all faces of all meshes + */ +class ASSIMP_API_WINONLY GenFaceNormalsProcess : public BaseProcess { public: + GenFaceNormalsProcess() = default; + ~GenFaceNormalsProcess() = default; - GenFaceNormalsProcess(); - ~GenFaceNormalsProcess(); - -public: // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. * @param pFlags The processing flags the importer was called with. A bitwise diff --git a/code/PostProcessing/GenVertexNormalsProcess.cpp b/code/PostProcessing/GenVertexNormalsProcess.cpp index 5b9033383..c8afac297 100644 --- a/code/PostProcessing/GenVertexNormalsProcess.cpp +++ b/code/PostProcessing/GenVertexNormalsProcess.cpp @@ -60,10 +60,6 @@ GenVertexNormalsProcess::GenVertexNormalsProcess() : // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -GenVertexNormalsProcess::~GenVertexNormalsProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool GenVertexNormalsProcess::IsActive(unsigned int pFlags) const { @@ -109,10 +105,10 @@ void GenVertexNormalsProcess::Execute(aiScene *pScene) { // Executes the post processing step on the given imported data. bool GenVertexNormalsProcess::GenMeshVertexNormals(aiMesh *pMesh, unsigned int meshIndex) { if (nullptr != pMesh->mNormals) { - if (force_) - delete[] pMesh->mNormals; - else + if (!force_) { return false; + } + delete[] pMesh->mNormals; } // If the mesh consists of lines and/or points but not of @@ -144,8 +140,9 @@ bool GenVertexNormalsProcess::GenMeshVertexNormals(aiMesh *pMesh, unsigned int m const aiVector3D *pV3 = &pMesh->mVertices[face.mIndices[face.mNumIndices - 1]]; // Boolean XOR - if either but not both of these flags is set, then the winding order has // changed and the cross product to calculate the normal needs to be reversed - if (flippedWindingOrder_ != leftHanded_) + if (flippedWindingOrder_ != leftHanded_) { std::swap(pV2, pV3); + } const aiVector3D vNor = ((*pV2 - *pV1) ^ (*pV3 - *pV1)).NormalizeSafe(); for (unsigned int i = 0; i < face.mNumIndices; ++i) { diff --git a/code/PostProcessing/GenVertexNormalsProcess.h b/code/PostProcessing/GenVertexNormalsProcess.h index 370bf42b1..3a15bf4a8 100644 --- a/code/PostProcessing/GenVertexNormalsProcess.h +++ b/code/PostProcessing/GenVertexNormalsProcess.h @@ -61,7 +61,7 @@ namespace Assimp { class ASSIMP_API GenVertexNormalsProcess : public BaseProcess { public: GenVertexNormalsProcess(); - ~GenVertexNormalsProcess(); + ~GenVertexNormalsProcess() = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag. From 1147f0c8bde73b8e86adf24d1ef986cd841e80f1 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 14 Mar 2023 21:04:43 +0100 Subject: [PATCH 38/49] Refactoring: Code cleanup post-processing. --- code/PostProcessing/DeboneProcess.cpp | 79 +++----- code/PostProcessing/DeboneProcess.h | 2 +- .../PostProcessing/DropFaceNormalsProcess.cpp | 8 - code/PostProcessing/DropFaceNormalsProcess.h | 4 +- code/PostProcessing/EmbedTexturesProcess.cpp | 4 - code/PostProcessing/EmbedTexturesProcess.h | 4 +- code/PostProcessing/FindDegenerates.h | 2 +- code/PostProcessing/FindInstancesProcess.h | 2 +- code/PostProcessing/FindInvalidDataProcess.h | 24 +-- code/PostProcessing/FixNormalsStep.h | 4 +- code/PostProcessing/GenBoundingBoxesProcess.h | 14 +- code/PostProcessing/GenFaceNormalsProcess.h | 4 +- code/PostProcessing/GenVertexNormalsProcess.h | 4 +- code/PostProcessing/ImproveCacheLocality.cpp | 4 - code/PostProcessing/ImproveCacheLocality.h | 13 +- code/PostProcessing/JoinVerticesProcess.h | 12 +- .../LimitBoneWeightsProcess.cpp | 8 +- code/PostProcessing/LimitBoneWeightsProcess.h | 4 +- code/PostProcessing/MakeVerboseFormat.cpp | 4 - code/PostProcessing/MakeVerboseFormat.h | 13 +- code/PostProcessing/OptimizeGraph.cpp | 4 - code/PostProcessing/OptimizeGraph.h | 4 +- code/PostProcessing/OptimizeMeshes.cpp | 4 - code/PostProcessing/OptimizeMeshes.h | 7 +- code/PostProcessing/PretransformVertices.cpp | 4 - code/PostProcessing/PretransformVertices.h | 4 +- .../RemoveRedundantMaterials.cpp | 4 - .../PostProcessing/RemoveRedundantMaterials.h | 7 +- code/PostProcessing/RemoveVCProcess.cpp | 4 - code/PostProcessing/RemoveVCProcess.h | 7 +- code/PostProcessing/ScaleProcess.cpp | 43 ++-- code/PostProcessing/ScaleProcess.h | 7 +- code/PostProcessing/SortByPTypeProcess.cpp | 4 - code/PostProcessing/SortByPTypeProcess.h | 4 +- .../SplitByBoneCountProcess.cpp | 184 ++++++------------ code/PostProcessing/SplitByBoneCountProcess.h | 37 ++-- code/PostProcessing/SplitLargeMeshes.cpp | 6 - code/PostProcessing/SplitLargeMeshes.h | 23 +-- code/PostProcessing/TextureTransform.cpp | 34 +--- code/PostProcessing/TextureTransform.h | 17 +- code/PostProcessing/TriangulateProcess.cpp | 9 - code/PostProcessing/TriangulateProcess.h | 6 +- code/PostProcessing/ValidateDataStructure.cpp | 7 +- code/PostProcessing/ValidateDataStructure.h | 6 +- 44 files changed, 235 insertions(+), 414 deletions(-) diff --git a/code/PostProcessing/DeboneProcess.cpp b/code/PostProcessing/DeboneProcess.cpp index 22a4397bf..2a8499dc5 100644 --- a/code/PostProcessing/DeboneProcess.cpp +++ b/code/PostProcessing/DeboneProcess.cpp @@ -43,42 +43,26 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// @file DeboneProcess.cpp /** Implementation of the DeboneProcess post processing step */ - - // internal headers of the post-processing framework #include "ProcessHelper.h" #include "DeboneProcess.h" #include - using namespace Assimp; // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -DeboneProcess::DeboneProcess() -{ - mNumBones = 0; - mNumBonesCanDoWithout = 0; - - mThreshold = AI_DEBONE_THRESHOLD; - mAllOrNone = false; -} - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -DeboneProcess::~DeboneProcess() = default; +DeboneProcess::DeboneProcess() : mNumBones(0), mNumBonesCanDoWithout(0), mThreshold(AI_DEBONE_THRESHOLD), mAllOrNone(false) {} // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. -bool DeboneProcess::IsActive( unsigned int pFlags) const -{ +bool DeboneProcess::IsActive( unsigned int pFlags) const { return (pFlags & aiProcess_Debone) != 0; } // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void DeboneProcess::SetupProperties(const Importer* pImp) -{ +void DeboneProcess::SetupProperties(const Importer* pImp) { // get the current value of the property mAllOrNone = pImp->GetPropertyInteger(AI_CONFIG_PP_DB_ALL_OR_NONE,0)?true:false; mThreshold = pImp->GetPropertyFloat(AI_CONFIG_PP_DB_THRESHOLD,AI_DEBONE_THRESHOLD); @@ -86,8 +70,7 @@ void DeboneProcess::SetupProperties(const Importer* pImp) // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void DeboneProcess::Execute( aiScene* pScene) -{ +void DeboneProcess::Execute( aiScene* pScene) { ASSIMP_LOG_DEBUG("DeboneProcess begin"); if(!pScene->mNumMeshes) { @@ -117,10 +100,8 @@ void DeboneProcess::Execute( aiScene* pScene) // build a new array of meshes for the scene std::vector meshes; - for(unsigned int a=0;amNumMeshes;a++) - { + for (unsigned int a=0;amNumMeshes; ++a) { aiMesh* srcMesh = pScene->mMeshes[a]; - std::vector > newMeshes; if(splitList[a]) { @@ -150,8 +131,7 @@ void DeboneProcess::Execute( aiScene* pScene) // and destroy the source mesh. It should be completely contained inside the new submeshes delete srcMesh; - } - else { + } else { // Mesh is kept unchanged - store it's new place in the mesh array mSubMeshIndices[a].emplace_back(static_cast(meshes.size()), (aiNode *)nullptr); meshes.push_back(srcMesh); @@ -173,8 +153,7 @@ void DeboneProcess::Execute( aiScene* pScene) // ------------------------------------------------------------------------------------------------ // Counts bones total/removable in a given mesh. -bool DeboneProcess::ConsiderMesh(const aiMesh* pMesh) -{ +bool DeboneProcess::ConsiderMesh(const aiMesh* pMesh) { if(!pMesh->HasBones()) { return false; } @@ -193,25 +172,23 @@ bool DeboneProcess::ConsiderMesh(const aiMesh* pMesh) for(unsigned int i=0;imNumBones;i++) { for(unsigned int j=0;jmBones[i]->mNumWeights;j++) { float w = pMesh->mBones[i]->mWeights[j].mWeight; - - if(w==0.0f) { + if (w == 0.0f) { continue; } unsigned int vid = pMesh->mBones[i]->mWeights[j].mVertexId; - if(w>=mThreshold) { - - if(vertexBones[vid]!=cUnowned) { - if(vertexBones[vid]==i) //double entry - { + if (w >= mThreshold) { + if (vertexBones[vid] != cUnowned) { + //double entry + if(vertexBones[vid]==i) { ASSIMP_LOG_WARN("Encountered double entry in bone weights"); - } - else //TODO: track attraction in order to break tie - { + } else { + //TODO: track attraction in order to break tie vertexBones[vid] = cCoowned; } - } - else vertexBones[vid] = i; + } else { + vertexBones[vid] = i; + } } if(!isBoneNecessary[i]) { @@ -227,13 +204,16 @@ bool DeboneProcess::ConsiderMesh(const aiMesh* pMesh) if(isInterstitialRequired) { for(unsigned int i=0;imNumFaces;i++) { unsigned int v = vertexBones[pMesh->mFaces[i].mIndices[0]]; - - for(unsigned int j=1;jmFaces[i].mNumIndices;j++) { + for (unsigned int j=1;jmFaces[i].mNumIndices;j++) { unsigned int w = vertexBones[pMesh->mFaces[i].mIndices[j]]; - if(v!=w) { - if(vmNumBones) isBoneNecessary[v] = true; - if(wmNumBones) isBoneNecessary[w] = true; + if (v != w) { + if(vmNumBones) { + isBoneNecessary[v] = true; + } + if (wmNumBones) { + isBoneNecessary[w] = true; + } } } } @@ -252,8 +232,7 @@ bool DeboneProcess::ConsiderMesh(const aiMesh* pMesh) // ------------------------------------------------------------------------------------------------ // Splits the given mesh by bone count. -void DeboneProcess::SplitMesh( const aiMesh* pMesh, std::vector< std::pair< aiMesh*,const aiBone* > >& poNewMeshes) const -{ +void DeboneProcess::SplitMesh( const aiMesh* pMesh, std::vector< std::pair< aiMesh*,const aiBone* > >& poNewMeshes) const { // same deal here as ConsiderMesh basically std::vector isBoneNecessary(pMesh->mNumBones,false); @@ -371,8 +350,7 @@ void DeboneProcess::SplitMesh( const aiMesh* pMesh, std::vector< std::pair< aiMe // ------------------------------------------------------------------------------------------------ // Recursively updates the node's mesh list to account for the changed mesh list -void DeboneProcess::UpdateNode(aiNode* pNode) const -{ +void DeboneProcess::UpdateNode(aiNode* pNode) const { // rebuild the node's mesh index list std::vector newMeshList; @@ -430,8 +408,7 @@ void DeboneProcess::UpdateNode(aiNode* pNode) const // ------------------------------------------------------------------------------------------------ // Apply the node transformation to a mesh -void DeboneProcess::ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat)const -{ +void DeboneProcess::ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat)const { // Check whether we need to transform the coordinates at all if (!mat.IsIdentity()) { diff --git a/code/PostProcessing/DeboneProcess.h b/code/PostProcessing/DeboneProcess.h index cb072b7eb..080bc30d1 100644 --- a/code/PostProcessing/DeboneProcess.h +++ b/code/PostProcessing/DeboneProcess.h @@ -70,7 +70,7 @@ namespace Assimp { class DeboneProcess : public BaseProcess { public: DeboneProcess(); - ~DeboneProcess(); + ~DeboneProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag. diff --git a/code/PostProcessing/DropFaceNormalsProcess.cpp b/code/PostProcessing/DropFaceNormalsProcess.cpp index f85daa588..223482374 100644 --- a/code/PostProcessing/DropFaceNormalsProcess.cpp +++ b/code/PostProcessing/DropFaceNormalsProcess.cpp @@ -54,14 +54,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -DropFaceNormalsProcess::DropFaceNormalsProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -DropFaceNormalsProcess::~DropFaceNormalsProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool DropFaceNormalsProcess::IsActive( unsigned int pFlags) const { diff --git a/code/PostProcessing/DropFaceNormalsProcess.h b/code/PostProcessing/DropFaceNormalsProcess.h index 50abdc727..719c98dc7 100644 --- a/code/PostProcessing/DropFaceNormalsProcess.h +++ b/code/PostProcessing/DropFaceNormalsProcess.h @@ -55,8 +55,8 @@ namespace Assimp { */ class ASSIMP_API_WINONLY DropFaceNormalsProcess : public BaseProcess { public: - DropFaceNormalsProcess(); - ~DropFaceNormalsProcess(); + DropFaceNormalsProcess() = default; + ~DropFaceNormalsProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. diff --git a/code/PostProcessing/EmbedTexturesProcess.cpp b/code/PostProcessing/EmbedTexturesProcess.cpp index dc7e54ac1..d5d2ef872 100644 --- a/code/PostProcessing/EmbedTexturesProcess.cpp +++ b/code/PostProcessing/EmbedTexturesProcess.cpp @@ -49,10 +49,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -EmbedTexturesProcess::EmbedTexturesProcess() = default; - -EmbedTexturesProcess::~EmbedTexturesProcess() = default; - bool EmbedTexturesProcess::IsActive(unsigned int pFlags) const { return (pFlags & aiProcess_EmbedTextures) != 0; } diff --git a/code/PostProcessing/EmbedTexturesProcess.h b/code/PostProcessing/EmbedTexturesProcess.h index c3e63612c..e09fa0896 100644 --- a/code/PostProcessing/EmbedTexturesProcess.h +++ b/code/PostProcessing/EmbedTexturesProcess.h @@ -62,10 +62,10 @@ namespace Assimp { class ASSIMP_API EmbedTexturesProcess : public BaseProcess { public: /// The default class constructor. - EmbedTexturesProcess(); + EmbedTexturesProcess() = default; /// The class destructor. - virtual ~EmbedTexturesProcess(); + ~EmbedTexturesProcess() override = default; /// Overwritten, @see BaseProcess virtual bool IsActive(unsigned int pFlags) const; diff --git a/code/PostProcessing/FindDegenerates.h b/code/PostProcessing/FindDegenerates.h index 55a9dd981..d1eb81615 100644 --- a/code/PostProcessing/FindDegenerates.h +++ b/code/PostProcessing/FindDegenerates.h @@ -59,7 +59,7 @@ namespace Assimp { class ASSIMP_API FindDegeneratesProcess : public BaseProcess { public: FindDegeneratesProcess(); - ~FindDegeneratesProcess() = default; + ~FindDegeneratesProcess() override = default; // ------------------------------------------------------------------- // Check whether step is active diff --git a/code/PostProcessing/FindInstancesProcess.h b/code/PostProcessing/FindInstancesProcess.h index d1daeb1f4..b6c61527a 100644 --- a/code/PostProcessing/FindInstancesProcess.h +++ b/code/PostProcessing/FindInstancesProcess.h @@ -110,7 +110,7 @@ inline bool CompareArrays(const aiColor4D* first, const aiColor4D* second, class FindInstancesProcess : public BaseProcess { public: FindInstancesProcess(); - ~FindInstancesProcess() = default; + ~FindInstancesProcess() override = default; // ------------------------------------------------------------------- // Check whether step is active in given flags combination diff --git a/code/PostProcessing/FindInvalidDataProcess.h b/code/PostProcessing/FindInvalidDataProcess.h index c5c7067a6..14746864f 100644 --- a/code/PostProcessing/FindInvalidDataProcess.h +++ b/code/PostProcessing/FindInvalidDataProcess.h @@ -64,35 +64,37 @@ namespace Assimp { * which have zero normal vectors. */ class ASSIMP_API FindInvalidDataProcess : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. FindInvalidDataProcess(); - ~FindInvalidDataProcess() = default; + ~FindInvalidDataProcess() override = default; // ------------------------------------------------------------------- - // + /// Returns active state. bool IsActive(unsigned int pFlags) const; // ------------------------------------------------------------------- - // Setup import settings + /// Setup import settings void SetupProperties(const Importer *pImp); // ------------------------------------------------------------------- - // Run the step + /// Run the step void Execute(aiScene *pScene); // ------------------------------------------------------------------- - /** Executes the post-processing step on the given mesh - * @param pMesh The mesh to process. - * @return 0 - nothing, 1 - removed sth, 2 - please delete me */ + /// Executes the post-processing step on the given mesh + /// @param pMesh The mesh to process. + /// @return 0 - nothing, 1 - removed sth, 2 - please delete me */ int ProcessMesh(aiMesh *pMesh); // ------------------------------------------------------------------- - /** Executes the post-processing step on the given animation - * @param anim The animation to process. */ + /// Executes the post-processing step on the given animation + /// @param anim The animation to process. */ void ProcessAnimation(aiAnimation *anim); // ------------------------------------------------------------------- - /** Executes the post-processing step on the given anim channel - * @param anim The animation channel to process.*/ + /// Executes the post-processing step on the given anim channel + /// @param anim The animation channel to process.*/ void ProcessAnimationChannel(aiNodeAnim *anim); private: diff --git a/code/PostProcessing/FixNormalsStep.h b/code/PostProcessing/FixNormalsStep.h index ec546c987..e5f5c8a1f 100644 --- a/code/PostProcessing/FixNormalsStep.h +++ b/code/PostProcessing/FixNormalsStep.h @@ -58,8 +58,10 @@ namespace Assimp { */ class FixInfacingNormalsProcess : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. FixInfacingNormalsProcess() = default; - ~FixInfacingNormalsProcess() = default; + ~FixInfacingNormalsProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. diff --git a/code/PostProcessing/GenBoundingBoxesProcess.h b/code/PostProcessing/GenBoundingBoxesProcess.h index a880a0638..0cf8514f4 100644 --- a/code/PostProcessing/GenBoundingBoxesProcess.h +++ b/code/PostProcessing/GenBoundingBoxesProcess.h @@ -60,13 +60,17 @@ namespace Assimp { */ class ASSIMP_API GenBoundingBoxesProcess : public BaseProcess { public: - /// The class constructor. + // ------------------------------------------------------------------- + /// The default class constructor / destructor. GenBoundingBoxesProcess() = default; - /// The class destructor. - ~GenBoundingBoxesProcess() = default; - /// Will return true, if aiProcess_GenBoundingBoxes is defined. + ~GenBoundingBoxesProcess() override = default; + + // ------------------------------------------------------------------- + /// @brief Will return true, if aiProcess_GenBoundingBoxes is defined. bool IsActive(unsigned int pFlags) const override; - /// The execution callback. + + // ------------------------------------------------------------------- + /// @brief The execution callback. void Execute(aiScene* pScene) override; }; diff --git a/code/PostProcessing/GenFaceNormalsProcess.h b/code/PostProcessing/GenFaceNormalsProcess.h index 68e3fee0b..b1babca28 100644 --- a/code/PostProcessing/GenFaceNormalsProcess.h +++ b/code/PostProcessing/GenFaceNormalsProcess.h @@ -55,8 +55,10 @@ namespace Assimp { */ class ASSIMP_API_WINONLY GenFaceNormalsProcess : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. GenFaceNormalsProcess() = default; - ~GenFaceNormalsProcess() = default; + ~GenFaceNormalsProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. diff --git a/code/PostProcessing/GenVertexNormalsProcess.h b/code/PostProcessing/GenVertexNormalsProcess.h index 3a15bf4a8..3999fc3e9 100644 --- a/code/PostProcessing/GenVertexNormalsProcess.h +++ b/code/PostProcessing/GenVertexNormalsProcess.h @@ -60,8 +60,10 @@ namespace Assimp { */ class ASSIMP_API GenVertexNormalsProcess : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. GenVertexNormalsProcess(); - ~GenVertexNormalsProcess() = default; + ~GenVertexNormalsProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag. diff --git a/code/PostProcessing/ImproveCacheLocality.cpp b/code/PostProcessing/ImproveCacheLocality.cpp index 197856171..9336d6b17 100644 --- a/code/PostProcessing/ImproveCacheLocality.cpp +++ b/code/PostProcessing/ImproveCacheLocality.cpp @@ -68,10 +68,6 @@ ImproveCacheLocalityProcess::ImproveCacheLocalityProcess() // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -ImproveCacheLocalityProcess::~ImproveCacheLocalityProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool ImproveCacheLocalityProcess::IsActive( unsigned int pFlags) const { diff --git a/code/PostProcessing/ImproveCacheLocality.h b/code/PostProcessing/ImproveCacheLocality.h index b2074a17c..34a3883da 100644 --- a/code/PostProcessing/ImproveCacheLocality.h +++ b/code/PostProcessing/ImproveCacheLocality.h @@ -51,8 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. struct aiMesh; -namespace Assimp -{ +namespace Assimp { // --------------------------------------------------------------------------- /** The ImproveCacheLocalityProcess reorders all faces for improved vertex @@ -61,14 +60,12 @@ namespace Assimp * * @note This step expects triagulated input data. */ -class ImproveCacheLocalityProcess : public BaseProcess -{ +class ImproveCacheLocalityProcess : public BaseProcess { public: - + // ------------------------------------------------------------------- + /// The default class constructor / destructor. ImproveCacheLocalityProcess(); - ~ImproveCacheLocalityProcess(); - -public: + ~ImproveCacheLocalityProcess() override = default; // ------------------------------------------------------------------- // Check whether the pp step is active diff --git a/code/PostProcessing/JoinVerticesProcess.h b/code/PostProcessing/JoinVerticesProcess.h index b05d74ef5..de8cea691 100644 --- a/code/PostProcessing/JoinVerticesProcess.h +++ b/code/PostProcessing/JoinVerticesProcess.h @@ -51,8 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. struct aiMesh; -namespace Assimp -{ +namespace Assimp { // --------------------------------------------------------------------------- /** The JoinVerticesProcess unites identical vertices in all imported meshes. @@ -65,12 +64,9 @@ namespace Assimp class ASSIMP_API JoinVerticesProcess : public BaseProcess { public: // ------------------------------------------------------------------- - /// @brief The default class constructor. - JoinVerticesProcess() = default; - - // ------------------------------------------------------------------- - /// @brief The default class destructor. - ~JoinVerticesProcess() = default; + /// The default class constructor / destructor. + JoinVerticesProcess() = default; + ~JoinVerticesProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. diff --git a/code/PostProcessing/LimitBoneWeightsProcess.cpp b/code/PostProcessing/LimitBoneWeightsProcess.cpp index 51fb43dfc..7047ec0f1 100644 --- a/code/PostProcessing/LimitBoneWeightsProcess.cpp +++ b/code/PostProcessing/LimitBoneWeightsProcess.cpp @@ -53,11 +53,9 @@ namespace Assimp { // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -LimitBoneWeightsProcess::LimitBoneWeightsProcess() : mMaxWeights(AI_LMW_MAX_WEIGHTS) {} - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -LimitBoneWeightsProcess::~LimitBoneWeightsProcess() = default; +LimitBoneWeightsProcess::LimitBoneWeightsProcess() : mMaxWeights(AI_LMW_MAX_WEIGHTS) { + // empty +} // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. diff --git a/code/PostProcessing/LimitBoneWeightsProcess.h b/code/PostProcessing/LimitBoneWeightsProcess.h index 22d286b68..855c8628a 100644 --- a/code/PostProcessing/LimitBoneWeightsProcess.h +++ b/code/PostProcessing/LimitBoneWeightsProcess.h @@ -74,8 +74,10 @@ namespace Assimp { */ class ASSIMP_API LimitBoneWeightsProcess : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. LimitBoneWeightsProcess(); - ~LimitBoneWeightsProcess(); + ~LimitBoneWeightsProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag. diff --git a/code/PostProcessing/MakeVerboseFormat.cpp b/code/PostProcessing/MakeVerboseFormat.cpp index 0f5276cf3..1cc2fdc02 100644 --- a/code/PostProcessing/MakeVerboseFormat.cpp +++ b/code/PostProcessing/MakeVerboseFormat.cpp @@ -49,10 +49,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; -// ------------------------------------------------------------------------------------------------ -MakeVerboseFormatProcess::MakeVerboseFormatProcess() = default; -// ------------------------------------------------------------------------------------------------ -MakeVerboseFormatProcess::~MakeVerboseFormatProcess() = default; // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. void MakeVerboseFormatProcess::Execute(aiScene *pScene) { diff --git a/code/PostProcessing/MakeVerboseFormat.h b/code/PostProcessing/MakeVerboseFormat.h index 6b81da622..b7ac10019 100644 --- a/code/PostProcessing/MakeVerboseFormat.h +++ b/code/PostProcessing/MakeVerboseFormat.h @@ -66,15 +66,12 @@ namespace Assimp { * The step has been added because it was required by the viewer, however * it has been moved to the main library since others might find it * useful, too. */ -class ASSIMP_API_WINONLY MakeVerboseFormatProcess : public BaseProcess -{ -public: - - - MakeVerboseFormatProcess(); - ~MakeVerboseFormatProcess(); - +class ASSIMP_API_WINONLY MakeVerboseFormatProcess : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. + MakeVerboseFormatProcess() = default; + ~MakeVerboseFormatProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. diff --git a/code/PostProcessing/OptimizeGraph.cpp b/code/PostProcessing/OptimizeGraph.cpp index 26b06e9b6..bcd654634 100644 --- a/code/PostProcessing/OptimizeGraph.cpp +++ b/code/PostProcessing/OptimizeGraph.cpp @@ -78,10 +78,6 @@ OptimizeGraphProcess::OptimizeGraphProcess() : // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -OptimizeGraphProcess::~OptimizeGraphProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool OptimizeGraphProcess::IsActive(unsigned int pFlags) const { diff --git a/code/PostProcessing/OptimizeGraph.h b/code/PostProcessing/OptimizeGraph.h index f5caa139c..23e59e67d 100644 --- a/code/PostProcessing/OptimizeGraph.h +++ b/code/PostProcessing/OptimizeGraph.h @@ -71,8 +71,10 @@ namespace Assimp { */ class OptimizeGraphProcess : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. OptimizeGraphProcess(); - ~OptimizeGraphProcess(); + ~OptimizeGraphProcess() override = default; // ------------------------------------------------------------------- bool IsActive( unsigned int pFlags) const override; diff --git a/code/PostProcessing/OptimizeMeshes.cpp b/code/PostProcessing/OptimizeMeshes.cpp index a8c01e2d7..0fd597808 100644 --- a/code/PostProcessing/OptimizeMeshes.cpp +++ b/code/PostProcessing/OptimizeMeshes.cpp @@ -69,10 +69,6 @@ OptimizeMeshesProcess::OptimizeMeshesProcess() // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -OptimizeMeshesProcess::~OptimizeMeshesProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool OptimizeMeshesProcess::IsActive( unsigned int pFlags) const diff --git a/code/PostProcessing/OptimizeMeshes.h b/code/PostProcessing/OptimizeMeshes.h index b80f98d5d..1109a30e7 100644 --- a/code/PostProcessing/OptimizeMeshes.h +++ b/code/PostProcessing/OptimizeMeshes.h @@ -68,11 +68,10 @@ namespace Assimp { */ class OptimizeMeshesProcess : public BaseProcess { public: - /// @brief The class constructor. + // ------------------------------------------------------------------- + /// The default class constructor / destructor. OptimizeMeshesProcess(); - - /// @brief The class destructor. - ~OptimizeMeshesProcess(); + ~OptimizeMeshesProcess() override = default; /** @brief Internal utility to store additional mesh info */ diff --git a/code/PostProcessing/PretransformVertices.cpp b/code/PostProcessing/PretransformVertices.cpp index 9ac90d277..b6bb6155e 100644 --- a/code/PostProcessing/PretransformVertices.cpp +++ b/code/PostProcessing/PretransformVertices.cpp @@ -68,10 +68,6 @@ PretransformVertices::PretransformVertices() : // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -PretransformVertices::~PretransformVertices() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool PretransformVertices::IsActive(unsigned int pFlags) const { diff --git a/code/PostProcessing/PretransformVertices.h b/code/PostProcessing/PretransformVertices.h index 14e5139ec..7c2b5e99e 100644 --- a/code/PostProcessing/PretransformVertices.h +++ b/code/PostProcessing/PretransformVertices.h @@ -68,8 +68,10 @@ namespace Assimp { */ class ASSIMP_API PretransformVertices : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. PretransformVertices(); - ~PretransformVertices(); + ~PretransformVertices() override = default; // ------------------------------------------------------------------- // Check whether step is active diff --git a/code/PostProcessing/RemoveRedundantMaterials.cpp b/code/PostProcessing/RemoveRedundantMaterials.cpp index 3c3cd59e0..dbc3c8822 100644 --- a/code/PostProcessing/RemoveRedundantMaterials.cpp +++ b/code/PostProcessing/RemoveRedundantMaterials.cpp @@ -62,10 +62,6 @@ RemoveRedundantMatsProcess::RemoveRedundantMatsProcess() // nothing to do here } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -RemoveRedundantMatsProcess::~RemoveRedundantMatsProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool RemoveRedundantMatsProcess::IsActive( unsigned int pFlags) const diff --git a/code/PostProcessing/RemoveRedundantMaterials.h b/code/PostProcessing/RemoveRedundantMaterials.h index e8c1478fd..0fbf33c1b 100644 --- a/code/PostProcessing/RemoveRedundantMaterials.h +++ b/code/PostProcessing/RemoveRedundantMaterials.h @@ -59,11 +59,10 @@ namespace Assimp { */ class ASSIMP_API RemoveRedundantMatsProcess : public BaseProcess { public: - /// The default class constructor. + // ------------------------------------------------------------------- + /// The default class constructor / destructor. RemoveRedundantMatsProcess(); - - /// The class destructor. - ~RemoveRedundantMatsProcess(); + ~RemoveRedundantMatsProcess() override = default; // ------------------------------------------------------------------- // Check whether step is active diff --git a/code/PostProcessing/RemoveVCProcess.cpp b/code/PostProcessing/RemoveVCProcess.cpp index 8bbe791f6..35047dc0a 100644 --- a/code/PostProcessing/RemoveVCProcess.cpp +++ b/code/PostProcessing/RemoveVCProcess.cpp @@ -56,10 +56,6 @@ using namespace Assimp; RemoveVCProcess::RemoveVCProcess() : configDeleteFlags(), mScene() {} -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -RemoveVCProcess::~RemoveVCProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool RemoveVCProcess::IsActive(unsigned int pFlags) const { diff --git a/code/PostProcessing/RemoveVCProcess.h b/code/PostProcessing/RemoveVCProcess.h index cf1086882..dfd9b59ff 100644 --- a/code/PostProcessing/RemoveVCProcess.h +++ b/code/PostProcessing/RemoveVCProcess.h @@ -58,11 +58,10 @@ namespace Assimp { */ class ASSIMP_API RemoveVCProcess : public BaseProcess { public: - /// The default class constructor. + // ------------------------------------------------------------------- + /// The default class constructor / destructor. RemoveVCProcess(); - - /// The class destructor. - ~RemoveVCProcess(); + ~RemoveVCProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. diff --git a/code/PostProcessing/ScaleProcess.cpp b/code/PostProcessing/ScaleProcess.cpp index 34f68539a..665f28a7e 100644 --- a/code/PostProcessing/ScaleProcess.cpp +++ b/code/PostProcessing/ScaleProcess.cpp @@ -47,25 +47,27 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { -ScaleProcess::ScaleProcess() -: BaseProcess() -, mScale( AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT ) { +// ------------------------------------------------------------------------------------------------ +ScaleProcess::ScaleProcess() : BaseProcess(), mScale( AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT ) { + // empty } -ScaleProcess::~ScaleProcess() = default; - +// ------------------------------------------------------------------------------------------------ void ScaleProcess::setScale( ai_real scale ) { mScale = scale; } +// ------------------------------------------------------------------------------------------------ ai_real ScaleProcess::getScale() const { return mScale; } +// ------------------------------------------------------------------------------------------------ bool ScaleProcess::IsActive( unsigned int pFlags ) const { return ( pFlags & aiProcess_GlobalScale ) != 0; } +// ------------------------------------------------------------------------------------------------ void ScaleProcess::SetupProperties( const Importer* pImp ) { // User scaling mScale = pImp->GetPropertyFloat( AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY, 1.0f ); @@ -78,6 +80,7 @@ void ScaleProcess::SetupProperties( const Importer* pImp ) { mScale *= importerScale; } +// ------------------------------------------------------------------------------------------------ void ScaleProcess::Execute( aiScene* pScene ) { if(mScale == 1.0f) { return; // nothing to scale @@ -96,37 +99,30 @@ void ScaleProcess::Execute( aiScene* pScene ) { } // Process animations and update position transform to new unit system - for( unsigned int animationID = 0; animationID < pScene->mNumAnimations; animationID++ ) - { + for( unsigned int animationID = 0; animationID < pScene->mNumAnimations; animationID++ ) { aiAnimation* animation = pScene->mAnimations[animationID]; - for( unsigned int animationChannel = 0; animationChannel < animation->mNumChannels; animationChannel++) - { + for( unsigned int animationChannel = 0; animationChannel < animation->mNumChannels; animationChannel++) { aiNodeAnim* anim = animation->mChannels[animationChannel]; - for( unsigned int posKey = 0; posKey < anim->mNumPositionKeys; posKey++) - { + for( unsigned int posKey = 0; posKey < anim->mNumPositionKeys; posKey++) { aiVectorKey& vectorKey = anim->mPositionKeys[posKey]; vectorKey.mValue *= mScale; } } } - for( unsigned int meshID = 0; meshID < pScene->mNumMeshes; meshID++) - { + for( unsigned int meshID = 0; meshID < pScene->mNumMeshes; meshID++) { aiMesh *mesh = pScene->mMeshes[meshID]; // Reconstruct mesh vertices to the new unit system - for( unsigned int vertexID = 0; vertexID < mesh->mNumVertices; vertexID++) - { + for( unsigned int vertexID = 0; vertexID < mesh->mNumVertices; vertexID++) { aiVector3D& vertex = mesh->mVertices[vertexID]; vertex *= mScale; } - // bone placement / scaling - for( unsigned int boneID = 0; boneID < mesh->mNumBones; boneID++) - { + for( unsigned int boneID = 0; boneID < mesh->mNumBones; boneID++) { // Reconstruct matrix by transform rather than by scale // This prevent scale values being changed which can // be meaningful in some cases @@ -152,12 +148,10 @@ void ScaleProcess::Execute( aiScene* pScene ) { // animation mesh processing // convert by position rather than scale. - for( unsigned int animMeshID = 0; animMeshID < mesh->mNumAnimMeshes; animMeshID++) - { + for( unsigned int animMeshID = 0; animMeshID < mesh->mNumAnimMeshes; animMeshID++) { aiAnimMesh * animMesh = mesh->mAnimMeshes[animMeshID]; - for( unsigned int vertexID = 0; vertexID < animMesh->mNumVertices; vertexID++) - { + for( unsigned int vertexID = 0; vertexID < animMesh->mNumVertices; vertexID++) { aiVector3D& vertex = animMesh->mVertices[vertexID]; vertex *= mScale; } @@ -167,16 +161,17 @@ void ScaleProcess::Execute( aiScene* pScene ) { traverseNodes( pScene->mRootNode ); } +// ------------------------------------------------------------------------------------------------ void ScaleProcess::traverseNodes( aiNode *node, unsigned int nested_node_id ) { applyScaling( node ); - for( size_t i = 0; i < node->mNumChildren; i++) - { + for( size_t i = 0; i < node->mNumChildren; i++) { // recurse into the tree until we are done! traverseNodes( node->mChildren[i], nested_node_id+1 ); } } +// ------------------------------------------------------------------------------------------------ void ScaleProcess::applyScaling( aiNode *currentNode ) { if ( nullptr != currentNode ) { // Reconstruct matrix by transform rather than by scale diff --git a/code/PostProcessing/ScaleProcess.h b/code/PostProcessing/ScaleProcess.h index b6eb75de7..4d706bfc3 100644 --- a/code/PostProcessing/ScaleProcess.h +++ b/code/PostProcessing/ScaleProcess.h @@ -62,11 +62,10 @@ namespace Assimp { */ class ASSIMP_API ScaleProcess : public BaseProcess { public: - /// The default class constructor. + // ------------------------------------------------------------------- + /// The default class constructor / destructor. ScaleProcess(); - - /// The class destructor. - virtual ~ScaleProcess(); + ~ScaleProcess() override = default; /// Will set the scale manually. void setScale( ai_real scale ); diff --git a/code/PostProcessing/SortByPTypeProcess.cpp b/code/PostProcessing/SortByPTypeProcess.cpp index 6312fa173..1be75fc48 100644 --- a/code/PostProcessing/SortByPTypeProcess.cpp +++ b/code/PostProcessing/SortByPTypeProcess.cpp @@ -59,10 +59,6 @@ SortByPTypeProcess::SortByPTypeProcess() : // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -SortByPTypeProcess::~SortByPTypeProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool SortByPTypeProcess::IsActive(unsigned int pFlags) const { diff --git a/code/PostProcessing/SortByPTypeProcess.h b/code/PostProcessing/SortByPTypeProcess.h index e30342a86..c004e0549 100644 --- a/code/PostProcessing/SortByPTypeProcess.h +++ b/code/PostProcessing/SortByPTypeProcess.h @@ -60,8 +60,10 @@ namespace Assimp { */ class ASSIMP_API SortByPTypeProcess : public BaseProcess { public: + // ------------------------------------------------------------------- + /// The default class constructor / destructor. SortByPTypeProcess(); - ~SortByPTypeProcess(); + ~SortByPTypeProcess() override = default; // ------------------------------------------------------------------- bool IsActive( unsigned int pFlags) const; diff --git a/code/PostProcessing/SplitByBoneCountProcess.cpp b/code/PostProcessing/SplitByBoneCountProcess.cpp index a501d3bd6..5324160d4 100644 --- a/code/PostProcessing/SplitByBoneCountProcess.cpp +++ b/code/PostProcessing/SplitByBoneCountProcess.cpp @@ -40,7 +40,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- */ - /// @file SplitByBoneCountProcess.cpp /// Implementation of the SplitByBoneCount postprocessing step @@ -59,47 +58,36 @@ using namespace Assimp::Formatter; // ------------------------------------------------------------------------------------------------ // Constructor -SplitByBoneCountProcess::SplitByBoneCountProcess() -{ - // set default, might be overridden by importer config - mMaxBoneCount = AI_SBBC_DEFAULT_MAX_BONES; +SplitByBoneCountProcess::SplitByBoneCountProcess() : mMaxBoneCount(AI_SBBC_DEFAULT_MAX_BONES) { + // empty } -// ------------------------------------------------------------------------------------------------ -// Destructor -SplitByBoneCountProcess::~SplitByBoneCountProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag. -bool SplitByBoneCountProcess::IsActive( unsigned int pFlags) const -{ +bool SplitByBoneCountProcess::IsActive( unsigned int pFlags) const { return !!(pFlags & aiProcess_SplitByBoneCount); } // ------------------------------------------------------------------------------------------------ // Updates internal properties -void SplitByBoneCountProcess::SetupProperties(const Importer* pImp) -{ +void SplitByBoneCountProcess::SetupProperties(const Importer* pImp) { mMaxBoneCount = pImp->GetPropertyInteger(AI_CONFIG_PP_SBBC_MAX_BONES,AI_SBBC_DEFAULT_MAX_BONES); } // ------------------------------------------------------------------------------------------------ // Executes the post processing step on the given imported data. -void SplitByBoneCountProcess::Execute( aiScene* pScene) -{ +void SplitByBoneCountProcess::Execute( aiScene* pScene) { ASSIMP_LOG_DEBUG("SplitByBoneCountProcess begin"); // early out bool isNecessary = false; for( unsigned int a = 0; a < pScene->mNumMeshes; ++a) - if( pScene->mMeshes[a]->mNumBones > mMaxBoneCount ) - { + if( pScene->mMeshes[a]->mNumBones > mMaxBoneCount ) { isNecessary = true; break; } - if( !isNecessary ) - { + if( !isNecessary ) { ASSIMP_LOG_DEBUG("SplitByBoneCountProcess early-out: no meshes with more than ", mMaxBoneCount, " bones." ); return; } @@ -111,28 +99,23 @@ void SplitByBoneCountProcess::Execute( aiScene* pScene) // build a new array of meshes for the scene std::vector meshes; - for( unsigned int a = 0; a < pScene->mNumMeshes; ++a) - { + for( unsigned int a = 0; a < pScene->mNumMeshes; ++a) { aiMesh* srcMesh = pScene->mMeshes[a]; std::vector newMeshes; SplitMesh( pScene->mMeshes[a], newMeshes); // mesh was split - if( !newMeshes.empty() ) - { + if( !newMeshes.empty() ) { // store new meshes and indices of the new meshes - for( unsigned int b = 0; b < newMeshes.size(); ++b) - { + for( unsigned int b = 0; b < newMeshes.size(); ++b) { mSubMeshIndices[a].push_back( static_cast(meshes.size())); meshes.push_back( newMeshes[b]); } // and destroy the source mesh. It should be completely contained inside the new submeshes delete srcMesh; - } - else - { + } else { // Mesh is kept unchanged - store it's new place in the mesh array mSubMeshIndices[a].push_back( static_cast(meshes.size())); meshes.push_back( srcMesh); @@ -153,11 +136,9 @@ void SplitByBoneCountProcess::Execute( aiScene* pScene) // ------------------------------------------------------------------------------------------------ // Splits the given mesh by bone count. -void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vector& poNewMeshes) const -{ +void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vector& poNewMeshes) const { // skip if not necessary - if( pMesh->mNumBones <= mMaxBoneCount ) - { + if( pMesh->mNumBones <= mMaxBoneCount ) { return; } @@ -165,27 +146,22 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vector BoneWeight; std::vector< std::vector > vertexBones( pMesh->mNumVertices); - for( unsigned int a = 0; a < pMesh->mNumBones; ++a) - { + for( unsigned int a = 0; a < pMesh->mNumBones; ++a) { const aiBone* bone = pMesh->mBones[a]; - for( unsigned int b = 0; b < bone->mNumWeights; ++b) - { - if (bone->mWeights[b].mWeight > 0.0f) - { - int vertexId = bone->mWeights[b].mVertexId; - vertexBones[vertexId].emplace_back(a, bone->mWeights[b].mWeight); - if (vertexBones[vertexId].size() > mMaxBoneCount) - { - throw DeadlyImportError("SplitByBoneCountProcess: Single face requires more bones than specified max bone count!"); + for( unsigned int b = 0; b < bone->mNumWeights; ++b) { + if (bone->mWeights[b].mWeight > 0.0f) { + int vertexId = bone->mWeights[b].mVertexId; + vertexBones[vertexId].emplace_back(a, bone->mWeights[b].mWeight); + if (vertexBones[vertexId].size() > mMaxBoneCount) { + throw DeadlyImportError("SplitByBoneCountProcess: Single face requires more bones than specified max bone count!"); + } } - } } } unsigned int numFacesHandled = 0; std::vector isFaceHandled( pMesh->mNumFaces, false); - while( numFacesHandled < pMesh->mNumFaces ) - { + while( numFacesHandled < pMesh->mNumFaces ) { // which bones are used in the current submesh unsigned int numBones = 0; std::vector isBoneUsed( pMesh->mNumBones, false); @@ -196,11 +172,9 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vectormNumFaces; ++a) - { + for( unsigned int a = 0; a < pMesh->mNumFaces; ++a) { // skip if the face is already stored in a submesh - if( isFaceHandled[a] ) - { + if( isFaceHandled[a] ) { continue; } // a small local set of new bones for the current face. State of all used bones for that face @@ -209,33 +183,27 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vectormFaces[a]; // check every vertex if its bones would still fit into the current submesh - for( unsigned int b = 0; b < face.mNumIndices; ++b ) - { - const std::vector& vb = vertexBones[face.mIndices[b]]; - for( unsigned int c = 0; c < vb.size(); ++c) - { - unsigned int boneIndex = vb[c].first; - if( !isBoneUsed[boneIndex] ) - { - newBonesAtCurrentFace.insert(boneIndex); + for( unsigned int b = 0; b < face.mNumIndices; ++b ) { + const std::vector& vb = vertexBones[face.mIndices[b]]; + for( unsigned int c = 0; c < vb.size(); ++c) { + unsigned int boneIndex = vb[c].first; + if( !isBoneUsed[boneIndex] ) { + newBonesAtCurrentFace.insert(boneIndex); + } } - } } // leave out the face if the new bones required for this face don't fit the bone count limit anymore - if( numBones + newBonesAtCurrentFace.size() > mMaxBoneCount ) - { + if( numBones + newBonesAtCurrentFace.size() > mMaxBoneCount ) { continue; } // mark all new bones as necessary - for (std::set::iterator it = newBonesAtCurrentFace.begin(); it != newBonesAtCurrentFace.end(); ++it) - { - if (!isBoneUsed[*it]) - { - isBoneUsed[*it] = true; - numBones++; - } + for (std::set::iterator it = newBonesAtCurrentFace.begin(); it != newBonesAtCurrentFace.end(); ++it) { + if (!isBoneUsed[*it]) { + isBoneUsed[*it] = true; + numBones++; + } } // store the face index and the vertex count @@ -261,27 +229,21 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vectormNumVertices = numSubMeshVertices; newMesh->mNumFaces = static_cast(subMeshFaces.size()); newMesh->mVertices = new aiVector3D[newMesh->mNumVertices]; - if( pMesh->HasNormals() ) - { + if( pMesh->HasNormals() ) { newMesh->mNormals = new aiVector3D[newMesh->mNumVertices]; } - if( pMesh->HasTangentsAndBitangents() ) - { + if( pMesh->HasTangentsAndBitangents() ) { newMesh->mTangents = new aiVector3D[newMesh->mNumVertices]; newMesh->mBitangents = new aiVector3D[newMesh->mNumVertices]; } - for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) - { - if( pMesh->HasTextureCoords( a) ) - { + for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a ) { + if( pMesh->HasTextureCoords( a) ) { newMesh->mTextureCoords[a] = new aiVector3D[newMesh->mNumVertices]; } newMesh->mNumUVComponents[a] = pMesh->mNumUVComponents[a]; } - for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a ) - { - if( pMesh->HasVertexColors( a) ) - { + for( unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a ) { + if( pMesh->HasVertexColors( a) ) { newMesh->mColors[a] = new aiColor4D[newMesh->mNumVertices]; } } @@ -290,41 +252,33 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vectormFaces = new aiFace[subMeshFaces.size()]; unsigned int nvi = 0; // next vertex index std::vector previousVertexIndices( numSubMeshVertices, std::numeric_limits::max()); // per new vertex: its index in the source mesh - for( unsigned int a = 0; a < subMeshFaces.size(); ++a ) - { + for( unsigned int a = 0; a < subMeshFaces.size(); ++a ) { const aiFace& srcFace = pMesh->mFaces[subMeshFaces[a]]; aiFace& dstFace = newMesh->mFaces[a]; dstFace.mNumIndices = srcFace.mNumIndices; dstFace.mIndices = new unsigned int[dstFace.mNumIndices]; // accumulate linearly all the vertices of the source face - for( unsigned int b = 0; b < dstFace.mNumIndices; ++b ) - { + for( unsigned int b = 0; b < dstFace.mNumIndices; ++b ) { unsigned int srcIndex = srcFace.mIndices[b]; dstFace.mIndices[b] = nvi; previousVertexIndices[nvi] = srcIndex; newMesh->mVertices[nvi] = pMesh->mVertices[srcIndex]; - if( pMesh->HasNormals() ) - { + if( pMesh->HasNormals() ) { newMesh->mNormals[nvi] = pMesh->mNormals[srcIndex]; } - if( pMesh->HasTangentsAndBitangents() ) - { + if( pMesh->HasTangentsAndBitangents() ) { newMesh->mTangents[nvi] = pMesh->mTangents[srcIndex]; newMesh->mBitangents[nvi] = pMesh->mBitangents[srcIndex]; } - for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c ) - { - if( pMesh->HasTextureCoords( c) ) - { + for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++c ) { + if( pMesh->HasTextureCoords( c) ) { newMesh->mTextureCoords[c][nvi] = pMesh->mTextureCoords[c][srcIndex]; } } - for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c ) - { - if( pMesh->HasVertexColors( c) ) - { + for( unsigned int c = 0; c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c ) { + if( pMesh->HasVertexColors( c) ) { newMesh->mColors[c][nvi] = pMesh->mColors[c][srcIndex]; } } @@ -340,10 +294,8 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vectormBones = new aiBone*[numBones]; std::vector mappedBoneIndex( pMesh->mNumBones, std::numeric_limits::max()); - for( unsigned int a = 0; a < pMesh->mNumBones; ++a ) - { - if( !isBoneUsed[a] ) - { + for( unsigned int a = 0; a < pMesh->mNumBones; ++a ) { + if( !isBoneUsed[a] ) { continue; } @@ -360,24 +312,20 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vectormNumBones == numBones ); // iterate over all new vertices and count which bones affected its old vertex in the source mesh - for( unsigned int a = 0; a < numSubMeshVertices; ++a ) - { + for( unsigned int a = 0; a < numSubMeshVertices; ++a ) { unsigned int oldIndex = previousVertexIndices[a]; const std::vector& bonesOnThisVertex = vertexBones[oldIndex]; - for( unsigned int b = 0; b < bonesOnThisVertex.size(); ++b ) - { + for( unsigned int b = 0; b < bonesOnThisVertex.size(); ++b ) { unsigned int newBoneIndex = mappedBoneIndex[ bonesOnThisVertex[b].first ]; - if( newBoneIndex != std::numeric_limits::max() ) - { + if( newBoneIndex != std::numeric_limits::max() ) { newMesh->mBones[newBoneIndex]->mNumWeights++; } } } // allocate all bone weight arrays accordingly - for( unsigned int a = 0; a < newMesh->mNumBones; ++a ) - { + for( unsigned int a = 0; a < newMesh->mNumBones; ++a ) { aiBone* bone = newMesh->mBones[a]; ai_assert( bone->mNumWeights > 0 ); bone->mWeights = new aiVertexWeight[bone->mNumWeights]; @@ -385,16 +333,14 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vector& bonesOnThisVertex = vertexBones[previousIndex]; // all of the bones affecting it should be present in the new submesh, or else // the face it comprises shouldn't be present - for( unsigned int b = 0; b < bonesOnThisVertex.size(); ++b) - { + for( unsigned int b = 0; b < bonesOnThisVertex.size(); ++b) { unsigned int newBoneIndex = mappedBoneIndex[ bonesOnThisVertex[b].first ]; ai_assert( newBoneIndex != std::numeric_limits::max() ); aiVertexWeight* dstWeight = newMesh->mBones[newBoneIndex]->mWeights + newMesh->mBones[newBoneIndex]->mNumWeights; @@ -450,14 +396,11 @@ void SplitByBoneCountProcess::SplitMesh( const aiMesh* pMesh, std::vectormNumMeshes > 0 ) - { + if( pNode->mNumMeshes == 0 ) { std::vector newMeshList; - for( unsigned int a = 0; a < pNode->mNumMeshes; ++a) - { + for( unsigned int a = 0; a < pNode->mNumMeshes; ++a) { unsigned int srcIndex = pNode->mMeshes[a]; const std::vector& replaceMeshes = mSubMeshIndices[srcIndex]; newMeshList.insert( newMeshList.end(), replaceMeshes.begin(), replaceMeshes.end()); @@ -470,8 +413,7 @@ void SplitByBoneCountProcess::UpdateNode( aiNode* pNode) const } // do that also recursively for all children - for( unsigned int a = 0; a < pNode->mNumChildren; ++a ) - { + for( unsigned int a = 0; a < pNode->mNumChildren; ++a ) { UpdateNode( pNode->mChildren[a]); } } diff --git a/code/PostProcessing/SplitByBoneCountProcess.h b/code/PostProcessing/SplitByBoneCountProcess.h index 938b00c7f..e2377a995 100644 --- a/code/PostProcessing/SplitByBoneCountProcess.h +++ b/code/PostProcessing/SplitByBoneCountProcess.h @@ -51,9 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -namespace Assimp -{ - +namespace Assimp { /** Postprocessing filter to split meshes with many bones into submeshes * so that each submesh has a certain max bone count. @@ -61,33 +59,28 @@ namespace Assimp * Applied BEFORE the JoinVertices-Step occurs. * Returns NON-UNIQUE vertices, splits by bone count. */ -class SplitByBoneCountProcess : public BaseProcess -{ +class SplitByBoneCountProcess : public BaseProcess { public: - + // ------------------------------------------------------------------- + /// The default class constructor / destructor. SplitByBoneCountProcess(); - ~SplitByBoneCountProcess(); + ~SplitByBoneCountProcess() override = default; -public: - /** Returns whether the processing step is present in the given flag. - * @param pFlags The processing flags the importer was called with. A - * bitwise combination of #aiPostProcessSteps. - * @return true if the process is present in this flag fields, - * false if not. - */ + /// @brief Returns whether the processing step is present in the given flag. + /// @param pFlags The processing flags the importer was called with. A + /// bitwise combination of #aiPostProcessSteps. + /// @return true if the process is present in this flag fields, false if not. bool IsActive( unsigned int pFlags) const; - /** Called prior to ExecuteOnScene(). - * The function is a request to the process to update its configuration - * basing on the Importer's configuration property list. - */ + /// @brief Called prior to ExecuteOnScene(). + /// The function is a request to the process to update its configuration + /// basing on the Importer's configuration property list. virtual void SetupProperties(const Importer* pImp); protected: - /** Executes the post processing step on the given imported data. - * At the moment a process is not supposed to fail. - * @param pScene The imported data to work at. - */ + /// Executes the post processing step on the given imported data. + /// At the moment a process is not supposed to fail. + /// @param pScene The imported data to work at. void Execute( aiScene* pScene); /// Splits the given mesh by bone count. diff --git a/code/PostProcessing/SplitLargeMeshes.cpp b/code/PostProcessing/SplitLargeMeshes.cpp index 151ac4991..73e0cc5d8 100644 --- a/code/PostProcessing/SplitLargeMeshes.cpp +++ b/code/PostProcessing/SplitLargeMeshes.cpp @@ -55,9 +55,6 @@ SplitLargeMeshesProcess_Triangle::SplitLargeMeshesProcess_Triangle() { LIMIT = AI_SLM_DEFAULT_MAX_TRIANGLES; } -// ------------------------------------------------------------------------------------------------ -SplitLargeMeshesProcess_Triangle::~SplitLargeMeshesProcess_Triangle() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool SplitLargeMeshesProcess_Triangle::IsActive( unsigned int pFlags) const { @@ -329,9 +326,6 @@ SplitLargeMeshesProcess_Vertex::SplitLargeMeshesProcess_Vertex() { LIMIT = AI_SLM_DEFAULT_MAX_VERTICES; } -// ------------------------------------------------------------------------------------------------ -SplitLargeMeshesProcess_Vertex::~SplitLargeMeshesProcess_Vertex() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool SplitLargeMeshesProcess_Vertex::IsActive( unsigned int pFlags) const { diff --git a/code/PostProcessing/SplitLargeMeshes.h b/code/PostProcessing/SplitLargeMeshes.h index e5a8d4c1b..605f0477d 100644 --- a/code/PostProcessing/SplitLargeMeshes.h +++ b/code/PostProcessing/SplitLargeMeshes.h @@ -83,16 +83,15 @@ class SplitLargeMeshesProcess_Vertex; * Applied BEFORE the JoinVertices-Step occurs. * Returns NON-UNIQUE vertices, splits by triangle number. */ -class ASSIMP_API SplitLargeMeshesProcess_Triangle : public BaseProcess -{ +class ASSIMP_API SplitLargeMeshesProcess_Triangle : public BaseProcess { friend class SplitLargeMeshesProcess_Vertex; public: - + // ------------------------------------------------------------------- + /// The default class constructor / destructor. SplitLargeMeshesProcess_Triangle(); - ~SplitLargeMeshesProcess_Triangle(); + ~SplitLargeMeshesProcess_Triangle() override = default; -public: // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag. * @param pFlags The processing flags the importer was called with. A @@ -102,14 +101,12 @@ public: */ bool IsActive( unsigned int pFlags) const; - // ------------------------------------------------------------------- /** Called prior to ExecuteOnScene(). * The function is a request to the process to update its configuration * basing on the Importer's configuration property list. */ - virtual void SetupProperties(const Importer* pImp); - + void SetupProperties(const Importer* pImp) override; //! Set the split limit - needed for unit testing inline void SetLimit(unsigned int l) @@ -119,8 +116,6 @@ public: inline unsigned int GetLimit() const {return LIMIT;} -public: - // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. @@ -144,21 +139,17 @@ public: unsigned int LIMIT; }; - // --------------------------------------------------------------------------- /** Post-processing filter to split large meshes into sub-meshes * * Applied AFTER the JoinVertices-Step occurs. * Returns UNIQUE vertices, splits by vertex number. */ -class ASSIMP_API SplitLargeMeshesProcess_Vertex : public BaseProcess -{ +class ASSIMP_API SplitLargeMeshesProcess_Vertex : public BaseProcess { public: - SplitLargeMeshesProcess_Vertex(); - ~SplitLargeMeshesProcess_Vertex(); + ~SplitLargeMeshesProcess_Vertex() override = default; -public: // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. * @param pFlags The processing flags the importer was called with. A bitwise diff --git a/code/PostProcessing/TextureTransform.cpp b/code/PostProcessing/TextureTransform.cpp index efbf4d2c6..2ed17f390 100644 --- a/code/PostProcessing/TextureTransform.cpp +++ b/code/PostProcessing/TextureTransform.cpp @@ -56,33 +56,24 @@ using namespace Assimp; // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -TextureTransformStep::TextureTransformStep() : - configFlags() -{ +TextureTransformStep::TextureTransformStep() : configFlags() { // nothing to do here } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -TextureTransformStep::~TextureTransformStep() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. -bool TextureTransformStep::IsActive( unsigned int pFlags) const -{ +bool TextureTransformStep::IsActive( unsigned int pFlags) const { return (pFlags & aiProcess_TransformUVCoords) != 0; } // ------------------------------------------------------------------------------------------------ // Setup properties -void TextureTransformStep::SetupProperties(const Importer* pImp) -{ +void TextureTransformStep::SetupProperties(const Importer* pImp) { configFlags = pImp->GetPropertyInteger(AI_CONFIG_PP_TUV_EVALUATE,AI_UVTRAFO_ALL); } // ------------------------------------------------------------------------------------------------ -void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info) -{ +void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info) { /* This function tries to simplify the input UV transformation. * That's very important as it allows us to reduce the number * of output UV channels. The order in which the transformations @@ -90,7 +81,7 @@ void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info) */ int rounded; - char szTemp[512]; + char szTemp[512] = {}; /* Optimize the rotation angle. That's slightly difficult as * we have an inprecise floating-point number (when comparing @@ -98,12 +89,10 @@ void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info) * an epsilon of 5 degrees). If there is a rotation value, we can't * perform any further optimizations. */ - if (info.mRotation) - { + if (info.mRotation) { float out = info.mRotation; rounded = static_cast((info.mRotation / static_cast(AI_MATH_TWO_PI))); - if (rounded) - { + if (rounded) { out -= rounded * static_cast(AI_MATH_PI); ASSIMP_LOG_INFO("Texture coordinate rotation ", info.mRotation, " can be simplified to ", out); } @@ -187,8 +176,7 @@ void TextureTransformStep::PreProcessUVTransform(STransformVecInfo& info) } // ------------------------------------------------------------------------------------------------ -void UpdateUVIndex(const std::list& l, unsigned int n) -{ +void UpdateUVIndex(const std::list& l, unsigned int n) { // Don't set if == 0 && wasn't set before for (std::list::const_iterator it = l.begin();it != l.end(); ++it) { const TTUpdateInfo& info = *it; @@ -203,8 +191,7 @@ void UpdateUVIndex(const std::list& l, unsigned int n) } // ------------------------------------------------------------------------------------------------ -inline const char* MappingModeToChar(aiTextureMapMode map) -{ +inline static const char* MappingModeToChar(aiTextureMapMode map) { if (aiTextureMapMode_Wrap == map) return "-w"; @@ -215,8 +202,7 @@ inline const char* MappingModeToChar(aiTextureMapMode map) } // ------------------------------------------------------------------------------------------------ -void TextureTransformStep::Execute( aiScene* pScene) -{ +void TextureTransformStep::Execute( aiScene* pScene) { ASSIMP_LOG_DEBUG("TransformUVCoordsProcess begin"); diff --git a/code/PostProcessing/TextureTransform.h b/code/PostProcessing/TextureTransform.h index c1cccf8ef..873789f3e 100644 --- a/code/PostProcessing/TextureTransform.h +++ b/code/PostProcessing/TextureTransform.h @@ -193,14 +193,12 @@ struct STransformVecInfo : public aiUVTransform { /** Helper step to compute final UV coordinate sets if there are scalings * or rotations in the original data read from the file. */ -class TextureTransformStep : public BaseProcess -{ +class TextureTransformStep : public BaseProcess { public: - + // ------------------------------------------------------------------- + /// The default class constructor / destructor. TextureTransformStep(); - ~TextureTransformStep(); - -public: + ~TextureTransformStep() override = default; // ------------------------------------------------------------------- bool IsActive( unsigned int pFlags) const; @@ -213,8 +211,6 @@ public: protected: - - // ------------------------------------------------------------------- /** Preprocess a specific UV transformation setup * @@ -223,10 +219,9 @@ protected: void PreProcessUVTransform(STransformVecInfo& info); private: - unsigned int configFlags; }; - -} + +} // namespace Assimp #endif //! AI_TEXTURE_TRANSFORM_H_INCLUDED diff --git a/code/PostProcessing/TriangulateProcess.cpp b/code/PostProcessing/TriangulateProcess.cpp index 8ba6456b7..52cfa66bf 100644 --- a/code/PostProcessing/TriangulateProcess.cpp +++ b/code/PostProcessing/TriangulateProcess.cpp @@ -156,15 +156,6 @@ namespace { } - -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -TriangulateProcess::TriangulateProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -TriangulateProcess::~TriangulateProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool TriangulateProcess::IsActive( unsigned int pFlags) const diff --git a/code/PostProcessing/TriangulateProcess.h b/code/PostProcessing/TriangulateProcess.h index ed5f4a587..fefeac61c 100644 --- a/code/PostProcessing/TriangulateProcess.h +++ b/code/PostProcessing/TriangulateProcess.h @@ -61,8 +61,10 @@ namespace Assimp { */ class ASSIMP_API TriangulateProcess : public BaseProcess { public: - TriangulateProcess(); - ~TriangulateProcess(); + // ------------------------------------------------------------------- + /// The default class constructor / destructor. + TriangulateProcess() = default; + ~TriangulateProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. diff --git a/code/PostProcessing/ValidateDataStructure.cpp b/code/PostProcessing/ValidateDataStructure.cpp index d234e220b..cae35b895 100644 --- a/code/PostProcessing/ValidateDataStructure.cpp +++ b/code/PostProcessing/ValidateDataStructure.cpp @@ -60,12 +60,7 @@ using namespace Assimp; // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer -ValidateDSProcess::ValidateDSProcess() : - mScene() {} - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -ValidateDSProcess::~ValidateDSProcess() = default; +ValidateDSProcess::ValidateDSProcess() : mScene(nullptr) {} // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. diff --git a/code/PostProcessing/ValidateDataStructure.h b/code/PostProcessing/ValidateDataStructure.h index 077a47b70..87c88669f 100644 --- a/code/PostProcessing/ValidateDataStructure.h +++ b/code/PostProcessing/ValidateDataStructure.h @@ -72,11 +72,11 @@ namespace Assimp { class ValidateDSProcess : public BaseProcess { public: - + // ------------------------------------------------------------------- + /// The default class constructor / destructor. ValidateDSProcess(); - ~ValidateDSProcess(); + ~ValidateDSProcess() override = default; -public: // ------------------------------------------------------------------- bool IsActive( unsigned int pFlags) const; From 31ae9cde1ca1bd1df71c38fd39c2dab6a4ecba1a Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 14 Mar 2023 23:32:03 +0100 Subject: [PATCH 39/49] Refactoring: Code cleanup post-processing. --- code/PostProcessing/CalcTangentsProcess.cpp | 4 --- code/PostProcessing/CalcTangentsProcess.h | 15 ++++------- .../ComputeUVMappingProcess.cpp | 8 ------ code/PostProcessing/ComputeUVMappingProcess.h | 19 +++++-------- code/PostProcessing/ConvertToLHProcess.cpp | 16 ----------- code/PostProcessing/ConvertToLHProcess.h | 27 +++++++------------ code/PostProcessing/DeboneProcess.h | 4 +-- code/PostProcessing/DropFaceNormalsProcess.h | 5 ++-- code/PostProcessing/EmbedTexturesProcess.h | 6 ++--- code/PostProcessing/FindDegenerates.h | 6 ++--- code/PostProcessing/FindInstancesProcess.h | 15 +++++------ code/PostProcessing/FindInvalidDataProcess.h | 6 ++--- code/PostProcessing/FixNormalsStep.h | 4 +-- code/PostProcessing/GenFaceNormalsProcess.h | 5 ++-- code/PostProcessing/GenVertexNormalsProcess.h | 7 +++-- code/PostProcessing/ImproveCacheLocality.h | 6 ++--- code/PostProcessing/JoinVerticesProcess.h | 4 +-- code/PostProcessing/LimitBoneWeightsProcess.h | 18 ++++++------- code/PostProcessing/MakeVerboseFormat.h | 4 +-- code/PostProcessing/OptimizeMeshes.h | 8 +++--- .../PostProcessing/RemoveRedundantMaterials.h | 6 ++--- code/PostProcessing/RemoveVCProcess.h | 12 ++++----- code/PostProcessing/ScaleProcess.h | 6 ++--- code/PostProcessing/SortByPTypeProcess.h | 6 ++--- code/PostProcessing/SplitByBoneCountProcess.h | 2 +- code/PostProcessing/SplitLargeMeshes.h | 4 +-- code/PostProcessing/TextureTransform.h | 7 +++-- code/PostProcessing/TriangulateProcess.h | 4 +-- code/PostProcessing/ValidateDataStructure.h | 8 +++--- 29 files changed, 93 insertions(+), 149 deletions(-) diff --git a/code/PostProcessing/CalcTangentsProcess.cpp b/code/PostProcessing/CalcTangentsProcess.cpp index efc457766..a23ac856b 100644 --- a/code/PostProcessing/CalcTangentsProcess.cpp +++ b/code/PostProcessing/CalcTangentsProcess.cpp @@ -60,10 +60,6 @@ CalcTangentsProcess::CalcTangentsProcess() : // nothing to do here } -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -CalcTangentsProcess::~CalcTangentsProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool CalcTangentsProcess::IsActive(unsigned int pFlags) const { diff --git a/code/PostProcessing/CalcTangentsProcess.h b/code/PostProcessing/CalcTangentsProcess.h index 018789bae..94be99cd6 100644 --- a/code/PostProcessing/CalcTangentsProcess.h +++ b/code/PostProcessing/CalcTangentsProcess.h @@ -59,14 +59,11 @@ namespace Assimp * because the joining of vertices also considers tangents and bitangents for * uniqueness. */ -class ASSIMP_API_WINONLY CalcTangentsProcess : public BaseProcess -{ +class ASSIMP_API_WINONLY CalcTangentsProcess : public BaseProcess { public: - CalcTangentsProcess(); - ~CalcTangentsProcess(); + ~CalcTangentsProcess() override = default; -public: // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag. * @param pFlags The processing flags the importer was called with. @@ -74,19 +71,17 @@ public: * @return true if the process is present in this flag fields, * false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Called prior to ExecuteOnScene(). * The function is a request to the process to update its configuration * basing on the Importer's configuration property list. */ - void SetupProperties(const Importer* pImp); - + void SetupProperties(const Importer* pImp) override; // setter for configMaxAngle - inline void SetMaxSmoothAngle(float f) - { + void SetMaxSmoothAngle(float f) { configMaxAngle =f; } diff --git a/code/PostProcessing/ComputeUVMappingProcess.cpp b/code/PostProcessing/ComputeUVMappingProcess.cpp index 237409f02..a5472668b 100644 --- a/code/PostProcessing/ComputeUVMappingProcess.cpp +++ b/code/PostProcessing/ComputeUVMappingProcess.cpp @@ -57,14 +57,6 @@ namespace { const static ai_real angle_epsilon = ai_real( 0.95 ); } -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -ComputeUVMappingProcess::ComputeUVMappingProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -ComputeUVMappingProcess::~ComputeUVMappingProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool ComputeUVMappingProcess::IsActive( unsigned int pFlags) const diff --git a/code/PostProcessing/ComputeUVMappingProcess.h b/code/PostProcessing/ComputeUVMappingProcess.h index 74744be7f..c4158f402 100644 --- a/code/PostProcessing/ComputeUVMappingProcess.h +++ b/code/PostProcessing/ComputeUVMappingProcess.h @@ -59,13 +59,10 @@ namespace Assimp { /** ComputeUVMappingProcess - converts special mappings, such as spherical, * cylindrical or boxed to proper UV coordinates for rendering. */ -class ComputeUVMappingProcess : public BaseProcess -{ -public: - ComputeUVMappingProcess(); - ~ComputeUVMappingProcess(); - +class ComputeUVMappingProcess : public BaseProcess { public: + ComputeUVMappingProcess() = default; + ~ComputeUVMappingProcess() override = default; // ------------------------------------------------------------------- /** Returns whether the processing step is present in the given flag field. @@ -73,14 +70,14 @@ public: * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; protected: @@ -125,8 +122,7 @@ protected: private: // temporary structure to describe a mapping - struct MappingInfo - { + struct MappingInfo { explicit MappingInfo(aiTextureMapping _type) : type (_type) , axis (0.f,1.f,0.f) @@ -137,8 +133,7 @@ private: aiVector3D axis; unsigned int uv; - bool operator== (const MappingInfo& other) - { + bool operator== (const MappingInfo& other) { return type == other.type && axis == other.axis; } }; diff --git a/code/PostProcessing/ConvertToLHProcess.cpp b/code/PostProcessing/ConvertToLHProcess.cpp index 359c5a284..08e3fe48a 100644 --- a/code/PostProcessing/ConvertToLHProcess.cpp +++ b/code/PostProcessing/ConvertToLHProcess.cpp @@ -79,14 +79,6 @@ void flipUVs(aiMeshType *pMesh) { } // namespace -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -MakeLeftHandedProcess::MakeLeftHandedProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -MakeLeftHandedProcess::~MakeLeftHandedProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool MakeLeftHandedProcess::IsActive(unsigned int pFlags) const { @@ -305,14 +297,6 @@ void FlipUVsProcess::ProcessMesh(aiMesh *pMesh) { #ifndef ASSIMP_BUILD_NO_FLIPWINDING_PROCESS // # FlipWindingOrderProcess -// ------------------------------------------------------------------------------------------------ -// Constructor to be privately used by Importer -FlipWindingOrderProcess::FlipWindingOrderProcess() = default; - -// ------------------------------------------------------------------------------------------------ -// Destructor, private as well -FlipWindingOrderProcess::~FlipWindingOrderProcess() = default; - // ------------------------------------------------------------------------------------------------ // Returns whether the processing step is present in the given flag field. bool FlipWindingOrderProcess::IsActive(unsigned int pFlags) const { diff --git a/code/PostProcessing/ConvertToLHProcess.h b/code/PostProcessing/ConvertToLHProcess.h index 474056c3a..d0532277d 100644 --- a/code/PostProcessing/ConvertToLHProcess.h +++ b/code/PostProcessing/ConvertToLHProcess.h @@ -4,7 +4,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2022, assimp team - All rights reserved. Redistribution and use of this software in source and binary forms, @@ -72,22 +71,18 @@ namespace Assimp { * * @note RH-LH and LH-RH is the same, so this class can be used for both */ -class MakeLeftHandedProcess : public BaseProcess -{ - - +class MakeLeftHandedProcess : public BaseProcess { public: - MakeLeftHandedProcess(); - ~MakeLeftHandedProcess(); + MakeLeftHandedProcess() = default; + ~MakeLeftHandedProcess() override = default; // ------------------------------------------------------------------- - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; protected: - // ------------------------------------------------------------------- /** Recursively converts a node and all of its children */ @@ -120,24 +115,22 @@ protected: // --------------------------------------------------------------------------- /** Postprocessing step to flip the face order of the imported data */ -class FlipWindingOrderProcess : public BaseProcess -{ +class FlipWindingOrderProcess : public BaseProcess { friend class Importer; public: /** Constructor to be privately used by Importer */ - FlipWindingOrderProcess(); + FlipWindingOrderProcess() = default; /** Destructor, private as well */ - ~FlipWindingOrderProcess(); + ~FlipWindingOrderProcess() override = default; // ------------------------------------------------------------------- - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; -public: /** Some other types of post-processing require winding order flips */ static void ProcessMesh( aiMesh* pMesh); }; diff --git a/code/PostProcessing/DeboneProcess.h b/code/PostProcessing/DeboneProcess.h index 080bc30d1..6e55ce238 100644 --- a/code/PostProcessing/DeboneProcess.h +++ b/code/PostProcessing/DeboneProcess.h @@ -79,14 +79,14 @@ public: * @return true if the process is present in this flag fields, * false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Called prior to ExecuteOnScene(). * The function is a request to the process to update its configuration * basing on the Importer's configuration property list. */ - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; protected: // ------------------------------------------------------------------- diff --git a/code/PostProcessing/DropFaceNormalsProcess.h b/code/PostProcessing/DropFaceNormalsProcess.h index 719c98dc7..df542f2ba 100644 --- a/code/PostProcessing/DropFaceNormalsProcess.h +++ b/code/PostProcessing/DropFaceNormalsProcess.h @@ -64,15 +64,14 @@ public: * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); - + void Execute( aiScene* pScene) override; private: bool DropMeshFaceNormals(aiMesh* pcMesh); diff --git a/code/PostProcessing/EmbedTexturesProcess.h b/code/PostProcessing/EmbedTexturesProcess.h index e09fa0896..77d4d9c72 100644 --- a/code/PostProcessing/EmbedTexturesProcess.h +++ b/code/PostProcessing/EmbedTexturesProcess.h @@ -68,13 +68,13 @@ public: ~EmbedTexturesProcess() override = default; /// Overwritten, @see BaseProcess - virtual bool IsActive(unsigned int pFlags) const; + bool IsActive(unsigned int pFlags) const override; /// Overwritten, @see BaseProcess - virtual void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; /// Overwritten, @see BaseProcess - virtual void Execute(aiScene* pScene); + virtual void Execute(aiScene* pScene) override; private: // Resolve the path and add the file content to the scene as a texture. diff --git a/code/PostProcessing/FindDegenerates.h b/code/PostProcessing/FindDegenerates.h index d1eb81615..6b37a47cf 100644 --- a/code/PostProcessing/FindDegenerates.h +++ b/code/PostProcessing/FindDegenerates.h @@ -63,15 +63,15 @@ public: // ------------------------------------------------------------------- // Check whether step is active - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- // Execute step on a given scene - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- // Setup import settings - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; // ------------------------------------------------------------------- // Execute step on a given mesh diff --git a/code/PostProcessing/FindInstancesProcess.h b/code/PostProcessing/FindInstancesProcess.h index b6c61527a..6927301ca 100644 --- a/code/PostProcessing/FindInstancesProcess.h +++ b/code/PostProcessing/FindInstancesProcess.h @@ -50,7 +50,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "PostProcessing/ProcessHelper.h" class FindInstancesProcessTest; -namespace Assimp { + +namespace Assimp { // ------------------------------------------------------------------------------- /** @brief Get a pseudo(!)-hash representing a mesh. @@ -60,8 +61,7 @@ namespace Assimp { * @param in Input mesh * @return Hash. */ -inline -uint64_t GetMeshHash(aiMesh* in) { +inline uint64_t GetMeshHash(aiMesh* in) { ai_assert(nullptr != in); // ... get an unique value representing the vertex format of the mesh @@ -83,8 +83,7 @@ uint64_t GetMeshHash(aiMesh* in) { * @param e Epsilon * @return true if the arrays are identical */ -inline -bool CompareArrays(const aiVector3D* first, const aiVector3D* second, +inline bool CompareArrays(const aiVector3D* first, const aiVector3D* second, unsigned int size, float e) { for (const aiVector3D* end = first+size; first != end; ++first,++second) { if ( (*first - *second).SquareLength() >= e) @@ -114,15 +113,15 @@ public: // ------------------------------------------------------------------- // Check whether step is active in given flags combination - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- // Execute step on a given scene - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- // Setup properties prior to executing the process - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; private: bool configSpeedFlag; diff --git a/code/PostProcessing/FindInvalidDataProcess.h b/code/PostProcessing/FindInvalidDataProcess.h index 14746864f..024eb9b1e 100644 --- a/code/PostProcessing/FindInvalidDataProcess.h +++ b/code/PostProcessing/FindInvalidDataProcess.h @@ -71,15 +71,15 @@ public: // ------------------------------------------------------------------- /// Returns active state. - bool IsActive(unsigned int pFlags) const; + bool IsActive(unsigned int pFlags) const override; // ------------------------------------------------------------------- /// Setup import settings - void SetupProperties(const Importer *pImp); + void SetupProperties(const Importer *pImp) override; // ------------------------------------------------------------------- /// Run the step - void Execute(aiScene *pScene); + void Execute(aiScene *pScene) override; // ------------------------------------------------------------------- /// Executes the post-processing step on the given mesh diff --git a/code/PostProcessing/FixNormalsStep.h b/code/PostProcessing/FixNormalsStep.h index e5f5c8a1f..20be1958b 100644 --- a/code/PostProcessing/FixNormalsStep.h +++ b/code/PostProcessing/FixNormalsStep.h @@ -69,14 +69,14 @@ public: * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; protected: diff --git a/code/PostProcessing/GenFaceNormalsProcess.h b/code/PostProcessing/GenFaceNormalsProcess.h index b1babca28..94794631e 100644 --- a/code/PostProcessing/GenFaceNormalsProcess.h +++ b/code/PostProcessing/GenFaceNormalsProcess.h @@ -66,15 +66,14 @@ public: * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); - + void Execute( aiScene* pScene) override; private: bool GenMeshFaceNormals(aiMesh* pcMesh); diff --git a/code/PostProcessing/GenVertexNormalsProcess.h b/code/PostProcessing/GenVertexNormalsProcess.h index 3999fc3e9..b7db9c4f2 100644 --- a/code/PostProcessing/GenVertexNormalsProcess.h +++ b/code/PostProcessing/GenVertexNormalsProcess.h @@ -72,22 +72,21 @@ public: * @return true if the process is present in this flag fields, * false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Called prior to ExecuteOnScene(). * The function is a request to the process to update its configuration * basing on the Importer's configuration property list. */ - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); - + void Execute( aiScene* pScene) override; // setter for configMaxAngle inline void SetMaxSmoothAngle(ai_real f) { diff --git a/code/PostProcessing/ImproveCacheLocality.h b/code/PostProcessing/ImproveCacheLocality.h index 34a3883da..6f4d55719 100644 --- a/code/PostProcessing/ImproveCacheLocality.h +++ b/code/PostProcessing/ImproveCacheLocality.h @@ -69,15 +69,15 @@ public: // ------------------------------------------------------------------- // Check whether the pp step is active - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- // Executes the pp step on a given scene - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- // Configures the pp step - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; protected: // ------------------------------------------------------------------- diff --git a/code/PostProcessing/JoinVerticesProcess.h b/code/PostProcessing/JoinVerticesProcess.h index de8cea691..aa8dc5794 100644 --- a/code/PostProcessing/JoinVerticesProcess.h +++ b/code/PostProcessing/JoinVerticesProcess.h @@ -74,14 +74,14 @@ public: * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- /** Unites identical vertices in the given mesh. diff --git a/code/PostProcessing/LimitBoneWeightsProcess.h b/code/PostProcessing/LimitBoneWeightsProcess.h index 855c8628a..b19d536cf 100644 --- a/code/PostProcessing/LimitBoneWeightsProcess.h +++ b/code/PostProcessing/LimitBoneWeightsProcess.h @@ -86,27 +86,27 @@ public: * @return true if the process is present in this flag fields, * false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Called prior to ExecuteOnScene(). * The function is a request to the process to update its configuration * basing on the Importer's configuration property list. */ - void SetupProperties(const Importer* pImp); - - // ------------------------------------------------------------------- - /** Limits the bone weight count for all vertices in the given mesh. - * @param pMesh The mesh to process. - */ - void ProcessMesh( aiMesh* pMesh); + void SetupProperties(const Importer* pImp) override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; + + // ------------------------------------------------------------------- + /** Limits the bone weight count for all vertices in the given mesh. + * @param pMesh The mesh to process. + */ + void ProcessMesh( aiMesh* pMesh); // ------------------------------------------------------------------- /** Describes a bone weight on a vertex */ diff --git a/code/PostProcessing/MakeVerboseFormat.h b/code/PostProcessing/MakeVerboseFormat.h index b7ac10019..f21f5919e 100644 --- a/code/PostProcessing/MakeVerboseFormat.h +++ b/code/PostProcessing/MakeVerboseFormat.h @@ -78,7 +78,7 @@ public: * @param pFlags The processing flags the importer was called with. A bitwise * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not */ - bool IsActive( unsigned int /*pFlags*/ ) const + bool IsActive( unsigned int /*pFlags*/ ) const override { // NOTE: There is no direct flag that corresponds to // this postprocess step. @@ -89,7 +89,7 @@ public: /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; public: diff --git a/code/PostProcessing/OptimizeMeshes.h b/code/PostProcessing/OptimizeMeshes.h index 1109a30e7..0b062959a 100644 --- a/code/PostProcessing/OptimizeMeshes.h +++ b/code/PostProcessing/OptimizeMeshes.h @@ -93,16 +93,14 @@ public: unsigned int output_id; }; -public: // ------------------------------------------------------------------- - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- - void SetupProperties(const Importer* pImp); - + void SetupProperties(const Importer* pImp) override; // ------------------------------------------------------------------- /** @brief Specify whether you want meshes with different diff --git a/code/PostProcessing/RemoveRedundantMaterials.h b/code/PostProcessing/RemoveRedundantMaterials.h index 0fbf33c1b..1b42bea55 100644 --- a/code/PostProcessing/RemoveRedundantMaterials.h +++ b/code/PostProcessing/RemoveRedundantMaterials.h @@ -66,15 +66,15 @@ public: // ------------------------------------------------------------------- // Check whether step is active - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- // Execute step on a given scene - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- // Setup import settings - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; // ------------------------------------------------------------------- /** @brief Set list of fixed (inmutable) materials diff --git a/code/PostProcessing/RemoveVCProcess.h b/code/PostProcessing/RemoveVCProcess.h index dfd9b59ff..45c0b3a71 100644 --- a/code/PostProcessing/RemoveVCProcess.h +++ b/code/PostProcessing/RemoveVCProcess.h @@ -69,37 +69,35 @@ public: * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- /** Called prior to ExecuteOnScene(). * The function is a request to the process to update its configuration * basing on the Importer's configuration property list. */ - virtual void SetupProperties(const Importer* pImp); + virtual void SetupProperties(const Importer* pImp) override; // ------------------------------------------------------------------- /** Manually setup the configuration flags for the step * * @param Bitwise combination of the #aiComponent enumerated values. */ - void SetDeleteFlags(unsigned int f) - { + void SetDeleteFlags(unsigned int f) { configDeleteFlags = f; } // ------------------------------------------------------------------- /** Query the current configuration. */ - unsigned int GetDeleteFlags() const - { + unsigned int GetDeleteFlags() const { return configDeleteFlags; } diff --git a/code/PostProcessing/ScaleProcess.h b/code/PostProcessing/ScaleProcess.h index 4d706bfc3..ae1c3ed00 100644 --- a/code/PostProcessing/ScaleProcess.h +++ b/code/PostProcessing/ScaleProcess.h @@ -74,13 +74,13 @@ public: ai_real getScale() const; /// Overwritten, @see BaseProcess - virtual bool IsActive( unsigned int pFlags ) const; + virtual bool IsActive( unsigned int pFlags ) const override; /// Overwritten, @see BaseProcess - virtual void SetupProperties( const Importer* pImp ); + virtual void SetupProperties( const Importer* pImp ) override; /// Overwritten, @see BaseProcess - virtual void Execute( aiScene* pScene ); + virtual void Execute( aiScene* pScene ) override; private: void traverseNodes( aiNode *currentNode, unsigned int nested_node_id = 0 ); diff --git a/code/PostProcessing/SortByPTypeProcess.h b/code/PostProcessing/SortByPTypeProcess.h index c004e0549..ce4f7da62 100644 --- a/code/PostProcessing/SortByPTypeProcess.h +++ b/code/PostProcessing/SortByPTypeProcess.h @@ -66,13 +66,13 @@ public: ~SortByPTypeProcess() override = default; // ------------------------------------------------------------------- - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- - void SetupProperties(const Importer* pImp); + void SetupProperties(const Importer* pImp) override; private: int mConfigRemoveMeshes; diff --git a/code/PostProcessing/SplitByBoneCountProcess.h b/code/PostProcessing/SplitByBoneCountProcess.h index e2377a995..e8a36140d 100644 --- a/code/PostProcessing/SplitByBoneCountProcess.h +++ b/code/PostProcessing/SplitByBoneCountProcess.h @@ -75,7 +75,7 @@ public: /// @brief Called prior to ExecuteOnScene(). /// The function is a request to the process to update its configuration /// basing on the Importer's configuration property list. - virtual void SetupProperties(const Importer* pImp); + virtual void SetupProperties(const Importer* pImp) override; protected: /// Executes the post processing step on the given imported data. diff --git a/code/PostProcessing/SplitLargeMeshes.h b/code/PostProcessing/SplitLargeMeshes.h index 605f0477d..955b03aa6 100644 --- a/code/PostProcessing/SplitLargeMeshes.h +++ b/code/PostProcessing/SplitLargeMeshes.h @@ -99,7 +99,7 @@ public: * @return true if the process is present in this flag fields, * false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Called prior to ExecuteOnScene(). @@ -121,7 +121,7 @@ public: * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- //! Apply the algorithm to a given mesh diff --git a/code/PostProcessing/TextureTransform.h b/code/PostProcessing/TextureTransform.h index 873789f3e..c9f0480ba 100644 --- a/code/PostProcessing/TextureTransform.h +++ b/code/PostProcessing/TextureTransform.h @@ -201,14 +201,13 @@ public: ~TextureTransformStep() override = default; // ------------------------------------------------------------------- - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- - void SetupProperties(const Importer* pImp); - + void SetupProperties(const Importer* pImp) override; protected: // ------------------------------------------------------------------- diff --git a/code/PostProcessing/TriangulateProcess.h b/code/PostProcessing/TriangulateProcess.h index fefeac61c..ac31e4377 100644 --- a/code/PostProcessing/TriangulateProcess.h +++ b/code/PostProcessing/TriangulateProcess.h @@ -72,14 +72,14 @@ public: * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- /** Triangulates the given mesh. diff --git a/code/PostProcessing/ValidateDataStructure.h b/code/PostProcessing/ValidateDataStructure.h index 87c88669f..9cfd4ced1 100644 --- a/code/PostProcessing/ValidateDataStructure.h +++ b/code/PostProcessing/ValidateDataStructure.h @@ -69,8 +69,7 @@ namespace Assimp { /** Validates the whole ASSIMP scene data structure for correctness. * ImportErrorException is thrown of the scene is corrupt.*/ // -------------------------------------------------------------------------------------- -class ValidateDSProcess : public BaseProcess -{ +class ValidateDSProcess : public BaseProcess { public: // ------------------------------------------------------------------- /// The default class constructor / destructor. @@ -78,13 +77,12 @@ public: ~ValidateDSProcess() override = default; // ------------------------------------------------------------------- - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; protected: - // ------------------------------------------------------------------- /** Report a validation error. This will throw an exception, * control won't return. From 45c1da26b3916a49478ed196b80ba9b5dc9590d2 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 14 Mar 2023 23:34:31 +0100 Subject: [PATCH 40/49] Refactoring: Code cleanup post-processing. --- code/PostProcessing/DeboneProcess.h | 2 +- code/PostProcessing/SplitByBoneCountProcess.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/PostProcessing/DeboneProcess.h b/code/PostProcessing/DeboneProcess.h index 6e55ce238..ae4448e0e 100644 --- a/code/PostProcessing/DeboneProcess.h +++ b/code/PostProcessing/DeboneProcess.h @@ -94,7 +94,7 @@ protected: * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- /** Counts bones total/removable in a given mesh. diff --git a/code/PostProcessing/SplitByBoneCountProcess.h b/code/PostProcessing/SplitByBoneCountProcess.h index e8a36140d..270e00c49 100644 --- a/code/PostProcessing/SplitByBoneCountProcess.h +++ b/code/PostProcessing/SplitByBoneCountProcess.h @@ -81,7 +81,7 @@ protected: /// Executes the post processing step on the given imported data. /// At the moment a process is not supposed to fail. /// @param pScene The imported data to work at. - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; /// Splits the given mesh by bone count. /// @param pMesh the Mesh to split. Is not changed at all, but might be superfluous in case it was split. From 59d97119283aac5f4404c4badcc17618279b7e89 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 14 Mar 2023 23:37:10 +0100 Subject: [PATCH 41/49] Refactoring: Code cleanup post-processing. --- code/PostProcessing/SplitLargeMeshes.h | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/code/PostProcessing/SplitLargeMeshes.h b/code/PostProcessing/SplitLargeMeshes.h index 955b03aa6..4e0d764c1 100644 --- a/code/PostProcessing/SplitLargeMeshes.h +++ b/code/PostProcessing/SplitLargeMeshes.h @@ -156,15 +156,14 @@ public: * combination of #aiPostProcessSteps. * @return true if the process is present in this flag fields, false if not. */ - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; // ------------------------------------------------------------------- /** Called prior to ExecuteOnScene(). * The function is a request to the process to update its configuration * basing on the Importer's configuration property list. */ - virtual void SetupProperties(const Importer* pImp); - + void SetupProperties(const Importer* pImp) override; //! Set the split limit - needed for unit testing inline void SetLimit(unsigned int l) @@ -174,14 +173,12 @@ public: inline unsigned int GetLimit() const {return LIMIT;} -public: - // ------------------------------------------------------------------- /** Executes the post processing step on the given imported data. * At the moment a process is not supposed to fail. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; // ------------------------------------------------------------------- //! Apply the algorithm to a given mesh From c537bd78d0f3c80d0048845a427adc2dd3b1f2c7 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 14 Mar 2023 23:39:36 +0100 Subject: [PATCH 42/49] Refactoring: Code cleanup post-processing. --- code/PostProcessing/CalcTangentsProcess.h | 4 +--- code/PostProcessing/SplitByBoneCountProcess.h | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/code/PostProcessing/CalcTangentsProcess.h b/code/PostProcessing/CalcTangentsProcess.h index 94be99cd6..aaccb5307 100644 --- a/code/PostProcessing/CalcTangentsProcess.h +++ b/code/PostProcessing/CalcTangentsProcess.h @@ -86,7 +86,6 @@ public: } protected: - // ------------------------------------------------------------------- /** Calculates tangents and bitangents for a specific mesh. * @param pMesh The mesh to process. @@ -98,10 +97,9 @@ protected: /** Executes the post processing step on the given imported data. * @param pScene The imported data to work at. */ - void Execute( aiScene* pScene); + void Execute( aiScene* pScene) override; private: - /** Configuration option: maximum smoothing angle, in radians*/ float configMaxAngle; unsigned int configSourceUV; diff --git a/code/PostProcessing/SplitByBoneCountProcess.h b/code/PostProcessing/SplitByBoneCountProcess.h index 270e00c49..98ca6a61e 100644 --- a/code/PostProcessing/SplitByBoneCountProcess.h +++ b/code/PostProcessing/SplitByBoneCountProcess.h @@ -70,7 +70,7 @@ public: /// @param pFlags The processing flags the importer was called with. A /// bitwise combination of #aiPostProcessSteps. /// @return true if the process is present in this flag fields, false if not. - bool IsActive( unsigned int pFlags) const; + bool IsActive( unsigned int pFlags) const override; /// @brief Called prior to ExecuteOnScene(). /// The function is a request to the process to update its configuration From 7e5a178637ff4dbac91e55937035af0434d86bb0 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Tue, 14 Mar 2023 23:49:41 +0100 Subject: [PATCH 43/49] Update: Add missing geo doc. --- code/Geometry/GeometryUtils.cpp | 43 ++++++++++++++- code/Geometry/GeometryUtils.h | 54 +++++++++++++++++++ code/PostProcessing/SplitByBoneCountProcess.h | 1 - 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/code/Geometry/GeometryUtils.cpp b/code/Geometry/GeometryUtils.cpp index 9b29af1dd..ab735aa6e 100644 --- a/code/Geometry/GeometryUtils.cpp +++ b/code/Geometry/GeometryUtils.cpp @@ -1,3 +1,44 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + #include "GeometryUtils.h" #include @@ -20,8 +61,6 @@ ai_real GeometryUtils::distance3D( const aiVector3D &vA, aiVector3D &vB ) { return d; } - - ai_real GeometryUtils::calculateAreaOfTriangle( const aiFace& face, aiMesh* mesh ) { ai_real area = 0; diff --git a/code/Geometry/GeometryUtils.h b/code/Geometry/GeometryUtils.h index 2eb96926d..ab49380de 100644 --- a/code/Geometry/GeometryUtils.h +++ b/code/Geometry/GeometryUtils.h @@ -1,12 +1,66 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2022, assimp team + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + #include #include namespace Assimp { +// --------------------------------------------------------------------------- +/// @brief This helper class supports some basic geometry algorithms. +// --------------------------------------------------------------------------- class GeometryUtils { public: static ai_real heron( ai_real a, ai_real b, ai_real c ); + + /// @brief Will compute the distance between 2 3D-vectors + /// @param vA Vector a. + /// @param vB Vector b. + /// @return The distance. static ai_real distance3D( const aiVector3D &vA, aiVector3D &vB ); + + /// @brief Will calculate the area of a triangle described by a aiFace. + /// @param face The face + /// @param mesh The mesh containing the face + /// @return The area. static ai_real calculateAreaOfTriangle( const aiFace& face, aiMesh* mesh ); }; diff --git a/code/PostProcessing/SplitByBoneCountProcess.h b/code/PostProcessing/SplitByBoneCountProcess.h index 98ca6a61e..625019e0c 100644 --- a/code/PostProcessing/SplitByBoneCountProcess.h +++ b/code/PostProcessing/SplitByBoneCountProcess.h @@ -4,7 +4,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2022, assimp team - All rights reserved. Redistribution and use of this software in source and binary forms, From 16f9ba4935c2b1c83e13cc4081a92294c792c9f0 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Wed, 15 Mar 2023 13:26:24 +0100 Subject: [PATCH 44/49] Remove alarm badge --- Readme.md | 1 - 1 file changed, 1 deletion(-) diff --git a/Readme.md b/Readme.md index 917b8e8aa..1af71fad8 100644 --- a/Readme.md +++ b/Readme.md @@ -14,7 +14,6 @@ A library to import and export various 3d-model-formats including scene-post-pro [![Join the chat at https://gitter.im/assimp/assimp](https://badges.gitter.im/assimp/assimp.svg)](https://gitter.im/assimp/assimp?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Average time to resolve an issue") [![Percentage of issues still open](http://isitmaintained.com/badge/open/assimp/assimp.svg)](http://isitmaintained.com/project/assimp/assimp "Percentage of issues still open") -[![Total alerts](https://img.shields.io/lgtm/alerts/g/assimp/assimp.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/assimp/assimp/alerts/)
APIs are provided for C and C++. There are various bindings to other languages (C#, Java, Python, Delphi, D). Assimp also runs on Android and iOS. From e2063b3ba6d3bee5b115edc75b00cede072c251a Mon Sep 17 00:00:00 2001 From: Steve M Date: Fri, 17 Mar 2023 22:42:17 -0700 Subject: [PATCH 45/49] Revert to commit 13 May 2014 (08bacc7) Changes were introduced to the binary portion of this file at some point --- test/models/PLY/pond.0.ply | Bin 2171788 -> 2171857 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/models/PLY/pond.0.ply b/test/models/PLY/pond.0.ply index 02b7683cee063655c5b93ade29bc14b3f04e15dd..a45e0322d856bbef01c46b7a5a45a0570fae69be 100644 GIT binary patch delta 600 zcmWmBU1(Ba7zc2U?x^S3d#1~DXFKY!hJv+nyC_;s`h?oDY|NmWE{cL$Y~8hrsLZ-) z6h`e-td%qwG2F(2S7N2mQqWAI#`m{s zsX|*Q!~Vtf!FzT z?p?5ARV8oW`_Zt9M#Jj-Zam8+HI+-6<9ks5w~1RXLi>JhsuO;`$6cs_haYn7n2QdE zxYmO(R^e_}>z?g=mVstBzwCr{XZXrFs9fL|v@l=bH_pS_xVF9x+J9>^X}I)y)#5Su z%|glpm?0$-nvw-Ak8Ca^d>3GJN}zVw_eD6bgNsE$(Tm<=sNn5*%LZjb*206 z>K~clkLUWQO7sLGdYn2Mh^+2=f_%wiV7zd7#2_c&;K?;R-=Z(;G}5fov|>Y+_L!0i zWI?Zq2EFyZpU4UwV&W9`9tn%p-}5dk(zR&ur$p*cHEj5ZY;w5q#b$V7u90)Wvnq-1 zh3gJT1`clVNiVm;Uw5QetqMYhCf-O#apz{GiPJFJVorBM|21=Sb@Z`-nT`c4v!lp* Yoz_d$wHMl~>O-iN{Z?A`Hw};c2fN+ZF8}}l delta 471 zcmV;|0Vw{_$Akfl$Akf}+)T6GOuWsvni>Hpl83{r0k^}f0{nZIz$gPAx8EoOJP3yz zF$0GjF$A|9F$F=Ew_e8uO9q#!%?0V zhsve~x5}mm;6#V3>j$^1>j+~-w+}H1EE%^vN(r$`x9*h*dIYy=nh84yx22*9GEcYI z@d=Mhw`e^I&jGhMKMM6Lw=Qc6@o~2x-3r|cxBln~FW0v&zzY~Nw@32}&C9nHzYOPJ zxBVUs?|Fyqm<_k>m<|gHxA~$D;|{mou@1KJ9>IhY?l}w-HtlK5(~C z#tx~925z+b5 zI=3#79KAA^?ZX^ew@AbsuWGmWE*;BLx5R=SB?E`*g&nu*g&xGEx34Q7d|0 NhctvAw={$xou8R9&XoWF From 3a69e353f3ca5cc831797b61cd54bb50d5018114 Mon Sep 17 00:00:00 2001 From: Turo Lamminen Date: Mon, 13 Mar 2023 13:40:16 +0200 Subject: [PATCH 46/49] Make Blender MVert no field optional --- code/AssetLib/Blender/BlenderScene.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/AssetLib/Blender/BlenderScene.cpp b/code/AssetLib/Blender/BlenderScene.cpp index 3a9a02fd0..ac10d7302 100644 --- a/code/AssetLib/Blender/BlenderScene.cpp +++ b/code/AssetLib/Blender/BlenderScene.cpp @@ -569,7 +569,7 @@ void Structure ::Convert( const FileDatabase &db) const { ReadFieldArray(dest.co, "co", db); - ReadFieldArray(dest.no, "no", db); + ReadFieldArray(dest.no, "no", db); ReadField(dest.flag, "flag", db); //ReadField(dest.mat_nr,"mat_nr",db); ReadField(dest.bweight, "bweight", db); From 48d89622ee436bf30e7bf9e799802e51674c9e2e Mon Sep 17 00:00:00 2001 From: Turo Lamminen Date: Thu, 9 Mar 2023 13:38:06 +0200 Subject: [PATCH 47/49] Use ASSERT_NE to check that scene has loaded EXPECT_NE tries to continue so it ended up dereferencing null pointers and crashed. --- .../MDL/utMDLImporter_HL1_ImportSettings.cpp | 2 +- .../MDL/utMDLImporter_HL1_Materials.cpp | 16 +++++++-------- .../MDL/utMDLImporter_HL1_Nodes.cpp | 20 +++++++++---------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/test/unit/ImportExport/MDL/utMDLImporter_HL1_ImportSettings.cpp b/test/unit/ImportExport/MDL/utMDLImporter_HL1_ImportSettings.cpp index 4614066e6..d50c2b35a 100644 --- a/test/unit/ImportExport/MDL/utMDLImporter_HL1_ImportSettings.cpp +++ b/test/unit/ImportExport/MDL/utMDLImporter_HL1_ImportSettings.cpp @@ -190,7 +190,7 @@ private: Assimp::Importer importer; importer.SetPropertyBool(setting_key, setting_value); const aiScene *scene = importer.ReadFile(file_path, aiProcess_ValidateDataStructure); - EXPECT_NE(nullptr, scene); + ASSERT_NE(nullptr, scene); func(scene); } diff --git a/test/unit/ImportExport/MDL/utMDLImporter_HL1_Materials.cpp b/test/unit/ImportExport/MDL/utMDLImporter_HL1_Materials.cpp index 2389c0ffc..f733893ca 100644 --- a/test/unit/ImportExport/MDL/utMDLImporter_HL1_Materials.cpp +++ b/test/unit/ImportExport/MDL/utMDLImporter_HL1_Materials.cpp @@ -61,8 +61,8 @@ public: void flatShadeTexture() { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "chrome_sphere.mdl", aiProcess_ValidateDataStructure); - EXPECT_NE(nullptr, scene); - EXPECT_NE(nullptr, scene->mMaterials); + ASSERT_NE(nullptr, scene); + ASSERT_NE(nullptr, scene->mMaterials); aiShadingMode shading_mode = aiShadingMode_Flat; scene->mMaterials[0]->Get(AI_MATKEY_SHADING_MODEL, shading_mode); @@ -74,8 +74,8 @@ public: void chromeTexture() { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "chrome_sphere.mdl", aiProcess_ValidateDataStructure); - EXPECT_NE(nullptr, scene); - EXPECT_NE(nullptr, scene->mMaterials); + ASSERT_NE(nullptr, scene); + ASSERT_NE(nullptr, scene->mMaterials); int chrome; scene->mMaterials[0]->Get(AI_MDL_HL1_MATKEY_CHROME(aiTextureType_DIFFUSE, 0), chrome); @@ -87,8 +87,8 @@ public: void additiveBlendTexture() { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "blend_additive.mdl", aiProcess_ValidateDataStructure); - EXPECT_NE(nullptr, scene); - EXPECT_NE(nullptr, scene->mMaterials); + ASSERT_NE(nullptr, scene); + ASSERT_NE(nullptr, scene->mMaterials); aiBlendMode blend_mode = aiBlendMode_Default; scene->mMaterials[0]->Get(AI_MATKEY_BLEND_FUNC, blend_mode); @@ -101,8 +101,8 @@ public: void textureWithColorMask() { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "alpha_test.mdl", aiProcess_ValidateDataStructure); - EXPECT_NE(nullptr, scene); - EXPECT_NE(nullptr, scene->mMaterials); + ASSERT_NE(nullptr, scene); + ASSERT_NE(nullptr, scene->mMaterials); int texture_flags = 0; scene->mMaterials[0]->Get(AI_MATKEY_TEXFLAGS_DIFFUSE(0), texture_flags); diff --git a/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp b/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp index 712f4da11..49ae8a16c 100644 --- a/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp +++ b/test/unit/ImportExport/MDL/utMDLImporter_HL1_Nodes.cpp @@ -136,7 +136,7 @@ public: void emptyBonesNames() { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "unnamed_bones.mdl", aiProcess_ValidateDataStructure); - EXPECT_NE(nullptr, scene); + ASSERT_NE(nullptr, scene); const StringVector expected_bones_names = { "Bone", @@ -172,7 +172,7 @@ public: void emptyBodypartsNames() { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "unnamed_bodyparts.mdl", aiProcess_ValidateDataStructure); - EXPECT_NE(nullptr, scene); + ASSERT_NE(nullptr, scene); const StringVector expected_bodyparts_names = { "Bodypart", @@ -209,7 +209,7 @@ public: void duplicateBodypartsNames() { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "duplicate_bodyparts.mdl", aiProcess_ValidateDataStructure); - EXPECT_NE(nullptr, scene); + ASSERT_NE(nullptr, scene); const StringVector expected_bodyparts_names = { "Bodypart", @@ -254,7 +254,7 @@ public: void duplicateSubModelsNames() { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "duplicate_submodels.mdl", aiProcess_ValidateDataStructure); - EXPECT_NE(nullptr, scene); + ASSERT_NE(nullptr, scene); const std::vector expected_bodypart_sub_models_names = { { @@ -272,7 +272,7 @@ public: }; const aiNode *bodyparts_node = scene->mRootNode->FindNode(AI_MDL_HL1_NODE_BODYPARTS); - EXPECT_NE(nullptr, bodyparts_node); + ASSERT_NE(nullptr, bodyparts_node); EXPECT_EQ(3u, bodyparts_node->mNumChildren); StringVector actual_submodels_names; @@ -301,7 +301,7 @@ public: void duplicateSequenceNames() { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "duplicate_sequences.mdl", aiProcess_ValidateDataStructure); - EXPECT_NE(nullptr, scene); + ASSERT_NE(nullptr, scene); const StringVector expected_sequence_names = { "idle_1", @@ -337,7 +337,7 @@ public: void emptySequenceNames() { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "unnamed_sequences.mdl", aiProcess_ValidateDataStructure); - EXPECT_NE(nullptr, scene); + ASSERT_NE(nullptr, scene); const StringVector expected_sequence_names = { "Sequence", @@ -374,7 +374,7 @@ public: void duplicateSequenceGroupNames() { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "duplicate_sequence_groups/duplicate_sequence_groups.mdl", aiProcess_ValidateDataStructure); - EXPECT_NE(nullptr, scene); + ASSERT_NE(nullptr, scene); const StringVector expected_sequence_names = { "default", @@ -412,7 +412,7 @@ public: void emptySequenceGroupNames() { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MDL_HL1_MODELS_DIR "unnamed_sequence_groups/unnamed_sequence_groups.mdl", aiProcess_ValidateDataStructure); - EXPECT_NE(nullptr, scene); + ASSERT_NE(nullptr, scene); const StringVector expected_sequence_names = { "default", @@ -440,7 +440,7 @@ public: Assimp::Importer importer; const aiScene *scene = importer.ReadFile(MDL_HL1_FILE_MAN, aiProcess_ValidateDataStructure); - EXPECT_NE(nullptr, scene); + ASSERT_NE(nullptr, scene); aiNode *scene_bones_node = scene->mRootNode->FindNode(AI_MDL_HL1_NODE_BONES); From ea3cc378903f698f81a93612f646100e0477c014 Mon Sep 17 00:00:00 2001 From: Turo Lamminen Date: Thu, 9 Mar 2023 14:31:29 +0200 Subject: [PATCH 48/49] Check node parents in ValidateDataStructure --- code/PostProcessing/ValidateDataStructure.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/code/PostProcessing/ValidateDataStructure.cpp b/code/PostProcessing/ValidateDataStructure.cpp index cae35b895..e31054972 100644 --- a/code/PostProcessing/ValidateDataStructure.cpp +++ b/code/PostProcessing/ValidateDataStructure.cpp @@ -911,7 +911,12 @@ void ValidateDSProcess::Validate(const aiNode *pNode) { nodeName, pNode->mNumChildren); } for (unsigned int i = 0; i < pNode->mNumChildren; ++i) { - Validate(pNode->mChildren[i]); + const aiNode *pChild = pNode->mChildren[i]; + Validate(pChild); + if (pChild->mParent != pNode) { + const char *parentName = (pChild->mParent != nullptr) ? pChild->mParent->mName.C_Str() : "null"; + ReportError("aiNode \"%s\" child %i \"%s\" parent is someone else: \"%s\"", pNode->mName.C_Str(), i, pChild->mName.C_Str(), parentName); + } } } } From 65440f17a1d25a098d1f754a0b8f6fa7fc48303f Mon Sep 17 00:00:00 2001 From: Turo Lamminen Date: Mon, 20 Mar 2023 15:22:05 +0200 Subject: [PATCH 49/49] Add more ASE model unit tests --- test/unit/utASEImportExport.cpp | 110 ++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/test/unit/utASEImportExport.cpp b/test/unit/utASEImportExport.cpp index 8014cbbc7..af05a2fe5 100644 --- a/test/unit/utASEImportExport.cpp +++ b/test/unit/utASEImportExport.cpp @@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include using namespace Assimp; @@ -63,3 +64,112 @@ public: TEST_F(utASEImportExport, importACFromFileTest) { EXPECT_TRUE(importerTest()); } + + +TEST_F(utASEImportExport, importAnim1) { + ::Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/ASE/anim.ASE", aiProcess_ValidateDataStructure); + + ASSERT_NE(nullptr, scene); +} + + +TEST_F(utASEImportExport, importAnim2) { + ::Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/ASE/anim2.ASE", aiProcess_ValidateDataStructure); + + ASSERT_NE(nullptr, scene); +} + + +TEST_F(utASEImportExport, importCameraRollAnim) { + ::Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/ASE/CameraRollAnim.ase", aiProcess_ValidateDataStructure); + + ASSERT_NE(nullptr, scene); +} + + +TEST_F(utASEImportExport, importMotionCaptureROM) { + ::Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/ASE/MotionCaptureROM.ase", aiProcess_ValidateDataStructure); + + ASSERT_NE(nullptr, scene); +} + + +TEST_F(utASEImportExport, importRotatingCube) { + ::Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/ASE/RotatingCube.ASE", aiProcess_ValidateDataStructure); + + ASSERT_NE(nullptr, scene); +} + + +TEST_F(utASEImportExport, importTargetCameraAnim) { + ::Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/ASE/TargetCameraAnim.ase", aiProcess_ValidateDataStructure); + + ASSERT_NE(nullptr, scene); +} + + +TEST_F(utASEImportExport, importTestFormatDetection) { + ::Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/ASE/TestFormatDetection", aiProcess_ValidateDataStructure); + + ASSERT_NE(nullptr, scene); +} + + +TEST_F(utASEImportExport, importThreeCubesGreen) { + ::Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/ASE/ThreeCubesGreen.ASE", aiProcess_ValidateDataStructure); + + ASSERT_NE(nullptr, scene); + + ::Assimp::Importer importerLE; + const aiScene *sceneLE = importerLE.ReadFile(ASSIMP_TEST_MODELS_DIR "/ASE/ThreeCubesGreen_UTF16LE.ASE", aiProcess_ValidateDataStructure); + + ASSERT_NE(nullptr, sceneLE); + + ::Assimp::Importer importerBE; + const aiScene *sceneBE = importerBE.ReadFile(ASSIMP_TEST_MODELS_DIR "/ASE/ThreeCubesGreen_UTF16BE.ASE", aiProcess_ValidateDataStructure); + + ASSERT_NE(nullptr, sceneBE); + + // TODO: these scenes should probably be identical + // verify that is the case and then add tests to check it +} + + +TEST_F(utASEImportExport, importUVTransform_Normal) { + ::Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/ASE/TestUVTransform/UVTransform_Normal.ASE", aiProcess_ValidateDataStructure); + + ASSERT_NE(nullptr, scene); +} + + +TEST_F(utASEImportExport, importUVTransform_ScaleUV1_2_OffsetUV0_0_9_Rotate_72_mirrorU) { + ::Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/ASE/TestUVTransform/UVTransform_ScaleUV1-2_OffsetUV0-0.9_Rotate-72_mirrorU.ase", aiProcess_ValidateDataStructure); + + ASSERT_NE(nullptr, scene); +} + + +TEST_F(utASEImportExport, importUVTransform_ScaleUV2x) { + ::Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/ASE/TestUVTransform/UVTransform_ScaleUV2x.ASE", aiProcess_ValidateDataStructure); + + ASSERT_NE(nullptr, scene); +} + + +TEST_F(utASEImportExport, importUVTransform_ScaleUV2x_Rotate45) { + ::Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/ASE/TestUVTransform/UVTransform_ScaleUV2x_Rotate45.ASE", aiProcess_ValidateDataStructure); + + ASSERT_NE(nullptr, scene); +}