update assimp to 6.0.5

This commit is contained in:
AzaezelX 2026-06-09 12:46:56 -05:00
parent 2d2eb57e2e
commit f5cf21cfeb
941 changed files with 22718 additions and 12240 deletions

View file

@ -1,23 +1,21 @@
_**Contents**_
- [Building](#building)
- [CMake Basics](#cmake-basics)
- [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)
- [WebAssembly Decoder](#webassembly-decoder)
- [WebAssembly Mesh Only Decoder](#webassembly-mesh-only-decoder)
- [WebAssembly Point Cloud Only Decoder](#webassembly-point-cloud-only-decoder)
- [Javascript Encoder/Decoder](#javascript-encoderdecoder)
- [iOS Builds](#ios-builds)
- [Native Android Builds](#native-android-builds)
- [Android Studio Project Integration](#android-studio-project-integration)
- [Draco - Static Library](#draco---static-library)
- [vcpkg](#vcpkg)
* [CMake Basics](#cmake-basics)
* [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)
* [WebAssembly Point Cloud Only Decoder](#webassembly-point-cloud-only-decoder)
* [iOS Builds](#ios-builds)
* [Android Studio Project Integration](#android-studio-project-integration)
* [Native Android Builds](#native-android-builds)
* [vcpkg](#vcpkg)
Building
========
@ -327,7 +325,7 @@ Draco - Static Library
To include Draco in an existing or new Android Studio project, reference it
from the `cmake` file of an existing native project that has a minimum SDK
version of 18 or higher. The project must support C++11 at least.
version of 18 or higher. The project must support C++11.
To add Draco to your project:
1. Create a new "Native C++" project.

View file

@ -13,6 +13,9 @@
# the License.
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
if(DRACO_TRANSCODER_SUPPORTED)
set(CMAKE_CXX_STANDARD 17)
endif()
project(draco C CXX)
if(NOT CMAKE_BUILD_TYPE)
@ -21,7 +24,7 @@ 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(
@ -479,10 +482,14 @@ list(
"${draco_src_root}/metadata/geometry_metadata.h"
"${draco_src_root}/metadata/metadata.cc"
"${draco_src_root}/metadata/metadata.h"
"${draco_src_root}/metadata/property_attribute.cc"
"${draco_src_root}/metadata/property_attribute.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")
"${draco_src_root}/metadata/structural_metadata.h"
"${draco_src_root}/metadata/structural_metadata_schema.cc"
"${draco_src_root}/metadata/structural_metadata_schema.h")
list(APPEND draco_metadata_enc_sources
"${draco_src_root}/metadata/metadata_encoder.cc"

View file

@ -14,6 +14,14 @@ 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.7 release:
* Using the versioned www.gstatic.com WASM and Javascript decoders continues
to be recommended. To use v1.5.7, use this URL:
* https://www.gstatic.com/draco/versioned/decoders/1.5.7/*
* Added support for normalized attributes to Emscripten encoder API.
* Bug fixes.
* Security fixes.
### 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:

View file

@ -56,7 +56,7 @@ macro(draco_set_build_definitions)
# passed to libtool.
#
# We set DRACO_SOVERSION = [c-a].a.r
set(LT_CURRENT 8)
set(LT_CURRENT 9)
set(LT_REVISION 0)
set(LT_AGE 0)
math(EXPR DRACO_SOVERSION_MAJOR "${LT_CURRENT} - ${LT_AGE}")

View file

@ -23,99 +23,101 @@ include(GNUInstallDirs)
# Sets up the draco install targets. Must be called after the static library
# target is created.
macro(draco_setup_install_target)
set(bin_path "${CMAKE_INSTALL_BINDIR}")
set(data_path "${CMAKE_INSTALL_DATAROOTDIR}")
set(includes_path "${CMAKE_INSTALL_INCLUDEDIR}")
set(libs_path "${CMAKE_INSTALL_LIBDIR}")
if(DRACO_INSTALL)
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$")
list(APPEND draco_api_includes ${file})
foreach(file ${draco_sources})
if(file MATCHES "h$")
list(APPEND draco_api_includes ${file})
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}/" "")
foreach(draco_api_include ${draco_api_includes})
get_filename_component(file_directory ${draco_api_include} 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 "${includes_path}/draco/")
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()
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}/" "")
foreach(draco_api_include ${draco_api_includes})
get_filename_component(file_directory ${draco_api_include} 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 "${includes_path}/draco/")
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
EXPORT dracoExport
RUNTIME DESTINATION "${bin_path}"
ARCHIVE DESTINATION "${libs_path}"
LIBRARY DESTINATION "${libs_path}")
else()
install(
TARGETS draco_static
EXPORT dracoExport
DESTINATION "${libs_path}")
if(BUILD_SHARED_LIBS)
if(MSVC)
install(
TARGETS draco_shared
TARGETS draco
EXPORT dracoExport
RUNTIME DESTINATION "${bin_path}"
ARCHIVE DESTINATION "${libs_path}"
LIBRARY DESTINATION "${libs_path}")
else()
install(
TARGETS draco_static
EXPORT dracoExport
DESTINATION "${libs_path}")
if(BUILD_SHARED_LIBS)
install(
TARGETS draco_shared
EXPORT dracoExport
RUNTIME DESTINATION "${bin_path}"
ARCHIVE DESTINATION "${libs_path}"
LIBRARY DESTINATION "${libs_path}")
endif()
endif()
endif()
if(DRACO_UNITY_PLUGIN)
install(TARGETS dracodec_unity DESTINATION "${libs_path}")
endif()
if(DRACO_UNITY_PLUGIN)
install(TARGETS dracodec_unity DESTINATION "${libs_path}")
endif()
if(DRACO_MAYA_PLUGIN)
install(TARGETS draco_maya_wrapper 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")
# 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")
# 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)
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")
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(
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")
install(FILES "${draco_build}/draco-config.cmake"
"${draco_build}/draco-config-version.cmake"
DESTINATION "${data_path}/cmake/draco")
endif(DRACO_INSTALL)
endmacro()

View file

@ -131,6 +131,10 @@ macro(draco_set_default_options)
NAME DRACO_DEBUG_COMPILER_WARNINGS
HELPSTRING "Turn on more warnings."
VALUE OFF)
draco_option(
NAME DRACO_INSTALL
HELPSTRING "Enable installation."
VALUE ON)
draco_check_deprecated_options()
endmacro()

View file

@ -89,8 +89,10 @@ if(DRACO_TRANSCODER_SUPPORTED)
"${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_attribute_test.cc"
"${draco_src_root}/metadata/property_table_test.cc"
"${draco_src_root}/metadata/structural_metadata_test.cc"
"${draco_src_root}/metadata/structural_metadata_schema_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"

View file

@ -14,6 +14,11 @@
//
#include "draco/attributes/attribute_quantization_transform.h"
#include <cmath>
#include <cstring>
#include <memory>
#include <vector>
#include "draco/attributes/attribute_transform_type.h"
#include "draco/core/quantization_utils.h"
@ -144,6 +149,9 @@ bool AttributeQuantizationTransform::ComputeParameters(
++i) {
attribute.GetValue(i, att_val.get());
for (int c = 0; c < num_components; ++c) {
if (std::isnan(att_val[c])) {
return false;
}
if (min_values_[c] > att_val[c]) {
min_values_[c] = att_val[c];
}

View file

@ -59,6 +59,9 @@ bool GeometryAttribute::CopyFrom(const GeometryAttribute &src_att) {
}
buffer_->Update(src_att.buffer_->data(), src_att.buffer_->data_size());
}
#ifdef DRACO_TRANSCODER_SUPPORTED
name_ = src_att.name_;
#endif
return true;
}
@ -87,6 +90,11 @@ bool GeometryAttribute::operator==(const GeometryAttribute &va) const {
if (byte_offset_ != va.byte_offset_) {
return false;
}
#ifdef DRACO_TRANSCODER_SUPPORTED
if (name_ != va.name_) {
return false;
}
#endif
return true;
}

View file

@ -245,6 +245,16 @@ class GeometryAttribute {
return "TEX_COORD";
case GENERIC:
return "GENERIC";
#ifdef DRACO_TRANSCODER_SUPPORTED
case TANGENT:
return "TANGENT";
case MATERIAL:
return "MATERIAL";
case JOINTS:
return "JOINTS";
case WEIGHTS:
return "WEIGHTS";
#endif
default:
return "UNKNOWN";
}
@ -276,6 +286,10 @@ class GeometryAttribute {
DataBufferDescriptor buffer_descriptor() const { return buffer_descriptor_; }
uint32_t unique_id() const { return unique_id_; }
void set_unique_id(uint32_t id) { unique_id_ = id; }
#ifdef DRACO_TRANSCODER_SUPPORTED
std::string name() const { return name_; }
void set_name(std::string name) { name_ = name; }
#endif
protected:
// Sets a new internal storage for the attribute.
@ -445,6 +459,10 @@ class GeometryAttribute {
// multiple attribute of the same type in a point cloud.
uint32_t unique_id_;
#ifdef DRACO_TRANSCODER_SUPPORTED
std::string name_;
#endif
friend struct GeometryAttributeHasher;
};

View file

@ -14,8 +14,8 @@
//
#include "draco/attributes/point_attribute.h"
#include <tuple>
#include <unordered_map>
using std::unordered_map;
// Shortcut for typed conditionals.
@ -166,10 +166,12 @@ AttributeValueIndex::ValueType PointAttribute::DeduplicateFormattedValues(
AttributeValueIndex unique_vals(0);
typedef std::array<T, num_components_t> AttributeValue;
typedef std::array<HashType, num_components_t> AttributeHashableValue;
typedef unordered_map<AttributeHashableValue, AttributeValueIndex,
HashArray<AttributeHashableValue>>
ValueToIndexMap;
// Hash map storing index of the first attribute with a given value.
unordered_map<AttributeHashableValue, AttributeValueIndex,
HashArray<AttributeHashableValue>>
value_to_index_map;
ValueToIndexMap value_to_index_map;
AttributeValue att_value;
AttributeHashableValue hashable_value;
IndexTypeVector<AttributeValueIndex, AttributeValueIndex> value_map(
@ -180,19 +182,19 @@ AttributeValueIndex::ValueType PointAttribute::DeduplicateFormattedValues(
// Convert the value to hashable type. Bit-copy real attributes to integers.
memcpy(&(hashable_value[0]), &(att_value[0]), sizeof(att_value));
// Check if the given attribute value has been used before already.
auto it = value_to_index_map.find(hashable_value);
if (it != value_to_index_map.end()) {
typename ValueToIndexMap::iterator it;
bool inserted;
std::tie(it, inserted) = value_to_index_map.insert(
std::pair<AttributeHashableValue, AttributeValueIndex>(hashable_value,
unique_vals));
// Try to update the hash map with a new entry pointing to the latest unique
// vertex index.
if (!inserted) {
// Duplicated value found. Update index mapping.
value_map[i] = it->second;
} else {
// New unique value.
// Update the hash map with a new entry pointing to the latest unique
// vertex index.
value_to_index_map.insert(
std::pair<AttributeHashableValue, AttributeValueIndex>(hashable_value,
unique_vals));
// Add the unique value to the mesh builder.
SetAttributeValue(unique_vals, &att_value);
// Update index mapping.
value_map[i] = unique_vals;

View file

@ -200,7 +200,7 @@ bool MeshPredictionSchemeConstrainedMultiParallelogramEncoder<
// Bit-field used for computing permutations of excluded edges
// (parallelograms).
bool exluded_parallelograms[kMaxNumParallelograms];
bool excluded_parallelograms[kMaxNumParallelograms];
// Data about the number of used parallelogram and total number of available
// parallelogram for each context. Used to compute overhead needed for storing
@ -291,12 +291,12 @@ bool MeshPredictionSchemeConstrainedMultiParallelogramEncoder<
num_used_parallelograms <= num_parallelograms;
++num_used_parallelograms) {
// Mark all parallelograms as excluded.
std::fill(exluded_parallelograms,
exluded_parallelograms + num_parallelograms, true);
std::fill(excluded_parallelograms,
excluded_parallelograms + num_parallelograms, true);
// TODO(draco-eng) maybe this should be another std::fill.
// Mark the first |num_used_parallelograms| as not excluded.
for (int j = 0; j < num_used_parallelograms; ++j) {
exluded_parallelograms[j] = false;
excluded_parallelograms[j] = false;
}
// Permute over the excluded edges and compute error for each
// configuration (permutation of excluded parallelograms).
@ -307,7 +307,7 @@ bool MeshPredictionSchemeConstrainedMultiParallelogramEncoder<
}
uint8_t configuration = 0;
for (int j = 0; j < num_parallelograms; ++j) {
if (exluded_parallelograms[j]) {
if (excluded_parallelograms[j]) {
continue;
}
for (int c = 0; c < num_components; ++c) {
@ -322,15 +322,13 @@ bool MeshPredictionSchemeConstrainedMultiParallelogramEncoder<
}
error = ComputeError(multi_pred_vals.data(), in_data + dst_offset,
&current_residuals[0], num_components);
if (num_parallelograms > 0) {
const int64_t new_overhead_bits = ComputeOverheadBits(
total_used_parallelograms[num_parallelograms - 1] +
num_used_parallelograms,
total_parallelograms[num_parallelograms - 1]);
const int64_t new_overhead_bits = ComputeOverheadBits(
total_used_parallelograms[num_parallelograms - 1] +
num_used_parallelograms,
total_parallelograms[num_parallelograms - 1]);
// Add overhead bits to the total error.
error.num_bits += new_overhead_bits;
}
// Add overhead bits to the total error.
error.num_bits += new_overhead_bits;
if (error < best_prediction.error) {
best_prediction.error = error;
best_prediction.configuration = configuration;
@ -340,8 +338,9 @@ bool MeshPredictionSchemeConstrainedMultiParallelogramEncoder<
best_prediction.residuals.assign(current_residuals.begin(),
current_residuals.end());
}
} while (std::next_permutation(
exluded_parallelograms, exluded_parallelograms + num_parallelograms));
} while (
std::next_permutation(excluded_parallelograms,
excluded_parallelograms + num_parallelograms));
}
if (num_parallelograms > 0) {
total_used_parallelograms[num_parallelograms - 1] +=

View file

@ -151,6 +151,10 @@ bool MeshPredictionSchemeGeometricNormalDecoder<
if (!buffer->Decode(&prediction_mode)) {
return false;
}
if (prediction_mode > TRIANGLE_AREA) {
// Invalid prediction mode.
return false;
}
if (!predictor_.SetNormalPredictionMode(
NormalPredictionMode(prediction_mode))) {

View file

@ -169,7 +169,7 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor<
}
const int64_t pn_uv_absmax_element =
std::max(std::abs(pn_uv[0]), std::abs(pn_uv[1]));
if (cn_dot_pn >
if (std::abs(cn_dot_pn) >
std::numeric_limits<int64_t>::max() / pn_uv_absmax_element) {
// Return false if squared length calculation would overflow.
return false;
@ -177,7 +177,8 @@ bool MeshPredictionSchemeTexCoordsPortablePredictor<
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<int64_t>::max() / pn_absmax_element) {
if (std::abs(cn_dot_pn) >
std::numeric_limits<int64_t>::max() / pn_absmax_element) {
// Return false if squared length calculation would overflow.
return false;
}

View file

@ -48,6 +48,11 @@ bool SequentialIntegerAttributeDecoder::DecodeValues(
if (!in_buffer->Decode(&prediction_scheme_method)) {
return false;
}
// Check that decoded prediction scheme method type is valid.
if (prediction_scheme_method < PREDICTION_NONE ||
prediction_scheme_method >= NUM_PREDICTION_SCHEMES) {
return false;
}
if (prediction_scheme_method != PREDICTION_NONE) {
int8_t prediction_transform_type;
if (!in_buffer->Decode(&prediction_transform_type)) {

View file

@ -515,6 +515,74 @@ TEST_F(EncodeTest, TestDracoCompressionOptionsGridQuantization) {
15.f * 0.1f, 1e-6f);
}
TEST_F(EncodeTest, TestPointCloudGridQuantization) {
// Test verifies that we can set position quantization via grid spacing for a
// point cloud.
const auto pc = draco::ReadPointCloudFromTestFile("cube_att.obj");
ASSERT_NE(pc, nullptr);
const int pos_att_id =
pc->GetNamedAttributeId(draco::GeometryAttribute::POSITION);
draco::ExpertEncoder encoder(*pc);
encoder.SetAttributeGridQuantization(*pc, pos_att_id, 0.15);
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.
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 3 quantization bits for 8 grid values [0.00, 0.15, ...,1.05].
ASSERT_EQ(
encoder.options().GetAttributeInt(pos_att_id, "quantization_bits", -1),
3);
// The quantization range should be ((1 << quantization_bits) - 1) * spacing.
ASSERT_NEAR(encoder.options().GetAttributeFloat(pos_att_id,
"quantization_range", 0.f),
1.05f, 1e-6f);
}
TEST_F(EncodeTest, TestPointCloudGridQuantizationFromCompressionOptions) {
// Test verifies that we can set position quantization via grid spacing for a
// point cloud using DracoCompressionOptions.
const auto pc = draco::ReadPointCloudFromTestFile("cube_att.obj");
ASSERT_NE(pc, nullptr);
pc->SetCompressionEnabled(true);
// Set grid quantization for positions.
draco::DracoCompressionOptions compression_options;
// This should result in 10x10x10 quantization.
compression_options.quantization_position.SetGrid(0.15);
pc->SetCompressionOptions(compression_options);
draco::ExpertEncoder encoder(*pc);
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 =
pc->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 3 quantization bits for 8 grid values [0.00, 0.15, ...,1.05].
ASSERT_EQ(
encoder.options().GetAttributeInt(pos_att_id, "quantization_bits", -1),
3);
// The quantization range should be ((1 << quantization_bits) - 1) * spacing.
ASSERT_NEAR(encoder.options().GetAttributeFloat(pos_att_id,
"quantization_range", 0.f),
1.05f, 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.

View file

@ -51,6 +51,11 @@ Status ExpertEncoder::EncodeToBuffer(EncoderBuffer *out_buffer) {
Status ExpertEncoder::EncodePointCloudToBuffer(const PointCloud &pc,
EncoderBuffer *out_buffer) {
#ifdef DRACO_POINT_CLOUD_COMPRESSION_SUPPORTED
#ifdef DRACO_TRANSCODER_SUPPORTED
// Apply DracoCompressionOptions associated with the point cloud.
DRACO_RETURN_IF_ERROR(ApplyCompressionOptions(pc));
#endif // DRACO_TRANSCODER_SUPPORTED
std::unique_ptr<PointCloudEncoder> encoder;
const int encoding_method = options().GetGlobalInt("encoding_method", -1);
@ -195,11 +200,11 @@ Status ExpertEncoder::SetAttributePredictionScheme(
}
#ifdef DRACO_TRANSCODER_SUPPORTED
Status ExpertEncoder::ApplyCompressionOptions(const Mesh &mesh) {
if (!mesh.IsCompressionEnabled()) {
Status ExpertEncoder::ApplyCompressionOptions(const PointCloud &pc) {
if (!pc.IsCompressionEnabled()) {
return OkStatus();
}
const auto &compression_options = mesh.GetCompressionOptions();
const auto &compression_options = pc.GetCompressionOptions();
// Set any encoder options that haven't been explicitly set by users (don't
// override existing options).
@ -208,12 +213,12 @@ Status ExpertEncoder::ApplyCompressionOptions(const Mesh &mesh) {
10 - compression_options.compression_level);
}
for (int ai = 0; ai < mesh.num_attributes(); ++ai) {
for (int ai = 0; ai < pc.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();
const auto type = pc.attribute(ai)->attribute_type();
switch (type) {
case GeometryAttribute::POSITION:
if (compression_options.quantization_position
@ -221,7 +226,7 @@ Status ExpertEncoder::ApplyCompressionOptions(const Mesh &mesh) {
quantization_bits =
compression_options.quantization_position.quantization_bits();
} else {
DRACO_RETURN_IF_ERROR(ApplyGridQuantization(mesh, ai));
DRACO_RETURN_IF_ERROR(ApplyGridQuantization(pc, ai));
}
break;
case GeometryAttribute::TEX_COORD:
@ -252,17 +257,29 @@ Status ExpertEncoder::ApplyCompressionOptions(const Mesh &mesh) {
return OkStatus();
}
Status ExpertEncoder::ApplyGridQuantization(const Mesh &mesh,
Status ExpertEncoder::ApplyGridQuantization(const PointCloud &pc,
int attribute_index) {
const auto compression_options = mesh.GetCompressionOptions();
if (mesh.attribute(attribute_index)->num_components() != 3) {
const auto compression_options = pc.GetCompressionOptions();
const float spacing = compression_options.quantization_position.spacing();
return SetAttributeGridQuantization(pc, attribute_index, spacing);
}
Status ExpertEncoder::SetAttributeGridQuantization(const PointCloud &pc,
int attribute_index,
float spacing) {
const auto *const att = pc.attribute(attribute_index);
if (att->attribute_type() != GeometryAttribute::POSITION) {
return ErrorStatus(
"Invalid attribute type: Grid quantization is currently supported only "
"for positions.");
}
if (att->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();
const auto &bbox = pc.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.

View file

@ -132,6 +132,13 @@ class ExpertEncoder : public EncoderBase<EncoderOptions> {
Status SetAttributePredictionScheme(int32_t attribute_id,
int prediction_scheme_method);
#ifdef DRACO_TRANSCODER_SUPPORTED
// Applies grid quantization to position attribute in point cloud |pc| at
// |attribute_index| with a given grid |spacing|.
Status SetAttributeGridQuantization(const PointCloud &pc, int attribute_index,
float spacing);
#endif // DRACO_TRANSCODER_SUPPORTED
private:
Status EncodePointCloudToBuffer(const PointCloud &pc,
EncoderBuffer *out_buffer);
@ -139,9 +146,9 @@ class ExpertEncoder : public EncoderBase<EncoderOptions> {
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);
// Applies compression options stored in |pc|.
Status ApplyCompressionOptions(const PointCloud &pc);
Status ApplyGridQuantization(const PointCloud &pc, int attribute_index);
#endif // DRACO_TRANSCODER_SUPPORTED
const PointCloud *point_cloud_;

View file

@ -15,6 +15,7 @@
#include "draco/compression/mesh/mesh_edgebreaker_decoder_impl.h"
#include <algorithm>
#include <cstdint>
#include "draco/compression/attributes/sequential_attribute_decoders_controller.h"
#include "draco/compression/mesh/mesh_edgebreaker_decoder.h"
@ -299,6 +300,22 @@ bool MeshEdgebreakerDecoderImpl<TraversalDecoder>::DecodeConnectivity() {
if (static_cast<uint32_t>(num_encoded_vertices_) > num_faces * 3) {
return false; // There cannot be more vertices than 3 * num_faces.
}
// Minimum number of edges of the mesh assuming each edge is shared between
// two faces.
const uint32_t min_num_face_edges = 3 * num_faces / 2;
// Maximum number of edges that can exist between |num_encoded_vertices_|.
// This is based on graph theory assuming simple connected graph.
const uint64_t num_encoded_vertices_64 =
static_cast<uint64_t>(num_encoded_vertices_);
const uint64_t max_num_vertex_edges =
num_encoded_vertices_64 * (num_encoded_vertices_64 - 1) / 2;
if (max_num_vertex_edges < min_num_face_edges) {
// It is impossible to construct a manifold mesh with these properties.
return false;
}
uint8_t num_attribute_data;
if (!decoder_->buffer()->Decode(&num_attribute_data)) {
return false;

View file

@ -14,6 +14,9 @@
//
#include "draco/compression/mesh/mesh_sequential_decoder.h"
#include <cstdint>
#include <limits>
#include "draco/compression/attributes/linear_sequencer.h"
#include "draco/compression/attributes/sequential_attribute_decoders_controller.h"
#include "draco/compression/entropy/symbol_decoding.h"
@ -48,7 +51,6 @@ bool MeshSequentialDecoder::DecodeConnectivity() {
// Check that num_faces and num_points are valid values.
const uint64_t faces_64 = static_cast<uint64_t>(num_faces);
const uint64_t points_64 = static_cast<uint64_t>(num_points);
// Compressed sequential encoding can only handle (2^32 - 1) / 3 indices.
if (faces_64 > 0xffffffff / 3) {
return false;
@ -58,9 +60,6 @@ bool MeshSequentialDecoder::DecodeConnectivity() {
// fit in the remaining size of the buffer.
return false;
}
if (points_64 > faces_64 * 3) {
return false;
}
uint8_t connectivity_method;
if (!buffer()->Decode(&connectivity_method)) {
return false;
@ -147,7 +146,7 @@ bool MeshSequentialDecoder::DecodeAndDecompressIndices(uint32_t num_faces) {
}
// Reconstruct the indices from the differences.
// See MeshSequentialEncoder::CompressAndEncodeIndices() for more details.
int32_t last_index_value = 0;
int32_t last_index_value = 0; // This will always be >= 0.
int vertex_index = 0;
for (uint32_t i = 0; i < num_faces; ++i) {
Mesh::Face face;
@ -155,13 +154,19 @@ bool MeshSequentialDecoder::DecodeAndDecompressIndices(uint32_t num_faces) {
const uint32_t encoded_val = indices_buffer[vertex_index++];
int32_t index_diff = (encoded_val >> 1);
if (encoded_val & 1) {
if (index_diff > last_index_value) {
// Subtracting index_diff would result in a negative index.
return false;
}
index_diff = -index_diff;
} else {
if (index_diff >
(std::numeric_limits<int32_t>::max() - last_index_value)) {
// Adding index_diff to last_index_value would overflow.
return false;
}
}
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;
}

View file

@ -76,7 +76,7 @@ class FloatPointsTreeDecoder {
static const uint32_t version_ = 3;
QuantizationInfo qinfo_;
PointCloudCompressionMethod method_;
int8_t method_;
uint32_t num_points_;
uint32_t compression_level_;
@ -113,7 +113,7 @@ bool FloatPointsTreeDecoder::DecodePointCloud(DecoderBuffer *buffer,
return false;
}
method_ = static_cast<PointCloudCompressionMethod>(method_number);
method_ = method_number;
if (method_ == KDTREE) {
if (!DecodePointCloudKdTreeInternal(buffer, &qpoints)) {

View file

@ -18,7 +18,7 @@
namespace draco {
// Draco version is comprised of <major>.<minor>.<revision>.
static const char kDracoVersion[] = "1.5.6";
static const char kDracoVersion[] = "1.5.7";
} // namespace draco

View file

@ -34,12 +34,6 @@
#include <iostream>
namespace draco {
#ifndef DISALLOW_COPY_AND_ASSIGN
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName &) = delete; \
void operator=(const TypeName &) = delete;
#endif // DISALLOW_COPY_AND_ASSIGN
#ifndef FALLTHROUGH_INTENDED
#if defined(__clang__) && defined(__has_warning)
#if __has_feature(cxx_attributes) && __has_warning("-Wimplicit-fallthrough")
@ -67,6 +61,12 @@ namespace draco {
} // namespace draco
#ifndef DISALLOW_COPY_AND_ASSIGN
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName &) = delete; \
void operator=(const TypeName &) = delete;
#endif // DISALLOW_COPY_AND_ASSIGN
#ifdef DRACO_DEBUG
#define DRACO_DCHECK(x) (assert(x));
#define DRACO_DCHECK_EQ(a, b) assert((a) == (b));

View file

@ -38,11 +38,10 @@ bool DirectoryExists(const std::string &path_arg) {
struct stat path_stat;
std::string path = path_arg;
#if defined(_WIN32) && not defined(__MINGW32__)
#if defined(_WIN32) && !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()] != '/') {
// trailing slash. To keep it simple, append a path separator to all paths.
if (!path.empty() && path.back() != '\\' && path.back() != '/') {
path.append("\\");
}
#endif
@ -68,9 +67,7 @@ bool CheckAndCreatePathForFile(const std::string &filename) {
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;
return DirectoryExists(path);
}
} // namespace draco

View file

@ -15,6 +15,9 @@
#include "draco/io/gltf_decoder.h"
#ifdef DRACO_TRANSCODER_SUPPORTED
#include <array>
#include <cstdint>
#include <map>
#include <memory>
#include <set>
#include <string>
@ -27,11 +30,15 @@
#include "draco/core/hash_utils.h"
#include "draco/core/status.h"
#include "draco/core/status_or.h"
#include "draco/io/file_utils.h"
#include "draco/io/texture_io.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/geometry_metadata.h"
#include "draco/metadata/metadata.h"
#include "draco/metadata/property_table.h"
#include "draco/point_cloud/point_cloud_builder.h"
#include "draco/scene/scene_indices.h"
@ -80,6 +87,10 @@ GeometryAttribute::Type GltfAttributeToDracoAttribute(
} else if (attribute_name.rfind("_FEATURE_ID_") == 0) {
// Feature ID attribute like _FEATURE_ID_5 from EXT_mesh_features extension.
return GeometryAttribute::GENERIC;
} else if (attribute_name.rfind('_', 0) == 0) {
// Feature ID attribute like _DIRECTION from EXT_structural_metadata
// extension whose name begins with an underscore.
return GeometryAttribute::GENERIC;
}
return GeometryAttribute::INVALID;
}
@ -302,7 +313,7 @@ Status CopyDataFromBufferView(const tinygltf::Model &model, int buffer_view_id,
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);
memcpy(data->data(), data_start, buffer_view.byteLength);
return OkStatus();
}
@ -540,6 +551,9 @@ StatusOr<std::unique_ptr<Mesh>> GltfDecoder::BuildMesh() {
DRACO_RETURN_IF_ERROR(AddAttributesToDracoMesh(&pb_));
}
// Clear attribute indices before populating attributes in |mb_| or |pb_|.
feature_id_attribute_indices_.clear();
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();
@ -548,43 +562,59 @@ StatusOr<std::unique_ptr<Mesh>> GltfDecoder::BuildMesh() {
}
DRACO_ASSIGN_OR_RETURN(
std::unique_ptr<Mesh> mesh,
BuildMeshFromBuilder(total_face_indices_count_ > 0, &mb_, &pb_));
BuildMeshFromBuilder(total_face_indices_count_ > 0, &mb_, &pb_,
deduplicate_vertices_));
DRACO_RETURN_IF_ERROR(CopyTextures<Mesh>(mesh.get()));
SetAttributePropertiesOnDracoMesh(mesh.get());
DRACO_RETURN_IF_ERROR(AddMaterialsToDracoMesh(mesh.get()));
DRACO_RETURN_IF_ERROR(AddMeshFeaturesToDracoMesh(mesh.get()));
DRACO_RETURN_IF_ERROR(AddPrimitiveExtensionsToDracoMesh(mesh.get()));
DRACO_RETURN_IF_ERROR(AddStructuralMetadataToGeometry(mesh.get()));
MoveNonMaterialTextures(mesh.get());
DRACO_RETURN_IF_ERROR(AddAssetMetadata(mesh.get()));
return mesh;
}
Status GltfDecoder::AddMeshFeaturesToDracoMesh(Mesh *mesh) {
Status GltfDecoder::AddPrimitiveExtensionsToDracoMesh(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));
DRACO_RETURN_IF_ERROR(
AddPrimitiveExtensionsToDracoMesh(scene.nodes[i], mesh));
}
}
return OkStatus();
}
Status GltfDecoder::AddMeshFeaturesToDracoMesh(int node_index, Mesh *mesh) {
Status GltfDecoder::AddPrimitiveExtensionsToDracoMesh(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(
// Decode extensions present in this primitive.
DRACO_RETURN_IF_ERROR(AddPrimitiveExtensionsToDracoMesh(
primitive, &mesh->GetMaterialLibrary().MutableTextureLibrary(),
mesh));
}
}
for (int i = 0; i < node.children.size(); ++i) {
DRACO_RETURN_IF_ERROR(AddMeshFeaturesToDracoMesh(node.children[i], mesh));
DRACO_RETURN_IF_ERROR(
AddPrimitiveExtensionsToDracoMesh(node.children[i], mesh));
}
return OkStatus();
}
Status GltfDecoder::AddPrimitiveExtensionsToDracoMesh(
const tinygltf::Primitive &primitive, TextureLibrary *texture_library,
Mesh *mesh) {
// Decode mesh feature ID sets if present in this |primitive|.
DRACO_RETURN_IF_ERROR(DecodeMeshFeatures(primitive, texture_library, mesh));
// Decode structural metadata if present in this |primitive|.
DRACO_RETURN_IF_ERROR(DecodeStructuralMetadata(primitive, mesh));
return OkStatus();
}
Status GltfDecoder::CheckUnsupportedFeatures() {
// Check for morph targets.
for (const auto &mesh : gltf_model_.meshes) {
@ -853,6 +883,12 @@ Status GltfDecoder::AddAttributesToDracoMesh(BuilderT *builder) {
attribute_name_to_draco_mesh_attribute_id_[attribute.first] = -1;
continue;
}
// TODO(vytyaz): Check that when glTF is decoded into a single draco::Mesh
// the feature ID vertex attributes are consistent with geometry, e.g., the
// number of values in attribute with name _FEATURE_ID_5 accumulated from
// all primitives must be equal to the accumulated number of mesh vertices.
// Furthermore, accumulated attributes should probably come from mesh
// feature ID sets with the same labels.
DRACO_ASSIGN_OR_RETURN(
const int att_id,
AddAttribute(draco_att_type, attribute.second.component_type,
@ -875,6 +911,13 @@ Status GltfDecoder::AddAttributesToDracoMesh(BuilderT *builder) {
return OkStatus();
}
// Returns the index from a feature ID vertex attribute name like _FEATURE_ID_5.
int GetIndexFromFeatureIdAttributeName(const std::string &name) {
const std::string prefix = "_FEATURE_ID_";
const std::string number = name.substr(prefix.length());
return std::stoi(number);
}
template <typename BuilderT>
Status GltfDecoder::AddAttributeValuesToBuilder(
const std::string &attribute_name, const tinygltf::Accessor &accessor,
@ -903,6 +946,16 @@ Status GltfDecoder::AddAttributeValuesToBuilder(
DRACO_RETURN_IF_ERROR(AddFeatureIdToBuilder(
accessor, indices_data, att_id, number_of_elements, reverse_winding,
attribute_name, builder));
// Populate map from the index in attribute name like _FEATURE_ID_5 to the
// attribute index in the builder.
const int index = GetIndexFromFeatureIdAttributeName(attribute_name);
feature_id_attribute_indices_[index] = att_id;
} else if (attribute_name.rfind('_', 0) == 0) {
// This is a structural metadata property attribute with a name like
// _DIRECTION that begins with an underscore.
DRACO_RETURN_IF_ERROR(AddPropertyAttributeToBuilder(
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,
@ -987,12 +1040,22 @@ Status GltfDecoder::AddFeatureIdToBuilder(
DRACO_RETURN_IF_ERROR(AddAttributeDataByTypes(accessor, indices_data, att_id,
number_of_elements,
reverse_winding, builder));
return OkStatus();
}
// Store feature ID attribute name with index like _FEATURE_ID_5 in Draco
// attribute metadata.
std::unique_ptr<AttributeMetadata> metadata(new draco::AttributeMetadata());
metadata->AddEntryString("attribute_name", attribute_name);
builder->AddAttributeMetadata(att_id, std::move(metadata));
template <typename BuilderT>
Status GltfDecoder::AddPropertyAttributeToBuilder(
const tinygltf::Accessor &accessor,
const std::vector<uint32_t> &indices_data, int att_id,
int number_of_elements, bool reverse_winding,
const std::string &attribute_name, BuilderT *builder) {
// Set property attribute values to mesh.
DRACO_RETURN_IF_ERROR(AddAttributeDataByTypes(accessor, indices_data, att_id,
number_of_elements,
reverse_winding, builder));
// Store property attribute name like _DIRECTION in Draco attribute.
builder->SetAttributeName(att_id, attribute_name);
return OkStatus();
}
@ -1277,7 +1340,7 @@ 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);
auto it = gltf_primitive_material_to_draco_material_.find(-1);
if (it != gltf_primitive_material_to_draco_material_.end()) {
default_material_index = it->second;
}
@ -1286,14 +1349,19 @@ Status GltfDecoder::AddMaterialsToDracoMesh(Mesh *mesh) {
for (int input_material_index = 0;
input_material_index < gltf_model_.materials.size();
++input_material_index) {
it = gltf_primitive_material_to_draco_material_.find(input_material_index);
if (it == gltf_primitive_material_to_draco_material_.end()) {
continue;
}
output_material_index = it->second;
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++);
mesh->GetMaterialLibrary().MutableMaterial(output_material_index);
}
Material *const output_material =
mesh->GetMaterialLibrary().MutableMaterial(output_material_index++);
mesh->GetMaterialLibrary().MutableMaterial(output_material_index);
DRACO_RETURN_IF_ERROR(
AddGltfMaterial(input_material_index, output_material));
if (output_material->GetTextureMapByType(
@ -1415,6 +1483,7 @@ Status GltfDecoder::DecodeGltfToScene() {
DRACO_RETURN_IF_ERROR(AddMaterialsToScene());
DRACO_RETURN_IF_ERROR(AddSkinsToScene());
MoveNonMaterialTextures(scene_.get());
DRACO_RETURN_IF_ERROR(AddAssetMetadata(scene_.get()));
return OkStatus();
}
@ -1511,172 +1580,278 @@ Status GltfDecoder::AddStructuralMetadataToGeometry(GeometryT *geometry) {
}
const tinygltf::Value::Object &o = e->second.Get<tinygltf::Value::Object>();
// 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.");
}
// Decode structural metadata schema.
DRACO_RETURN_IF_ERROR(AddStructuralMetadataSchemaToGeometry(o, geometry));
// 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<tinygltf::Value::Object>()) {
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<std::string>());
break;
case tinygltf::INT_TYPE:
object->SetInteger(value.Get<int>());
break;
case tinygltf::BOOL_TYPE:
object->SetBoolean(value.Get<bool>());
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();
}
};
// Decode structural metadata property tables.
DRACO_RETURN_IF_ERROR(AddPropertyTablesToGeometry(o, geometry));
// 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 structural metadata property attributes.
DRACO_RETURN_IF_ERROR(AddPropertyAttributesToGeometry(o, geometry));
// Check that structural metadata has either property tables, or property
// attribute, or property textures (in the future).
if (geometry->GetStructuralMetadata().NumPropertyTables() == 0 &&
geometry->GetStructuralMetadata().NumPropertyAttributes() == 0) {
return ErrorStatus(
"Structural metadata has no property tables, no property attributes.");
}
return OkStatus();
}
template <typename GeometryT>
Status GltfDecoder::AddStructuralMetadataSchemaToGeometry(
const tinygltf::Value::Object &extension, GeometryT *geometry) {
const auto &value = extension.find("schema");
if (value == extension.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.");
}
// Decode property tables.
{
const auto &tables = o.find("propertyTables");
if (tables == o.end()) {
return ErrorStatus(
"Structural metadata extension has no property tables.");
// Decodes tinygltf::Value into StructuralMetadataSchema::Object.
struct SchemaParser {
static Status Parse(const tinygltf::Value &value,
StructuralMetadataSchema::Object *object) {
switch (value.Type()) {
case tinygltf::OBJECT_TYPE: {
for (auto &it : value.Get<tinygltf::Value::Object>()) {
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<std::string>());
break;
case tinygltf::INT_TYPE:
object->SetInteger(value.Get<int>());
break;
case tinygltf::BOOL_TYPE:
object->SetBoolean(value.Get<bool>());
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();
}
const tinygltf::Value &tables_array = tables->second;
if (!tables_array.IsArray()) {
return ErrorStatus("Property tables array is malformed.");
};
// Parse schema of the structural metadata and set it to |geometry|.
StructuralMetadataSchema schema;
DRACO_RETURN_IF_ERROR(SchemaParser::Parse(object, &schema.json));
geometry->GetStructuralMetadata().SetSchema(schema);
return OkStatus();
}
template <typename GeometryT>
Status GltfDecoder::AddPropertyTablesToGeometry(
const tinygltf::Value::Object &extension, GeometryT *geometry) {
const auto &tables = extension.find("propertyTables");
if (tables == extension.end()) {
// Structural metadata has no property tables.
return OkStatus();
}
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<PropertyTable> 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<tinygltf::Value::Object>();
// 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.");
}
// 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<PropertyTable> property_table(new PropertyTable());
// 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.");
}
const auto &object = tables_array.Get(i);
if (!object.IsObject()) {
// 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 auto o = object.Get<tinygltf::Value::Object>();
// 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.");
const tinygltf::Value &value = object.Get(kName);
if (!value.IsObject()) {
return ErrorStatus("Property table properties property 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.");
}
// Loop over property table properties.
for (const auto &key : value.Keys()) {
// Create a property table property and populate it below.
std::unique_ptr<PropertyTable::Property> property(
new PropertyTable::Property());
// 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 auto &property_object = value.Get(key);
if (!property_object.IsObject()) {
return ErrorStatus("Property entry is malformed.");
}
const tinygltf::Value &value = object.Get(kName);
if (!value.IsObject()) {
return ErrorStatus(
"Property table properties property is malformed.");
property->SetName(key);
const auto o = property_object.Get<tinygltf::Value::Object>();
// The "values" property is required.
DRACO_ASSIGN_OR_RETURN(success, DecodePropertyTableData(
"values", o, &property->GetData()));
if (!success) {
return ErrorStatus("Property values 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<PropertyTable::Property> 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<tinygltf::Value::Object>();
// 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));
// 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 table to structural metadata.
geometry->GetStructuralMetadata().AddPropertyTable(
std::move(property_table));
// 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();
}
template <typename GeometryT>
Status GltfDecoder::AddPropertyAttributesToGeometry(
const tinygltf::Value::Object &extension, GeometryT *geometry) {
const auto &attributes = extension.find("propertyAttributes");
if (attributes == extension.end()) {
// Structural metadata has no property attributes.
return OkStatus();
}
const tinygltf::Value &attributes_array = attributes->second;
if (!attributes_array.IsArray()) {
return ErrorStatus("Property attributes array is malformed.");
}
// Loop over all property attributes.
for (int i = 0; i < attributes_array.Size(); i++) {
// Create a property attribute and populate it below.
std::unique_ptr<PropertyAttribute> property_attribute(
new PropertyAttribute());
const auto &object = attributes_array.Get(i);
if (!object.IsObject()) {
return ErrorStatus("Property attribute is malformed.");
}
const auto o = object.Get<tinygltf::Value::Object>();
// The "class" property is required.
bool success;
std::string str_value;
DRACO_ASSIGN_OR_RETURN(success, DecodeString("class", o, &str_value));
if (success) {
property_attribute->SetClass(str_value);
} else {
return ErrorStatus("Property class is malformed.");
}
// The "name" property is optional.
DRACO_ASSIGN_OR_RETURN(success, DecodeString("name", o, &str_value));
if (success) {
property_attribute->SetName(str_value);
}
// Decode property attribute properties.
{
constexpr char kName[] = "properties";
if (!object.Has(kName)) {
return ErrorStatus("Property attribute is malformed.");
}
const tinygltf::Value &value = object.Get(kName);
if (!value.IsObject()) {
return ErrorStatus(
"Property attribute properties property is malformed.");
}
// Loop over property attribute properties.
for (const auto &key : value.Keys()) {
// Create a property attribute property and populate it below.
std::unique_ptr<PropertyAttribute::Property> property(
new PropertyAttribute::Property());
// Decode property name corresponding to a schema class property name.
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<tinygltf::Value::Object>();
// The "attribute" property is required.
DRACO_ASSIGN_OR_RETURN(success,
DecodeString("attribute", o, &str_value));
if (success) {
property->SetAttributeName(str_value);
} else {
return ErrorStatus("Property attribute is malformed.");
}
// Add property to the property attribute.
property_attribute->AddProperty(std::move(property));
}
}
// Add property attribute to structural metadata.
geometry->GetStructuralMetadata().AddPropertyAttribute(
std::move(property_attribute));
}
return OkStatus();
}
@ -1829,6 +2004,9 @@ Status GltfDecoder::DecodePrimitiveForScene(
pb.Start(number_of_points);
}
// Clear attribute indices before populating attributes in |mb| or |pb|.
feature_id_attribute_indices_.clear();
std::set<int32_t> normalized_attributes;
for (const auto &attribute : primitive.attributes) {
if (attribute.second >= gltf_model_.accessors.size()) {
@ -1869,15 +2047,16 @@ Status GltfDecoder::DecodePrimitiveForScene(
DRACO_ASSIGN_OR_RETURN(
std::unique_ptr<Mesh> mesh,
BuildMeshFromBuilder(primitive.mode == TINYGLTF_MODE_TRIANGLES, &mb,
&pb));
BuildMeshFromBuilder(primitive.mode == TINYGLTF_MODE_TRIANGLES, &mb, &pb,
deduplicate_vertices_));
// 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(
// Decode extensions present in this primitive.
DRACO_RETURN_IF_ERROR(AddPrimitiveExtensionsToDracoMesh(
primitive, &scene_->GetMaterialLibrary().MutableTextureLibrary(),
mesh.get()));
@ -1973,6 +2152,33 @@ Status GltfDecoder::DecodeMeshFeatures(const tinygltf::Primitive &primitive,
return OkStatus();
}
Status GltfDecoder::DecodeStructuralMetadata(
const tinygltf::Primitive &primitive, Mesh *mesh) {
const auto &e = primitive.extensions.find("EXT_structural_metadata");
if (e == primitive.extensions.end()) {
return OkStatus();
}
std::vector<int> property_attributes_indices;
DRACO_RETURN_IF_ERROR(DecodeStructuralMetadata(
e->second.Get<tinygltf::Value::Object>(), &property_attributes_indices));
for (const int pai : property_attributes_indices) {
const int index = mesh->AddPropertyAttributesIndex(pai);
if (scene_ == nullptr) {
// If we are decoding to a mesh, we need to restrict the property
// attributes indices to the primitive's material.
// TODO(ostava): This will not work properly when two primitives share the
// same material but have different property attributes indices. 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->AddPropertyAttributesIndexMaterialMask(index, mat_it->second);
}
}
}
return OkStatus();
}
Status GltfDecoder::DecodeMeshFeatures(
const tinygltf::Value::Object &extension, TextureLibrary *texture_library,
std::vector<std::unique_ptr<MeshFeatures>> *mesh_features) {
@ -2055,7 +2261,11 @@ Status GltfDecoder::DecodeMeshFeatures(
if (!value.IsInt()) {
return ErrorStatus("Attribute property is malformed.");
}
features.SetAttributeIndex(value.Get<int>());
// Convert index in feature ID vertex attribute name like _FEATURE_ID_5
// to attribute index in draco::Mesh.
const int att_name_index = value.Get<int>();
const int att_index = feature_id_attribute_indices_[att_name_index];
features.SetAttributeIndex(att_index);
}
}
{
@ -2066,7 +2276,7 @@ Status GltfDecoder::DecodeMeshFeatures(
return ErrorStatus("Texture property is malformed.");
}
// Decode texture contining mesh feature IDs into the |features| object
// Decode texture containing mesh feature IDs into the |features| object
// via a temporary |material| object.
Material material(texture_library);
const auto &container_object = object.Get<tinygltf::Value::Object>();
@ -2113,6 +2323,32 @@ Status GltfDecoder::DecodeMeshFeatures(
return OkStatus();
}
Status GltfDecoder::DecodeStructuralMetadata(
const tinygltf::Value::Object &extension,
std::vector<int> *property_attributes) {
// Decode all structural metadata from JSON like this in glTF primitive:
// "EXT_structural_metadata": {
// "propertyAttributes": [0]
// }
const auto &object = extension.find("propertyAttributes");
if (object == extension.end()) {
// TODO(vytyaz): Extension might contain property textures, support that.
return OkStatus();
}
const tinygltf::Value &array = object->second;
if (!array.IsArray()) {
return ErrorStatus("Property attributes array is malformed.");
}
for (int i = 0; i < array.Size(); i++) {
const auto &value = array.Get(i);
if (!value.IsInt()) {
return ErrorStatus("Property attributes array entry is malformed.");
}
property_attributes->push_back(value.Get<int>());
}
return OkStatus();
}
template <typename BuilderT>
StatusOr<int> GltfDecoder::AddAttribute(const std::string &attribute_name,
int component_type, int type,
@ -2150,9 +2386,54 @@ StatusOr<int> GltfDecoder::AddAttribute(GeometryAttribute::Type attribute_type,
if (att_id < 0) {
return Status(Status::DRACO_ERROR, "Could not add attribute.");
}
// When glTF is loaded as a mesh, initialize color attribute values to white
// opaque color. Mesh regions corresponding to glTF primitives without vertex
// color will end up having the white color. Since vertex color is used as a
// multiplier for material base color in rendering shader, the white color
// will keep the model appearance unchanged.
if (scene_ == nullptr && attribute_type == GeometryAttribute::Type::COLOR) {
SetWhiteVertexColor(att_id, draco_component_type, builder);
}
return att_id;
}
template <typename BuilderT>
void GltfDecoder::SetWhiteVertexColor(int color_att_id, draco::DataType type,
BuilderT *builder) {
// Valid glTF vertex color types are float, unsigned byte, and unsigned short.
if (type == DT_FLOAT32) {
SetWhiteVertexColorOfType<float>(color_att_id, builder);
} else if (type == DT_UINT8) {
SetWhiteVertexColorOfType<uint8_t>(color_att_id, builder);
} else if (type == DT_UINT16) {
SetWhiteVertexColorOfType<uint16_t>(color_att_id, builder);
}
}
template <typename ComponentT>
void GltfDecoder::SetWhiteVertexColorOfType(int color_att_id,
TriangleSoupMeshBuilder *builder) {
// The alpha component will not be copied for the RGB vertex colors.
std::array<ComponentT, 4> white{1, 1, 1, 1};
const int num_faces = total_face_indices_count_ / 3;
for (FaceIndex fi(0); fi < num_faces; fi++) {
builder->SetAttributeValuesForFace(color_att_id, fi, white.data(),
white.data(), white.data());
}
}
template <typename ComponentT>
void GltfDecoder::SetWhiteVertexColorOfType(int color_att_id,
PointCloudBuilder *builder) {
// The alpha component will not be copied for the RGB vertex colors.
std::array<ComponentT, 4> white{1, 1, 1, 1};
const int num_points = total_point_indices_count_;
for (PointIndex pi(0); pi < num_points; pi++) {
builder->SetAttributeValueForPoint(color_att_id, pi, white.data());
}
}
StatusOr<bool> GltfDecoder::CheckKhrTextureTransform(
const tinygltf::ExtensionMap &extension, TextureTransform *transform) {
bool transform_set = false;
@ -2870,12 +3151,13 @@ size_t GltfDecoder::PrimitiveSignature::Hash::operator()(
}
StatusOr<std::unique_ptr<Mesh>> GltfDecoder::BuildMeshFromBuilder(
bool use_mesh_builder, TriangleSoupMeshBuilder *mb, PointCloudBuilder *pb) {
bool use_mesh_builder, TriangleSoupMeshBuilder *mb, PointCloudBuilder *pb,
bool deduplicate_vertices) {
std::unique_ptr<Mesh> mesh;
if (use_mesh_builder) {
mesh = mb->Finalize();
} else {
std::unique_ptr<PointCloud> pc = pb->Finalize(true);
std::unique_ptr<PointCloud> pc = pb->Finalize(deduplicate_vertices);
if (pc) {
mesh.reset(new Mesh());
PointCloud *mesh_pc = mesh.get();
@ -2888,6 +3170,37 @@ StatusOr<std::unique_ptr<Mesh>> GltfDecoder::BuildMeshFromBuilder(
return mesh;
}
Status GltfDecoder::AddAssetMetadata(Scene *scene) {
return AddAssetMetadata(&scene->GetMetadata());
}
Status GltfDecoder::AddAssetMetadata(Mesh *mesh) {
Metadata *metadata = nullptr;
std::unique_ptr<GeometryMetadata> metadata_owned;
// Use metadata of the mesh or create new one.
if (mesh->GetMetadata() != nullptr) {
metadata = mesh->metadata();
} else {
metadata_owned = std::make_unique<GeometryMetadata>();
metadata = metadata_owned.get();
}
DRACO_RETURN_IF_ERROR(AddAssetMetadata(metadata));
if (metadata_owned != nullptr && metadata->num_entries() > 0) {
// Some metadata was added to the |metadata_owned| instance. Add it to the
// mesh.
mesh->AddMetadata(std::move(metadata_owned));
}
return OkStatus();
}
Status GltfDecoder::AddAssetMetadata(Metadata *metadata) {
// Store the copyright information in the |metadata|.
if (!gltf_model_.asset.copyright.empty()) {
metadata->AddEntryString("copyright", gltf_model_.asset.copyright);
}
return OkStatus();
}
} // namespace draco
#endif // DRACO_TRANSCODER_SUPPORTED

View file

@ -71,6 +71,18 @@ class GltfDecoder {
gltf_scene_graph_mode_ = mode;
}
// By default, the decoder will attempt to deduplicate vertices after decoding
// the mesh. This means lower memory usage and smaller output glTFs after
// reencoding. However, for very large meshes, this may become an expensive
// operation. If that becomes an issue, you might want to consider disabling
// deduplication with |SetDeduplicateVertices(false)|.
//
// Note that at this moment, disabling deduplication works ONLY for point
// clouds.
void SetDeduplicateVertices(bool deduplicate_vertices) {
deduplicate_vertices_ = deduplicate_vertices;
}
private:
// Loads |file_name| into |gltf_model_|. Fills |input_files| with paths to all
// input files when non-null.
@ -192,6 +204,18 @@ class GltfDecoder {
const std::string &attribute_name,
BuilderT *builder);
// Copies the property 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 <typename BuilderT>
Status AddPropertyAttributeToBuilder(
const tinygltf::Accessor &accessor,
const std::vector<uint32_t> &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
@ -220,6 +244,16 @@ class GltfDecoder {
const std::vector<T> &data, bool reverse_winding,
PointCloudBuilder *builder);
// Sets colors of for all vertices to white.
template <typename BuilderT>
void SetWhiteVertexColor(int color_att_id, draco::DataType type,
BuilderT *builder);
template <typename ComponentT>
void SetWhiteVertexColorOfType(int color_att_id,
TriangleSoupMeshBuilder *builder);
template <typename ComponentT>
void SetWhiteVertexColorOfType(int color_att_id, 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 <typename T>
@ -306,28 +340,55 @@ class GltfDecoder {
const tinygltf::Value::Object &extension,
std::vector<MeshGroup::MaterialsVariantsMapping> *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);
// Decode extensions on all primitives of all scenes, such as mesh features
// and structural metadata extensions, and add their contents to |mesh|.
Status AddPrimitiveExtensionsToDracoMesh(Mesh *mesh);
Status AddPrimitiveExtensionsToDracoMesh(int node_index, Mesh *mesh);
Status AddPrimitiveExtensionsToDracoMesh(const tinygltf::Primitive &primitive,
TextureLibrary *texture_library,
Mesh *mesh);
// Decodes glTF structural metadata from glTF model and adds it to |geometry|.
template <typename GeometryT>
Status AddStructuralMetadataToGeometry(GeometryT *geometry);
// Decodes glTF structural metadata schema from |extension| and adds it to
// |geometry|.
template <typename GeometryT>
Status AddStructuralMetadataSchemaToGeometry(
const tinygltf::Value::Object &extension, GeometryT *geometry);
// Decodes glTF structural metadata property tables from |extension| and adds
// them to |geometry|.
template <typename GeometryT>
Status AddPropertyTablesToGeometry(const tinygltf::Value::Object &extension,
GeometryT *geometry);
// Decodes glTF structural metadata property attributes from |extension| and
// adds them to |geometry|.
template <typename GeometryT>
Status AddPropertyAttributesToGeometry(
const tinygltf::Value::Object &extension, 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 structural metadata from |primitive| and adds it to |mesh|.
Status DecodeStructuralMetadata(const tinygltf::Primitive &primitive,
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<std::unique_ptr<MeshFeatures>> *mesh_features);
// Decodes glTF structural metadata from |extension| of a glTF primitive and
// adds its property attribute indices to the |property_attributes| vector.
Status DecodeStructuralMetadata(const tinygltf::Value::Object &extension,
std::vector<int> *property_attributes);
// Adds an attribute of type |attribute_name| to |builder|. Returns the
// attribute id.
template <typename BuilderT>
@ -427,6 +488,11 @@ class GltfDecoder {
// Adds the skins to the scene.
Status AddSkinsToScene();
// Adds various asset metadata to the scene or mesh.
Status AddAssetMetadata(Scene *scene);
Status AddAssetMetadata(Mesh *mesh);
Status AddAssetMetadata(Metadata *metadata);
// 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
@ -441,8 +507,8 @@ class GltfDecoder {
// point cloud builder |pb|. Mesh builder is used if |use_mesh_builder| is set
// to true.
static StatusOr<std::unique_ptr<Mesh>> BuildMeshFromBuilder(
bool use_mesh_builder, TriangleSoupMeshBuilder *mb,
PointCloudBuilder *pb);
bool use_mesh_builder, TriangleSoupMeshBuilder *mb, PointCloudBuilder *pb,
bool deduplicate_vertices);
// Map of glTF Mesh to Draco scene mesh group.
std::map<int, MeshGroupIndex> gltf_mesh_to_scene_mesh_group_;
@ -457,6 +523,10 @@ class GltfDecoder {
TriangleSoupMeshBuilder mb_;
PointCloudBuilder pb_;
// Map from the index in a feature ID vertex attribute name like _FEATURE_ID_5
// to the corresponding attribute index in the current geometry builder.
std::unordered_map<int, int> feature_id_attribute_indices_;
// Next face index used when adding attribute data to the Draco mesh.
int next_face_id_;
@ -504,6 +574,9 @@ class GltfDecoder {
// Selected mode of the decoded scene graph.
GltfSceneGraphMode gltf_scene_graph_mode_ = GltfSceneGraphMode::TREE;
// Whether vertices should be deduplicated after loading.
bool deduplicate_vertices_ = true;
// Functionality for deduping primitives on decode.
struct PrimitiveSignature {
const tinygltf::Primitive &primitive;

View file

@ -14,9 +14,12 @@
//
#include "draco/io/gltf_decoder.h"
#include <array>
#include <cmath>
#include <iostream>
#include <limits>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
@ -738,17 +741,14 @@ TEST(GltfDecoderTest, TextureTransformTest) {
"KhronosSampleModels/TextureTransformTest/glTF/TextureTransformTest.gltf";
const std::unique_ptr<Mesh> 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()));
std::set<int> expected_default_transforms = {4, 5, 6};
for (int i = 0; i < 9; ++i) {
const bool expected_default = (expected_default_transforms.count(i) != 0);
EXPECT_EQ(TextureTransform::IsDefault(mesh->GetMaterialLibrary()
.GetMaterial(i)
->GetTextureMapByIndex(0)
->texture_transform()),
expected_default);
}
const std::unique_ptr<Scene> scene(DecodeGltfFileToScene(filename));
@ -1139,7 +1139,7 @@ TEST(GltfDecoderTest, CorrectVolumeThicknessFactor) {
ASSERT_NE(scene, nullptr);
auto instances = draco::SceneUtils::ComputeAllInstances(*scene);
ASSERT_EQ(instances.size(), 2);
ASSERT_EQ(instances[MeshInstanceIndex(0)].transform.col(0).norm(),
ASSERT_EQ(instances[MeshInstanceIndex(1)].transform.col(0).norm(),
kDragonScale);
ASSERT_EQ(scene->GetMaterialLibrary().GetMaterial(1)->GetThicknessFactor(),
kDragonVolumeThickness);
@ -1256,47 +1256,84 @@ TEST(GltfDecoderTest, MaterialsVariants) {
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");
GltfTestHelper::UseCase use_case;
use_case.has_mesh_features = true;
use_case.has_structural_metadata = true;
draco::GltfDecoder decoder;
DRACO_ASSIGN_OR_ASSERT(auto mesh, decoder.DecodeFromFile(path));
ASSERT_NE(mesh, nullptr);
GltfTestHelper::CheckBoxMetaMeshFeatures(*mesh, kDracoCompressionEnabled);
GltfTestHelper::CheckBoxMetaStructuralMetadata(*mesh);
GltfTestHelper::CheckBoxMetaMeshFeatures(*mesh, use_case);
GltfTestHelper::CheckBoxMetaStructuralMetadata(*mesh, use_case);
}
TEST(GltfDecoderTest, DecodeMeshWithStructuralMetadataWithEmptyStringBuffer) {
// Checks that the decoder correctly handles 0-length buffers. An example case
// where this could happen is an EXT_structural_metadata extension with a
// buffer containing an empty string.
const auto path =
GetTestFileFullPath("ZeroLengthBufferView/ZeroLengthBufferView.gltf");
GltfTestHelper::UseCase use_case;
use_case.has_mesh_features = true;
use_case.has_structural_metadata = true;
draco::GltfDecoder decoder;
DRACO_ASSIGN_OR_ASSERT(auto mesh, decoder.DecodeFromFile(path));
ASSERT_NE(mesh, nullptr);
ASSERT_EQ(mesh->GetStructuralMetadata().NumPropertyTables(), 1);
ASSERT_EQ(mesh->GetStructuralMetadata().GetPropertyTable(0).GetCount(), 1);
ASSERT_EQ(mesh->GetStructuralMetadata().GetPropertyTable(0).NumProperties(),
1);
ASSERT_EQ(mesh->GetStructuralMetadata()
.GetPropertyTable(0)
.GetProperty(0)
.GetData()
.data.size(),
0);
}
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");
GltfTestHelper::UseCase use_case;
use_case.has_draco_compression = true;
use_case.has_mesh_features = true;
draco::GltfDecoder decoder;
DRACO_ASSIGN_OR_ASSERT(auto mesh, decoder.DecodeFromFile(path));
ASSERT_NE(mesh, nullptr);
GltfTestHelper::CheckBoxMetaMeshFeatures(*mesh, kDracoCompressionEnabled);
GltfTestHelper::CheckBoxMetaMeshFeatures(*mesh, use_case);
}
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");
GltfTestHelper::UseCase use_case;
use_case.has_mesh_features = true;
use_case.has_structural_metadata = true;
draco::GltfDecoder decoder;
DRACO_ASSIGN_OR_ASSERT(auto scene, decoder.DecodeFromFileToScene(path));
ASSERT_NE(scene, nullptr);
GltfTestHelper::CheckBoxMetaMeshFeatures(*scene, kHasDracoCompression);
GltfTestHelper::CheckBoxMetaStructuralMetadata(*scene);
GltfTestHelper::CheckBoxMetaMeshFeatures(*scene, use_case);
GltfTestHelper::CheckBoxMetaStructuralMetadata(*scene, use_case);
}
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");
GltfTestHelper::UseCase use_case;
use_case.has_draco_compression = true;
use_case.has_mesh_features = true;
draco::GltfDecoder decoder;
DRACO_ASSIGN_OR_ASSERT(auto scene, decoder.DecodeFromFileToScene(path));
ASSERT_NE(scene, nullptr);
GltfTestHelper::CheckBoxMetaMeshFeatures(*scene, kHasDracoCompression);
GltfTestHelper::CheckBoxMetaMeshFeatures(*scene, use_case);
}
TEST(GltfDecoderTest, DecodePointCloudToMesh) {
@ -1316,6 +1353,10 @@ TEST(GltfDecoderTest, DecodePointCloudToMesh) {
ASSERT_EQ(mesh->NumNamedAttributes(draco::GeometryAttribute::TANGENT), 1);
ASSERT_EQ(mesh->NumNamedAttributes(draco::GeometryAttribute::MATERIAL), 1);
// Verify that vertex deduplication was performed
ASSERT_LT(mesh->GetNamedAttribute(draco::GeometryAttribute::NORMAL)->size(),
462);
// Check the point cloud has two materials.
ASSERT_EQ(mesh->GetNamedAttribute(draco::GeometryAttribute::MATERIAL)->size(),
2);
@ -1398,5 +1439,52 @@ TEST(GltfDecoderTest, TestLoadUnsupportedTexCoordAttributes) {
2);
}
TEST(GltfDecoderTest, TestInvertedMaterials) {
// Checks that GltfDecoder assigns materials properly to sub-meshes when the
// material indices are in reverse order in the input glTF.
auto mesh = draco::ReadMeshFromTestFile("two_objects_inverse_materials.gltf");
ASSERT_NE(mesh, nullptr);
ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 2);
// There are two sub-meshes. A cube with 12 faces and a sphere. The cube
// should be mapped to a "Red" material and the sphere to a "Green" material.
ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(0)->GetName(), "Red");
ASSERT_EQ(mesh->GetMaterialLibrary().GetMaterial(1)->GetName(), "Green");
// Count the number of faces for each material index in the mesh.
std::array<int, 2> num_material_faces = {0, 0};
const draco::PointAttribute *const mat_att =
mesh->GetNamedAttribute(draco::GeometryAttribute::MATERIAL);
for (draco::FaceIndex i(0); i < mesh->num_faces(); ++i) {
const auto f = mesh->face(i);
uint32_t mat_index = 0;
mat_att->GetMappedValue(f[0], &mat_index);
ASSERT_TRUE(mat_index == 0 || mat_index == 1);
num_material_faces[mat_index]++;
}
// There should be 12 faces mapped to the red material (index 0), rest should
// be mapped to the green one.
ASSERT_EQ(num_material_faces[0], 12);
}
TEST(GltfDecoderTest, DecodePointCloudToMeshWithDeduplicationDisabled) {
// Checks that no deduplication is performed when it is explicitly disabled.
const auto path = GetTestFileFullPath(
"SphereTwoMaterials/sphere_two_materials_point_cloud.gltf");
draco::GltfDecoder decoder;
decoder.SetDeduplicateVertices(false);
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);
// Verify that no deduplication was performed.
ASSERT_EQ(mesh->GetNamedAttribute(draco::GeometryAttribute::NORMAL)->size(),
462);
}
} // namespace draco
#endif // DRACO_TRANSCODER_SUPPORTED

View file

@ -45,6 +45,7 @@
#include "draco/mesh/mesh_features.h"
#include "draco/mesh/mesh_splitter.h"
#include "draco/mesh/mesh_utils.h"
#include "draco/metadata/property_attribute.h"
#include "draco/scene/instance_array.h"
#include "draco/scene/scene_indices.h"
#include "draco/scene/scene_utils.h"
@ -90,22 +91,46 @@ int TextureAxisWrappingModeToGltfValue(TextureMap::AxisWrappingMode mode) {
}
}
// 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;
// Returns a boolean indicating whether |mesh| attribute at |att_index| is a
// feature ID vertex attribute referred to by any of the feature ID sets stored
// in the |mesh|.
bool IsFeatureIdAttribute(int att_index, const Mesh &mesh) {
for (MeshFeaturesIndex i(0); i < mesh.NumMeshFeatures(); ++i) {
if (mesh.GetMeshFeatures(i).GetAttributeIndex() == att_index) {
return true;
}
}
return false;
}
// Returns a boolean indicating whether |mesh| attribute at |att_index| is a
// property attribute referred to by the |mesh| and its |structural_metadata|.
bool IsPropertyAttribute(int att_index, const Mesh &mesh,
const StructuralMetadata &structural_metadata) {
// First check if structural metadata has any property attributes.
if (structural_metadata.NumPropertyAttributes() == 0) {
return false;
}
// Property attribute name must start with an underscore like _DIRECTION.
const std::string attribute_name = mesh.attribute(att_index)->name();
if (attribute_name.rfind('_', 0) != 0) {
return false;
}
// Look for an |attribute_name| among all property attributes in the |mesh|.
for (int i = 0; i < mesh.NumPropertyAttributesIndices(); ++i) {
const int property_attribute_index = mesh.GetPropertyAttributesIndex(i);
const PropertyAttribute &attribute =
structural_metadata.GetPropertyAttribute(property_attribute_index);
for (int i = 0; i < attribute.NumProperties(); ++i) {
const PropertyAttribute::Property &property = attribute.GetProperty(i);
if (property.GetAttributeName() == attribute_name) {
return true;
}
}
}
return std::string();
return false;
}
// Struct to hold glTF Scene data.
@ -123,7 +148,7 @@ struct GltfNode {
root_node(false) {}
std::string name;
std::vector<int> childern_indices;
std::vector<int> children_indices;
int mesh_index;
int skin_index;
int light_index;
@ -220,8 +245,13 @@ struct GltfPrimitive {
int material;
std::vector<MeshGroup::MaterialsVariantsMapping> material_variants_mappings;
std::vector<const MeshFeatures *> mesh_features;
std::vector<int> property_attributes;
std::map<std::string, int> attributes;
GltfDracoCompressedMesh compressed_mesh_info;
// Map from the index of a feature ID vertex attribute in draco::Mesh to the
// index in the feature ID vertex attribute name like _FEATURE_ID_5.
std::unordered_map<int, int> feature_id_name_indices;
};
struct GltfMesh {
@ -249,6 +279,8 @@ class GltfAsset {
GltfAsset();
void set_copyright(const std::string &copyright) { copyright_ = copyright; }
std::string copyright() const { return copyright_; }
std::string generator() const { return generator_; }
std::string version() const { return version_; }
std::string buffer_name() const { return buffer_name_; }
@ -351,13 +383,12 @@ class GltfAsset {
int AddDracoJoints(const Mesh &mesh, int num_encoded_points);
int AddDracoWeights(const Mesh &mesh, int num_encoded_points);
std::vector<std::pair<std::string, int>> AddDracoGenerics(
const Mesh &mesh, int num_encoded_points);
const Mesh &mesh, int num_encoded_points,
std::unordered_map<int, int> *feature_id_name_indices);
// 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);
// to the asset.
void 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
@ -395,10 +426,8 @@ class GltfAsset {
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);
// to the asset.
void 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
@ -515,9 +544,26 @@ class GltfAsset {
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.
// Add a mesh Draco attribute |att| to the glTF data. Returns the index
// accessor added to the glTF data. Returns -1 on error.
int AddAttribute(const PointAttribute &att, int num_points,
int num_encoded_points, bool compress) {
const int nep = num_encoded_points;
switch (att.data_type()) {
case DT_UINT8:
return AddAttribute<uint8_t>(att, num_points, nep, compress);
case DT_UINT16:
return AddAttribute<uint16_t>(att, num_points, nep, compress);
case DT_FLOAT32:
return AddAttribute<float>(att, num_points, nep, compress);
default:
return -1;
}
}
// Add a mesh Draco attribute |att| that is comprised of |att_data_t| values
// to the glTF data. Returns the index accessor added to the glTF data.
// Returns -1 on error.
template <class att_data_t>
int AddAttribute(const PointAttribute &att, int num_points,
int num_encoded_points, bool compress) {
@ -554,6 +600,10 @@ class GltfAsset {
int num_encoded_points, const std::string &type,
bool compress);
void SetCopyrightFromScene(const Scene &scene);
void SetCopyrightFromMesh(const Mesh &mesh);
std::string copyright_;
std::string generator_;
std::string version_;
std::vector<GltfScene> scenes_;
@ -609,8 +659,7 @@ class GltfAsset {
std::vector<std::unique_ptr<Light>> lights_;
std::vector<std::string> materials_variants_names_;
std::vector<EncoderInstanceArray> instance_arrays_;
PropertyTable::Schema property_table_schema_;
std::vector<const PropertyTable *> property_tables_;
const StructuralMetadata *structural_metadata_;
// Indicates whether Draco compression is used for any of the asset meshes.
bool draco_compression_used_;
@ -618,6 +667,9 @@ class GltfAsset {
// Indicates whether mesh features are used.
bool mesh_features_used_;
// Indicates whether structural metadata is used.
bool structural_metadata_used_;
// Counter for naming mesh feature textures.
int mesh_features_texture_index_;
@ -667,8 +719,10 @@ GltfAsset::GltfAsset()
version_("2.0"),
scene_index_(-1),
buffer_name_("buffer0.bin"),
structural_metadata_(nullptr),
draco_compression_used_(false),
mesh_features_used_(false),
structural_metadata_used_(false),
mesh_features_texture_index_(0),
add_images_to_buffer_(false),
output_type_(GltfEncoder::COMPACT) {}
@ -678,14 +732,15 @@ bool GltfAsset::AddDracoMesh(const Mesh &mesh) {
if (scene_index < 0) {
return false;
}
if (!AddMaterials(mesh)) {
return false;
}
AddMaterials(mesh);
GltfMesh gltf_mesh;
meshes_.push_back(gltf_mesh);
AddStructuralMetadata(mesh);
if (copyright_.empty()) {
SetCopyrightFromMesh(mesh);
}
const int32_t material_att_id =
mesh.GetNamedAttributeId(GeometryAttribute::MATERIAL);
@ -713,6 +768,10 @@ bool GltfAsset::AddDracoMesh(const Mesh &mesh) {
// Copy over mesh features for a given material index.
Mesh::CopyMeshFeaturesForMaterial(mesh, split_meshes[i].get(), mat_index);
// Copy over property attributes indices for a given material index.
Mesh::CopyPropertyAttributesIndicesForMaterial(
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
@ -847,12 +906,19 @@ Status GltfAsset::CompressMeshWithDraco(const Mesh &mesh,
// Create Draco encoder.
EncoderBuffer buffer;
ExpertEncoder encoder(*mesh_copy);
encoder.SetTrackEncodedProperties(true);
std::unique_ptr<ExpertEncoder> encoder;
if (mesh_copy->num_faces() > 0) {
// Encode mesh.
encoder.reset(new ExpertEncoder(*mesh_copy));
} else {
return Status(Status::DRACO_ERROR,
"Draco compression is not supported for glTF point clouds.");
}
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);
encoder->SetSpeedOptions(speed, speed);
// Configure attribute quantization.
for (int i = 0; i < mesh_copy->num_attributes(); ++i) {
@ -904,19 +970,19 @@ Status GltfAsset::CompressMeshWithDraco(const Mesh &mesh,
num_quantization_bits = compression_options.quantization_bits_weight;
break;
case GeometryAttribute::GENERIC:
if (GetFeatureIdAttributeName(*att, *mesh_copy).empty()) {
if (!IsFeatureIdAttribute(i, *mesh_copy)) {
num_quantization_bits =
compression_options.quantization_bits_generic;
} else {
// Quantization is explicitly disabled for feature ID attributes.
encoder.SetAttributeQuantization(i, -1);
encoder->SetAttributeQuantization(i, -1);
}
break;
default:
break;
}
if (num_quantization_bits > 0) {
encoder.SetAttributeQuantization(i, num_quantization_bits);
encoder->SetAttributeQuantization(i, num_quantization_bits);
}
}
}
@ -947,9 +1013,13 @@ Status GltfAsset::CompressMeshWithDraco(const Mesh &mesh,
// |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();
DRACO_RETURN_IF_ERROR(encoder->EncodeToBuffer(&buffer));
*num_encoded_points = encoder->num_encoded_points();
if (mesh_copy->num_faces() > 0) {
*num_encoded_faces = encoder->num_encoded_faces();
} else {
*num_encoded_faces = 0;
}
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.");
@ -1056,7 +1126,8 @@ bool GltfAsset::AddDracoMesh(
const int joints_accessor_index = AddDracoJoints(mesh, num_encoded_points);
const int weights_accessor_index = AddDracoWeights(mesh, num_encoded_points);
const std::vector<std::pair<std::string, int>> generics_accessors =
AddDracoGenerics(mesh, num_encoded_points);
AddDracoGenerics(mesh, num_encoded_points,
&primitive.feature_id_name_indices);
if (num_encoded_faces == 0) {
primitive.mode = 0; // POINTS mode.
@ -1067,6 +1138,10 @@ bool GltfAsset::AddDracoMesh(
for (MeshFeaturesIndex i(0); i < mesh.NumMeshFeatures(); ++i) {
primitive.mesh_features.push_back(&mesh.GetMeshFeatures(i));
}
primitive.property_attributes.reserve(mesh.NumPropertyAttributesIndices());
for (int i = 0; i < mesh.NumPropertyAttributesIndices(); ++i) {
primitive.property_attributes.push_back(mesh.GetPropertyAttributesIndex(i));
}
primitive.indices = indices_index;
primitive.attributes.insert(
std::pair<std::string, int>("POSITION", position_index));
@ -1205,10 +1280,13 @@ int GltfAsset::AddDracoNormals(const Mesh &mesh, int num_encoded_points) {
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})) {
if (!CheckDracoAttribute(att, {DT_UINT8, DT_UINT16, DT_FLOAT32}, {3, 4})) {
return -1;
}
if (att->data_type() == DT_UINT16) {
return AddAttribute<uint16_t>(*att, mesh.num_points(), num_encoded_points,
mesh.IsCompressionEnabled());
}
if (att->data_type() == DT_FLOAT32) {
return AddAttribute<float>(*att, mesh.num_points(), num_encoded_points,
mesh.IsCompressionEnabled());
@ -1312,73 +1390,77 @@ int GltfAsset::AddDracoWeights(const Mesh &mesh, int 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.
// Adds generic attributes that have metadata describing the attribute name,
// attributes referred to by one of the mesh feature ID sets or in the |mesh|,
// and attributes referred to by one of the property attributes in the |mesh|.
// This allows for export of application-specific attributes, feature ID
// attributes defined in glTF extension EXT_mesh_features, and property
// attributes defined in glTF extension EXT_structural_metadata. Returns a
// vector of attribute-name, accessor pairs for each valid attribute. Populates
// map from |mesh| attribute index in the feature ID attribute name like
// _FEATURE_ID_5 or _DIRECTION for each feature ID and property attribute in the
// |mesh|.
std::vector<std::pair<std::string, int>> GltfAsset::AddDracoGenerics(
const Mesh &mesh, int num_encoded_points) {
const int num_attributes =
const Mesh &mesh, int num_encoded_points,
std::unordered_map<int, int> *feature_id_name_indices) {
const int num_generic_attributes =
mesh.NumNamedAttributes(GeometryAttribute::GENERIC);
std::vector<std::pair<std::string, int>> 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());
std::vector<std::pair<std::string, int>> attrs;
int feature_id_count = 0;
for (int i = 0; i < num_generic_attributes; ++i) {
const int att_index =
mesh.GetNamedAttributeId(GeometryAttribute::GENERIC, i);
const PointAttribute *const att = mesh.attribute(att_index);
std::string attr_name;
int accessor = -1;
auto const *metadata = mesh.GetAttributeMetadataByAttributeId(att_index);
if (metadata) {
std::string attr_name;
if (metadata->GetEntryString(GltfEncoder::kDracoMetadataGltfAttributeName,
&attr_name)) {
if (att->data_type() == DT_FLOAT32) {
int accessor =
accessor =
AddAttribute<float>(*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<uint8_t>(*att, mesh.num_points(),
num_encoded_points,
mesh.IsCompressionEnabled());
break;
case DT_UINT16:
accessor = AddAttribute<uint16_t>(*att, mesh.num_points(),
num_encoded_points,
mesh.IsCompressionEnabled());
break;
case DT_FLOAT32:
accessor = AddAttribute<float>(*att, mesh.num_points(),
num_encoded_points,
mesh.IsCompressionEnabled());
break;
default:
continue;
}
attrs[i] = {attr_name, accessor};
}
}
} else {
if (IsFeatureIdAttribute(att_index, mesh) && att->num_components() == 1) {
// This is an attribute referred to by one of the mesh feature ID sets
// as defined by the EXT_mesh_features glTF extension.
// TODO(vytyaz): Report an error if the number of components is not one.
accessor = AddAttribute(*att, mesh.num_points(), num_encoded_points,
mesh.IsCompressionEnabled());
// Generate attribute name like _FEATURE_ID_N where N starts at 0 for
// the first feature ID vertex attribute and continues with consecutive
// positive integers as dictated by the EXT_mesh_features extension.
attr_name =
std::string("_FEATURE_ID_") + std::to_string(feature_id_count);
// Populate map from attribute index in the |mesh| to the index in a
// feature ID vertex attribute name like _FEATURE_ID_5.
(*feature_id_name_indices)[att_index] = feature_id_count;
feature_id_count++;
} else if (IsPropertyAttribute(att_index, mesh, *structural_metadata_)) {
// This is a property attribute as defined by the
// EXT_structural_metadata glTF extension.
accessor = AddAttribute(*att, mesh.num_points(), num_encoded_points,
mesh.IsCompressionEnabled());
attr_name = att->name();
}
}
if (accessor != -1 && !attr_name.empty()) {
attrs.emplace_back(attr_name, accessor);
}
}
return attrs;
}
bool GltfAsset::AddMaterials(const Mesh &mesh) {
if (mesh.GetMaterialLibrary().NumMaterials() == 0) {
return true;
void GltfAsset::AddMaterials(const Mesh &mesh) {
if (mesh.GetMaterialLibrary().NumMaterials()) {
material_library_.Copy(mesh.GetMaterialLibrary());
}
material_library_.Copy(mesh.GetMaterialLibrary());
return true;
}
bool GltfAsset::CheckDracoAttribute(const PointAttribute *attribute,
@ -1432,13 +1514,7 @@ StatusOr<int> GltfAsset::AddImage(const std::string &image_stem,
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;
}
image.mime_type = TextureUtils::GetTargetMimeType(*texture);
// For KTX2 with Basis compression, state that its extension is required.
if (extension == "ktx2") {
@ -1508,9 +1584,9 @@ Status GltfAsset::AddScene(const Scene &scene) {
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.");
}
AddMaterials(scene);
AddStructuralMetadata(scene);
// Initialize base mesh transforms that may be needed when the base meshes are
// compressed with Draco.
base_mesh_transforms_ = SceneUtils::FindLargestBaseMeshTransforms(scene);
@ -1526,7 +1602,9 @@ Status GltfAsset::AddScene(const Scene &scene) {
DRACO_RETURN_IF_ERROR(AddLights(scene));
DRACO_RETURN_IF_ERROR(AddMaterialsVariantsNames(scene));
DRACO_RETURN_IF_ERROR(AddInstanceArrays(scene));
AddStructuralMetadata(scene);
if (copyright_.empty()) {
SetCopyrightFromScene(scene);
}
return OkStatus();
}
@ -1542,7 +1620,7 @@ Status GltfAsset::AddSceneNode(const Scene &scene,
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());
node.children_indices.push_back(scene_node->Child(i).value());
}
const MeshGroupIndex mesh_group_index = scene_node->GetMeshGroupIndex();
@ -1589,6 +1667,12 @@ Status GltfAsset::AddSceneNode(const Scene &scene,
for (MeshFeaturesIndex j(0); j < mesh.NumMeshFeatures(); ++j) {
primitive.mesh_features.push_back(&mesh.GetMeshFeatures(j));
}
primitive.property_attributes.reserve(
mesh.NumPropertyAttributesIndices());
for (int i = 0; i < mesh.NumPropertyAttributesIndices(); ++i) {
primitive.property_attributes.push_back(
mesh.GetPropertyAttributesIndex(i));
}
meshes_.back().primitives.push_back(primitive);
}
}
@ -1604,12 +1688,10 @@ Status GltfAsset::AddSceneNode(const Scene &scene,
return OkStatus();
}
bool GltfAsset::AddMaterials(const Scene &scene) {
if (scene.GetMaterialLibrary().NumMaterials() == 0) {
return true;
void GltfAsset::AddMaterials(const Scene &scene) {
if (scene.GetMaterialLibrary().NumMaterials()) {
material_library_.Copy(scene.GetMaterialLibrary());
}
material_library_.Copy(scene.GetMaterialLibrary());
return true;
}
Status GltfAsset::AddAnimations(const Scene &scene) {
@ -1884,14 +1966,7 @@ Status GltfAsset::AddInstanceArrays(const Scene &scene) {
template <typename GeometryT>
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));
}
}
structural_metadata_ = &geometry.GetStructuralMetadata();
}
StatusOr<int> GltfAsset::AddData(const std::vector<float> &data,
@ -1972,6 +2047,9 @@ bool GltfAsset::EncodeAssetProperty(EncoderBuffer *buf_out) {
gltf_json_.BeginObject("asset");
gltf_json_.OutputValue("version", version_);
gltf_json_.OutputValue("generator", generator_);
if (!copyright_.empty()) {
gltf_json_.OutputValue("copyright", copyright_);
}
gltf_json_.EndObject();
const std::string asset_str = gltf_json_.MoveData();
@ -2044,10 +2122,10 @@ bool GltfAsset::EncodeNodesProperty(EncoderBuffer *buf_out) {
gltf_json_.EndObject();
}
if (!nodes_[i].childern_indices.empty()) {
if (!nodes_[i].children_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]);
for (int j = 0; j < nodes_[i].children_indices.size(); ++j) {
gltf_json_.OutputValue(nodes_[i].children_indices[j]);
}
gltf_json_.EndArray();
}
@ -2170,9 +2248,10 @@ Status GltfAsset::EncodePrimitiveExtensionsProperty(
primitive.compressed_mesh_info.buffer_view_index >= 0;
const bool has_materials_variants =
!primitive.material_variants_mappings.empty();
const bool has_structural_metadata = !primitive.property_attributes.empty();
const bool has_mesh_features = !primitive.mesh_features.empty();
if (!has_draco_mesh_compression && !has_materials_variants &&
!has_mesh_features) {
!has_mesh_features && !has_structural_metadata) {
return OkStatus();
}
@ -2216,7 +2295,10 @@ Status GltfAsset::EncodePrimitiveExtensionsProperty(
}
gltf_json_.OutputValue("featureCount", features->GetFeatureCount());
if (features->GetAttributeIndex() != -1) {
gltf_json_.OutputValue("attribute", features->GetAttributeIndex());
// Index referring to mesh feature ID attribute name like _FEATURE_ID_5.
const int index =
primitive.feature_id_name_indices.at(features->GetAttributeIndex());
gltf_json_.OutputValue("attribute", index);
}
if (features->GetPropertyTableIndex() != -1) {
gltf_json_.OutputValue("propertyTable",
@ -2250,6 +2332,16 @@ Status GltfAsset::EncodePrimitiveExtensionsProperty(
gltf_json_.EndArray(); // featureIds array.
gltf_json_.EndObject(); // EXT_mesh_features entry.
}
if (has_structural_metadata) {
structural_metadata_used_ = true;
gltf_json_.BeginObject("EXT_structural_metadata");
gltf_json_.BeginArray("propertyAttributes");
for (const int property_attribute_index : primitive.property_attributes) {
gltf_json_.OutputValue(property_attribute_index);
}
gltf_json_.EndArray(); // propertyAttributes array.
gltf_json_.EndObject(); // EXT_structural_metadata entry.
}
gltf_json_.EndObject(); // extensions entry.
return OkStatus();
}
@ -2963,7 +3055,8 @@ Status GltfAsset::EncodeSkinsProperty(EncoderBuffer *buf_out) {
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()) {
structural_metadata_->NumPropertyTables() == 0 &&
structural_metadata_->NumPropertyAttributes() == 0) {
return OkStatus();
}
@ -3048,38 +3141,39 @@ Status GltfAsset::EncodeMaterialsVariantsNamesProperty(EncoderBuffer *buf_out) {
}
Status GltfAsset::EncodeStructuralMetadataProperty(EncoderBuffer *buf_out) {
if (property_table_schema_.Empty()) {
if (structural_metadata_->GetSchema().Empty()) {
return OkStatus();
}
structural_metadata_used_ = true;
gltf_json_.BeginObject("EXT_structural_metadata");
// Encodes property table schema.
// Encodes structural metadata schema.
struct SchemaWriter {
static void Write(const PropertyTable::Schema::Object &object,
JsonWriter *json_writer) {
typedef StructuralMetadataSchema::Object Object;
static void Write(const Object &object, JsonWriter *json_writer) {
switch (object.GetType()) {
case PropertyTable::Schema::Object::OBJECT:
case Object::OBJECT:
json_writer->BeginObject(object.GetName());
for (const PropertyTable::Schema::Object &obj : object.GetObjects()) {
for (const Object &obj : object.GetObjects()) {
Write(obj, json_writer);
}
json_writer->EndObject();
break;
case PropertyTable::Schema::Object::ARRAY:
case Object::ARRAY:
json_writer->BeginArray(object.GetName());
for (const PropertyTable::Schema::Object &obj : object.GetArray()) {
for (const Object &obj : object.GetArray()) {
Write(obj, json_writer);
}
json_writer->EndArray();
break;
case PropertyTable::Schema::Object::STRING:
case Object::STRING:
json_writer->OutputValue(object.GetName(), object.GetString());
break;
case PropertyTable::Schema::Object::INTEGER:
case Object::INTEGER:
json_writer->OutputValue(object.GetName(), object.GetInteger());
break;
case PropertyTable::Schema::Object::BOOLEAN:
case Object::BOOLEAN:
json_writer->OutputValue(object.GetName(), object.GetBoolean());
break;
}
@ -3087,11 +3181,13 @@ Status GltfAsset::EncodeStructuralMetadataProperty(EncoderBuffer *buf_out) {
};
// Encode property table schema.
SchemaWriter::Write(property_table_schema_.json, &gltf_json_);
SchemaWriter::Write(structural_metadata_->GetSchema().json, &gltf_json_);
// Encode all property tables.
gltf_json_.BeginArray("propertyTables");
for (const PropertyTable *const table : property_tables_) {
for (int i = 0; i < structural_metadata_->NumPropertyTables(); i++) {
const PropertyTable *const table =
&structural_metadata_->GetPropertyTable(i);
gltf_json_.BeginObject();
if (!table->GetName().empty()) {
gltf_json_.OutputValue("name", table->GetName());
@ -3138,7 +3234,33 @@ Status GltfAsset::EncodeStructuralMetadataProperty(EncoderBuffer *buf_out) {
gltf_json_.EndObject(); // properties entry.
gltf_json_.EndObject();
}
gltf_json_.EndArray(); // propertyTables entry.
gltf_json_.EndArray(); // propertyTables entry.
// Encode all property attributes.
gltf_json_.BeginArray("propertyAttributes");
for (int i = 0; i < structural_metadata_->NumPropertyAttributes(); i++) {
const PropertyAttribute *const attribute =
&structural_metadata_->GetPropertyAttribute(i);
gltf_json_.BeginObject();
if (!attribute->GetName().empty()) {
gltf_json_.OutputValue("name", attribute->GetName());
}
if (!attribute->GetClass().empty()) {
gltf_json_.OutputValue("class", attribute->GetClass());
}
// Encoder all property attribute properties.
gltf_json_.BeginObject("properties");
for (int i = 0; i < attribute->NumProperties(); ++i) {
const PropertyAttribute::Property &property = attribute->GetProperty(i);
gltf_json_.BeginObject(property.GetName());
gltf_json_.OutputValue("attribute", property.GetAttributeName());
gltf_json_.EndObject(); // Named property entry.
}
gltf_json_.EndObject(); // properties entry.
gltf_json_.EndObject();
}
gltf_json_.EndArray(); // propertyAttributes entry.
gltf_json_.EndObject(); // EXT_structural_metadata entry.
return OkStatus();
}
@ -3246,7 +3368,7 @@ Status GltfAsset::EncodeExtensionsProperties(EncoderBuffer *buf_out) {
if (mesh_features_used_) {
extensions_used_.insert("EXT_mesh_features");
}
if (!property_table_schema_.Empty()) {
if (structural_metadata_used_) {
extensions_used_.insert("EXT_structural_metadata");
}
@ -3383,6 +3505,20 @@ int GltfAsset::AddAttribute(const PointAttribute &att, int num_points,
return static_cast<int>(accessors_.size() - 1);
}
void GltfAsset::SetCopyrightFromScene(const Scene &scene) {
std::string copyright;
scene.GetMetadata().GetEntryString("copyright", &copyright);
set_copyright(copyright);
}
void GltfAsset::SetCopyrightFromMesh(const Mesh &mesh) {
if (mesh.GetMetadata() != nullptr) {
std::string copyright;
mesh.GetMetadata()->GetEntryString("copyright", &copyright);
set_copyright(copyright);
}
}
const char GltfEncoder::kDracoMetadataGltfAttributeName[] =
"//GLTF/ApplicationSpecificAttributeName";
@ -3436,6 +3572,7 @@ Status GltfEncoder::EncodeFile(const T &geometry, const std::string &filename,
}
GltfAsset gltf_asset;
gltf_asset.set_copyright(copyright_);
gltf_asset.set_output_type(output_type_);
if (extension == "gltf") {
@ -3465,6 +3602,7 @@ Status GltfEncoder::EncodeToBuffer(const T &geometry,
gltf_asset.set_output_type(output_type_);
gltf_asset.buffer_name("");
gltf_asset.set_add_images_to_buffer(true);
gltf_asset.set_copyright(copyright_);
// Encode the geometry into a buffer.
EncoderBuffer buffer;

View file

@ -84,6 +84,9 @@ class GltfEncoder {
void set_output_type(OutputType type) { output_type_ = type; }
OutputType output_type() const { return output_type_; }
void set_copyright(const std::string &copyright) { copyright_ = copyright; }
std::string copyright() const { return copyright_; }
// 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
@ -126,6 +129,7 @@ class GltfEncoder {
EncoderBuffer *out_buffer_;
OutputType output_type_;
std::string copyright_;
};
} // namespace draco

View file

@ -15,10 +15,15 @@
#include "draco/io/gltf_encoder.h"
#ifdef DRACO_TRANSCODER_SUPPORTED
#include <array>
#include <iostream>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
#include "draco/core/draco_test_base.h"
#include "draco/core/draco_test_utils.h"
@ -1278,7 +1283,7 @@ TEST_F(GltfEncoderTest, PbrNextExtensions) {
const std::string tmp_name = draco::GetTestTempFileFullPath("tmp.gltf");
DRACO_ASSERT_OK(encoder.EncodeFile<Scene>(*original, tmp_name));
// Read model from the temporay file.
// Read model from the temporary file.
GltfDecoder decoder;
DRACO_ASSIGN_OR_ASSERT(auto encoded, decoder.DecodeFromFileToScene(tmp_name));
ASSERT_NE(encoded, nullptr);
@ -1483,6 +1488,44 @@ TEST_F(GltfEncoderTest, EncodeToBuffer) {
ASSERT_EQ(std::memcmp(file_data.data(), buffer.data(), buffer.size()), 0);
}
TEST_F(GltfEncoderTest, CopyrightAssetIsEncoded) {
// Load scene from file.
const std::string file_name = "CesiumMilkTruck/glTF/CesiumMilkTruck.gltf";
const std::unique_ptr<Scene> scene = ReadSceneFromTestFile(file_name);
ASSERT_NE(scene, nullptr);
std::array<std::pair<std::string, std::string>, 3> test_cases = {
{{"Google", "Google"}, {"", ""}, {"GMaps", ""}}};
for (const std::pair<std::string, std::string> &test_case : test_cases) {
// Encode scene to buffer in GLB format.
GltfEncoder encoder;
encoder.set_copyright(test_case.first);
EncoderBuffer buffer;
DRACO_ASSERT_OK(encoder.EncodeToBuffer(*scene, &buffer));
ASSERT_NE(buffer.size(), 0);
const std::string glb_file_path =
draco::GetTestTempFileFullPath(test_case.first + "temp.glb");
std::string folder_path;
std::string glb_file_name;
draco::SplitPath(glb_file_path, &folder_path, &glb_file_name);
encoder.set_copyright(test_case.second);
encoder.EncodeToFile<Scene>(*scene, glb_file_path, folder_path);
std::vector<char> file_data;
ASSERT_TRUE(ReadFileToBuffer(glb_file_path, &file_data));
if (test_case.first == test_case.second) {
ASSERT_EQ(buffer.size(), draco::GetFileSize(glb_file_path))
<< glb_file_path;
ASSERT_EQ(std::memcmp(file_data.data(), buffer.data(), buffer.size()), 0);
} else {
ASSERT_NE(buffer.size(), draco::GetFileSize(glb_file_path))
<< glb_file_path;
}
}
}
// Tests that a scene with lights can be encoded into a file.
TEST_F(GltfEncoderTest, EncodeLights) {
const std::string file_name = "sphere_lights.gltf";
@ -1565,9 +1608,9 @@ TEST_F(GltfEncoderTest, EncodeMaterialsVariants) {
// 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;
GltfTestHelper::UseCase use_case;
use_case.has_mesh_features = true;
use_case.has_structural_metadata = true;
// Read test file from file.
const std::unique_ptr<Scene> scene(DecodeTestGltfFileToScene(file_name));
@ -1577,18 +1620,17 @@ TEST_F(GltfEncoderTest, EncodeSceneWithMeshFeaturesWithStructuralMetadata) {
std::unique_ptr<Scene> 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);
GltfTestHelper::CheckBoxMetaMeshFeatures(*scene_from_gltf, use_case);
GltfTestHelper::CheckBoxMetaStructuralMetadata(*scene_from_gltf, use_case);
}
// 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;
GltfTestHelper::UseCase use_case;
use_case.has_draco_compression = true;
use_case.has_mesh_features = true;
// Read test file from file.
const std::unique_ptr<Scene> scene(DecodeTestGltfFileToScene(file_name));
@ -1598,15 +1640,16 @@ TEST_F(GltfEncoderTest, EncodeSceneWithMeshFeaturesWithDracoCompression) {
std::unique_ptr<Scene> scene_from_gltf;
SceneToDecodedGltfScene(*scene, &scene_from_gltf);
ASSERT_NE(scene_from_gltf, nullptr);
GltfTestHelper::CheckBoxMetaMeshFeatures(*scene_from_gltf,
kHasDracoCompression);
GltfTestHelper::CheckBoxMetaMeshFeatures(*scene_from_gltf, use_case);
}
// 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;
GltfTestHelper::UseCase use_case;
use_case.has_mesh_features = true;
use_case.has_structural_metadata = true;
// Read test file from file.
const std::unique_ptr<Mesh> mesh(ReadMeshFromTestFile(file_name));
@ -1616,16 +1659,17 @@ TEST_F(GltfEncoderTest, EncodeMeshWithMeshFeaturesWithStructuralMetadata) {
std::unique_ptr<Mesh> 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);
GltfTestHelper::CheckBoxMetaMeshFeatures(*mesh_from_gltf, use_case);
GltfTestHelper::CheckBoxMetaStructuralMetadata(*mesh_from_gltf, use_case);
}
// 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";
GltfTestHelper::UseCase use_case;
use_case.has_draco_compression = true;
use_case.has_mesh_features = true;
// Read test file from file.
const std::unique_ptr<Mesh> mesh(ReadMeshFromTestFile(file_name));
@ -1635,8 +1679,37 @@ TEST_F(GltfEncoderTest, EncodeMeshWithMeshFeaturesWithDracoCompression) {
std::unique_ptr<Mesh> mesh_from_gltf;
MeshToDecodedGltfMesh(*mesh, &mesh_from_gltf);
ASSERT_NE(mesh_from_gltf, nullptr);
GltfTestHelper::CheckBoxMetaMeshFeatures(*mesh_from_gltf,
kHasDracoCompression);
GltfTestHelper::CheckBoxMetaMeshFeatures(*mesh_from_gltf, use_case);
}
// This test verifies that b/245519530 is fixed. It loads mesh with various mesh
// feature ID sets, enables Draco compression, converts mesh to scene, and
// encodes the scene to glTF.
TEST_F(GltfEncoderTest, EncodeMeshWithMeshFeaturesWithDracoCompressionAsScene) {
// Note that although the mesh is loaded from file with no Draco compression,
// the compression is enabled later on.
const std::string file_name = "BoxMeta/glTF/BoxMeta.gltf";
GltfTestHelper::UseCase use_case;
use_case.has_draco_compression = true;
use_case.has_mesh_features = true;
use_case.has_structural_metadata = true;
// Read test file from file.
std::unique_ptr<Mesh> mesh(ReadMeshFromTestFile(file_name));
ASSERT_NE(mesh, nullptr);
// Enable Draco compression.
mesh->SetCompressionEnabled(use_case.has_draco_compression);
// Convert mesh to scene.
std::unique_ptr<Scene> scene =
draco::SceneUtils::MeshToScene(std::move(mesh)).value();
// Encode the scene to glTF and decode it back to draco::Scene and check.
std::unique_ptr<Scene> scene_from_gltf;
SceneToDecodedGltfScene(*scene, &scene_from_gltf);
ASSERT_NE(scene_from_gltf, nullptr);
GltfTestHelper::CheckBoxMetaMeshFeatures(*scene_from_gltf, use_case);
}
// Tests encoding of draco::Mesh with mesh features associated with different
@ -1688,6 +1761,49 @@ TEST_F(GltfEncoderTest, EncodeMeshWithMeshFeaturesWithMultiplePrimitives) {
ASSERT_EQ(scene_from_gltf->GetNonMaterialTextureLibrary().NumTextures(), 2);
}
// Tests encoding of draco::Mesh with property attributes associated with
// different mesh primitives.
TEST_F(GltfEncoderTest,
EncodeMeshWithPropertyAttributesWithMultiplePrimitives) {
const std::string file_name = "BoxesMeta/glTF/BoxesMeta.gltf";
// Read test file from file.
const std::unique_ptr<Mesh> mesh(ReadMeshFromTestFile(file_name));
ASSERT_NE(mesh, nullptr);
ASSERT_EQ(mesh->NumPropertyAttributesIndices(), 2);
// Encode the scene to glTF and decode it back to draco::Mesh and check.
std::unique_ptr<Mesh> 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->NumPropertyAttributesIndices(), 2);
// First property attribute should be used by material 0 and the second by
// material 1.
ASSERT_EQ(mesh_from_gltf->NumPropertyAttributesIndexMaterialMasks(0), 1);
ASSERT_EQ(mesh_from_gltf->NumPropertyAttributesIndexMaterialMasks(1), 1);
ASSERT_EQ(mesh_from_gltf->GetPropertyAttributesIndexMaterialMask(0, 0), 0);
ASSERT_EQ(mesh_from_gltf->GetPropertyAttributesIndexMaterialMask(1, 0), 1);
// Ensure it still works correctly when we re-encode the source |mesh| as a
// scene.
std::unique_ptr<Scene> scene_from_gltf;
MeshToDecodedGltfScene(*mesh, &scene_from_gltf);
ASSERT_NE(scene_from_gltf, nullptr);
ASSERT_EQ(scene_from_gltf->NumMeshes(), 2);
// Both meshes should have one property attributes indices.
ASSERT_EQ(scene_from_gltf->GetMesh(draco::MeshIndex(0))
.NumPropertyAttributesIndices(),
1);
ASSERT_EQ(scene_from_gltf->GetMesh(draco::MeshIndex(1))
.NumPropertyAttributesIndices(),
1);
}
// Tests encoding of draco::Mesh containing a point cloud and two materials.
TEST_F(GltfEncoderTest, EncodePointCloudWithMaterials) {
const std::string file_name =

View file

@ -20,8 +20,10 @@
#include <utility>
#include <vector>
#include "draco/attributes/geometry_attribute.h"
#include "draco/core/draco_test_base.h"
#include "draco/core/draco_test_utils.h"
#include "draco/metadata/property_attribute.h"
#include "draco/metadata/property_table.h"
#include "draco/texture/texture_library.h"
@ -59,9 +61,6 @@ void GltfTestHelper::AddBoxMetaMeshFeatures(Scene *scene) {
pa->SetAttributeValue(avi, &val);
}
const int att_id = mesh.AddPerFaceAttribute(std::move(pa));
std::unique_ptr<AttributeMetadata> 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<MeshFeatures> features(new MeshFeatures());
@ -69,7 +68,7 @@ void GltfTestHelper::AddBoxMetaMeshFeatures(Scene *scene) {
features->SetFeatureCount(num_faces);
features->SetNullFeatureId(100);
features->SetPropertyTableIndex(0);
features->SetAttributeIndex(0);
features->SetAttributeIndex(att_id);
mesh.AddMeshFeatures(std::move(features));
}
@ -84,9 +83,6 @@ void GltfTestHelper::AddBoxMetaMeshFeatures(Scene *scene) {
pa->SetAttributeValue(avi, &val);
}
const int att_id = mesh.AddPerVertexAttribute(std::move(pa));
std::unique_ptr<AttributeMetadata> 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<MeshFeatures> features(new MeshFeatures());
@ -94,7 +90,7 @@ void GltfTestHelper::AddBoxMetaMeshFeatures(Scene *scene) {
features->SetFeatureCount(num_vertices);
features->SetNullFeatureId(101);
features->SetPropertyTableIndex(1);
features->SetAttributeIndex(1);
features->SetAttributeIndex(att_id);
mesh.AddMeshFeatures(std::move(features));
}
@ -113,14 +109,11 @@ void GltfTestHelper::AddBoxMetaMeshFeatures(Scene *scene) {
}
const int att_id =
mesh.AddAttributeWithConnectivity(std::move(pa), corner_to_value);
std::unique_ptr<AttributeMetadata> 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<MeshFeatures> features(new MeshFeatures());
features->SetFeatureCount(num_corners);
features->SetAttributeIndex(2);
features->SetAttributeIndex(att_id);
mesh.AddMeshFeatures(std::move(features));
}
@ -163,7 +156,7 @@ void GltfTestHelper::AddBoxMetaMeshFeatures(Scene *scene) {
}
void GltfTestHelper::AddBoxMetaStructuralMetadata(Scene *scene) {
// Add structural metadata property table schema in the following JSON:
// Add structural metadata schema in the following JSON:
// "schema": {
// "id": "galaxy",
// "classes": {
@ -181,11 +174,30 @@ void GltfTestHelper::AddBoxMetaStructuralMetadata(Scene *scene) {
// "type": "STRING"
// }
// "sequence": {
// "componentType": "FLOAT32",
// "description": "The number sequence.",
// "required": false,
// "type": "SCALAR"
// }
// }
// },
// "movement": {
// "name": "The movement.",
// "description": "Vertex movement.",
// "properties": {
// "direction": {
// "description": "Movement direction.",
// "type": "VEC3",
// "componentType": "FLOAT32",
// "required": true
// },
// "magnitude": {
// "description": "Movement magnitude.",
// "type": "SCALAR",
// "componentType": "FLOAT32",
// "required": true
// }
// }
// }
// },
// "enums": {
@ -201,35 +213,76 @@ void GltfTestHelper::AddBoxMetaStructuralMetadata(Scene *scene) {
// ]
// }
// }
// }
typedef PropertyTable::Schema::Object Object;
PropertyTable::Schema schema;
// },
// "propertyAttributes": [{
// "name": "The movement.",
// "class": "movement",
// "properties": {
// "direction": {
// "attribute": "_DIRECTION",
// },
// "magnitude": {
// "attribute": "_MAGNITUDE",
// }
// }
// }]
typedef StructuralMetadataSchema::Object Object;
StructuralMetadataSchema 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");
// Add class "planet" to schema.
{
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("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("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("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");
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("componentType", "FLOAT32");
sequence.SetObjects().emplace_back("description", "The number sequence.");
sequence.SetObjects().emplace_back("required", false);
sequence.SetObjects().emplace_back("type", "SCALAR");
}
// Add class "movement" to schema.
{
json.SetObjects().back().SetObjects().emplace_back("movement");
Object &movement = json.SetObjects().back().SetObjects().back();
movement.SetObjects().emplace_back("name", "The movement.");
movement.SetObjects().emplace_back("description", "Vertex movement.");
movement.SetObjects().emplace_back("properties");
Object &properties = movement.SetObjects().back();
properties.SetObjects().emplace_back("direction");
Object &direction = properties.SetObjects().back();
direction.SetObjects().emplace_back("componentType", "FLOAT32");
direction.SetObjects().emplace_back("description", "Movement direction.");
direction.SetObjects().emplace_back("required", true);
direction.SetObjects().emplace_back("type", "VEC3");
properties.SetObjects().emplace_back("magnitude");
Object &mag = properties.SetObjects().back();
mag.SetObjects().emplace_back("componentType", "FLOAT32");
mag.SetObjects().emplace_back("description", "Movement magnitude.");
mag.SetObjects().emplace_back("required", true);
mag.SetObjects().emplace_back("type", "SCALAR");
}
json.SetObjects().emplace_back("enums");
json.SetObjects().back().SetObjects().emplace_back("classifications");
@ -260,8 +313,8 @@ void GltfTestHelper::AddBoxMetaStructuralMetadata(Scene *scene) {
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 schema to the scene.
scene->GetStructuralMetadata().SetSchema(schema);
// Add structural metadata property table.
std::unique_ptr<PropertyTable> table(new PropertyTable());
@ -334,9 +387,9 @@ void GltfTestHelper::AddBoxMetaStructuralMetadata(Scene *scene) {
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};
11, 1, 0, 0, // Geonosis
31, 1, 0, 0, // UNLABELED
40, 1, 0, 0};
table->AddProperty(std::move(property));
}
@ -391,36 +444,95 @@ void GltfTestHelper::AddBoxMetaStructuralMetadata(Scene *scene) {
// Add property table to the scene.
scene->GetStructuralMetadata().AddPropertyTable(std::move(table));
// Add structural metadata property attribute.
std::unique_ptr<PropertyAttribute> attribute(new PropertyAttribute());
attribute->SetName("The movement.");
attribute->SetClass("movement");
{
std::unique_ptr<PropertyAttribute::Property> property(
new PropertyAttribute::Property());
property->SetName("direction");
property->SetAttributeName("_DIRECTION");
attribute->AddProperty(std::move(property));
}
{
std::unique_ptr<PropertyAttribute::Property> property(
new PropertyAttribute::Property());
property->SetName("magnitude");
property->SetAttributeName("_MAGNITUDE");
attribute->AddProperty(std::move(property));
}
scene->GetStructuralMetadata().AddPropertyAttribute(std::move(attribute));
// Get mesh element counts.
Mesh &mesh = scene->GetMesh(MeshIndex(0));
ASSERT_EQ(mesh.num_faces(), 12);
ASSERT_EQ(mesh.num_points(), 36);
const int num_vertices =
mesh.GetNamedAttribute(GeometryAttribute::POSITION)->size();
// Add per-vertex Float32 3D vector property attribute named _DIRECTION.
{
// Create property attribute.
constexpr DataType kType = DataType::DT_FLOAT32;
std::unique_ptr<PointAttribute> pa(new PointAttribute());
pa->Init(GeometryAttribute::GENERIC, 3, kType, false, num_vertices);
for (AttributeValueIndex avi(0); avi < num_vertices; ++avi) {
const std::array<float, 3> val = {
avi.value() + 0.10f, avi.value() + 0.20f, avi.value() + 0.30f};
pa->SetAttributeValue(avi, &val);
}
const int att_id = mesh.AddPerVertexAttribute(std::move(pa));
mesh.attribute(att_id)->set_name("_DIRECTION");
}
// Add per-vertex Float32 scalar property attribute named _MAGNITUDE.
{
// Create property attribute.
constexpr DataType kType = DataType::DT_FLOAT32;
std::unique_ptr<PointAttribute> pa(new PointAttribute());
pa->Init(GeometryAttribute::GENERIC, 1, kType, false, num_vertices);
for (AttributeValueIndex avi(0); avi < num_vertices; ++avi) {
const float val = avi.value();
pa->SetAttributeValue(avi, &val);
}
const int att_id = mesh.AddPerVertexAttribute(std::move(pa));
mesh.attribute(att_id)->set_name("_MAGNITUDE");
}
// Add property attribute to the mesh.
mesh.AddPropertyAttributesIndex(0);
}
template <>
void GltfTestHelper::CheckBoxMetaMeshFeatures(const Mesh &geometry,
bool has_draco_compression) {
const UseCase &use_case) {
CheckBoxMetaMeshFeatures(geometry, geometry.GetNonMaterialTextureLibrary(),
has_draco_compression);
use_case);
}
template <>
void GltfTestHelper::CheckBoxMetaMeshFeatures(const Scene &geometry,
bool has_draco_compression) {
const UseCase &use_case) {
ASSERT_EQ(geometry.NumMeshes(), 1);
CheckBoxMetaMeshFeatures(geometry.GetMesh(MeshIndex(0)),
geometry.GetNonMaterialTextureLibrary(),
has_draco_compression);
geometry.GetNonMaterialTextureLibrary(), use_case);
}
void GltfTestHelper::CheckBoxMetaMeshFeatures(const Mesh &mesh,
const TextureLibrary &texture_lib,
bool has_draco_compression) {
const UseCase &use_case) {
// 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_attributes(), use_case.has_structural_metadata ? 9 : 7);
ASSERT_EQ(mesh.num_points(), 36);
ASSERT_EQ(mesh.NumNamedAttributes(GeometryAttribute::GENERIC), 3);
ASSERT_EQ(mesh.NumNamedAttributes(GeometryAttribute::GENERIC),
use_case.has_structural_metadata ? 5 : 3);
ASSERT_EQ(mesh.NumNamedAttributes(GeometryAttribute::TEX_COORD), 2);
// Get mesh element counts.
@ -437,15 +549,15 @@ void GltfTestHelper::CheckBoxMetaMeshFeatures(const Mesh &mesh,
ASSERT_EQ(features.GetFeatureCount(), num_faces);
ASSERT_EQ(features.GetNullFeatureId(), 100);
ASSERT_EQ(features.GetPropertyTableIndex(), 0);
ASSERT_EQ(features.GetAttributeIndex(), 0);
ASSERT_EQ(features.GetAttributeIndex(),
use_case.has_structural_metadata ? 5 : 4);
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);
const int att_id = features.GetAttributeIndex();
const auto att = mesh.attribute(att_id);
ASSERT_NE(att, nullptr);
ASSERT_EQ(att->attribute_type(), GeometryAttribute::GENERIC);
ASSERT_EQ(att->data_type(), DataType::DT_UINT8);
@ -455,7 +567,7 @@ void GltfTestHelper::CheckBoxMetaMeshFeatures(const Mesh &mesh,
// Check that the values are all the numbers from 0 to 12.
const std::vector<uint8_t> expected_values =
has_draco_compression
use_case.has_draco_compression
? std::vector<uint8_t>{7, 11, 10, 3, 2, 5, 4, 1, 6, 9, 8, 0}
: std::vector<uint8_t>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
for (int i = 0; i < num_faces; i++) {
@ -482,15 +594,15 @@ void GltfTestHelper::CheckBoxMetaMeshFeatures(const Mesh &mesh,
ASSERT_EQ(features.GetFeatureCount(), num_vertices);
ASSERT_EQ(features.GetNullFeatureId(), 101);
ASSERT_EQ(features.GetPropertyTableIndex(), 1);
ASSERT_EQ(features.GetAttributeIndex(), 1);
ASSERT_EQ(features.GetAttributeIndex(),
use_case.has_structural_metadata ? 6 : 5);
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);
const int att_id = features.GetAttributeIndex();
const auto att = mesh.attribute(att_id);
ASSERT_NE(att, nullptr);
ASSERT_EQ(att->attribute_type(), GeometryAttribute::GENERIC);
ASSERT_EQ(att->data_type(), DataType::DT_UINT16);
@ -500,8 +612,9 @@ void GltfTestHelper::CheckBoxMetaMeshFeatures(const Mesh &mesh,
// Check that the values are all the numbers from 0 to 7.
const std::vector<uint16_t> expected_values =
has_draco_compression ? std::vector<uint16_t>{3, 6, 7, 4, 5, 0, 1, 2}
: std::vector<uint16_t>{0, 1, 2, 3, 4, 5, 6, 7};
use_case.has_draco_compression
? std::vector<uint16_t>{3, 6, 7, 4, 5, 0, 1, 2}
: std::vector<uint16_t>{0, 1, 2, 3, 4, 5, 6, 7};
for (int i = 0; i < num_vertices; i++) {
uint16_t val;
att->GetValue(AttributeValueIndex(i), &val);
@ -528,15 +641,15 @@ void GltfTestHelper::CheckBoxMetaMeshFeatures(const Mesh &mesh,
ASSERT_EQ(features.GetFeatureCount(), num_corners);
ASSERT_EQ(features.GetNullFeatureId(), -1);
ASSERT_EQ(features.GetPropertyTableIndex(), -1);
ASSERT_EQ(features.GetAttributeIndex(), 2);
ASSERT_EQ(features.GetAttributeIndex(),
use_case.has_structural_metadata ? 7 : 6);
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);
const int att_id = features.GetAttributeIndex();
const auto att = mesh.attribute(att_id);
ASSERT_NE(att, nullptr);
ASSERT_EQ(att->attribute_type(), GeometryAttribute::GENERIC);
ASSERT_EQ(att->data_type(), DataType::DT_FLOAT32);
@ -547,7 +660,7 @@ void GltfTestHelper::CheckBoxMetaMeshFeatures(const Mesh &mesh,
// Check that the values are from 0 to 35.
const std::vector<float> expected_values =
has_draco_compression
use_case.has_draco_compression
? std::vector<float>{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}
@ -598,55 +711,104 @@ void GltfTestHelper::CheckBoxMetaMeshFeatures(const Mesh &mesh,
}
void GltfTestHelper::CheckBoxMetaStructuralMetadata(
const StructuralMetadata &structural_metadata) {
// Check property table schema.
const Mesh &mesh, const StructuralMetadata &structural_metadata,
const UseCase &use_case) {
// Check structural metadata schema.
{
const PropertyTable::Schema &schema =
structural_metadata.GetPropertyTableSchema();
const StructuralMetadataSchema &schema = structural_metadata.GetSchema();
ASSERT_FALSE(schema.Empty());
const PropertyTable::Schema::Object &json = schema.json;
const StructuralMetadataSchema::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);
ASSERT_EQ(json.GetObjects()[0].GetObjects().size(), 2);
const auto &properties =
json.GetObjects()[0].GetObjects()[0].GetObjects()[0];
ASSERT_EQ(properties.GetName(), "properties");
ASSERT_EQ(properties.GetObjects().size(), 3);
// Check class "movement".
{
const auto item = json.GetObjects()[0].GetObjects()[0];
ASSERT_EQ(item.GetName(), "movement");
ASSERT_EQ(item.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 &description = item.GetObjects()[0];
ASSERT_EQ(description.GetName(), "description");
ASSERT_EQ(description.GetString(), "Vertex movement.");
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 &name = item.GetObjects()[1];
ASSERT_EQ(name.GetName(), "name");
ASSERT_EQ(name.GetString(), "The movement.");
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");
const auto &properties = item.GetObjects()[2];
ASSERT_EQ(properties.GetName(), "properties");
ASSERT_EQ(properties.GetObjects().size(), 2);
const auto &direction = properties.GetObjects()[0];
ASSERT_EQ(direction.GetName(), "direction");
ASSERT_EQ(direction.GetObjects().size(), 4);
ASSERT_EQ(direction.GetObjects()[0].GetName(), "componentType");
ASSERT_EQ(direction.GetObjects()[1].GetName(), "description");
ASSERT_EQ(direction.GetObjects()[2].GetName(), "required");
ASSERT_EQ(direction.GetObjects()[3].GetName(), "type");
ASSERT_EQ(direction.GetObjects()[0].GetString(), "FLOAT32");
ASSERT_EQ(direction.GetObjects()[1].GetString(), "Movement direction.");
ASSERT_EQ(direction.GetObjects()[2].GetBoolean(), true);
ASSERT_EQ(direction.GetObjects()[3].GetString(), "VEC3");
const auto &mag = properties.GetObjects()[1];
ASSERT_EQ(mag.GetName(), "magnitude");
ASSERT_EQ(mag.GetObjects().size(), 4);
ASSERT_EQ(mag.GetObjects()[0].GetName(), "componentType");
ASSERT_EQ(mag.GetObjects()[1].GetName(), "description");
ASSERT_EQ(mag.GetObjects()[2].GetName(), "required");
ASSERT_EQ(mag.GetObjects()[3].GetName(), "type");
ASSERT_EQ(mag.GetObjects()[0].GetString(), "FLOAT32");
ASSERT_EQ(mag.GetObjects()[1].GetString(), "Movement magnitude.");
ASSERT_EQ(mag.GetObjects()[2].GetBoolean(), true);
ASSERT_EQ(mag.GetObjects()[3].GetString(), "SCALAR");
}
// Check class "planet".
{
const auto item = json.GetObjects()[0].GetObjects()[1];
ASSERT_EQ(item.GetName(), "planet");
ASSERT_EQ(item.GetObjects().size(), 1);
const auto &properties = item.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(), 4);
ASSERT_EQ(sequence.GetObjects()[0].GetName(), "componentType");
ASSERT_EQ(sequence.GetObjects()[1].GetName(), "description");
ASSERT_EQ(sequence.GetObjects()[2].GetName(), "required");
ASSERT_EQ(sequence.GetObjects()[3].GetName(), "type");
ASSERT_EQ(sequence.GetObjects()[0].GetString(), "FLOAT32");
ASSERT_EQ(sequence.GetObjects()[1].GetString(), "The number sequence.");
ASSERT_FALSE(sequence.GetObjects()[2].GetBoolean());
ASSERT_EQ(sequence.GetObjects()[3].GetString(), "SCALAR");
}
ASSERT_EQ(json.GetObjects()[1].GetName(), "enums");
const auto &classifications = json.GetObjects()[1].GetObjects()[0];
@ -736,11 +898,11 @@ void GltfTestHelper::CheckBoxMetaStructuralMetadata(
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[60], 31); // 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[64], 40); // Beyond UNLABELED 296.
ASSERT_EQ(offsets[65], 1);
ASSERT_EQ(offsets[66], 0);
ASSERT_EQ(offsets[67], 0);
@ -748,8 +910,8 @@ void GltfTestHelper::CheckBoxMetaStructuralMetadata(
struct Name {
static std::string Extract(const std::vector<uint8_t> &data,
const std::vector<uint8_t> &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];
const int b = offsets[4 * (row + 0)] + 256 * offsets[4 * (row + 0) + 1];
const int e = offsets[4 * (row + 1)] + 256 * offsets[4 * (row + 1) + 1];
return std::string(data.begin() + b, data.begin() + e);
}
};
@ -757,6 +919,9 @@ void GltfTestHelper::CheckBoxMetaStructuralMetadata(
// 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, 12), "named_class:Bespin");
ASSERT_EQ(Name::Extract(data, offsets, 13), "named_class:Yavin");
ASSERT_EQ(Name::Extract(data, offsets, 14), "named_class:Geonosis");
ASSERT_EQ(Name::Extract(data, offsets, 15), "UNLABELED");
ASSERT_TRUE(property.GetArrayOffsets().type.empty());
@ -816,6 +981,101 @@ void GltfTestHelper::CheckBoxMetaStructuralMetadata(
ASSERT_TRUE(property.GetStringOffsets().data.data.empty());
ASSERT_EQ(property.GetStringOffsets().data.target, 0);
}
// Check property attributes in structural metadata.
ASSERT_EQ(structural_metadata.NumPropertyAttributes(), 1);
{
const PropertyAttribute &attribute =
structural_metadata.GetPropertyAttribute(0);
ASSERT_EQ(attribute.GetName(), "The movement.");
ASSERT_EQ(attribute.GetClass(), "movement");
ASSERT_EQ(attribute.NumProperties(), 2);
const PropertyAttribute::Property &direction = attribute.GetProperty(0);
ASSERT_EQ(direction.GetName(), "direction");
ASSERT_EQ(direction.GetAttributeName(), "_DIRECTION");
const PropertyAttribute::Property &magnitude = attribute.GetProperty(1);
ASSERT_EQ(magnitude.GetName(), "magnitude");
ASSERT_EQ(magnitude.GetAttributeName(), "_MAGNITUDE");
}
// Check property attributes in the |mesh|.
ASSERT_EQ(mesh.NumPropertyAttributesIndices(), 1);
ASSERT_EQ(mesh.GetPropertyAttributesIndex(0), 0);
ASSERT_EQ(mesh.num_faces(), 12);
ASSERT_EQ(mesh.num_attributes(), 9);
ASSERT_EQ(mesh.num_points(), 36);
ASSERT_EQ(mesh.NumNamedAttributes(GeometryAttribute::GENERIC), 5);
// Get mesh element counts.
const int num_corners = 3 * mesh.num_faces();
const int num_vertices =
mesh.GetNamedAttribute(GeometryAttribute::POSITION)->size();
// Check property attribute named _DIRECTION.
{
const auto att =
mesh.GetNamedAttributeByName(GeometryAttribute::GENERIC, "_DIRECTION");
ASSERT_NE(att, nullptr);
ASSERT_EQ(att->attribute_type(), GeometryAttribute::GENERIC);
ASSERT_EQ(att->data_type(), DataType::DT_FLOAT32);
ASSERT_EQ(att->num_components(), 3);
ASSERT_EQ(att->size(), num_vertices);
ASSERT_EQ(att->indices_map_size(), num_corners);
// Check attribute values.
// clang-format off
const std::vector<float> expected_values =
use_case.has_draco_compression
? std::vector<float>{3.1f, 3.2f, 3.3f,
6.1f, 6.2f, 6.3f,
7.1f, 7.2f, 7.3f,
4.1f, 4.2f, 4.3f,
5.1f, 5.2f, 5.3f,
0.1f, 0.2f, 0.3f,
1.1f, 1.2f, 1.3f,
2.1f, 2.2f, 2.3f}
: std::vector<float>{0.1f, 0.2f, 0.3f,
1.1f, 1.2f, 1.3f,
2.1f, 2.2f, 2.3f,
3.1f, 3.2f, 3.3f,
4.1f, 4.2f, 4.3f,
5.1f, 5.2f, 5.3f,
6.1f, 6.2f, 6.3f,
7.1f, 7.2f, 7.3f};
// clang-format on
for (int i = 0; i < num_vertices; i++) {
std::array<float, 3> val;
att->GetValue(AttributeValueIndex(i), &val);
ASSERT_EQ(val[0], expected_values[3 * i + 0]);
ASSERT_EQ(val[1], expected_values[3 * i + 1]);
ASSERT_EQ(val[2], expected_values[3 * i + 2]);
}
}
// Check property attribute named _MAGNITUDE.
{
const auto att =
mesh.GetNamedAttributeByName(GeometryAttribute::GENERIC, "_MAGNITUDE");
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_vertices);
ASSERT_EQ(att->indices_map_size(), num_corners);
// Check attribute values.
const std::vector<float> expected_values =
use_case.has_draco_compression
? std::vector<float>{3.f, 6.f, 7.f, 4.f, 5.f, 0.f, 1.f, 2.f}
: std::vector<float>{0.f, 1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f};
for (int i = 0; i < num_vertices; i++) {
float val;
att->GetValue(AttributeValueIndex(i), &val);
ASSERT_EQ(val, expected_values[i]);
}
}
}
#endif // DRACO_TRANSCODER_SUPPORTED

View file

@ -25,9 +25,15 @@ 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.
struct UseCase {
bool has_draco_compression = false;
bool has_mesh_features = false;
bool has_structural_metadata = false;
};
// Adds various mesh feature ID sets (via attributes and via textures),
// structural metadata schema, property table, and property attributes to the
// box |scene| loaded from the test file testdata/Box/glTF/Box.gltf.
static void AddBoxMetaMeshFeatures(Scene *scene);
static void AddBoxMetaStructuralMetadata(Scene *scene);
@ -37,22 +43,31 @@ class GltfTestHelper {
// 2. testdata/BoxMetaDraco/glTF/BoxMetaDraco.gltf
template <typename GeometryT>
static void CheckBoxMetaMeshFeatures(const GeometryT &geometry,
bool has_draco_compression);
const UseCase &use_case);
// 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.
// metadata that includes schema, property table, and property attributes
// loaded from test file testdata/BoxMeta/glTF/BoxMeta.gltf.
template <typename GeometryT>
static void CheckBoxMetaStructuralMetadata(const GeometryT &geometry) {
CheckBoxMetaStructuralMetadata(geometry.GetStructuralMetadata());
static void CheckBoxMetaStructuralMetadata(const GeometryT &geometry,
const UseCase &use_case) {
if constexpr (std::is_same_v<GeometryT, Mesh>) {
CheckBoxMetaStructuralMetadata(geometry, geometry.GetStructuralMetadata(),
use_case);
} else {
CheckBoxMetaStructuralMetadata(geometry.GetMesh(MeshIndex(0)),
geometry.GetStructuralMetadata(),
use_case);
}
}
private:
static void CheckBoxMetaMeshFeatures(const Mesh &mesh,
const TextureLibrary &texture_lib,
bool has_draco_compression);
const UseCase &use_case);
static void CheckBoxMetaStructuralMetadata(
const StructuralMetadata &structural_metadata);
const Mesh &mesh, const StructuralMetadata &structural_metadata,
const UseCase &use_case);
};
} // namespace draco

View file

@ -79,6 +79,12 @@ void JsonWriter::EndObject() {
o_ << indent_ << "}";
}
void JsonWriter::BeginArray() {
FinishPreviousLine(BEGIN);
o_ << indent_ << "[";
indent_writer_.Increase();
}
void JsonWriter::BeginArray(const std::string &name) {
FinishPreviousLine(BEGIN);
o_ << indent_ << "\"" << name << "\":" << separator_ << "[";

View file

@ -90,6 +90,7 @@ class JsonWriter {
void EndObject();
// Every call to BeginArray should have a matching call to EndArray.
void BeginArray();
void BeginArray(const std::string &name);
void EndArray();

View file

@ -216,6 +216,13 @@ TEST_F(GltfUtilsTest, TestArrays) {
json_writer.EndArray();
json_writer.EndArray();
CompareGolden(&json_writer, "\"array1\": [\n \"array2\": [\n ]\n]");
json_writer.Reset();
json_writer.BeginArray("array1");
json_writer.BeginArray();
json_writer.EndArray();
json_writer.EndArray();
CompareGolden(&json_writer, "\"array1\": [\n [\n ]\n]");
}
TEST_F(GltfUtilsTest, TestGltfValues) {
@ -359,6 +366,13 @@ TEST_F(GltfUtilsTest, TestArraysCompact) {
json_writer.EndArray();
json_writer.EndArray();
CompareGolden(&json_writer, "\"array1\":[\"array2\":[]]");
json_writer.Reset();
json_writer.BeginArray("array1");
json_writer.BeginArray();
json_writer.EndArray();
json_writer.EndArray();
CompareGolden(&json_writer, "\"array1\":[[]]");
}
} // namespace draco

View file

@ -23,7 +23,13 @@
namespace draco {
// Enum defining image compression formats.
enum class ImageFormat { NONE, PNG, JPEG, BASIS, WEBP };
enum class ImageFormat {
NONE,
PNG,
JPEG,
BASIS,
WEBP,
};
} // namespace draco

View file

@ -541,7 +541,7 @@ bool ObjDecoder::ParseMaterial(Status * /* status */) {
parser::SkipWhitespace(&line_buffer);
std::string mat_name;
parser::ParseLine(&line_buffer, &mat_name);
if (mat_name.length() == 0) {
if (mat_name.empty()) {
return false;
}
auto it = material_name_to_id_.find(mat_name);
@ -572,7 +572,7 @@ bool ObjDecoder::ParseObject(Status *status) {
if (!parser::ParseString(&line_buffer, &obj_name)) {
return false;
}
if (obj_name.length() == 0) {
if (obj_name.empty()) {
return true; // Ignore empty name entries.
}
auto it = obj_name_to_id_.find(obj_name);

View file

@ -407,7 +407,8 @@ bool ObjEncoder::EncodeFaceCorner(PointIndex vert_index) {
}
void ObjEncoder::EncodeFloat(float val) {
snprintf(num_buffer_, sizeof(num_buffer_), "%f", val);
// Use %F instead of %f to make the floating point non-locale aware.
snprintf(num_buffer_, sizeof(num_buffer_), "%F", val);
buffer()->Encode(num_buffer_, strlen(num_buffer_));
}

View file

@ -94,7 +94,7 @@ Status PlyDecoder::DecodeInternal() {
Status PlyDecoder::DecodeFaceData(const PlyElement *face_element) {
// We accept point clouds now.
if (face_element == nullptr) {
return Status(Status::INVALID_PARAMETER, "face_element is null");
return OkStatus();
}
const PlyProperty *vertex_indices =
face_element->GetPropertyByName("vertex_indices");

View file

@ -36,14 +36,11 @@ class PlyDecoderTest : public ::testing::Test {
void test_decoding(const std::string &file_name, int num_faces,
uint32_t num_points, std::unique_ptr<Mesh> *out_mesh) {
// Don't test mesh decoding when the input is point cloud.
if (num_faces > 0) {
std::unique_ptr<Mesh> mesh(DecodePly<Mesh>(file_name));
ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name;
ASSERT_EQ(mesh->num_faces(), num_faces);
if (out_mesh) {
*out_mesh = std::move(mesh);
}
std::unique_ptr<Mesh> mesh(DecodePly<Mesh>(file_name));
ASSERT_NE(mesh, nullptr) << "Failed to load test model " << file_name;
ASSERT_EQ(mesh->num_faces(), num_faces);
if (out_mesh) {
*out_mesh = std::move(mesh);
}
const std::unique_ptr<PointCloud> pc(DecodePly<PointCloud>(file_name));

View file

@ -32,8 +32,15 @@ class StlEncoderTest : public ::testing::Test {
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());
ASSERT_EQ(mesh0->attribute(att_id)->attribute_type(),
mesh1->attribute(att_id)->attribute_type());
// Normals are recomputed during STL encoding and they may not
// correspond to the source ones.
if (mesh0->attribute(att_id)->attribute_type() !=
GeometryAttribute::NORMAL) {
ASSERT_EQ(mesh0->attribute(att_id)->size(),
mesh1->attribute(att_id)->size());
}
}
}

View file

@ -16,7 +16,11 @@
#ifdef DRACO_TRANSCODER_SUPPORTED
#include <algorithm>
#include <cstring>
#include "draco/io/file_utils.h"
#include "draco/texture/texture_utils.h"
namespace draco {
@ -25,12 +29,74 @@ namespace {
StatusOr<std::unique_ptr<Texture>> CreateDracoTextureInternal(
const std::vector<uint8_t> &image_data, SourceImage *out_source_image) {
std::unique_ptr<Texture> draco_texture(new Texture());
const auto format =
ImageFormatFromBuffer(image_data.data(), image_data.size());
out_source_image->MutableEncodedData() = image_data;
out_source_image->set_mime_type(TextureUtils::GetMimeType(format));
return std::move(draco_texture);
}
} // namespace
ImageFormat ImageFormatFromBuffer(const uint8_t *buffer, size_t buffer_size) {
if (buffer_size > 4) {
// These bytes are the Start of Image (SOI) and End of Image (EOI) markers
// in a JPEG data stream.
const std::array<uint8_t, 2> kJpegSOIMarker = {0xFF, 0xD8};
const std::array<uint8_t, 2> kJpegEOIMarker = {0xFF, 0xD9};
if (!memcmp(buffer, kJpegSOIMarker.data(), kJpegSOIMarker.size())) {
// Look for the last occurence of the end marker (allow trailing bytes).
if (std::find_end(buffer, buffer + buffer_size, kJpegEOIMarker.begin(),
kJpegEOIMarker.end()) != buffer + buffer_size) {
return ImageFormat::JPEG;
}
}
}
if (buffer_size > 2) {
// For Binomial Basis format input the stream always begins with
// the signature 'B' * 256 + 's', or 0x4273.
const std::array<uint8_t, 2> kBasisSignature = {0x42, 0x73};
if (!memcmp(buffer, kBasisSignature.data(), kBasisSignature.size())) {
return ImageFormat::BASIS;
}
}
if (buffer_size > 4) {
// For Binomial Basis/KTX2 format input the stream begins with 0xab 0x4b
// 0x54 0x58.
const std::array<uint8_t, 4> kKtx2Signature = {0xab, 0x4b, 0x54, 0x58};
if (!memcmp(buffer, kKtx2Signature.data(), kKtx2Signature.size())) {
return ImageFormat::BASIS;
}
}
if (buffer_size > 8) {
// The first eight bytes of a PNG stream always contain these values:
const std::array<uint8_t, 8> kPngSignature = {0x89, 0x50, 0x4e, 0x47,
0x0d, 0x0a, 0x1a, 0x0a};
if (!memcmp(buffer, kPngSignature.data(), kPngSignature.size())) {
return ImageFormat::PNG;
}
}
if (buffer_size > 12) {
// The WebP signature bytes are: RIFF 0 0 0 0 WEBP. The 0's are where WebP
// size information is encoded in the stream, but the check here just looks
// for RIFF and WEBP.
const std::array<uint8_t, 4> kRIFF = {0x52, 0x49, 0x46, 0x46};
const std::array<uint8_t, 4> kWEBP = {0x57, 0x45, 0x42, 0x50};
if (!memcmp(buffer, kRIFF.data(), kRIFF.size()) &&
!memcmp(buffer + 8, kWEBP.data(), kWEBP.size())) {
return ImageFormat::WEBP;
}
}
return ImageFormat::NONE;
}
StatusOr<std::unique_ptr<Texture>> ReadTextureFromFile(
const std::string &file_name) {
std::vector<uint8_t> image_data;
@ -42,23 +108,31 @@ StatusOr<std::unique_ptr<Texture>> ReadTextureFromFile(
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);
if (source_image.mime_type().empty()) {
// Try to set mime type from extension if we were not able to detect it
// automatically.
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<std::unique_ptr<Texture>> ReadTextureFromBuffer(const uint8_t *buffer,
size_t buffer_size) {
SourceImage source_image;
std::vector<uint8_t> image_data(buffer, buffer + buffer_size);
DRACO_ASSIGN_OR_RETURN(auto texture,
CreateDracoTextureInternal(image_data, &source_image));
texture->set_source_image(source_image);
return texture;
}
StatusOr<std::unique_ptr<Texture>> ReadTextureFromBuffer(
const uint8_t *buffer, size_t buffer_size, const std::string &mime_type) {
SourceImage source_image;
std::vector<uint8_t> 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;
return ReadTextureFromBuffer(buffer, buffer_size);
}
Status WriteTextureToFile(const std::string &file_name,

View file

@ -19,6 +19,8 @@
#ifdef DRACO_TRANSCODER_SUPPORTED
#include <memory>
#include <string>
#include <vector>
#include "draco/core/draco_types.h"
#include "draco/core/status_or.h"
@ -32,10 +34,10 @@ StatusOr<std::unique_ptr<Texture>> 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<std::unique_ptr<Texture>> ReadTextureFromBuffer(const uint8_t *buffer,
size_t buffer_size);
// Deprecated: |mime_type| is currently ignored and it is deducted automatically
// from the content of the |buffer|.
StatusOr<std::unique_ptr<Texture>> ReadTextureFromBuffer(
const uint8_t *buffer, size_t buffer_size, const std::string &mime_type);
@ -50,6 +52,10 @@ Status WriteTextureToFile(const std::string &file_name, const Texture &texture);
Status WriteTextureToBuffer(const Texture &texture,
std::vector<uint8_t> *buffer);
// Returns the image format of an encoded texture stored in |buffer|.
// ImageFormat::NONE is returned for unknown image formats.
ImageFormat ImageFormatFromBuffer(const uint8_t *buffer, size_t buffer_size);
} // namespace draco
#endif // DRACO_TRANSCODER_SUPPORTED

View file

@ -15,6 +15,7 @@
#include "draco/io/texture_io.h"
#ifdef DRACO_TRANSCODER_SUPPORTED
#include <cstdint>
#include <memory>
#include <string>
#include <unordered_map>
@ -50,6 +51,29 @@ TEST(TextureIoTest, TestLoadFromBuffer) {
}
}
// Tests that we can set mime type correctly even when the source file had
// an incorrect extension.
TEST(TextureIoTest, TestWrongExtension) {
const std::string file_name = draco::GetTestFileFullPath("this_is_png.jpg");
DRACO_ASSIGN_OR_ASSERT(std::unique_ptr<draco::Texture> texture,
draco::ReadTextureFromFile(file_name));
ASSERT_NE(texture, nullptr);
// Ensure the mime type was set to png even though the file extension was jpg.
ASSERT_EQ(texture->source_image().mime_type(), "image/png");
}
// Tests that we can load jpeg files that have some trailing bytes after the
// jpeg end marker.
TEST(TextureIoTest, TestTrailingJpegBytes) {
const std::string file_name = draco::GetTestFileFullPath("trailing_zero.jpg");
DRACO_ASSIGN_OR_ASSERT(std::unique_ptr<draco::Texture> texture,
draco::ReadTextureFromFile(file_name));
ASSERT_NE(texture, nullptr);
}
} // namespace
#endif // DRACO_TRANSCODER_SUPPORTED

View file

@ -115,6 +115,8 @@ interface PointCloudBuilder {
boolean AddMetadata(PointCloud pc, [Const] Metadata metadata);
boolean SetMetadataForAttribute(PointCloud pc, long attribute_id,
[Const] Metadata metadata);
boolean SetNormalizedFlagForAttribute(PointCloud pc, long attribute_id,
boolean normalized);
};
interface MeshBuilder : PointCloudBuilder {
@ -159,6 +161,8 @@ interface MeshBuilder : PointCloudBuilder {
boolean AddMetadata(PointCloud pc, [Const] Metadata metadata);
boolean SetMetadataForAttribute(PointCloud pc, long attribute_id,
[Const] Metadata metadata);
boolean SetNormalizedFlagForAttribute(PointCloud pc, long attribute_id,
boolean normalized);
};
interface Encoder {

View file

@ -179,6 +179,19 @@ bool PointCloudBuilder::SetMetadataForAttribute(PointCloud *pc,
return true;
}
bool PointCloudBuilder::SetNormalizedFlagForAttribute(draco::PointCloud *pc,
long attribute_id,
bool normalized) {
if (!pc) {
return false;
}
if (attribute_id < 0 || attribute_id >= pc->num_attributes()) {
return false;
}
pc->attribute(attribute_id)->set_normalized(normalized);
return true;
}
MeshBuilder::MeshBuilder() {}
bool MeshBuilder::AddFacesToMesh(Mesh *mesh, long num_faces, const int *faces) {

View file

@ -81,6 +81,8 @@ class PointCloudBuilder {
bool SetMetadataForAttribute(draco::PointCloud *pc, long attribute_id,
const draco::Metadata *metadata);
bool AddMetadata(draco::PointCloud *pc, const draco::Metadata *metadata);
bool SetNormalizedFlagForAttribute(draco::PointCloud *pc, long attribute_id,
bool normalized);
private:
template <typename DataTypeT>

View file

@ -26,11 +26,7 @@ namespace draco {
template <bool B, class T, class F>
using conditional_t = typename std::conditional<B, T, F>::type;
#ifdef DRACO_TRANSCODER_SUPPORTED
Mesh::Mesh() : compression_enabled_(false) {}
#else
Mesh::Mesh() {}
#endif
#ifdef DRACO_TRANSCODER_SUPPORTED
void Mesh::Copy(const Mesh &src) {
@ -39,8 +35,6 @@ void Mesh::Copy(const Mesh &src) {
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();
@ -67,6 +61,8 @@ void Mesh::Copy(const Mesh &src) {
// Copy structural metadata.
structural_metadata_.Copy(src.structural_metadata_);
property_attributes_ = src.property_attributes_;
property_attributes_material_mask_ = src.property_attributes_material_mask_;
}
namespace {
@ -338,6 +334,22 @@ void Mesh::RemoveUnusedMaterials(bool remove_unused_material_indices) {
}
}
// Check if any of the (unused) materials is used by property attributes
// indices. If so, user should remove unused property attributes indices
// first.
for (int i = 0; i < NumPropertyAttributesIndices(); ++i) {
for (int mask_index = 0;
mask_index < NumPropertyAttributesIndexMaterialMasks(i);
++mask_index) {
const int mat_index =
GetPropertyAttributesIndexMaterialMask(i, 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.
}
@ -408,6 +420,30 @@ void Mesh::RemoveUnusedMaterials(bool remove_unused_material_indices) {
}
}
}
// Update material indices on property attributes incices.
for (int i = 0; i < NumPropertyAttributesIndices(); ++i) {
for (int mask_index = 0;
mask_index < NumPropertyAttributesIndexMaterialMasks(i);
++mask_index) {
const int old_mat_index =
GetPropertyAttributesIndexMaterialMask(i, mask_index);
if (old_mat_index < num_materials && is_material_used[old_mat_index]) {
property_attributes_material_mask_[i][mask_index] =
old_to_new_material_index_map[old_mat_index];
}
}
}
}
bool Mesh::IsAttributeUsedByMeshFeatures(int att_id) const {
for (MeshFeaturesIndex mfi(0); mfi < NumMeshFeatures(); ++mfi) {
const auto &mf = GetMeshFeatures(mfi);
if (mf.GetAttributeIndex() == att_id) {
return true;
}
}
return false;
}
void Mesh::UpdateMeshFeaturesTexturePointer(
@ -448,6 +484,43 @@ void Mesh::CopyMeshFeaturesForMaterial(const Mesh &source_mesh,
}
}
void Mesh::CopyPropertyAttributesIndicesForMaterial(const Mesh &source_mesh,
Mesh *target_mesh,
int material_index) {
for (int i = 0; i < source_mesh.NumPropertyAttributesIndices(); ++i) {
// Property attributes index 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.NumPropertyAttributesIndexMaterialMasks(i) == 0;
for (int mask_index = 0;
!is_used &&
mask_index < source_mesh.NumPropertyAttributesIndexMaterialMasks(i);
++mask_index) {
if (source_mesh.GetPropertyAttributesIndexMaterialMask(i, mask_index) ==
material_index) {
is_used = true;
}
}
if (is_used) {
// Copy over the property attributes index to the target mesh.
target_mesh->AddPropertyAttributesIndex(
source_mesh.GetPropertyAttributesIndex(i));
}
}
}
void Mesh::UpdateMeshFeaturesAfterDeletedAttribute(int att_id) {
for (MeshFeaturesIndex mfi(0); mfi < NumMeshFeatures(); ++mfi) {
auto &mf = GetMeshFeatures(mfi);
if (mf.GetAttributeIndex() == att_id) {
// Mesh features is no longer associated with a vertex attribute.
mf.SetAttributeIndex(-1);
} else if (mf.GetAttributeIndex() > att_id) {
// Attribute index decremented by one.
mf.SetAttributeIndex(mf.GetAttributeIndex() - 1);
}
}
}
int32_t Mesh::AddPerFaceAttribute(std::unique_ptr<PointAttribute> att) {
IndexTypeVector<CornerIndex, AttributeValueIndex> corner_map(num_faces() * 3);
for (CornerIndex ci(0); ci < num_faces() * 3; ++ci) {

View file

@ -24,7 +24,6 @@
#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"
@ -94,6 +93,9 @@ class Mesh : public PointCloud {
if (att_id >= 0 && att_id < static_cast<int>(attribute_data_.size())) {
attribute_data_.erase(attribute_data_.begin() + att_id);
}
#ifdef DRACO_TRANSCODER_SUPPORTED
UpdateMeshFeaturesAfterDeletedAttribute(att_id);
#endif
}
#ifdef DRACO_TRANSCODER_SUPPORTED
@ -170,22 +172,6 @@ class Mesh : public PointCloud {
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_;
@ -208,12 +194,20 @@ class Mesh : public PointCloud {
MeshFeatures &GetMeshFeatures(MeshFeaturesIndex index) {
return *mesh_features_[index];
}
// Removes mesh features from the mesh. Note that removing a mesh feature does
// not delete any associated data such as vertex attributes or feature
// textures.
void RemoveMeshFeatures(MeshFeaturesIndex index) {
mesh_features_.erase(mesh_features_.begin() + index.value());
mesh_features_material_mask_.erase(mesh_features_material_mask_.begin() +
index.value());
}
// Returns true if an attribute with |att_id| is being used by any mesh
// features attached to the mesh.
bool IsAttributeUsedByMeshFeatures(int att_id) const;
// Restricts given mesh features to faces mapped to a material with
// |material_index|. Note that single mesh features can be restricted to
// multiple materials.
@ -249,6 +243,49 @@ class Mesh : public PointCloud {
return structural_metadata_;
}
StructuralMetadata &GetStructuralMetadata() { return structural_metadata_; }
// Property attributes indices as defined by EXT_structural_metadata glTF
// extension.
int AddPropertyAttributesIndex(int property_attribute_index) {
property_attributes_.push_back(property_attribute_index);
property_attributes_material_mask_.push_back({});
return property_attributes_.size() - 1;
}
int NumPropertyAttributesIndices() const {
return property_attributes_.size();
}
const int &GetPropertyAttributesIndex(int index) const {
return property_attributes_[index];
}
int &GetPropertyAttributesIndex(int index) {
return property_attributes_[index];
}
void RemovePropertyAttributesIndex(int index) {
property_attributes_.erase(property_attributes_.begin() + index);
property_attributes_material_mask_.erase(
property_attributes_material_mask_.begin() + index);
}
// Restricts given property attributes indices to faces mapped to a material
// with |material_index|. Note that single property attribute can be
// restricted to multiple materials.
void AddPropertyAttributesIndexMaterialMask(int index, int material_index) {
property_attributes_material_mask_[index].push_back(material_index);
}
size_t NumPropertyAttributesIndexMaterialMasks(int index) const {
return property_attributes_material_mask_[index].size();
}
int GetPropertyAttributesIndexMaterialMask(int index, int mask_index) const {
return property_attributes_material_mask_[index][mask_index];
}
// Copies over property attributes indices from |source_mesh| and stores them
// in |target_mesh| as long as the property attributes indices material mask
// is valid for given |material_index|.
static void CopyPropertyAttributesIndicesForMaterial(const Mesh &source_mesh,
Mesh *target_mesh,
int material_index);
#endif // DRACO_TRANSCODER_SUPPORTED
protected:
@ -266,6 +303,11 @@ class Mesh : public PointCloud {
IndexTypeVector<FaceIndex, Face> &faces() { return faces_; }
private:
#ifdef DRACO_TRANSCODER_SUPPORTED
// Updates attribute indices associated to all mesh features after a mesh
// attribute is deleted.
void UpdateMeshFeaturesAfterDeletedAttribute(int att_id);
#endif
// Mesh specific per-attribute data.
std::vector<AttributeData> attribute_data_;
@ -280,11 +322,6 @@ class Mesh : public PointCloud {
// 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<MeshFeaturesIndex, std::unique_ptr<MeshFeatures>>
mesh_features_;
@ -296,6 +333,15 @@ class Mesh : public PointCloud {
IndexTypeVector<MeshFeaturesIndex, std::vector<int>>
mesh_features_material_mask_;
// Indices pointing to property attributes in draco::StructuralMetadata.
std::vector<int> property_attributes_;
// When the Mesh contains multiple materials, this mask can be used to limit
// specific index into |property_attributes_| to a vector of material indices.
// If for a given property attributes index, the material indices are empty,
// the corresponding property attributes are applied to the entire mesh.
std::vector<std::vector<int>> property_attributes_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.

View file

@ -75,35 +75,28 @@ void MeshCleanup::RemoveDegeneratedFaces(Mesh *mesh) {
}
void MeshCleanup::RemoveDuplicateFaces(Mesh *mesh) {
const PointAttribute *const pos_att =
mesh->GetNamedAttribute(GeometryAttribute::POSITION);
typedef std::array<AttributeValueIndex::ValueType, 3> PosTriplet;
PosTriplet pos_indices;
std::unordered_set<PosTriplet, HashArray<PosTriplet>> is_face_used;
std::unordered_set<Mesh::Face, HashArray<Mesh::Face>> is_face_used;
uint32_t num_duplicate_faces = 0;
for (FaceIndex fi(0); fi < mesh->num_faces(); ++fi) {
const auto f = mesh->face(fi);
for (int c = 0; c < 3; ++c) {
pos_indices[c] = pos_att->mapped_index(f[c]).value();
}
// Shift the position indices until the smallest index is the first one.
while (pos_indices[0] > pos_indices[1] || pos_indices[0] > pos_indices[2]) {
auto face = mesh->face(fi);
// Shift the face indices until the smallest index is the first one.
while (face[0] > face[1] || face[0] > face[2]) {
// Shift to the left.
std::swap(pos_indices[0], pos_indices[1]);
std::swap(pos_indices[1], pos_indices[2]);
std::swap(face[0], face[1]);
std::swap(face[1], face[2]);
}
// Check if have encountered the same position triplet on a different face.
if (is_face_used.find(pos_indices) != is_face_used.end()) {
// Check if have encountered the same face before.
if (is_face_used.find(face) != is_face_used.end()) {
// Duplicate face. Ignore it.
num_duplicate_faces++;
} else {
// Insert new face to the set.
is_face_used.insert(pos_indices);
is_face_used.insert(face);
if (num_duplicate_faces > 0) {
// Copy the face to its new location.
mesh->SetFace(fi - num_duplicate_faces, f);
mesh->SetFace(fi - num_duplicate_faces, face);
}
}
}

View file

@ -141,38 +141,62 @@ TEST_F(MeshCleanupTest, TestAttributes) {
}
TEST_F(MeshCleanupTest, TestDuplicateFaces) {
// This test verifies that the mesh cleanup tool removes duplicate faces.
TriangleSoupMeshBuilder mb;
mb.Start(5);
const int pos_att_id =
mb.AddAttribute(GeometryAttribute::POSITION, 3, DT_FLOAT32);
const int norm_att_id =
mb.AddAttribute(GeometryAttribute::NORMAL, 3, DT_FLOAT32);
// Five faces where only two are unique.
// Five faces where only two are unique in spatial domain and three are unique
// when we take into account the normal attribute.
// clang-format off
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());
mb.SetAttributeValuesForFace(norm_att_id, FaceIndex(0),
Vector3f(0.f, 0.f, 1.f).data(),
Vector3f(0.f, 0.f, 1.f).data(),
Vector3f(0.f, 0.f, 1.f).data());
mb.SetAttributeValuesForFace(pos_att_id, FaceIndex(1),
Vector3f(0.f, 0.f, 0.f).data(),
Vector3f(1.f, 0.f, 0.f).data(),
Vector3f(0.f, 1.f, 0.f).data());
mb.SetAttributeValuesForFace(norm_att_id, FaceIndex(1),
Vector3f(0.f, 1.f, 0.f).data(),
Vector3f(0.f, 1.f, 0.f).data(),
Vector3f(0.f, 1.f, 0.f).data());
mb.SetAttributeValuesForFace(pos_att_id, FaceIndex(2),
Vector3f(0.f, 0.f, 0.f).data(),
Vector3f(1.f, 0.f, 0.f).data(),
Vector3f(0.f, 1.f, 1.f).data());
mb.SetAttributeValuesForFace(norm_att_id, FaceIndex(2),
Vector3f(0.f, 0.f, 1.f).data(),
Vector3f(0.f, 0.f, 1.f).data(),
Vector3f(0.f, 0.f, 1.f).data());
mb.SetAttributeValuesForFace(pos_att_id, FaceIndex(3),
Vector3f(1.f, 0.f, 0.f).data(),
Vector3f(0.f, 1.f, 0.f).data(),
Vector3f(0.f, 0.f, 0.f).data());
mb.SetAttributeValuesForFace(norm_att_id, FaceIndex(3),
Vector3f(0.f, 0.f, 1.f).data(),
Vector3f(0.f, 0.f, 1.f).data(),
Vector3f(0.f, 0.f, 1.f).data());
mb.SetAttributeValuesForFace(pos_att_id, FaceIndex(4),
Vector3f(0.f, 0.f, 0.f).data(),
Vector3f(1.f, 0.f, 0.f).data(),
Vector3f(0.f, 1.f, 1.f).data());
mb.SetAttributeValuesForFace(norm_att_id, FaceIndex(4),
Vector3f(0.f, 0.f, 1.f).data(),
Vector3f(0.f, 0.f, 1.f).data(),
Vector3f(0.f, 0.f, 1.f).data());
// clang-format on
std::unique_ptr<Mesh> mesh = mb.Finalize();
@ -180,7 +204,7 @@ TEST_F(MeshCleanupTest, TestDuplicateFaces) {
ASSERT_EQ(mesh->num_faces(), 5);
const MeshCleanupOptions cleanup_options;
DRACO_ASSERT_OK(MeshCleanup::Cleanup(mesh.get(), cleanup_options));
ASSERT_EQ(mesh->num_faces(), 2);
ASSERT_EQ(mesh->num_faces(), 3);
}
} // namespace draco

View file

@ -53,8 +53,8 @@ class MeshFeatures {
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.
// Index of the feature ID vertex attribute in draco::Mesh or -1 if the
// feature ID is not associated with vertices.
void SetAttributeIndex(int attribute_index);
int GetAttributeIndex() const;

View file

@ -18,6 +18,10 @@
#ifndef DRACO_MESH_MESH_MISC_FUNCTIONS_H_
#define DRACO_MESH_MESH_MISC_FUNCTIONS_H_
#include <array>
#include <cmath>
#include <memory>
#include "draco/mesh/corner_table.h"
#include "draco/mesh/mesh.h"
@ -77,6 +81,10 @@ InterpolatedVectorT ComputeInterpolatedAttributeValueOnMeshFace(
for (int c = 0; c < 3; ++c) {
attribute.GetMappedValue(face[c], &(val[c][0]));
}
if (val[1] == val[0] && val[2] == val[0]) {
// No need to interpolate anything if all values are the same.
return val[0];
}
// Return an interpolated value.
InterpolatedVectorT res;
for (int d = 0; d < InterpolatedVectorT::dimension; ++d) {

View file

@ -18,10 +18,13 @@
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>
#include "draco/attributes/geometry_attribute.h"
#include "draco/mesh/mesh_utils.h"
#include "draco/mesh/triangle_soup_mesh_builder.h"
#include "draco/point_cloud/point_cloud_builder.h"
#include "draco/texture/texture_map.h"
namespace draco {
@ -33,6 +36,7 @@ class MeshSplitterInternal {
struct WorkData : public MeshSplitter::WorkData {
// TriangleSoupMeshBuilder or PointCloudBuilder.
std::vector<BuilderT> builders;
std::vector<int> *att_id_map;
};
// Computes number of elements (faces or points) for each sub-mesh.
@ -46,8 +50,8 @@ class MeshSplitterInternal {
const PointAttribute *split_attribute,
WorkData *work_data) const;
// Builds the meshes from the data accumulated in the builders.
StatusOr<MeshSplitter::MeshVector> BuildMeshes(const Mesh &mesh,
WorkData *work_data) const;
StatusOr<MeshSplitter::MeshVector> BuildMeshes(
const Mesh &mesh, WorkData *work_data, bool deduplicate_vertices) const;
};
namespace {
@ -65,7 +69,9 @@ void AddElementToBuilder(
MeshSplitter::MeshSplitter()
: preserve_materials_(false),
remove_unused_material_indices_(true),
preserve_mesh_features_(false) {}
preserve_mesh_features_(false),
preserve_structural_metadata_(false),
deduplicate_vertices_(true) {}
StatusOr<MeshSplitter::MeshVector> MeshSplitter::SplitMesh(
const Mesh &mesh, uint32_t split_attribute_id) {
@ -86,7 +92,7 @@ StatusOr<MeshSplitter::MeshVector> MeshSplitter::SplitMeshInternal(
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
// |preserve_materials_| flag is set. Otherwise, the split attribute will get
// discarded.
// TODO(ostava): We may revisit this later and add an option to always
// preserve the split attribute.
@ -107,7 +113,8 @@ StatusOr<MeshSplitter::MeshVector> MeshSplitter::SplitMeshInternal(
// 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);
att_id_map_.resize(mesh.num_attributes(), -1);
work_data.att_id_map = &att_id_map_;
const int ignored_att_id =
(!preserve_split_attribute ? split_attribute_id : -1);
for (int mi = 0; mi < num_out_meshes; ++mi) {
@ -126,8 +133,9 @@ StatusOr<MeshSplitter::MeshVector> MeshSplitter::SplitMeshInternal(
splitter_internal.AddElementsToBuilder(mesh, split_attribute, &work_data);
DRACO_ASSIGN_OR_RETURN(MeshVector out_meshes,
splitter_internal.BuildMeshes(mesh, &work_data));
DRACO_ASSIGN_OR_RETURN(
MeshVector out_meshes,
splitter_internal.BuildMeshes(mesh, &work_data, deduplicate_vertices_));
return FinalizeMeshes(mesh, work_data, std::move(out_meshes));
}
@ -179,9 +187,11 @@ void MeshSplitterInternal<BuilderT>::InitializeBuilder(
continue;
}
const GeometryAttribute *const src_att = mesh.attribute(ai);
work_data->att_id_map[ai] = work_data->builders[b_index].AddAttribute(
(*work_data->att_id_map)[ai] = work_data->builders[b_index].AddAttribute(
src_att->attribute_type(), src_att->num_components(),
src_att->data_type());
src_att->data_type(), src_att->normalized());
work_data->builders[b_index].SetAttributeName(work_data->att_id_map->at(ai),
src_att->name());
}
}
@ -220,7 +230,7 @@ void AddElementToBuilder(
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];
const int target_att_id = work_data->att_id_map->at(ai);
if (target_att_id == -1) {
continue;
}
@ -237,7 +247,7 @@ void AddElementToBuilder(
MeshSplitterInternal<PointCloudBuilder>::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];
const int target_att_id = work_data->att_id_map->at(ai);
if (target_att_id == -1) {
continue;
}
@ -252,7 +262,7 @@ void AddElementToBuilder(
template <>
StatusOr<MeshSplitter::MeshVector>
MeshSplitterInternal<TriangleSoupMeshBuilder>::BuildMeshes(
const Mesh &mesh, WorkData *work_data) const {
const Mesh &mesh, WorkData *work_data, bool deduplicate_vertices) 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) {
@ -270,7 +280,7 @@ MeshSplitterInternal<TriangleSoupMeshBuilder>::BuildMeshes(
template <>
StatusOr<MeshSplitter::MeshVector>
MeshSplitterInternal<PointCloudBuilder>::BuildMeshes(
const Mesh &mesh, WorkData *work_data) const {
const Mesh &mesh, WorkData *work_data, bool deduplicate_vertices) 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) {
@ -279,7 +289,8 @@ MeshSplitterInternal<PointCloudBuilder>::BuildMeshes(
}
// For point clouds, we first build a point cloud and copy it over into
// a draco::Mesh.
std::unique_ptr<PointCloud> pc = work_data->builders[mi].Finalize(true);
std::unique_ptr<PointCloud> pc =
work_data->builders[mi].Finalize(deduplicate_vertices);
if (pc == nullptr) {
continue;
}
@ -311,7 +322,66 @@ StatusOr<MeshSplitter::MeshVector> MeshSplitter::FinalizeMeshes(
}
out_meshes[mi]->SetName(mesh.GetName());
if (preserve_materials_) {
out_meshes[mi]->GetMaterialLibrary().Copy(mesh.GetMaterialLibrary());
if (work_data.split_by_materials) {
// When splitting by material, only copy the material in use.
if (out_meshes[mi]->num_points() != 0 &&
mesh.GetMaterialLibrary().NumMaterials() != 0) {
uint64_t material_index = 0;
out_meshes[mi]
->GetNamedAttribute(GeometryAttribute::MATERIAL)
->GetMappedValue(PointIndex(0), &material_index);
// Populate empty materials and textures. Unused materials and
// textures will be cleared later.
out_meshes[mi]->GetMaterialLibrary().MutableMaterial(
mesh.GetMaterialLibrary().NumMaterials() - 1);
for (int i = 0;
i < mesh.GetMaterialLibrary().GetTextureLibrary().NumTextures();
++i) {
out_meshes[mi]
->GetMaterialLibrary()
.MutableTextureLibrary()
.PushTexture(std::make_unique<Texture>());
}
// Copy the material that we're actually going to use.
out_meshes[mi]
->GetMaterialLibrary()
.MutableMaterial(material_index)
->Copy(*mesh.GetMaterialLibrary().GetMaterial(material_index));
std::unordered_map<const Texture *, int> texture_to_index =
mesh.GetMaterialLibrary()
.GetTextureLibrary()
.ComputeTextureToIndexMap();
for (int tmi = 0; tmi < mesh.GetMaterialLibrary()
.GetMaterial(material_index)
->NumTextureMaps();
++tmi) {
const TextureMap *const source_texture_map =
mesh.GetMaterialLibrary()
.GetMaterial(material_index)
->GetTextureMapByIndex(tmi);
// Get the texture map index
const int texture_index =
texture_to_index[source_texture_map->texture()];
// Use the index to assign texture to the corresponding texture map
// on the split mesh.
TextureMap *new_texture_map = out_meshes[mi]
->GetMaterialLibrary()
.MutableMaterial(material_index)
->GetTextureMapByIndex(tmi);
new_texture_map->SetTexture(out_meshes[mi]
->GetMaterialLibrary()
.MutableTextureLibrary()
.GetTexture(texture_index));
new_texture_map->texture()->Copy(*source_texture_map->texture());
}
}
} else {
out_meshes[mi]->GetMaterialLibrary().Copy(mesh.GetMaterialLibrary());
}
}
// Copy metadata of the original mesh to the output meshes.
@ -323,7 +393,7 @@ StatusOr<MeshSplitter::MeshVector> MeshSplitter::FinalizeMeshes(
// 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];
const int mapped_att_id = att_id_map_[att_id];
if (mapped_att_id == -1) {
continue;
}
@ -363,6 +433,14 @@ StatusOr<MeshSplitter::MeshVector> MeshSplitter::FinalizeMeshes(
// Create a copy of source mesh features.
std::unique_ptr<MeshFeatures> mf(new MeshFeatures());
mf->Copy(mesh.GetMeshFeatures(mfi));
// Update mesh features attribute index if used.
if (mf->GetAttributeIndex() != -1) {
const int new_mf_attribute_index =
att_id_map_[mf->GetAttributeIndex()];
mf->SetAttributeIndex(new_mf_attribute_index);
}
const MeshFeaturesIndex new_mfi =
out_meshes[mi]->AddMeshFeatures(std::move(mf));
if (work_data.split_by_materials && !preserve_materials_) {
@ -404,6 +482,62 @@ StatusOr<MeshSplitter::MeshVector> MeshSplitter::FinalizeMeshes(
MeshUtils::RemoveUnusedMeshFeatures(out_meshes[mi].get()));
}
if (preserve_structural_metadata_) {
// Copy proeprty attributes indices from the source |mesh| to the
// |out_meshes[mi]|.
for (int i = 0; i < mesh.NumPropertyAttributesIndices(); ++i) {
if (work_data.split_by_materials) {
// Copy over only those property attribute indices that were masked to
// the material corresponding to |mi|.
bool is_used = false;
if (mesh.NumPropertyAttributesIndexMaterialMasks(i) == 0) {
is_used = true;
} else {
for (int mask_index = 0;
mask_index < mesh.NumPropertyAttributesIndexMaterialMasks(i);
++mask_index) {
if (mesh.GetPropertyAttributesIndexMaterialMask(i, mask_index) ==
mi) {
is_used = true;
break;
}
}
}
if (!is_used) {
// Ignore this property attributes index.
continue;
}
}
// Create a copy of source property attributes index.
const int new_i = out_meshes[mi]->AddPropertyAttributesIndex(
mesh.GetPropertyAttributesIndex(i));
if (work_data.split_by_materials && !preserve_materials_) {
// If the input |mesh| was split by materials and we didn't preserve
// the materials, all property attributes indices must be masked to
// material 0.
out_meshes[mi]->AddPropertyAttributesIndexMaterialMask(new_i, 0);
} else {
// Otherwise property attributes index uses 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.NumPropertyAttributesIndexMaterialMasks(i);
++mask_index) {
out_meshes[mi]->AddPropertyAttributesIndexMaterialMask(
new_i,
mesh.GetPropertyAttributesIndexMaterialMask(i, mask_index));
}
}
}
// This will remove any property attributes indices 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).
DRACO_RETURN_IF_ERROR(MeshUtils::RemoveUnusedPropertyAttributesIndices(
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_) {
@ -424,7 +558,8 @@ StatusOr<MeshSplitter::MeshVector> MeshSplitter::SplitMeshToComponents(
typename MeshSplitterInternal<TriangleSoupMeshBuilder>::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);
att_id_map_.resize(mesh.num_attributes(), -1);
work_data.att_id_map = &att_id_map_;
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;
@ -442,10 +577,15 @@ StatusOr<MeshSplitter::MeshVector> MeshSplitter::SplitMeshToComponents(
AddElementToBuilder(mi, fi, target_fi, mesh, &work_data);
}
}
DRACO_ASSIGN_OR_RETURN(auto out_meshes,
splitter_internal.BuildMeshes(mesh, &work_data));
DRACO_ASSIGN_OR_RETURN(
auto out_meshes,
splitter_internal.BuildMeshes(mesh, &work_data, deduplicate_vertices_));
return FinalizeMeshes(mesh, work_data, std::move(out_meshes));
}
int MeshSplitter::GetSplitMeshAttributeIndex(int source_mesh_att_index) const {
return att_id_map_[source_mesh_att_index];
}
} // namespace draco
#endif // DRACO_TRANSCODER_SUPPORTED

View file

@ -63,6 +63,25 @@ class MeshSplitter {
// Default = false.
void SetPreserveMeshFeatures(bool flag) { preserve_mesh_features_ = flag; }
// Sets a flag that tells the splitter to preserve structural metadata on the
// input mesh during mesh splitting. When set, the structural metadata like
// property attributes indices used on sub-meshes are going to be copied over.
// Any redundant structural metadata on sub-meshes are going to be deleted.
// Default = false.
void SetPreserveStructuralMetadata(bool flag) {
preserve_structural_metadata_ = flag;
}
// By default, the splitter will attempt to deduplicate vertices after
// splitting the mesh. This means lower memory usage and smaller output glTFs
// after encoding. However, for very large meshes, this may become an
// expensive operation. If that becomes an issue, you might want to consider
// disabling deduplication with |SetDeduplicateVertices(false)|.
//
// Note that at this moment, disabling deduplication works ONLY for point
// clouds.
void SetDeduplicateVertices(bool flag) { deduplicate_vertices_ = 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
@ -79,10 +98,13 @@ class MeshSplitter {
StatusOr<MeshVector> SplitMeshToComponents(
const Mesh &mesh, const MeshConnectedComponents &connected_components);
// Returns attribute index on each split mesh that corresponds to the
// |source_mesh_att_index| of the source Mesh.
// Must be called after SplitMesh() or SplitMeshToComponents().
int GetSplitMeshAttributeIndex(int source_mesh_att_index) const;
private:
struct WorkData {
// Map between attribute ids of the input and output meshes.
std::vector<int> att_id_map;
std::vector<int> num_sub_mesh_elements;
bool split_by_materials = false;
};
@ -98,6 +120,11 @@ class MeshSplitter {
bool preserve_materials_;
bool remove_unused_material_indices_;
bool preserve_mesh_features_;
bool preserve_structural_metadata_;
bool deduplicate_vertices_;
// Map between attribute ids of the input and output meshes.
std::vector<int> att_id_map_;
template <typename BuilderT>
friend class MeshSplitterInternal;

View file

@ -15,14 +15,21 @@
#include "draco/mesh/mesh_splitter.h"
#ifdef DRACO_TRANSCODER_SUPPORTED
#include <cstdint>
#include <memory>
#include <unordered_set>
#include <utility>
#include <vector>
#include "draco/attributes/geometry_attribute.h"
#include "draco/core/draco_test_base.h"
#include "draco/core/draco_test_utils.h"
#include "draco/core/draco_types.h"
#include "draco/core/vector_d.h"
#include "draco/io/mesh_io.h"
#include "draco/material/material.h"
#include "draco/mesh/mesh_misc_functions.h"
#include "draco/point_cloud/point_cloud_builder.h"
namespace {} // namespace
#endif // DRACO_TRANSCODER_SUPPORTED

View file

@ -16,6 +16,7 @@
#include <memory>
#include <utility>
#include <vector>
#include "draco/core/draco_test_base.h"
#include "draco/core/draco_test_utils.h"
@ -539,6 +540,57 @@ TEST(MeshTest, MeshCopyWithMeshFeatures) {
library_copy.GetTexture(1));
}
// Tests that mesh features are updated properly after a mesh attribute is
// deleted.
TEST(MeshTest, TestMeshFeaturesAttributeDeletion) {
const std::unique_ptr<draco::Mesh> mesh =
draco::ReadMeshFromTestFile("cube_att.obj");
ASSERT_NE(mesh, nullptr);
// Add feature ID set referring to an attribute.
const draco::MeshFeaturesIndex index_0 = mesh->AddMeshFeatures(
std::unique_ptr<draco::MeshFeatures>(new draco::MeshFeatures()));
mesh->GetMeshFeatures(index_0).SetLabel("planet");
mesh->GetMeshFeatures(index_0).SetFeatureCount(2);
mesh->GetMeshFeatures(index_0).SetAttributeIndex(1);
// Delete mesh attribute 0. This should update attribute index associated with
// mesh features |index_0| by one (to 0).
ASSERT_EQ(mesh->GetMeshFeatures(index_0).GetAttributeIndex(), 1);
mesh->DeleteAttribute(0);
ASSERT_EQ(mesh->GetMeshFeatures(index_0).GetAttributeIndex(), 0);
// Delete the new mesh attribute 0 and the mesh features |index_0| should not
// be associated with any attribute anymore.
mesh->DeleteAttribute(0);
ASSERT_EQ(mesh->GetMeshFeatures(index_0).GetAttributeIndex(), -1);
}
// Tests that we can identify which attributes are used by mesh features.
TEST(MeshTest, TestAttributeUsedByMeshFeatures) {
const std::unique_ptr<draco::Mesh> mesh =
draco::ReadMeshFromTestFile("cube_att.obj");
ASSERT_NE(mesh, nullptr);
// Add feature ID set referring to an attribute.
const draco::MeshFeaturesIndex index_0 = mesh->AddMeshFeatures(
std::unique_ptr<draco::MeshFeatures>(new draco::MeshFeatures()));
mesh->GetMeshFeatures(index_0).SetLabel("planet");
mesh->GetMeshFeatures(index_0).SetFeatureCount(2);
mesh->GetMeshFeatures(index_0).SetAttributeIndex(1);
// Ensure we can tell that attribute 1 is used by mesh features.
ASSERT_TRUE(mesh->IsAttributeUsedByMeshFeatures(1));
// Attribute 0 should not be used by mesh features.
ASSERT_FALSE(mesh->IsAttributeUsedByMeshFeatures(0));
// If the mesh features is deleted, attribute 1 should not be used by mesh
// features any more.
mesh->DeleteAttribute(1);
ASSERT_FALSE(mesh->IsAttributeUsedByMeshFeatures(1));
}
// Tests copying of a mesh with structural metadata.
TEST(MeshTest, TestCopyWithStructuralMetadata) {
const std::unique_ptr<draco::Mesh> mesh =
@ -546,22 +598,32 @@ TEST(MeshTest, TestCopyWithStructuralMetadata) {
ASSERT_NE(mesh, nullptr);
// Add structural metadata to the mesh.
draco::PropertyTable::Schema schema;
draco::StructuralMetadataSchema schema;
schema.json.SetString("Data");
mesh->GetStructuralMetadata().SetPropertyTableSchema(schema);
mesh->GetStructuralMetadata().SetSchema(schema);
mesh->AddPropertyAttributesIndex(0);
mesh->AddPropertyAttributesIndex(1);
// 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");
ASSERT_EQ(copy.GetStructuralMetadata().GetSchema().json.GetString(), "Data");
ASSERT_EQ(copy.NumPropertyAttributesIndices(), 2);
ASSERT_EQ(copy.GetPropertyAttributesIndex(0), 0);
ASSERT_EQ(copy.GetPropertyAttributesIndex(1), 1);
// Check that property attributes index can be removed.
copy.RemovePropertyAttributesIndex(0);
ASSERT_EQ(copy.NumPropertyAttributesIndices(), 1);
ASSERT_EQ(copy.GetPropertyAttributesIndex(0), 1);
}
// Tests removing of unused materials for a mesh with mesh features.
TEST(MeshTest, RemoveUnusedMaterialsWithMeshFeatures) {
// Tests removing of unused materials for a mesh with mesh features and property
// attributes indices.
TEST(MeshTest,
RemoveUnusedMaterialsWithMeshFeaturesAndPropertyAttributesIndices) {
const std::unique_ptr<draco::Mesh> mesh =
draco::ReadMeshFromTestFile("BoxesMeta/glTF/BoxesMeta.gltf");
ASSERT_NE(mesh, nullptr);
@ -580,6 +642,12 @@ TEST(MeshTest, RemoveUnusedMaterialsWithMeshFeatures) {
ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(4), 0),
1);
// Input has two property attributes, one associated with each of the two
// materials.
ASSERT_EQ(mesh->NumPropertyAttributesIndices(), 2);
ASSERT_EQ(mesh->GetPropertyAttributesIndexMaterialMask(0, 0), 0);
ASSERT_EQ(mesh->GetPropertyAttributesIndexMaterialMask(1, 0), 1);
// Remove material 0.
draco::PointAttribute *mat_att = mesh->attribute(
mesh->GetNamedAttributeId(draco::GeometryAttribute::MATERIAL));
@ -588,17 +656,21 @@ TEST(MeshTest, RemoveUnusedMaterialsWithMeshFeatures) {
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.
// by mesh features 0 and 1, as well as by property attributes at index 0.
mesh->RemoveUnusedMaterials();
ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 2);
ASSERT_EQ(mesh->NumMeshFeatures(), 5);
ASSERT_EQ(mesh->NumPropertyAttributesIndices(), 2);
// Now remove unused mesh features (should be 0 and 1).
// Now remove unused mesh features (should be 0 and 1) and property attributes
// indices (should be 0).
DRACO_ASSERT_OK(draco::MeshUtils::RemoveUnusedMeshFeatures(mesh.get()));
DRACO_ASSERT_OK(
draco::MeshUtils::RemoveUnusedPropertyAttributesIndices(mesh.get()));
ASSERT_EQ(mesh->NumMeshFeatures(), 3);
// All remaining mesh features should be still mapped to material 1.
ASSERT_EQ(mesh->NumMeshFeatures(), 3);
ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(0), 0),
1);
ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(1), 0),
@ -606,18 +678,50 @@ TEST(MeshTest, RemoveUnusedMaterialsWithMeshFeatures) {
ASSERT_EQ(mesh->GetMeshFeaturesMaterialMask(draco::MeshFeaturesIndex(2), 0),
1);
// Remaining property attributes index should be still mapped to material 1.
ASSERT_EQ(mesh->NumPropertyAttributesIndices(), 1);
ASSERT_EQ(mesh->GetPropertyAttributesIndexMaterialMask(0, 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.
// Only one material should be remaining.
ASSERT_EQ(mesh->GetMaterialLibrary().NumMaterials(), 1);
// All the mesh features should now be mapped to material 0.
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);
// Property attributes index should now be mapped to material 0.
ASSERT_EQ(mesh->GetPropertyAttributesIndexMaterialMask(0, 0), 0);
}
// Tests that when we remove mesh features from a mesh, the associated vertex
// attributes and textures are not removed.
TEST(MeshTest, TestDeleteMeshFeatures) {
// The loaded mesh has several vertex attributes and textures used by the
// mesh features.
const std::unique_ptr<draco::Mesh> mesh =
draco::ReadMeshFromTestFile("BoxesMeta/glTF/BoxesMeta.gltf");
ASSERT_NE(mesh, nullptr);
ASSERT_GT(mesh->NumMeshFeatures(), 0);
draco::Mesh mesh_copy;
mesh_copy.Copy(*mesh);
// Delete all mesh features from the copy and ensure all vertex attributes
// and textures stay the same.
while (mesh_copy.NumMeshFeatures() > 0) {
mesh_copy.RemoveMeshFeatures(draco::MeshFeaturesIndex(0));
}
ASSERT_EQ(mesh_copy.NumMeshFeatures(), 0);
ASSERT_EQ(mesh_copy.num_attributes(), mesh->num_attributes());
ASSERT_EQ(mesh_copy.GetNonMaterialTextureLibrary().NumTextures(),
mesh->GetNonMaterialTextureLibrary().NumTextures());
}
#endif // DRACO_TRANSCODER_SUPPORTED

View file

@ -167,15 +167,10 @@ void MeshUtils::MergeMetadata(const Mesh &src_mesh, Mesh *dst_mesh) {
}
}
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.
// Returns indices of all used materials on the |mesh|.
std::unordered_set<int> FindUsedMaterials(const Mesh &mesh) {
const PointAttribute *const mat_att =
mesh->GetNamedAttribute(GeometryAttribute::MATERIAL);
// Find which materials are used.
mesh.GetNamedAttribute(GeometryAttribute::MATERIAL);
std::unordered_set<int> used_materials;
if (mat_att == nullptr) {
// Only material with index 0 is assumed to be used.
@ -187,7 +182,16 @@ Status MeshUtils::RemoveUnusedMeshFeatures(Mesh *mesh) {
used_materials.insert(mat_index);
}
}
return used_materials;
}
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 std::unordered_set<int> used_materials = FindUsedMaterials(*mesh);
std::vector<MeshFeaturesIndex> unused_mesh_features;
for (MeshFeaturesIndex mfi(0); mfi < mesh->NumMeshFeatures(); ++mfi) {
bool is_used = false;
@ -247,6 +251,44 @@ Status MeshUtils::RemoveUnusedMeshFeatures(Mesh *mesh) {
return OkStatus();
}
Status MeshUtils::RemoveUnusedPropertyAttributesIndices(Mesh *mesh) {
// Unused property attributes indices are indices that are not used by any
// face / vertex of the |mesh|. Currently, each property attributes index 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
// property attributes from the mesh.
const std::unordered_set<int> used_materials = FindUsedMaterials(*mesh);
std::vector<int> unused_property_attributes_indices;
for (int i = 0; i < mesh->NumPropertyAttributesIndices(); ++i) {
bool is_used = false;
if (mesh->NumPropertyAttributesIndexMaterialMasks(i) == 0) {
is_used = true;
} else {
for (int mask_i = 0;
mask_i < mesh->NumPropertyAttributesIndexMaterialMasks(i);
++mask_i) {
const int material_index =
mesh->GetPropertyAttributesIndexMaterialMask(i, mask_i);
if (used_materials.count(material_index)) {
is_used = true;
break;
}
}
}
if (!is_used) {
unused_property_attributes_indices.push_back(i);
}
}
// Remove the unused property attributes indices (from back).
for (auto it = unused_property_attributes_indices.rbegin();
it != unused_property_attributes_indices.rend(); ++it) {
const int i = *it;
mesh->RemovePropertyAttributesIndex(i);
}
return OkStatus();
}
bool MeshUtils::FlipTextureUvValues(bool flip_u, bool flip_v,
PointAttribute *att) {
if (att->attribute_type() != GeometryAttribute::TEX_COORD) {

View file

@ -42,6 +42,9 @@ class MeshUtils {
// error is returned.
static Status RemoveUnusedMeshFeatures(Mesh *mesh);
// Removes unused property attributes indices from |mesh|.
static Status RemoveUnusedPropertyAttributesIndices(Mesh *mesh);
// Flips the UV values of |att|.
static bool FlipTextureUvValues(bool flip_u, bool flip_v,
PointAttribute *att);

View file

@ -386,6 +386,47 @@ TEST(MeshUtilsTest, RemoveUnusedMeshFeatures) {
}
}
TEST(MeshUtilsTest, RemoveUnusedPropertyAttributesIndices) {
// Test verifies that MeshUtils::RemoveUnusedPropertyAttributesIndices works
// as intended.
std::unique_ptr<draco::Mesh> mesh =
draco::ReadMeshFromTestFile("BoxesMeta/glTF/BoxesMeta.gltf");
ASSERT_NE(mesh, nullptr);
// The input mesh should have two property attributes indices.
ASSERT_EQ(mesh->NumPropertyAttributesIndices(), 2);
ASSERT_EQ(mesh->GetPropertyAttributesIndex(0), 0);
ASSERT_EQ(mesh->GetPropertyAttributesIndex(1), 1);
ASSERT_EQ(mesh->NumPropertyAttributesIndexMaterialMasks(0), 1);
ASSERT_EQ(mesh->NumPropertyAttributesIndexMaterialMasks(1), 1);
ASSERT_EQ(mesh->GetPropertyAttributesIndexMaterialMask(0, 0), 0);
ASSERT_EQ(mesh->GetPropertyAttributesIndexMaterialMask(1, 0), 1);
// Both indices should be used so calling the method below shouldn't do
// anything.
draco::MeshUtils::RemoveUnusedPropertyAttributesIndices(mesh.get());
ASSERT_EQ(mesh->NumPropertyAttributesIndices(), 2);
// Now remove material 1 that is mapped to second property attributes index.
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 property attributes indices again.
draco::MeshUtils::RemoveUnusedPropertyAttributesIndices(mesh.get());
// One of the property attributes indices should have been removed.
ASSERT_EQ(mesh->NumPropertyAttributesIndices(), 1);
// Ensure the remaining property attributes index is mapped to the correct
// material.
ASSERT_EQ(mesh->NumPropertyAttributesIndexMaterialMasks(0), 1);
ASSERT_EQ(mesh->GetPropertyAttributesIndexMaterialMask(0, 0), 0);
}
} // namespace
#endif // DRACO_TRANSCODER_SUPPORTED

View file

@ -14,6 +14,8 @@
//
#include "draco/mesh/triangle_soup_mesh_builder.h"
#include <string>
namespace draco {
void TriangleSoupMeshBuilder::Start(int num_faces) {
@ -96,4 +98,16 @@ std::unique_ptr<Mesh> TriangleSoupMeshBuilder::Finalize() {
return std::move(mesh_);
}
void TriangleSoupMeshBuilder::SetAttributeUniqueId(int att_id,
uint32_t unique_id) {
mesh_->attribute(att_id)->set_unique_id(unique_id);
}
#ifdef DRACO_TRANSCODER_SUPPORTED
void TriangleSoupMeshBuilder::SetAttributeName(int att_id,
const std::string &name) {
mesh_->attribute(att_id)->set_name(name);
}
#endif // DRACO_TRANSCODER_SUPPORTED
} // namespace draco

View file

@ -80,6 +80,14 @@ class TriangleSoupMeshBuilder {
mesh_->AddMetadata(std::move(metadata));
}
// Sets the unique ID for an attribute created with AddAttribute().
void SetAttributeUniqueId(int att_id, uint32_t unique_id);
#ifdef DRACO_TRANSCODER_SUPPORTED
// Sets attribute name.
void SetAttributeName(int att_id, const std::string &name);
#endif // DRACO_TRANSCODER_SUPPORTED
// Add metadata for an attribute.
void AddAttributeMetadata(int32_t att_id,
std::unique_ptr<AttributeMetadata> metadata) {

View file

@ -96,11 +96,15 @@ TEST_F(TriangleSoupMeshBuilderTest, CubeTest) {
Vector3f(0.f, 0.f, 1.f).data(),
Vector3f(0.f, 1.f, 0.f).data());
// clang-format on
#ifdef DRACO_TRANSCODER_SUPPORTED
mb.SetAttributeName(pos_att_id, "Bob");
#endif
std::unique_ptr<Mesh> mesh = mb.Finalize();
ASSERT_NE(mesh, nullptr) << "Failed to build the cube mesh.";
#ifdef DRACO_TRANSCODER_SUPPORTED
EXPECT_EQ(mesh->GetName(), "Cube");
EXPECT_EQ(mesh->attribute(pos_att_id)->name(), "Bob");
#endif
EXPECT_EQ(mesh->num_points(), 8) << "Unexpected number of vertices.";
EXPECT_EQ(mesh->num_faces(), 12) << "Unexpected number of faces.";
@ -207,6 +211,22 @@ TEST_F(TriangleSoupMeshBuilderTest, TestPerFaceAttribs) {
<< "Unexpected attribute element type.";
}
TEST_F(TriangleSoupMeshBuilderTest, PropagatesAttributeUniqueIds) {
// This test verifies that TriangleSoupMeshBuilder correctly applies
// unique IDs to attributes.
TriangleSoupMeshBuilder mb;
mb.Start(1);
const int pos_att_id =
mb.AddAttribute(GeometryAttribute::POSITION, 3, DT_FLOAT32);
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());
mb.SetAttributeUniqueId(pos_att_id, 1234);
std::unique_ptr<Mesh> mesh = mb.Finalize();
ASSERT_NE(mesh, nullptr);
ASSERT_EQ(mesh->GetAttributeByUniqueId(1234), mesh->attribute(pos_att_id));
}
#ifdef DRACO_TRANSCODER_SUPPORTED
TEST_F(TriangleSoupMeshBuilderTest, NormalizedColor) {
// This tests, verifies that the mesh builder constructs a valid model with

View file

@ -0,0 +1,106 @@
// Copyright 2023 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_attribute.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#ifdef DRACO_TRANSCODER_SUPPORTED
namespace draco {
bool PropertyAttribute::Property::operator==(const Property &other) const {
return name_ == other.name_ && attribute_name_ == other.attribute_name_;
}
void PropertyAttribute::Property::Copy(const Property &src) {
name_ = src.name_;
attribute_name_ = src.attribute_name_;
}
void PropertyAttribute::Property::SetName(const std::string &name) {
name_ = name;
}
const std::string &PropertyAttribute::Property::GetName() const {
return name_;
}
void PropertyAttribute::Property::SetAttributeName(const std::string &name) {
attribute_name_ = name;
}
const std::string &PropertyAttribute::Property::GetAttributeName() const {
return attribute_name_;
}
bool PropertyAttribute::operator==(const PropertyAttribute &other) const {
if (name_ != other.name_ || class_ != other.class_ ||
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 PropertyAttribute::Copy(const PropertyAttribute &src) {
name_ = src.name_;
class_ = src.class_;
properties_.clear();
properties_.reserve(src.properties_.size());
for (int i = 0; i < src.properties_.size(); ++i) {
std::unique_ptr<Property> property(new Property());
property->Copy(src.GetProperty(i));
properties_.push_back(std::move(property));
}
}
void PropertyAttribute::SetName(const std::string &value) { name_ = value; }
const std::string &PropertyAttribute::GetName() const { return name_; }
void PropertyAttribute::SetClass(const std::string &value) { class_ = value; }
const std::string &PropertyAttribute::GetClass() const { return class_; }
int PropertyAttribute::AddProperty(std::unique_ptr<Property> property) {
properties_.push_back(std::move(property));
return properties_.size() - 1;
}
int PropertyAttribute::NumProperties() const { return properties_.size(); }
const PropertyAttribute::Property &PropertyAttribute::GetProperty(
int index) const {
return *properties_[index];
}
PropertyAttribute::Property &PropertyAttribute::GetProperty(int index) {
return *properties_[index];
}
void PropertyAttribute::RemoveProperty(int index) {
properties_.erase(properties_.begin() + index);
}
} // namespace draco
#endif // DRACO_TRANSCODER_SUPPORTED

View file

@ -0,0 +1,107 @@
// Copyright 2023 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_ATTRIBUTE_H_
#define DRACO_METADATA_PROPERTY_ATTRIBUTE_H_
#include "draco/draco_features.h"
#ifdef DRACO_TRANSCODER_SUPPORTED
#include <memory>
#include <string>
#include <vector>
#include "draco/core/status_or.h"
namespace draco {
// Describes a property attribute as defined in the EXT_structural_metadata glTF
// extension.
class PropertyAttribute {
public:
// Describes where property is stored (as an attribute).
class Property {
public:
// Creates an empty property.
Property() = default;
// 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;
// Name of glTF attribute containing property values, like "_DIRECTION".
void SetAttributeName(const std::string &name);
const std::string &GetAttributeName() const;
private:
// Name of this property as in structural metadata schema class property.
std::string name_;
// Name of glTF attribute containing property values, like "_DIRECTION".
std::string attribute_name_;
// TODO(vytyaz): Support property value modifiers min, max, offset, scale.
};
// Creates an empty property attribute.
PropertyAttribute() = default;
// Methods for comparing two property attributes.
bool operator==(const PropertyAttribute &other) const;
bool operator!=(const PropertyAttribute &other) const {
return !(*this == other);
}
// Copies all data from |src| property attribute.
void Copy(const PropertyAttribute &src);
// Name of this property attribute.
void SetName(const std::string &value);
const std::string &GetName() const;
// Class of this property attribute.
void SetClass(const std::string &value);
const std::string &GetClass() const;
// Properties.
int AddProperty(std::unique_ptr<Property> property);
int NumProperties() const;
const Property &GetProperty(int index) const;
Property &GetProperty(int index);
void RemoveProperty(int index);
private:
// The name of the property attribute, e.g., for display purposes.
std::string name_;
// The class in structural metadata schema that property values conform to.
std::string class_;
// Properties corresponding to schema class properties, describing where the
// property values are stored (as attributes).
std::vector<std::unique_ptr<Property>> properties_;
};
} // namespace draco
#endif // DRACO_TRANSCODER_SUPPORTED
#endif // DRACO_METADATA_PROPERTY_ATTRIBUTE_H_

View file

@ -0,0 +1,247 @@
// Copyright 2023 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_attribute.h"
#include <memory>
#include <string>
#include <utility>
#include "draco/core/draco_test_utils.h"
namespace {
#ifdef DRACO_TRANSCODER_SUPPORTED
TEST(PropertyAttributeTest, TestPropertyDefaults) {
// Test construction of an empty property attribute property.
draco::PropertyAttribute::Property property;
ASSERT_TRUE(property.GetName().empty());
ASSERT_TRUE(property.GetAttributeName().empty());
}
TEST(PropertyAttributeTest, TestPropertyAttributeDefaults) {
// Test construction of an empty property attribute.
draco::PropertyAttribute attribute;
ASSERT_TRUE(attribute.GetName().empty());
ASSERT_TRUE(attribute.GetClass().empty());
ASSERT_EQ(attribute.NumProperties(), 0);
}
TEST(PropertyAttributeTest, TestPropertySettersAndGetters) {
// Test setter and getter methods of the property attribute property.
draco::PropertyAttribute::Property property;
property.SetName("The magnitude.");
property.SetAttributeName("_MAGNITUDE");
// Check that property members can be accessed via getters.
ASSERT_EQ(property.GetName(), "The magnitude.");
ASSERT_EQ(property.GetAttributeName(), "_MAGNITUDE");
}
TEST(PropertyAttributeTest, TestPropertyAttributeSettersAndGetters) {
// Test setter and getter methods of the property attribute.
draco::PropertyAttribute attribute;
attribute.SetName("The movement.");
attribute.SetClass("movement");
{
std::unique_ptr<draco::PropertyAttribute::Property> property(
new draco::PropertyAttribute::Property());
property->SetName("The magnitude.");
property->SetAttributeName("_MAGNITUDE");
ASSERT_EQ(attribute.AddProperty(std::move(property)), 0);
}
{
std::unique_ptr<draco::PropertyAttribute::Property> property(
new draco::PropertyAttribute::Property());
property->SetName("The direction.");
property->SetAttributeName("_DIRECTION");
ASSERT_EQ(attribute.AddProperty(std::move(property)), 1);
}
// Check that property attribute members can be accessed via getters.
ASSERT_EQ(attribute.GetName(), "The movement.");
ASSERT_EQ(attribute.GetClass(), "movement");
ASSERT_EQ(attribute.NumProperties(), 2);
ASSERT_EQ(attribute.GetProperty(0).GetName(), "The magnitude.");
ASSERT_EQ(attribute.GetProperty(0).GetAttributeName(), "_MAGNITUDE");
ASSERT_EQ(attribute.GetProperty(1).GetName(), "The direction.");
ASSERT_EQ(attribute.GetProperty(1).GetAttributeName(), "_DIRECTION");
// Check that properties can be removed.
attribute.RemoveProperty(0);
ASSERT_EQ(attribute.NumProperties(), 1);
ASSERT_EQ(attribute.GetProperty(0).GetName(), "The direction.");
ASSERT_EQ(attribute.GetProperty(0).GetAttributeName(), "_DIRECTION");
attribute.RemoveProperty(0);
ASSERT_EQ(attribute.NumProperties(), 0);
}
TEST(PropertyAttributeTest, TestPropertyCopy) {
// Test that property attribute property can be copied.
draco::PropertyAttribute::Property property;
property.SetName("The direction.");
property.SetAttributeName("_DIRECTION");
// Make a copy.
draco::PropertyAttribute::Property copy;
copy.Copy(property);
// Check the copy.
ASSERT_EQ(copy.GetName(), "The direction.");
ASSERT_EQ(copy.GetAttributeName(), "_DIRECTION");
}
TEST(PropertyAttributeTest, TestPropertyAttributeCopy) {
// Test that property attribute can be copied.
draco::PropertyAttribute attribute;
attribute.SetName("The movement.");
attribute.SetClass("movement");
{
std::unique_ptr<draco::PropertyAttribute::Property> property(
new draco::PropertyAttribute::Property());
property->SetName("The magnitude.");
property->SetAttributeName("_MAGNITUDE");
ASSERT_EQ(attribute.AddProperty(std::move(property)), 0);
}
{
std::unique_ptr<draco::PropertyAttribute::Property> property(
new draco::PropertyAttribute::Property());
property->SetName("The direction.");
property->SetAttributeName("_DIRECTION");
ASSERT_EQ(attribute.AddProperty(std::move(property)), 1);
}
// Make a copy.
draco::PropertyAttribute copy;
copy.Copy(attribute);
// Check the copy.
ASSERT_EQ(attribute.GetName(), "The movement.");
ASSERT_EQ(attribute.GetClass(), "movement");
ASSERT_EQ(attribute.NumProperties(), 2);
ASSERT_EQ(attribute.GetProperty(0).GetName(), "The magnitude.");
ASSERT_EQ(attribute.GetProperty(0).GetAttributeName(), "_MAGNITUDE");
ASSERT_EQ(attribute.GetProperty(1).GetName(), "The direction.");
ASSERT_EQ(attribute.GetProperty(1).GetAttributeName(), "_DIRECTION");
}
TEST(PropertyAttributeTest, TestPropertyCompare) {
// Test comparison of two properties.
typedef draco::PropertyAttribute::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("The magnitude.");
b.SetName("The direction.");
ASSERT_FALSE(a == b);
ASSERT_TRUE(a != b);
}
{
// Compare two property objects with different attribute names.
Property a;
Property b;
a.SetAttributeName("_MAGNITUDE");
b.SetAttributeName("_DIRECTION");
ASSERT_FALSE(a == b);
ASSERT_TRUE(a != b);
}
}
TEST(PropertyAttributeTest, TestPropertyAttributeCompare) {
// Test comparison of two property attributes.
typedef draco::PropertyAttribute PropertyAttribute;
typedef draco::PropertyAttribute::Property Property;
{
// Compare the same property attribute object.
PropertyAttribute a;
ASSERT_TRUE(a == a);
ASSERT_FALSE(a != a);
}
{
// Compare two default property attributes.
PropertyAttribute a;
PropertyAttribute b;
ASSERT_TRUE(a == b);
ASSERT_FALSE(a != b);
}
{
// Compare two property attributes with different names.
PropertyAttribute a;
PropertyAttribute b;
a.SetName("The movement.");
b.SetName("The reflection.");
ASSERT_FALSE(a == b);
ASSERT_TRUE(a != b);
}
{
// Compare two property attributes with different classes.
PropertyAttribute a;
PropertyAttribute b;
a.SetClass("movement");
b.SetClass("reflection");
ASSERT_FALSE(a == b);
ASSERT_TRUE(a != b);
}
{
// Compare two property attributes with identical properties.
PropertyAttribute a;
PropertyAttribute b;
a.AddProperty(std::unique_ptr<Property>(new Property));
b.AddProperty(std::unique_ptr<Property>(new Property));
ASSERT_TRUE(a == b);
ASSERT_FALSE(a != b);
}
{
// Compare two property attributes with different number of properties.
PropertyAttribute a;
PropertyAttribute b;
a.AddProperty(std::unique_ptr<Property>(new Property));
b.AddProperty(std::unique_ptr<Property>(new Property));
b.AddProperty(std::unique_ptr<Property>(new Property));
ASSERT_FALSE(a == b);
ASSERT_TRUE(a != b);
}
{
// Compare two property attributes with different properties.
PropertyAttribute a;
PropertyAttribute b;
std::unique_ptr<Property> p1(new Property);
std::unique_ptr<Property> p2(new Property);
p1->SetName("The magnitude.");
p2->SetName("The direction.");
a.AddProperty(std::move(p1));
b.AddProperty(std::move(p2));
ASSERT_FALSE(a == b);
ASSERT_TRUE(a != b);
}
}
#endif // DRACO_TRANSCODER_SUPPORTED
} // namespace

View file

@ -23,111 +23,56 @@
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 {
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 {
bool PropertyTable::Property::Offsets::operator==(const Offsets &other) const {
return data == other.data && type == other.type;
}
bool PropertyTable::Property::operator==(const Property& other) const {
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) {
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_; }
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() {
PropertyTable::Property::Data &PropertyTable::Property::GetData() {
return data_;
}
const PropertyTable::Property::Data& PropertyTable::Property::GetData() const {
const PropertyTable::Property::Data &PropertyTable::Property::GetData() const {
return data_;
}
const PropertyTable::Property::Offsets&
const PropertyTable::Property::Offsets &
PropertyTable::Property::GetArrayOffsets() const {
return array_offsets_;
}
PropertyTable::Property::Offsets& PropertyTable::Property::GetArrayOffsets() {
PropertyTable::Property::Offsets &PropertyTable::Property::GetArrayOffsets() {
return array_offsets_;
}
const PropertyTable::Property::Offsets&
const PropertyTable::Property::Offsets &
PropertyTable::Property::GetStringOffsets() const {
return string_offsets_;
}
PropertyTable::Property::Offsets& PropertyTable::Property::GetStringOffsets() {
PropertyTable::Property::Offsets &PropertyTable::Property::GetStringOffsets() {
return string_offsets_;
}
PropertyTable::PropertyTable() : count_(0) {}
bool PropertyTable::operator==(const PropertyTable& other) const {
bool PropertyTable::operator==(const PropertyTable &other) const {
if (name_ != other.name_ || class_ != other.class_ ||
count_ != other.count_ ||
properties_.size() != other.properties_.size()) {
@ -141,7 +86,7 @@ bool PropertyTable::operator==(const PropertyTable& other) const {
return true;
}
void PropertyTable::Copy(const PropertyTable& src) {
void PropertyTable::Copy(const PropertyTable &src) {
name_ = src.name_;
class_ = src.class_;
count_ = src.count_;
@ -154,11 +99,11 @@ void PropertyTable::Copy(const PropertyTable& src) {
}
}
void PropertyTable::SetName(const std::string& value) { name_ = value; }
const std::string& PropertyTable::GetName() const { return name_; }
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::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_; }
@ -168,10 +113,10 @@ int PropertyTable::AddProperty(std::unique_ptr<Property> property) {
return properties_.size() - 1;
}
int PropertyTable::NumProperties() const { return properties_.size(); }
const PropertyTable::Property& PropertyTable::GetProperty(int index) const {
const PropertyTable::Property &PropertyTable::GetProperty(int index) const {
return *properties_[index];
}
PropertyTable::Property& PropertyTable::GetProperty(int index) {
PropertyTable::Property &PropertyTable::GetProperty(int index) {
return *properties_[index];
}
void PropertyTable::RemoveProperty(int index) {

View file

@ -19,116 +19,29 @@
#ifdef DRACO_TRANSCODER_SUPPORTED
#include <cstdint>
#include <cstring>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "draco/core/status_or.h"
namespace draco {
// Describes a property table as defined in the EXT_structural_metadata glTF
// extension, including property table schema and table properties (columns).
// Describes a property table (properties are table columns) as defined in the
// EXT_structural_metadata glTF extension.
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<Object>& GetObjects() const { return objects_; }
const std::vector<Object>& 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<Object>& SetObjects() {
type_ = OBJECT;
return objects_;
}
std::vector<Object>& 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<Object> objects_;
std::vector<Object> 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); }
bool operator==(const Data &other) const;
bool operator!=(const Data &other) const { return !(*this == other); }
// Buffer view data.
std::vector<uint8_t> data;
@ -143,36 +56,102 @@ class PropertyTable {
// arrays.
struct Offsets {
// Methods for comparing two offsets.
bool operator==(const Offsets& other) const;
bool operator!=(const Offsets& other) const { return !(*this == other); }
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;
// Builds a new Offsets object given the offsets in |ints|. The resultant
// offsets will choose the smallest possible result.type that can contain
// all of the input |ints|.
static Offsets MakeFromInts(const std::vector<uint64_t> &ints) {
uint64_t max_value = 0;
for (uint64_t i = 0; i < ints.size(); ++i) {
if (ints[i] > max_value) {
max_value = ints[i];
}
}
Offsets result;
int bytes_per_int = 0;
if (max_value <= std::numeric_limits<uint8_t>::max()) {
result.type = "UINT8";
bytes_per_int = 1;
} else if (max_value <= std::numeric_limits<uint16_t>::max()) {
result.type = "UINT16";
bytes_per_int = 2;
} else if (max_value <= std::numeric_limits<uint32_t>::max()) {
result.type = "UINT32";
bytes_per_int = 4;
} else {
result.type = "UINT64";
bytes_per_int = 8;
}
result.data.data.resize(ints.size() * bytes_per_int);
for (uint64_t i = 0; i < ints.size(); ++i) {
// This assumes execution on a little endian platform.
memcpy(&result.data.data[i * bytes_per_int], &ints[i], bytes_per_int);
}
return result;
}
// Decodes the binary data in Offsets::data into offset integers as
// defined by the EXT_structural_metadata extension. Returns an error if
// Offsets::type is not one of the types allowed by the spec.
StatusOr<std::vector<uint64_t>> ParseToInts() const {
if (data.data.empty()) {
return std::vector<uint64_t>();
}
int bytes_per_int = 0;
if (type == "UINT8") {
bytes_per_int = 1;
} else if (type == "UINT16") {
bytes_per_int = 2;
} else if (type == "UINT32") {
bytes_per_int = 4;
} else if (type == "UINT64") {
bytes_per_int = 8;
} else {
return Status(Status::DRACO_ERROR, "Offsets data type invalid");
}
const int count = data.data.size() / bytes_per_int;
std::vector<uint64_t> result(count);
for (int i = 0; i < count; ++i) {
// This assumes execution on a little endian platform.
memcpy(&result[i], &data.data[i * bytes_per_int], bytes_per_int);
}
return result;
}
};
// Creates an empty property.
Property();
Property() = default;
// Methods for comparing two properties.
bool operator==(const Property& other) const;
bool operator!=(const Property& other) const { return !(*this == other); }
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);
void Copy(const Property &src);
// Name of this property.
void SetName(const std::string& name);
const std::string& GetName() const;
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;
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
@ -180,16 +159,16 @@ class PropertyTable {
// 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();
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();
const Offsets &GetStringOffsets() const;
Offsets &GetStringOffsets();
private:
std::string name_;
@ -203,21 +182,21 @@ class PropertyTable {
PropertyTable();
// Methods for comparing two property tables.
bool operator==(const PropertyTable& other) const;
bool operator!=(const PropertyTable& other) const {
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);
void Copy(const PropertyTable &src);
// Name of this property table.
void SetName(const std::string& value);
const std::string& GetName() const;
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;
void SetClass(const std::string &value);
const std::string &GetClass() const;
// Number of rows in this property table.
void SetCount(int count);
@ -226,8 +205,8 @@ class PropertyTable {
// Table properties (columns).
int AddProperty(std::unique_ptr<Property> property);
int NumProperties() const;
const Property& GetProperty(int index) const;
Property& GetProperty(int index);
const Property &GetProperty(int index) const;
Property &GetProperty(int index);
void RemoveProperty(int index);
private:

View file

@ -14,11 +14,12 @@
//
#include "draco/metadata/property_table.h"
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "draco/core/draco_test_base.h"
#include "draco/core/draco_test_utils.h"
namespace {
@ -60,266 +61,6 @@ TEST(PropertyTableTest, TestPropertyTableDefaults) {
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;
@ -619,6 +360,78 @@ TEST(PropertyTableTest, TestPropertyTableCompare) {
}
}
TEST(PropertyTableTest, EnodesAndDecodesOffsetBuffers) {
{
// Encoding an offset buffer from small integers that should fit in an 8 bit
// integer.
std::vector<uint64_t> sample_offsets = {0x5u, 0x21u, 0x7u, 0x32u, 0xffu};
auto encoded_offsets =
draco::PropertyTable::Property::Offsets::MakeFromInts(sample_offsets);
ASSERT_EQ(encoded_offsets.data.data,
std::vector<uint8_t>({0x5u, 0x21u, 0x7u, 0x32u, 0xffu}));
ASSERT_EQ(encoded_offsets.type, "UINT8");
DRACO_ASSIGN_OR_ASSERT(std::vector<uint64_t> decoded_offsets,
encoded_offsets.ParseToInts());
ASSERT_EQ(decoded_offsets, sample_offsets);
}
{
// Encoding an offset buffer from medium sized integers that should fit in a
// 16 bit integer.
std::vector<uint64_t> sample_offsets = {0x5u, 0x21u, 0xffffu};
auto encoded_offsets =
draco::PropertyTable::Property::Offsets::MakeFromInts(sample_offsets);
ASSERT_EQ(encoded_offsets.data.data,
std::vector<uint8_t>({0x5u, 0u, 0x21u, 0u, 0xffu, 0xffu}));
ASSERT_EQ(encoded_offsets.type, "UINT16");
DRACO_ASSIGN_OR_ASSERT(std::vector<uint64_t> decoded_offsets,
encoded_offsets.ParseToInts());
ASSERT_EQ(decoded_offsets, sample_offsets);
}
{
// Encoding an offset buffer from medium sized integers that should fit in a
// 32 bit integer.
std::vector<uint64_t> sample_offsets = {0x5u, 0x21u, 0xffffffffu};
auto encoded_offsets =
draco::PropertyTable::Property::Offsets::MakeFromInts(sample_offsets);
ASSERT_EQ(encoded_offsets.data.data,
std::vector<uint8_t>({0x5u, 0u, 0u, 0u, 0x21u, 0u, 0u, 0u, 0xffu,
0xffu, 0xffu, 0xffu}));
ASSERT_EQ(encoded_offsets.type, "UINT32");
DRACO_ASSIGN_OR_ASSERT(std::vector<uint64_t> decoded_offsets,
encoded_offsets.ParseToInts());
ASSERT_EQ(decoded_offsets, sample_offsets);
}
{
// Encoding an offset buffer from large integers that won't fit in a 32 bit
// integer.
std::vector<uint64_t> sample_offsets = {0x5u, 0x21u, 0x100000000u};
auto encoded_offsets =
draco::PropertyTable::Property::Offsets::MakeFromInts(sample_offsets);
ASSERT_EQ(encoded_offsets.data.data,
std::vector<uint8_t>({0x5u, 0u, 0u, 0u, 0u, 0u, 0u, 0u,
0x21u, 0u, 0u, 0u, 0u, 0u, 0u, 0u,
0u, 0u, 0u, 0u, 1u, 0u, 0u, 0u}));
ASSERT_EQ(encoded_offsets.type, "UINT64");
DRACO_ASSIGN_OR_ASSERT(std::vector<uint64_t> decoded_offsets,
encoded_offsets.ParseToInts());
ASSERT_EQ(decoded_offsets, sample_offsets);
}
{
// Decoding a malformed buffer should return an error.
draco::PropertyTable::Property::Offsets broken_offsets;
broken_offsets.data.data = std::vector<uint8_t>({0, 0, 0, 0});
broken_offsets.type = "BROKEN_TYPE";
draco::StatusOr<std::vector<uint64_t>> decoded_offsets =
broken_offsets.ParseToInts();
ASSERT_FALSE(decoded_offsets.ok());
}
}
#endif // DRACO_TRANSCODER_SUPPORTED
} // namespace

View file

@ -21,30 +21,54 @@
namespace draco {
StructuralMetadata::StructuralMetadata() {}
// Returns true if vectors |a| and |b| have the same size and their entries
// (unique pointers) point to objects that compare equally.
template <typename T>
bool VectorsAreEqual(const std::vector<std::unique_ptr<T>> &a,
const std::vector<std::unique_ptr<T>> &b) {
if (a.size() != b.size()) {
return false;
}
for (int i = 0; i < a.size(); ++i) {
if (*a[i] != *b[i]) {
return false;
}
}
return true;
}
bool StructuralMetadata::operator==(const StructuralMetadata &other) const {
return property_table_schema_ == other.property_table_schema_ &&
property_tables_ == other.property_tables_;
return schema_ == other.schema_ &&
VectorsAreEqual(property_tables_, other.property_tables_) &&
VectorsAreEqual(property_attributes_, other.property_attributes_);
}
void StructuralMetadata::Copy(const StructuralMetadata &src) {
property_table_schema_.json.Copy(src.property_table_schema_.json);
// Copy schema.
schema_.json.Copy(src.schema_.json);
// Copy property tables.
property_tables_.resize(src.property_tables_.size());
for (int i = 0; i < property_tables_.size(); ++i) {
property_tables_[i] = std::unique_ptr<PropertyTable>(new PropertyTable());
property_tables_[i]->Copy(*src.property_tables_[i]);
}
// Copy property attributes.
property_attributes_.resize(src.property_attributes_.size());
for (int i = 0; i < property_attributes_.size(); ++i) {
property_attributes_[i] =
std::unique_ptr<PropertyAttribute>(new PropertyAttribute());
property_attributes_[i]->Copy(*src.property_attributes_[i]);
}
}
void StructuralMetadata::SetPropertyTableSchema(
const PropertyTable::Schema &schema) {
property_table_schema_ = schema;
void StructuralMetadata::SetSchema(const StructuralMetadataSchema &schema) {
schema_ = schema;
}
const PropertyTable::Schema &StructuralMetadata::GetPropertyTableSchema()
const {
return property_table_schema_;
const StructuralMetadataSchema &StructuralMetadata::GetSchema() const {
return schema_;
}
int StructuralMetadata::AddPropertyTable(
@ -69,6 +93,29 @@ void StructuralMetadata::RemovePropertyTable(int index) {
property_tables_.erase(property_tables_.begin() + index);
}
int StructuralMetadata::AddPropertyAttribute(
std::unique_ptr<PropertyAttribute> property_attribute) {
property_attributes_.push_back(std::move(property_attribute));
return property_attributes_.size() - 1;
}
int StructuralMetadata::NumPropertyAttributes() const {
return property_attributes_.size();
}
const PropertyAttribute &StructuralMetadata::GetPropertyAttribute(
int index) const {
return *property_attributes_[index];
}
PropertyAttribute &StructuralMetadata::GetPropertyAttribute(int index) {
return *property_attributes_[index];
}
void StructuralMetadata::RemovePropertyAttribute(int index) {
property_attributes_.erase(property_attributes_.begin() + index);
}
} // namespace draco
#endif // DRACO_TRANSCODER_SUPPORTED

View file

@ -20,17 +20,18 @@
#ifdef DRACO_TRANSCODER_SUPPORTED
#include <memory>
#include <string>
#include <vector>
#include "draco/metadata/property_attribute.h"
#include "draco/metadata/property_table.h"
#include "draco/metadata/structural_metadata_schema.h"
namespace draco {
// Holds data associated with EXT_structural_metadata glTF extension.
class StructuralMetadata {
public:
StructuralMetadata();
StructuralMetadata() = default;
// Methods for comparing two structural metadata objects.
bool operator==(const StructuralMetadata &other) const;
@ -41,9 +42,9 @@ class StructuralMetadata {
// 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;
// Schema of the structural metadata.
void SetSchema(const StructuralMetadataSchema &schema);
const StructuralMetadataSchema &GetSchema() const;
// Property tables.
int AddPropertyTable(std::unique_ptr<PropertyTable> property_table);
@ -52,10 +53,23 @@ class StructuralMetadata {
PropertyTable &GetPropertyTable(int index);
void RemovePropertyTable(int index);
// Property attributes.
int AddPropertyAttribute(
std::unique_ptr<PropertyAttribute> property_attribute);
int NumPropertyAttributes() const;
const PropertyAttribute &GetPropertyAttribute(int index) const;
PropertyAttribute &GetPropertyAttribute(int index);
void RemovePropertyAttribute(int index);
private:
// Property table schema and property tables.
PropertyTable::Schema property_table_schema_;
// Schema of the structural metadata.
StructuralMetadataSchema schema_;
// Property tables.
std::vector<std::unique_ptr<PropertyTable>> property_tables_;
// Property attributes.
std::vector<std::unique_ptr<PropertyAttribute>> property_attributes_;
};
} // namespace draco

View file

@ -0,0 +1,158 @@
// Copyright 2023 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_schema.h"
#include <string>
#include <vector>
#ifdef DRACO_TRANSCODER_SUPPORTED
namespace draco {
StructuralMetadataSchema::Object::Object() : Object("") {}
StructuralMetadataSchema::Object::Object(const std::string &name)
: name_(name), type_(OBJECT), integer_(0), boolean_(false) {}
StructuralMetadataSchema::Object::Object(const std::string &name,
const std::string &value)
: Object(name) {
SetString(value);
}
StructuralMetadataSchema::Object::Object(const std::string &name,
const char *value)
: Object(name) {
SetString(value);
}
StructuralMetadataSchema::Object::Object(const std::string &name, int value)
: Object(name) {
SetInteger(value);
}
StructuralMetadataSchema::Object::Object(const std::string &name, bool value)
: Object(name) {
SetBoolean(value);
}
bool StructuralMetadataSchema::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;
}
bool StructuralMetadataSchema::Object::operator!=(const Object &other) const {
return !(*this == other);
}
void StructuralMetadataSchema::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_;
}
const StructuralMetadataSchema::Object *
StructuralMetadataSchema::Object::GetObjectByName(
const std::string &name) const {
for (const Object &obj : objects_) {
if (obj.GetName() == name) {
return &obj;
}
}
return nullptr;
}
std::vector<StructuralMetadataSchema::Object> &
StructuralMetadataSchema::Object::SetObjects() {
type_ = OBJECT;
return objects_;
}
std::vector<StructuralMetadataSchema::Object> &
StructuralMetadataSchema::Object::SetArray() {
type_ = ARRAY;
return array_;
}
void StructuralMetadataSchema::Object::SetString(const std::string &value) {
type_ = STRING;
string_ = value;
}
void StructuralMetadataSchema::Object::SetInteger(int value) {
type_ = INTEGER;
integer_ = value;
}
void StructuralMetadataSchema::Object::SetBoolean(bool value) {
type_ = BOOLEAN;
boolean_ = value;
}
bool StructuralMetadataSchema::operator==(
const StructuralMetadataSchema &other) const {
return json == other.json;
}
bool StructuralMetadataSchema::operator!=(
const StructuralMetadataSchema &other) const {
return !(*this == other);
}
} // namespace draco
#endif // DRACO_TRANSCODER_SUPPORTED

View file

@ -0,0 +1,118 @@
// Copyright 2023 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_SCHEMA_H_
#define DRACO_METADATA_SCHEMA_H_
#include "draco/draco_features.h"
#ifdef DRACO_TRANSCODER_SUPPORTED
#include <cstdint>
#include <cstring>
#include <limits>
#include <memory>
#include <string>
#include <vector>
#include "draco/core/status_or.h"
namespace draco {
// Defines schema that describes the structure of the metadata as defined in the
// EXT_structural_metadata glTF extension, in the form of a JSON object.
struct StructuralMetadataSchema {
// 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();
explicit Object(const std::string &name);
Object(const std::string &name, const std::string &value);
Object(const std::string &name, const char *value);
Object(const std::string &name, int value);
Object(const std::string &name, bool value);
// Methods for comparing two objects.
bool operator==(const Object &other) const;
bool operator!=(const Object &other) const;
// 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<Object> &GetObjects() const { return objects_; }
const std::vector<Object> &GetArray() const { return array_; }
const std::string &GetString() const { return string_; }
int GetInteger() const { return integer_; }
bool GetBoolean() const { return boolean_; }
// Looks for a child object matching the given |name|. If no object is
// found, returns nullptr.
//
// Note that this is not recursive. I.e., for the following object:
//
// { "level1": { "level2": "value" } }
//
// GetObjectByName("level1") will return '{ "level2": "value" }', but
// GetObjectByName("level2") will return nullptr. Instead, the user should
// use GetObjectByName("level1")->GetObjectByName("level2") to get the
// nested child. Note that this follows the typical JSON semantics.
const Object *GetObjectByName(const std::string &name) const;
// Methods for setting object value.
std::vector<Object> &SetObjects();
std::vector<Object> &SetArray();
void SetString(const std::string &value);
void SetInteger(int value);
void SetBoolean(bool value);
private:
std::string name_;
Type type_;
std::vector<Object> objects_;
std::vector<Object> array_;
std::string string_;
int integer_;
bool boolean_;
};
// Valid schema top-level JSON object name is "schema".
StructuralMetadataSchema() : json("schema") {}
// Methods for comparing two schemas.
bool operator==(const StructuralMetadataSchema &other) const;
bool operator!=(const StructuralMetadataSchema &other) const;
// 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;
};
} // namespace draco
#endif // DRACO_TRANSCODER_SUPPORTED
#endif // DRACO_METADATA_SCHEMA_H_

View file

@ -0,0 +1,323 @@
// Copyright 2023 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_schema.h"
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "draco/core/draco_test_utils.h"
namespace {
#ifdef DRACO_TRANSCODER_SUPPORTED
TEST(StructuralMetadataSchemaTest, TestSchemaDefaults) {
// Test construction of an empty schema.
draco::StructuralMetadataSchema schema;
ASSERT_TRUE(schema.Empty());
ASSERT_EQ(schema.json.GetName(), "schema");
ASSERT_EQ(schema.json.GetType(),
draco::StructuralMetadataSchema::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(StructuralMetadataSchemaTest, TestSchemaObjectDefaultConstructor) {
// Test construction of an empty schema object.
draco::StructuralMetadataSchema::Object object;
ASSERT_TRUE(object.GetName().empty());
ASSERT_EQ(object.GetType(), draco::StructuralMetadataSchema::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(StructuralMetadataSchemaTest, TestSchemaObjectNamedConstructor) {
// Test construction of a named schema object.
draco::StructuralMetadataSchema::Object object("Flexible Demeanour");
ASSERT_EQ(object.GetName(), "Flexible Demeanour");
ASSERT_EQ(object.GetType(), draco::StructuralMetadataSchema::Object::OBJECT);
ASSERT_TRUE(object.GetObjects().empty());
}
TEST(StructuralMetadataSchemaTest, TestSchemaObjectStringConstructor) {
// Test construction of schema object storing a string.
draco::StructuralMetadataSchema::Object object("Flexible Demeanour", "GCU");
ASSERT_EQ(object.GetName(), "Flexible Demeanour");
ASSERT_EQ(object.GetType(), draco::StructuralMetadataSchema::Object::STRING);
ASSERT_EQ(object.GetString(), "GCU");
}
TEST(StructuralMetadataSchemaTest, TestSchemaObjectIntegerConstructor) {
// Test construction of schema object storing an integer.
draco::StructuralMetadataSchema::Object object("Flexible Demeanour", 12);
ASSERT_EQ(object.GetName(), "Flexible Demeanour");
ASSERT_EQ(object.GetType(), draco::StructuralMetadataSchema::Object::INTEGER);
ASSERT_EQ(object.GetInteger(), 12);
}
TEST(StructuralMetadataSchemaTest, TestSchemaObjectBooleanConstructor) {
// Test construction of schema object storing a boolean.
draco::StructuralMetadataSchema::Object object("Flexible Demeanour", true);
ASSERT_EQ(object.GetName(), "Flexible Demeanour");
ASSERT_EQ(object.GetType(), draco::StructuralMetadataSchema::Object::BOOLEAN);
ASSERT_TRUE(object.GetBoolean());
}
TEST(StructuralMetadataSchemaTest, TestSchemaObjectSettersAndGetters) {
// Test value setters and getters of schema object.
typedef draco::StructuralMetadataSchema::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(StructuralMetadataSchemaTest, TestSchemaObjectLookupByName) {
// Test the GetObjectByName() getter.
typedef draco::StructuralMetadataSchema::Object Object;
Object object;
ASSERT_EQ(object.GetType(), Object::OBJECT);
auto &objects = object.SetObjects();
objects.push_back(Object("object1", 1));
objects.push_back(Object("object2", "two"));
Object object3("object3");
object3.SetObjects().push_back(Object("child_object", "child"));
objects.push_back(object3);
ASSERT_EQ(object.GetObjectByName("child_object"), nullptr);
ASSERT_NE(object.GetObjectByName("object1"), nullptr);
ASSERT_EQ(object.GetObjectByName("object1")->GetInteger(), 1);
ASSERT_NE(object.GetObjectByName("object2"), nullptr);
ASSERT_EQ(object.GetObjectByName("object2")->GetString(), "two");
ASSERT_NE(object.GetObjectByName("object3"), nullptr);
ASSERT_NE(object.GetObjectByName("object3")->GetObjectByName("child_object"),
nullptr);
ASSERT_EQ(object.GetObjectByName("object3")
->GetObjectByName("child_object")
->GetString(),
"child");
}
TEST(StructuralMetadataSchemaTest, TestSchemaCompare) {
typedef draco::StructuralMetadataSchema 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(StructuralMetadataSchemaTest, TestSchemaObjectCompare) {
// Test comparison of two schema JSON objects.
typedef draco::StructuralMetadataSchema::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);
}
}
#endif // DRACO_TRANSCODER_SUPPORTED
} // namespace

View file

@ -16,7 +16,6 @@
#include <memory>
#include <utility>
#include <vector>
#include "draco/core/draco_test_base.h"
#include "draco/core/draco_test_utils.h"
@ -30,9 +29,9 @@ TEST(StructuralMetadataTest, TestCopy) {
draco::StructuralMetadata structural_metadata;
// Add property table schema to structural metadata.
draco::PropertyTable::Schema schema;
draco::StructuralMetadataSchema schema;
schema.json.SetString("Culture");
structural_metadata.SetPropertyTableSchema(schema);
structural_metadata.SetSchema(schema);
// Add property table to structural metadata.
std::unique_ptr<draco::PropertyTable> table(new draco::PropertyTable());
@ -58,7 +57,7 @@ TEST(StructuralMetadataTest, TestCopy) {
copy.Copy(structural_metadata);
// Check that the structural metadata property table schema has been copied.
ASSERT_EQ(copy.GetPropertyTableSchema().json.GetString(), "Culture");
ASSERT_EQ(copy.GetSchema().json.GetString(), "Culture");
// Check that the structural metadata property table has been copied.
ASSERT_EQ(copy.NumPropertyTables(), 1);
@ -114,6 +113,7 @@ TEST(StructuralMetadataTest, TestPropertyTables) {
TEST(StructuralMetadataTest, TestCompare) {
// Test comparison of two structural metadata objects.
typedef draco::PropertyTable PropertyTable;
typedef draco::PropertyAttribute PropertyAttribute;
{
// Compare the same structural metadata object.
draco::StructuralMetadata a;
@ -131,17 +131,17 @@ TEST(StructuralMetadataTest, TestCompare) {
// Compare two structural metadata objects with different schemas.
draco::StructuralMetadata a;
draco::StructuralMetadata b;
PropertyTable::Schema s1;
PropertyTable::Schema s2;
draco::StructuralMetadataSchema s1;
draco::StructuralMetadataSchema s2;
s1.json.SetString("one");
s2.json.SetString("two");
a.SetPropertyTableSchema(s1);
b.SetPropertyTableSchema(s2);
a.SetSchema(s1);
b.SetSchema(s2);
ASSERT_FALSE(a == b);
ASSERT_TRUE(a != b);
}
{
// Compare two objects with different number of proeprty tables.
// Compare two objects with different number of property tables.
draco::StructuralMetadata a;
draco::StructuralMetadata b;
a.AddPropertyTable(std::unique_ptr<PropertyTable>(new PropertyTable()));
@ -151,7 +151,20 @@ TEST(StructuralMetadataTest, TestCompare) {
ASSERT_TRUE(a != b);
}
{
// Compare two objects with different proeprty tables.
// Compare two objects with identical property tables.
draco::StructuralMetadata a;
draco::StructuralMetadata b;
auto p1 = std::unique_ptr<PropertyTable>(new PropertyTable());
auto p2 = std::unique_ptr<PropertyTable>(new PropertyTable());
p1->SetName("one");
p2->SetName("one");
a.AddPropertyTable(std::move(p1));
b.AddPropertyTable(std::move(p2));
ASSERT_TRUE(a == b);
ASSERT_FALSE(a != b);
}
{
// Compare two objects with different property tables.
draco::StructuralMetadata a;
draco::StructuralMetadata b;
auto p1 = std::unique_ptr<PropertyTable>(new PropertyTable());
@ -163,6 +176,32 @@ TEST(StructuralMetadataTest, TestCompare) {
ASSERT_FALSE(a == b);
ASSERT_TRUE(a != b);
}
{
// Compare two objects with identical property attributes.
draco::StructuralMetadata a;
draco::StructuralMetadata b;
auto p1 = std::unique_ptr<PropertyAttribute>(new PropertyAttribute());
auto p2 = std::unique_ptr<PropertyAttribute>(new PropertyAttribute());
p1->SetName("one");
p2->SetName("one");
a.AddPropertyAttribute(std::move(p1));
b.AddPropertyAttribute(std::move(p2));
ASSERT_TRUE(a == b);
ASSERT_FALSE(a != b);
}
{
// Compare two objects with identical property attributes.
draco::StructuralMetadata a;
draco::StructuralMetadata b;
auto p1 = std::unique_ptr<PropertyAttribute>(new PropertyAttribute());
auto p2 = std::unique_ptr<PropertyAttribute>(new PropertyAttribute());
p1->SetName("one");
p2->SetName("two");
a.AddPropertyAttribute(std::move(p1));
b.AddPropertyAttribute(std::move(p2));
ASSERT_FALSE(a == b);
ASSERT_TRUE(a != b);
}
}
#endif // DRACO_TRANSCODER_SUPPORTED

View file

@ -37,6 +37,8 @@ void PointCloud::Copy(const PointCloud &src) {
attributes_[i] = std::unique_ptr<PointAttribute>(new PointAttribute());
attributes_[i]->CopyFrom(*src.attributes_[i]);
}
compression_enabled_ = src.compression_enabled_;
compression_options_ = src.compression_options_;
CopyMetadata(src);
}
@ -106,6 +108,20 @@ const PointAttribute *PointCloud::GetAttributeByUniqueId(
return attributes_[att_id].get();
}
#ifdef DRACO_TRANSCODER_SUPPORTED
const PointAttribute *PointCloud::GetNamedAttributeByName(
GeometryAttribute::Type type, const std::string &name) const {
const auto &index = named_attribute_index_;
for (size_t i = 0; i < index[type].size(); ++i) {
const PointAttribute *const att = attributes_[index[type][i]].get();
if (att->name() == name) {
return att;
}
}
return nullptr;
}
#endif // DRACO_TRANSCODER_SUPPORTED
int32_t PointCloud::GetAttributeIdByUniqueId(uint32_t unique_id) const {
for (size_t att_id = 0; att_id < attributes_.size(); ++att_id) {
if (attributes_[att_id]->unique_id() == unique_id) {

View file

@ -21,6 +21,10 @@
#include "draco/draco_features.h"
#include "draco/metadata/geometry_metadata.h"
#ifdef DRACO_TRANSCODER_SUPPORTED
#include "draco/compression/draco_compression_options.h"
#endif
namespace draco {
// PointCloud is a collection of n-dimensional points that are described by a
@ -62,6 +66,12 @@ class PointCloud {
const PointAttribute *GetAttributeByUniqueId(uint32_t id) const;
int32_t GetAttributeIdByUniqueId(uint32_t unique_id) const;
#ifdef DRACO_TRANSCODER_SUPPORTED
// Returns the named attribute with a given name.
const PointAttribute *GetNamedAttributeByName(GeometryAttribute::Type type,
const std::string &name) const;
#endif // DRACO_TRANSCODER_SUPPORTED
int32_t num_attributes() const {
return static_cast<int32_t>(attributes_.size());
}
@ -189,6 +199,24 @@ class PointCloud {
// cloud.
void set_num_points(PointIndex::ValueType num) { num_points_ = num; }
#ifdef DRACO_TRANSCODER_SUPPORTED
// 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_;
}
#endif // DRACO_TRANSCODER_SUPPORTED
protected:
#ifdef DRACO_TRANSCODER_SUPPORTED
// Copies metadata from the |src| point cloud.
@ -217,6 +245,13 @@ class PointCloud {
// in corresponding PointAttribute instances in the |attributes_| array.
PointIndex::ValueType num_points_;
#ifdef DRACO_TRANSCODER_SUPPORTED
// Compression options for this geometry.
// TODO(vytyaz): Store encoded bitstream that this geometry compresses into.
bool compression_enabled_ = false;
DracoCompressionOptions compression_options_;
#endif // DRACO_TRANSCODER_SUPPORTED
friend struct PointCloudHasher;
};

View file

@ -14,6 +14,7 @@
//
#include "draco/point_cloud/point_cloud_builder.h"
#include <string>
#include <utility>
namespace draco {
@ -27,8 +28,14 @@ void PointCloudBuilder::Start(PointIndex::ValueType num_points) {
int PointCloudBuilder::AddAttribute(GeometryAttribute::Type attribute_type,
int8_t num_components, DataType data_type) {
return AddAttribute(attribute_type, num_components, data_type, false);
}
int PointCloudBuilder::AddAttribute(GeometryAttribute::Type attribute_type,
int8_t num_components, DataType data_type,
bool normalized) {
GeometryAttribute ga;
ga.Init(attribute_type, nullptr, num_components, data_type, false,
ga.Init(attribute_type, nullptr, num_components, data_type, normalized,
DataTypeLength(data_type) * num_components, 0);
return point_cloud_->AddAttribute(ga, true, point_cloud_->num_points());
}
@ -75,4 +82,14 @@ std::unique_ptr<PointCloud> PointCloudBuilder::Finalize(
return std::move(point_cloud_);
}
void PointCloudBuilder::SetAttributeUniqueId(int att_id, uint32_t unique_id) {
point_cloud_->attribute(att_id)->set_unique_id(unique_id);
}
#ifdef DRACO_TRANSCODER_SUPPORTED
void PointCloudBuilder::SetAttributeName(int att_id, const std::string &name) {
point_cloud_->attribute(att_id)->set_name(name);
}
#endif // DRACO_TRANSCODER_SUPPORTED
} // namespace draco

View file

@ -50,6 +50,8 @@ class PointCloudBuilder {
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 attribute value for a specific point.
// |attribute_value| must contain data in the format specified by the
@ -65,6 +67,14 @@ class PointCloudBuilder {
void SetAttributeValuesForAllPoints(int att_id, const void *attribute_values,
int stride);
// Sets the unique ID for an attribute created with AddAttribute().
void SetAttributeUniqueId(int att_id, uint32_t unique_id);
#ifdef DRACO_TRANSCODER_SUPPORTED
// Sets attribute name.
void SetAttributeName(int att_id, const std::string &name);
#endif // DRACO_TRANSCODER_SUPPORTED
// Finalizes the PointCloud or returns nullptr on error.
// If |deduplicate_points| is set to true, the following happens:
// 1. Attribute values with duplicate entries are deduplicated.

View file

@ -60,9 +60,15 @@ TEST_F(PointCloudBuilderTest, IndividualTest_NoDedup) {
builder.SetAttributeValueForPoint(intensity_att_id, i,
intensity_data_.data() + i.value());
}
#ifdef DRACO_TRANSCODER_SUPPORTED
builder.SetAttributeName(pos_att_id, "Bob");
#endif
std::unique_ptr<PointCloud> res = builder.Finalize(false);
ASSERT_TRUE(res != nullptr);
ASSERT_EQ(res->num_points(), 10);
#ifdef DRACO_TRANSCODER_SUPPORTED
EXPECT_EQ(res->attribute(pos_att_id)->name(), "Bob");
#endif
}
TEST_F(PointCloudBuilderTest, IndividualTest_Dedup) {
@ -168,4 +174,25 @@ TEST_F(PointCloudBuilderTest, MultiUse) {
}
}
TEST_F(PointCloudBuilderTest, PropagatesAttributeUniqueIds) {
// This test verifies that PointCloudBuilder correctly applies unique IDs to
// attributes.
PointCloudBuilder builder;
builder.Start(10);
const int pos_att_id =
builder.AddAttribute(GeometryAttribute::POSITION, 3, DT_FLOAT32);
const int intensity_att_id =
builder.AddAttribute(GeometryAttribute::GENERIC, 1, DT_INT16);
for (PointIndex i(0); i < 10; ++i) {
builder.SetAttributeValueForPoint(pos_att_id, i,
pos_data_.data() + 3 * i.value());
builder.SetAttributeValueForPoint(intensity_att_id, i,
intensity_data_.data() + i.value());
}
builder.SetAttributeUniqueId(pos_att_id, 1234);
std::unique_ptr<PointCloud> res = builder.Finalize(false);
ASSERT_TRUE(res != nullptr);
ASSERT_EQ(res->GetAttributeByUniqueId(1234), res->attribute(pos_att_id));
}
} // namespace draco

View file

@ -77,6 +77,71 @@ TEST_F(PointCloudTest, PointCloudCopy) {
ASSERT_TRUE(att_metadata_copy->GetEntryInt("attribute_test", &att_test));
ASSERT_EQ(att_test, 3);
}
TEST_F(PointCloudTest, TestCompressionSettings) {
// Tests compression settings of a point cloud.
draco::PointCloud pc;
// Check that compression is disabled and compression settings are default.
ASSERT_FALSE(pc.IsCompressionEnabled());
const draco::DracoCompressionOptions default_compression_options;
ASSERT_EQ(pc.GetCompressionOptions(), default_compression_options);
// Check that compression options can be set without enabling compression.
draco::DracoCompressionOptions compression_options;
compression_options.quantization_bits_normal = 12;
pc.SetCompressionOptions(compression_options);
ASSERT_EQ(pc.GetCompressionOptions(), compression_options);
ASSERT_FALSE(pc.IsCompressionEnabled());
// Check that compression can be enabled.
pc.SetCompressionEnabled(true);
ASSERT_TRUE(pc.IsCompressionEnabled());
// Check that individual compression options can be updated.
pc.GetCompressionOptions().compression_level++;
pc.GetCompressionOptions().compression_level--;
// Check that compression settings can be copied.
draco::PointCloud pc_copy;
pc_copy.Copy(pc);
ASSERT_TRUE(pc_copy.IsCompressionEnabled());
ASSERT_EQ(pc_copy.GetCompressionOptions(), compression_options);
}
TEST_F(PointCloudTest, TestGetNamedAttributeByName) {
draco::PointCloud pc;
// Test whether we can get named attributes by name.
constexpr auto kPosition = draco::GeometryAttribute::POSITION;
constexpr auto kGeneric = draco::GeometryAttribute::GENERIC;
draco::GeometryAttribute pos_att;
draco::GeometryAttribute gen_att0;
draco::GeometryAttribute gen_att1;
pos_att.Init(kPosition, nullptr, 3, draco::DT_FLOAT32, false, 12, 0);
gen_att0.Init(kGeneric, nullptr, 3, draco::DT_FLOAT32, false, 12, 0);
gen_att1.Init(kGeneric, nullptr, 3, draco::DT_FLOAT32, false, 12, 0);
pos_att.set_name("Zero");
gen_att0.set_name("Zero");
gen_att1.set_name("One");
// Add one position, and two generic attributes.
pc.AddAttribute(pos_att, false, 0);
pc.AddAttribute(gen_att0, false, 0);
pc.AddAttribute(gen_att1, false, 0);
// Check added attributes.
ASSERT_EQ(pc.attribute(0)->attribute_type(), kPosition);
ASSERT_EQ(pc.attribute(1)->attribute_type(), kGeneric);
ASSERT_EQ(pc.attribute(2)->attribute_type(), kGeneric);
ASSERT_EQ(pc.attribute(0)->name(), "Zero");
ASSERT_EQ(pc.attribute(1)->name(), "Zero");
ASSERT_EQ(pc.attribute(2)->name(), "One");
// Check that we can get correct attributes by name.
ASSERT_EQ(pc.GetNamedAttributeByName(kPosition, "Zero"), pc.attribute(0));
ASSERT_EQ(pc.GetNamedAttributeByName(kGeneric, "Zero"), pc.attribute(1));
ASSERT_EQ(pc.GetNamedAttributeByName(kGeneric, "One"), pc.attribute(2));
}
#endif
TEST_F(PointCloudTest, TestAttributeDeletion) {

View file

@ -14,6 +14,7 @@
//
#include "draco/scene/scene.h"
#include <memory>
#include <utility>
#ifdef DRACO_TRANSCODER_SUPPORTED
@ -22,6 +23,8 @@
namespace draco {
Scene::Scene() { metadata_.reset(new Metadata()); }
void Scene::Copy(const Scene &s) {
meshes_.resize(s.meshes_.size());
for (MeshIndex i(0); i < meshes_.size(); ++i) {
@ -90,6 +93,9 @@ void Scene::Copy(const Scene &s) {
// Copy structural metadata.
structural_metadata_.Copy(s.structural_metadata_);
// Copy general metadata.
metadata_.reset(new Metadata(*s.metadata_));
}
Status Scene::RemoveMesh(MeshIndex index) {

View file

@ -23,6 +23,7 @@
#include "draco/animation/animation.h"
#include "draco/animation/skin.h"
#include "draco/mesh/mesh.h"
#include "draco/metadata/metadata.h"
#include "draco/metadata/structural_metadata.h"
#include "draco/scene/instance_array.h"
#include "draco/scene/light.h"
@ -41,7 +42,7 @@ namespace draco {
// meshes in this scene.
class Scene {
public:
Scene() {}
Scene();
void Copy(const Scene &s);
@ -221,6 +222,9 @@ class Scene {
return instance_arrays_[index].get();
}
const Metadata &GetMetadata() const { return *metadata_; }
Metadata &GetMetadata() { return *metadata_; }
private:
IndexTypeVector<MeshIndex, std::unique_ptr<Mesh>> meshes_;
IndexTypeVector<MeshGroupIndex, std::unique_ptr<MeshGroup>> mesh_groups_;
@ -250,6 +254,10 @@ class Scene {
// Structural metadata defined by the EXT_structural_metadata glTF extension.
StructuralMetadata structural_metadata_;
// General metadata associated with the scene (not related to the
// EXT_structural_metadata extension).
std::unique_ptr<Metadata> metadata_;
};
} // namespace draco

View file

@ -15,6 +15,7 @@
#include "draco/scene/scene.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
@ -276,18 +277,40 @@ TEST(SceneTest, TestCopyWithStructuralMetadata) {
draco::Scene &scene = *scene_ptr;
// Add structural metadata to the scene.
draco::PropertyTable::Schema schema;
draco::StructuralMetadataSchema schema;
schema.json.SetString("Data");
scene.GetStructuralMetadata().SetPropertyTableSchema(schema);
scene.GetStructuralMetadata().SetSchema(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");
ASSERT_EQ(copy.GetStructuralMetadata().GetSchema().json.GetString(), "Data");
}
TEST(SceneTest, TestCopyWithMetadata) {
// Tests copying of a scene with general metadata.
auto scene_ptr =
draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf");
ASSERT_NE(scene_ptr, nullptr);
draco::Scene &scene = *scene_ptr;
// Add metadata to the scene.
scene.GetMetadata().AddEntryString("test_name", "test_value");
scene.GetMetadata().AddEntryInt("test_int", 101);
// Copy the scene.
draco::Scene copy;
copy.Copy(scene);
// Check that the metadata has been copied.
std::string string_val;
int int_val;
copy.GetMetadata().GetEntryString("test_name", &string_val);
copy.GetMetadata().GetEntryInt("test_int", &int_val);
ASSERT_EQ(string_val, "test_value");
ASSERT_EQ(int_val, 101);
}
#endif // DRACO_TRANSCODER_SUPPORTED

View file

@ -36,6 +36,22 @@ namespace draco {
IndexTypeVector<MeshInstanceIndex, SceneUtils::MeshInstance>
SceneUtils::ComputeAllInstances(const Scene &scene) {
IndexTypeVector<MeshInstanceIndex, MeshInstance> instances;
for (int i = 0; i < scene.NumRootNodes(); ++i) {
const auto node_instances =
ComputeAllInstancesFromNode(scene, scene.GetRootNodeIndex(i));
const size_t old_size = instances.size();
instances.resize(instances.size() + node_instances.size());
for (MeshInstanceIndex mii(0); mii < node_instances.size(); ++mii) {
instances[mii + old_size] = node_instances[mii];
}
}
return instances;
}
IndexTypeVector<MeshInstanceIndex, SceneUtils::MeshInstance>
SceneUtils::ComputeAllInstancesFromNode(const Scene &scene,
SceneNodeIndex node_index) {
IndexTypeVector<MeshInstanceIndex, MeshInstance> instances;
// Traverse the scene assuming multiple root nodes.
const Eigen::Matrix4d transform = Eigen::Matrix4d::Identity();
@ -45,10 +61,7 @@ SceneUtils::ComputeAllInstances(const Scene &scene) {
Eigen::Matrix4d transform;
};
std::vector<Node> nodes;
nodes.reserve(scene.NumRootNodes());
for (int i = 0; i < scene.NumRootNodes(); ++i) {
nodes.push_back({scene.GetRootNodeIndex(i), transform});
}
nodes.push_back({node_index, transform});
while (!nodes.empty()) {
const Node node = nodes.back();
@ -220,7 +233,7 @@ void UpdateMeshFeaturesTexturesOnMesh(
} // namespace
StatusOr<std::unique_ptr<Scene>> SceneUtils::MeshToScene(
std::unique_ptr<Mesh> mesh) {
std::unique_ptr<Mesh> mesh, bool deduplicate_vertices) {
const size_t num_mesh_materials = mesh->GetMaterialLibrary().NumMaterials();
std::unique_ptr<Scene> scene(new Scene());
if (num_mesh_materials > 0) {
@ -231,6 +244,9 @@ StatusOr<std::unique_ptr<Scene>> SceneUtils::MeshToScene(
scene->GetMaterialLibrary().MutableMaterial(0);
}
// Copy structural metadata.
scene->GetStructuralMetadata().Copy(mesh->GetStructuralMetadata());
// Copy mesh feature textures.
scene->GetNonMaterialTextureLibrary().Copy(
mesh->GetNonMaterialTextureLibrary());
@ -255,6 +271,10 @@ StatusOr<std::unique_ptr<Scene>> SceneUtils::MeshToScene(
&scene->GetNonMaterialTextureLibrary(),
&scene->GetMesh(mesh_index));
// The non-material texture library is now in the scene. The non-material
// texture library of the mesh must be cleared, because mesh features may
// contain texture pointers invalid for this non-material texture library.
scene->GetMesh(mesh_index).GetNonMaterialTextureLibrary().Clear();
} else {
const int32_t mat_att_id =
mesh->GetNamedAttributeId(GeometryAttribute::MATERIAL);
@ -274,6 +294,7 @@ StatusOr<std::unique_ptr<Scene>> SceneUtils::MeshToScene(
}
MeshSplitter splitter;
splitter.SetDeduplicateVertices(deduplicate_vertices);
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.
@ -296,9 +317,30 @@ StatusOr<std::unique_ptr<Scene>> SceneUtils::MeshToScene(
// 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);
// Update mesh features attribute indices if needed.
for (MeshFeaturesIndex mfi(0); mfi < scene_mesh.NumMeshFeatures();
++mfi) {
auto &mesh_features = scene_mesh.GetMeshFeatures(mfi);
if (mesh_features.GetAttributeIndex() != -1) {
mesh_features.SetAttributeIndex(splitter.GetSplitMeshAttributeIndex(
mesh_features.GetAttributeIndex()));
}
}
UpdateMeshFeaturesTexturesOnMesh(old_texture_to_index_map,
&scene->GetNonMaterialTextureLibrary(),
&scene_mesh);
// Copy over property attibutes indices that were associated with the
// |material_index|.
Mesh::CopyPropertyAttributesIndicesForMaterial(*mesh, &scene_mesh,
material_index);
// The non-material texture library is now in the scene. The non-material
// texture library of the mesh must be cleared, because mesh features may
// contain texture pointers invalid for this non-material texture library.
scene_mesh.GetNonMaterialTextureLibrary().Clear();
}
}

View file

@ -43,6 +43,12 @@ class SceneUtils {
static IndexTypeVector<MeshInstanceIndex, MeshInstance> ComputeAllInstances(
const Scene &scene);
// Computes all mesh instances in the node hierarchy originating from
// |node_index|. All instance transformations will be relative to the source
// node. That is transformation of parent nodes will not be included.
static IndexTypeVector<MeshInstanceIndex, MeshInstance>
ComputeAllInstancesFromNode(const Scene &scene, SceneNodeIndex node_index);
// Computes global transform matrix of a |scene| node given by its |index|.
static Eigen::Matrix4d ComputeGlobalNodeTransform(const Scene &scene,
SceneNodeIndex index);
@ -97,8 +103,15 @@ class SceneUtils {
// 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.
//
// By default, |MeshToScene| will attempt to deduplicate vertices if the mesh
// has multiple materials. This means lower memory usage and smaller output
// glTFs after encoding. However, for very large meshes, this may become an
// expensive operation. If that becomes an issue, you might want to consider
// disabling deduplication by setting |deduplicate_vertices| to false. Note
// that at this moment, disabling deduplication works ONLY for point clouds.
static StatusOr<std::unique_ptr<Scene>> MeshToScene(
std::unique_ptr<Mesh> mesh);
std::unique_ptr<Mesh> mesh, bool deduplicate_vertices = true);
// 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

View file

@ -22,6 +22,8 @@
#include "draco/core/draco_test_base.h"
#include "draco/core/draco_test_utils.h"
#include "draco/io/texture_io.h"
#include "draco/metadata/property_table.h"
#include "draco/metadata/structural_metadata.h"
#include "draco/scene/scene_indices.h"
namespace {
@ -120,6 +122,47 @@ TEST(SceneUtilsTest, TestComputeAllInstances) {
expected_transform, 1e-6f);
}
TEST(SceneUtilsTest, TestComputeInstanceFromRootNode) {
// Tests that we can compute all instances from a root node of a scene.
// This should result in the same instances all the ComputeAllInstances().
auto scene =
draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf");
const auto node_instances = draco::SceneUtils::ComputeAllInstancesFromNode(
*scene, scene->GetRootNodeIndex(0));
const auto scene_instances = draco::SceneUtils::ComputeAllInstances(*scene);
ASSERT_EQ(node_instances.size(), scene_instances.size());
for (draco::MeshInstanceIndex i(0); i < node_instances.size(); ++i) {
ASSERT_EQ(node_instances[i].scene_node_index,
scene_instances[i].scene_node_index);
ASSERT_EQ(node_instances[i].mesh_index, scene_instances[i].mesh_index);
ASSERT_EQ(node_instances[i].mesh_group_mesh_index,
scene_instances[i].mesh_group_mesh_index);
ASSERT_EQ(node_instances[i].transform, scene_instances[i].transform);
}
}
TEST(SceneUtilsTest, TestComputeInstanceFromChildNode) {
// Tests that we can compute all instances from a child node of a scene.
auto scene =
draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf");
const auto node_instances = draco::SceneUtils::ComputeAllInstancesFromNode(
*scene, draco::SceneNodeIndex(1));
// There should be only one instance in this node chain.
ASSERT_EQ(node_instances.size(), 1);
// clang-format off
AssertMatrixNear(node_instances[draco::MeshInstanceIndex(0)].transform,
Eigen::Matrix4d{
{ 0.98434, 0.176278, 0, 1.43267},
{-0.176278, 0.98434, 0, 0.427722},
{0, 0, 1, -2.98e-8},
{0, 0, 0, 1}
}, 1e-6);
// clang-format on
}
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
@ -334,8 +377,17 @@ TEST(SceneUtilsTest, TestMeshToSceneMultipleMeshFeatures) {
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());
const int att_index_0 = scene_mf.GetAttributeIndex();
const int att_index_1 = scene_from_mesh_mf.GetAttributeIndex();
if (att_index_0 == -1) {
ASSERT_EQ(att_index_0, att_index_1);
} else {
ASSERT_EQ(scene->GetMesh(mi).attribute(att_index_0)->name(),
scene_from_mesh->GetMesh(mi).attribute(att_index_1)->name());
ASSERT_EQ(scene->GetMesh(mi).attribute(att_index_0)->size(),
scene_from_mesh->GetMesh(mi).attribute(att_index_1)->size());
}
ASSERT_EQ(scene_mf.GetPropertyTableIndex(),
scene_from_mesh_mf.GetPropertyTableIndex());
ASSERT_EQ(scene_mf.GetLabel(), scene_from_mesh_mf.GetLabel());
@ -351,6 +403,63 @@ TEST(SceneUtilsTest, TestMeshToSceneMultipleMeshFeatures) {
}
}
TEST(SceneUtilsTest, TestMeshToSceneMeshFeaturesWithAttributes) {
// Tests that converting a mesh into scene properly updates mesh features
// attribute indices.
auto mesh =
draco::ReadMeshFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf");
ASSERT_NE(mesh, nullptr);
// Add a new dummy mesh features and mesh features attribute to the mesh.
std::unique_ptr<draco::PointAttribute> mf_att(new draco::PointAttribute());
mf_att->Init(draco::GeometryAttribute::GENERIC, 1, draco::DT_FLOAT32, false,
mesh->num_points());
const int mf_att_id = mesh->AddAttribute(std::move(mf_att));
std::unique_ptr<draco::MeshFeatures> mf(new draco::MeshFeatures());
mf->SetAttributeIndex(mf_att_id);
mesh->AddMeshFeatures(std::move(mf));
// Convert the mesh into a scene.
DRACO_ASSIGN_OR_ASSERT(const std::unique_ptr<draco::Scene> scene_from_mesh,
draco::SceneUtils::MeshToScene(std::move(mesh)));
ASSERT_NE(scene_from_mesh, nullptr);
// Ensure the attribute indices on the meshes from scene are decremented by
// one because the material attribute was removed.
ASSERT_EQ(scene_from_mesh->NumMeshes(), 4);
for (draco::MeshIndex mi(0); mi < scene_from_mesh->NumMeshes(); ++mi) {
for (draco::MeshFeaturesIndex mfi(0);
mfi < scene_from_mesh->GetMesh(mi).NumMeshFeatures(); ++mfi) {
const auto &mf = scene_from_mesh->GetMesh(mi).GetMeshFeatures(mfi);
ASSERT_EQ(mf.GetAttributeIndex(), mf_att_id - 1);
}
}
}
TEST(SceneUtilsTest, TestMeshToSceneStructuralMetadata) {
const std::string filename = "cube_att.obj";
std::unique_ptr<draco::Mesh> mesh = draco::ReadMeshFromTestFile(filename);
ASSERT_NE(mesh, nullptr);
// Setting a sample schema to:
// {
// "classes": []
// }
draco::StructuralMetadataSchema sample_schema;
auto &classes_json = sample_schema.json.SetObjects().emplace_back("classes");
classes_json.SetArray();
mesh->GetStructuralMetadata().SetSchema(sample_schema);
draco::StructuralMetadata mesh_structural_metadata;
mesh_structural_metadata.Copy(mesh->GetStructuralMetadata());
ASSERT_FALSE(mesh_structural_metadata.GetSchema().Empty());
DRACO_ASSIGN_OR_ASSERT(const std::unique_ptr<draco::Scene> scene_from_mesh,
draco::SceneUtils::MeshToScene(std::move(mesh)));
ASSERT_NE(scene_from_mesh, nullptr);
ASSERT_EQ(scene_from_mesh->GetStructuralMetadata(), mesh_structural_metadata);
}
TEST(SceneUtilsTest, TestInstantiateMeshWithIdentityTransformation) {
auto scene =
draco::ReadSceneFromTestFile("CesiumMilkTruck/glTF/CesiumMilkTruck.gltf");

View file

@ -39,9 +39,10 @@ class TrsMatrix {
void Copy(const TrsMatrix &tm);
void SetMatrix(const Eigen::Matrix4d &matrix) {
TrsMatrix &SetMatrix(const Eigen::Matrix4d &matrix) {
matrix_ = matrix;
matrix_set_ = true;
return *this;
}
bool MatrixSet() const { return matrix_set_; }
StatusOr<const Eigen::Matrix4d> Matrix() const {
@ -51,9 +52,10 @@ class TrsMatrix {
return matrix_;
}
void SetTranslation(const Eigen::Vector3d &translation) {
TrsMatrix &SetTranslation(const Eigen::Vector3d &translation) {
translation_ = translation;
translation_set_ = true;
return *this;
}
bool TranslationSet() const { return translation_set_; }
StatusOr<const Eigen::Vector3d> Translation() const {
@ -63,9 +65,10 @@ class TrsMatrix {
return translation_;
}
void SetRotation(const Eigen::Quaterniond &rotation) {
TrsMatrix &SetRotation(const Eigen::Quaterniond &rotation) {
rotation_ = rotation;
rotation_set_ = true;
return *this;
}
bool RotationSet() const { return rotation_set_; }
StatusOr<const Eigen::Quaterniond> Rotation() const {
@ -75,9 +78,10 @@ class TrsMatrix {
return rotation_;
}
void SetScale(const Eigen::Vector3d &scale) {
TrsMatrix &SetScale(const Eigen::Vector3d &scale) {
scale_ = scale;
scale_set_ = true;
return *this;
}
bool ScaleSet() const { return scale_set_; }
StatusOr<const Eigen::Vector3d> Scale() const {

View file

@ -29,7 +29,7 @@ namespace draco {
// Texture class storing the source image data.
class Texture {
public:
void Copy(Texture &other) { source_image_.Copy(other.source_image_); }
void Copy(const 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_; }

View file

@ -23,6 +23,13 @@
#include <unordered_set>
#include <vector>
#include "draco/core/status.h"
#include "draco/core/status_or.h"
#include "draco/io/file_utils.h"
#include "draco/io/image_compression_options.h"
#include "draco/texture/texture.h"
#include "draco/texture/texture_map.h"
namespace draco {
std::string TextureUtils::GetTargetStem(const Texture &texture) {
@ -61,6 +68,42 @@ std::string TextureUtils::GetTargetExtension(const Texture &texture) {
return GetExtension(GetTargetFormat(texture));
}
std::string TextureUtils::GetTargetMimeType(const Texture &texture) {
const ImageFormat format = GetTargetFormat(texture);
if (format == ImageFormat::NONE) {
// Unknown format, try to re-use mime type stored in the source_image.
// This is mostly useful if users need to handle image formats not directly
// supported by the Draco library.
if (!texture.source_image().mime_type().empty()) {
return texture.source_image().mime_type();
} else if (!texture.source_image().filename().empty()) {
// Try to set mime type based on the extension of the filename.
const std::string extension =
LowercaseFileExtension(texture.source_image().filename());
if (!extension.empty()) {
return "image/" + extension;
}
}
}
return GetMimeType(format);
}
std::string TextureUtils::GetMimeType(ImageFormat image_format) {
switch (image_format) {
case ImageFormat::PNG:
return "image/png";
case ImageFormat::JPEG:
return "image/jpeg";
case ImageFormat::BASIS:
return "image/ktx2";
case ImageFormat::WEBP:
return "image/webp";
case ImageFormat::NONE:
default:
return "";
}
}
ImageFormat TextureUtils::GetSourceFormat(const Texture &texture) {
// Try to get the extension based on source image mime type.
std::string extension =

View file

@ -48,6 +48,13 @@ class TextureUtils {
// source image mime type or the source image filename.
static std::string GetTargetExtension(const Texture &texture);
// Returns mime type string based on |texture| compression settings, source
// image mime type or the source image filename.
static std::string GetTargetMimeType(const Texture &texture);
// Returns mime type string for a given |image_format|.
static std::string GetMimeType(ImageFormat image_format);
// Returns |texture| format based on source image mime type or the source
// image filename.
static ImageFormat GetSourceFormat(const Texture &texture);

View file

@ -89,6 +89,39 @@ TEST(TextureUtilsTest, TestGetFormat) {
ASSERT_EQ(draco::TextureUtils::GetFormat("bmp"), ImageFormat::NONE);
}
TEST(TextureUtilsTest, TestGetTargetMimeType) {
draco::Texture texture;
texture.source_image().set_mime_type("image/jpeg");
ASSERT_EQ(draco::TextureUtils::GetTargetMimeType(texture), "image/jpeg");
// Set compression settings to a different format.
draco::ImageCompressionOptions options;
options.target_image_format = draco::ImageFormat::BASIS;
texture.SetCompressionOptions(options);
ASSERT_EQ(draco::TextureUtils::GetTargetMimeType(texture), "image/ktx2");
// Test custom mime type in source image.
draco::Texture unknown_format;
unknown_format.source_image().set_mime_type("image/custom");
ASSERT_EQ(draco::TextureUtils::GetTargetMimeType(unknown_format),
"image/custom");
// Test custom mime type from file name.
draco::Texture unknown_format_file_name;
unknown_format_file_name.source_image().set_filename("test.extension");
ASSERT_EQ(draco::TextureUtils::GetTargetMimeType(unknown_format_file_name),
"image/extension");
}
TEST(TextureUtilsTest, TestGetMimeType) {
typedef draco::ImageFormat ImageFormat;
ASSERT_EQ(draco::TextureUtils::GetMimeType(ImageFormat::PNG), "image/png");
ASSERT_EQ(draco::TextureUtils::GetMimeType(ImageFormat::JPEG), "image/jpeg");
ASSERT_EQ(draco::TextureUtils::GetMimeType(ImageFormat::BASIS), "image/ktx2");
ASSERT_EQ(draco::TextureUtils::GetMimeType(ImageFormat::WEBP), "image/webp");
ASSERT_EQ(draco::TextureUtils::GetMimeType(ImageFormat::NONE), "");
}
TEST(TextureUtilsTest, TestGetExtension) {
typedef draco::ImageFormat ImageFormat;
ASSERT_EQ(draco::TextureUtils::GetExtension(ImageFormat::PNG), "png");