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

@ -166,7 +166,6 @@ namespace o3dgc
{
memset(this, 0, sizeof(SC3DMCStats));
};
~SC3DMCStats(void){};
double m_timeCoord;
double m_timeNormal;
@ -409,4 +408,3 @@ namespace o3dgc
}
}
#endif // O3DGC_COMMON_H

View file

@ -43,8 +43,6 @@ namespace o3dgc
m_isTriangularMesh = true;
m_creaseAngle = 30;
};
//! Destructor.
~IndexedFaceSet(void) {};
unsigned long GetNCoordIndex() const { return m_nCoordIndex ;}
// only coordIndex is supported
@ -260,4 +258,3 @@ namespace o3dgc
}
#include "o3dgcIndexedFaceSet.inl" // template implementation
#endif // O3DGC_INDEXED_FACE_SET_H

View file

@ -51,8 +51,6 @@ namespace o3dgc
m_intAttributePredMode[a] = O3DGC_SC3DMC_NO_PREDICTION;
}
};
//! Destructor.
~SC3DMCEncodeParams(void) {};
O3DGCStreamType GetStreamType() const { return m_streamTypeMode;}
O3DGCSC3DMCEncodingMode GetEncodeMode() const { return m_encodeMode;}
@ -137,4 +135,3 @@ namespace o3dgc
};
}
#endif // O3DGC_SC3DMC_ENCODE_PARAMS_H

View file

@ -113,7 +113,6 @@ namespace o3dgc
{
memset(this, 0, sizeof(Timer));
};
~Timer(void){};
void Tic()
{
clock_gettime(CLOCK_REALTIME, &m_start);

File diff suppressed because it is too large Load diff

View file

@ -45,9 +45,9 @@
//use_lines: Enables line clipping. Adds a very minor cost to performance.
#define use_lines
//use_deprecated: Enables temporary support for the obsolete functions
//#define use_deprecated
//#define use_deprecated
#include <vector>
#include <list>
@ -98,7 +98,7 @@ struct IntPoint {
}
friend inline bool operator!= (const IntPoint& a, const IntPoint& b)
{
return a.X != b.X || a.Y != b.Y;
return a.X != b.X || a.Y != b.Y;
}
};
//------------------------------------------------------------------------------
@ -133,8 +133,8 @@ enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRou
class PolyNode;
typedef std::vector< PolyNode* > PolyNodes;
class PolyNode
{
class PolyNode
{
public:
PolyNode();
virtual ~PolyNode(){};
@ -146,7 +146,7 @@ public:
bool IsOpen() const;
int ChildCount() const;
private:
//PolyNode& operator =(PolyNode& other);
//PolyNode& operator =(PolyNode& other);
unsigned Index; //node index in Parent.Childs
bool m_IsOpen;
JoinType m_jointype;
@ -154,11 +154,11 @@ private:
PolyNode* GetNextSiblingUp() const;
void AddChild(PolyNode& child);
friend class Clipper; //to access Index
friend class ClipperOffset;
friend class ClipperOffset;
};
class PolyTree: public PolyNode
{
{
public:
~PolyTree(){ Clear(); };
PolyNode* GetFirst() const;
@ -300,10 +300,10 @@ private:
PolyFillType m_ClipFillType;
PolyFillType m_SubjFillType;
bool m_ReverseOutput;
bool m_UsingPolyTree;
bool m_UsingPolyTree;
bool m_StrictSimple;
#ifdef use_xyz
ZFillCallback m_ZFill; //custom callback
ZFillCallback m_ZFill; //custom callback
#endif
void SetWindingCount(TEdge& edge);
bool IsEvenOddFillType(const TEdge& edge) const;
@ -357,7 +357,7 @@ private:
};
//------------------------------------------------------------------------------
class ClipperOffset
class ClipperOffset
{
public:
ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25);

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");

View file

@ -0,0 +1,15 @@
ISC License
Copyright (c) 2015, Mapbox
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

View file

@ -0,0 +1,814 @@
#pragma once
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <utility>
#include <vector>
namespace mapbox {
namespace util {
template <std::size_t I, typename T> struct nth {
inline static typename std::tuple_element<I, T>::type
get(const T& t) { return std::get<I>(t); };
};
}
namespace detail {
template <typename N = uint32_t>
class Earcut {
public:
std::vector<N> indices;
std::size_t vertices = 0;
template <typename Polygon>
void operator()(const Polygon& points);
private:
struct Node {
Node(N index, double x_, double y_) : i(index), x(x_), y(y_) {}
Node(const Node&) = delete;
Node& operator=(const Node&) = delete;
Node(Node&&) = delete;
Node& operator=(Node&&) = delete;
const N i;
const double x;
const double y;
// previous and next vertice nodes in a polygon ring
Node* prev = nullptr;
Node* next = nullptr;
// z-order curve value
int32_t z = 0;
// previous and next nodes in z-order
Node* prevZ = nullptr;
Node* nextZ = nullptr;
// indicates whether this is a steiner point
bool steiner = false;
};
template <typename Ring> Node* linkedList(const Ring& points, const bool clockwise);
Node* filterPoints(Node* start, Node* end = nullptr);
void earcutLinked(Node* ear, int pass = 0);
bool isEar(Node* ear);
bool isEarHashed(Node* ear);
Node* cureLocalIntersections(Node* start);
void splitEarcut(Node* start);
template <typename Polygon> Node* eliminateHoles(const Polygon& points, Node* outerNode);
Node* eliminateHole(Node* hole, Node* outerNode);
Node* findHoleBridge(Node* hole, Node* outerNode);
bool sectorContainsSector(const Node* m, const Node* p);
void indexCurve(Node* start);
Node* sortLinked(Node* list);
int32_t zOrder(const double x_, const double y_);
Node* getLeftmost(Node* start);
bool pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const;
bool isValidDiagonal(Node* a, Node* b);
double area(const Node* p, const Node* q, const Node* r) const;
bool equals(const Node* p1, const Node* p2);
bool intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2);
bool onSegment(const Node* p, const Node* q, const Node* r);
int sign(double val);
bool intersectsPolygon(const Node* a, const Node* b);
bool locallyInside(const Node* a, const Node* b);
bool middleInside(const Node* a, const Node* b);
Node* splitPolygon(Node* a, Node* b);
template <typename Point> Node* insertNode(std::size_t i, const Point& p, Node* last);
void removeNode(Node* p);
bool hashing;
double minX, maxX;
double minY, maxY;
double inv_size = 0;
template <typename T, typename Alloc = std::allocator<T>>
class ObjectPool {
public:
ObjectPool() { }
ObjectPool(std::size_t blockSize_) {
reset(blockSize_);
}
~ObjectPool() {
clear();
}
template <typename... Args>
T* construct(Args&&... args) {
if (currentIndex >= blockSize) {
currentBlock = alloc_traits::allocate(alloc, blockSize);
allocations.emplace_back(currentBlock);
currentIndex = 0;
}
T* object = &currentBlock[currentIndex++];
alloc_traits::construct(alloc, object, std::forward<Args>(args)...);
return object;
}
void reset(std::size_t newBlockSize) {
for (auto allocation : allocations) {
alloc_traits::deallocate(alloc, allocation, blockSize);
}
allocations.clear();
blockSize = std::max<std::size_t>(1, newBlockSize);
currentBlock = nullptr;
currentIndex = blockSize;
}
void clear() { reset(blockSize); }
private:
T* currentBlock = nullptr;
std::size_t currentIndex = 1;
std::size_t blockSize = 1;
std::vector<T*> allocations;
Alloc alloc;
typedef typename std::allocator_traits<Alloc> alloc_traits;
};
ObjectPool<Node> nodes;
};
template <typename N> template <typename Polygon>
void Earcut<N>::operator()(const Polygon& points) {
// reset
indices.clear();
vertices = 0;
if (points.empty()) return;
double x;
double y;
int threshold = 80;
std::size_t len = 0;
for (size_t i = 0; threshold >= 0 && i < points.size(); i++) {
threshold -= static_cast<int>(points[i].size());
len += points[i].size();
}
//estimate size of nodes and indices
nodes.reset(len * 3 / 2);
indices.reserve(len + points[0].size());
Node* outerNode = linkedList(points[0], true);
if (!outerNode || outerNode->prev == outerNode->next) return;
if (points.size() > 1) outerNode = eliminateHoles(points, outerNode);
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
hashing = threshold < 0;
if (hashing) {
Node* p = outerNode->next;
minX = maxX = outerNode->x;
minY = maxY = outerNode->y;
do {
x = p->x;
y = p->y;
minX = std::min<double>(minX, x);
minY = std::min<double>(minY, y);
maxX = std::max<double>(maxX, x);
maxY = std::max<double>(maxY, y);
p = p->next;
} while (p != outerNode);
// minX, minY and inv_size are later used to transform coords into integers for z-order calculation
inv_size = std::max<double>(maxX - minX, maxY - minY);
inv_size = inv_size != .0 ? (32767. / inv_size) : .0;
}
earcutLinked(outerNode);
nodes.clear();
}
// create a circular doubly linked list from polygon points in the specified winding order
template <typename N> template <typename Ring>
typename Earcut<N>::Node*
Earcut<N>::linkedList(const Ring& points, const bool clockwise) {
using Point = typename Ring::value_type;
double sum = 0;
const std::size_t len = points.size();
std::size_t i, j;
Node* last = nullptr;
// calculate original winding order of a polygon ring
for (i = 0, j = len > 0 ? len - 1 : 0; i < len; j = i++) {
const auto& p1 = points[i];
const auto& p2 = points[j];
const double p20 = util::nth<0, Point>::get(p2);
const double p10 = util::nth<0, Point>::get(p1);
const double p11 = util::nth<1, Point>::get(p1);
const double p21 = util::nth<1, Point>::get(p2);
sum += (p20 - p10) * (p11 + p21);
}
// link points into circular doubly-linked list in the specified winding order
if (clockwise == (sum > 0)) {
for (i = 0; i < len; i++) last = insertNode(vertices + i, points[i], last);
} else {
for (i = len; i-- > 0;) last = insertNode(vertices + i, points[i], last);
}
if (last && equals(last, last->next)) {
removeNode(last);
last = last->next;
}
vertices += len;
return last;
}
// eliminate colinear or duplicate points
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::filterPoints(Node* start, Node* end) {
if (!end) end = start;
Node* p = start;
bool again;
do {
again = false;
if (!p->steiner && (equals(p, p->next) || area(p->prev, p, p->next) == 0)) {
removeNode(p);
p = end = p->prev;
if (p == p->next) break;
again = true;
} else {
p = p->next;
}
} while (again || p != end);
return end;
}
// main ear slicing loop which triangulates a polygon (given as a linked list)
template <typename N>
void Earcut<N>::earcutLinked(Node* ear, int pass) {
if (!ear) return;
// interlink polygon nodes in z-order
if (!pass && hashing) indexCurve(ear);
Node* stop = ear;
Node* prev;
Node* next;
// iterate through ears, slicing them one by one
while (ear->prev != ear->next) {
prev = ear->prev;
next = ear->next;
if (hashing ? isEarHashed(ear) : isEar(ear)) {
// cut off the triangle
indices.emplace_back(prev->i);
indices.emplace_back(ear->i);
indices.emplace_back(next->i);
removeNode(ear);
// skipping the next vertice leads to less sliver triangles
ear = next->next;
stop = next->next;
continue;
}
ear = next;
// if we looped through the whole remaining polygon and can't find any more ears
if (ear == stop) {
// try filtering points and slicing again
if (!pass) earcutLinked(filterPoints(ear), 1);
// if this didn't work, try curing all small self-intersections locally
else if (pass == 1) {
ear = cureLocalIntersections(filterPoints(ear));
earcutLinked(ear, 2);
// as a last resort, try splitting the remaining polygon into two
} else if (pass == 2) splitEarcut(ear);
break;
}
}
}
// check whether a polygon node forms a valid ear with adjacent nodes
template <typename N>
bool Earcut<N>::isEar(Node* ear) {
const Node* a = ear->prev;
const Node* b = ear;
const Node* c = ear->next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
// now make sure we don't have other points inside the potential ear
Node* p = ear->next->next;
while (p != ear->prev) {
if (pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) &&
area(p->prev, p, p->next) >= 0) return false;
p = p->next;
}
return true;
}
template <typename N>
bool Earcut<N>::isEarHashed(Node* ear) {
const Node* a = ear->prev;
const Node* b = ear;
const Node* c = ear->next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
// triangle bbox; min & max are calculated like this for speed
const double minTX = std::min<double>(a->x, std::min<double>(b->x, c->x));
const double minTY = std::min<double>(a->y, std::min<double>(b->y, c->y));
const double maxTX = std::max<double>(a->x, std::max<double>(b->x, c->x));
const double maxTY = std::max<double>(a->y, std::max<double>(b->y, c->y));
// z-order range for the current triangle bbox;
const int32_t minZ = zOrder(minTX, minTY);
const int32_t maxZ = zOrder(maxTX, maxTY);
// first look for points inside the triangle in increasing z-order
Node* p = ear->nextZ;
while (p && p->z <= maxZ) {
if (p != ear->prev && p != ear->next &&
pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) &&
area(p->prev, p, p->next) >= 0) return false;
p = p->nextZ;
}
// then look for points in decreasing z-order
p = ear->prevZ;
while (p && p->z >= minZ) {
if (p != ear->prev && p != ear->next &&
pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) &&
area(p->prev, p, p->next) >= 0) return false;
p = p->prevZ;
}
return true;
}
// go through all polygon nodes and cure small local self-intersections
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::cureLocalIntersections(Node* start) {
Node* p = start;
do {
Node* a = p->prev;
Node* b = p->next->next;
// a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2])
if (!equals(a, b) && intersects(a, p, p->next, b) && locallyInside(a, b) && locallyInside(b, a)) {
indices.emplace_back(a->i);
indices.emplace_back(p->i);
indices.emplace_back(b->i);
// remove two nodes involved
removeNode(p);
removeNode(p->next);
p = start = b;
}
p = p->next;
} while (p != start);
return filterPoints(p);
}
// try splitting polygon into two and triangulate them independently
template <typename N>
void Earcut<N>::splitEarcut(Node* start) {
// look for a valid diagonal that divides the polygon into two
Node* a = start;
do {
Node* b = a->next->next;
while (b != a->prev) {
if (a->i != b->i && isValidDiagonal(a, b)) {
// split the polygon in two by the diagonal
Node* c = splitPolygon(a, b);
// filter colinear points around the cuts
a = filterPoints(a, a->next);
c = filterPoints(c, c->next);
// run earcut on each half
earcutLinked(a);
earcutLinked(c);
return;
}
b = b->next;
}
a = a->next;
} while (a != start);
}
// link every hole into the outer loop, producing a single-ring polygon without holes
template <typename N> template <typename Polygon>
typename Earcut<N>::Node*
Earcut<N>::eliminateHoles(const Polygon& points, Node* outerNode) {
const size_t len = points.size();
std::vector<Node*> queue;
for (size_t i = 1; i < len; i++) {
Node* list = linkedList(points[i], false);
if (list) {
if (list == list->next) list->steiner = true;
queue.push_back(getLeftmost(list));
}
}
std::sort(queue.begin(), queue.end(), [](const Node* a, const Node* b) {
return a->x < b->x;
});
// process holes from left to right
for (size_t i = 0; i < queue.size(); i++) {
outerNode = eliminateHole(queue[i], outerNode);
}
return outerNode;
}
// find a bridge between vertices that connects hole with an outer ring and and link it
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::eliminateHole(Node* hole, Node* outerNode) {
Node* bridge = findHoleBridge(hole, outerNode);
if (!bridge) {
return outerNode;
}
Node* bridgeReverse = splitPolygon(bridge, hole);
// filter collinear points around the cuts
filterPoints(bridgeReverse, bridgeReverse->next);
// Check if input node was removed by the filtering
return filterPoints(bridge, bridge->next);
}
// David Eberly's algorithm for finding a bridge between hole and outer polygon
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::findHoleBridge(Node* hole, Node* outerNode) {
Node* p = outerNode;
double hx = hole->x;
double hy = hole->y;
double qx = -std::numeric_limits<double>::infinity();
Node* m = nullptr;
// find a segment intersected by a ray from the hole's leftmost Vertex to the left;
// segment's endpoint with lesser x will be potential connection Vertex
do {
if (hy <= p->y && hy >= p->next->y && p->next->y != p->y) {
double x = p->x + (hy - p->y) * (p->next->x - p->x) / (p->next->y - p->y);
if (x <= hx && x > qx) {
qx = x;
m = p->x < p->next->x ? p : p->next;
if (x == hx) return m; // hole touches outer segment; pick leftmost endpoint
}
}
p = p->next;
} while (p != outerNode);
if (!m) return 0;
// look for points inside the triangle of hole Vertex, segment intersection and endpoint;
// if there are no points found, we have a valid connection;
// otherwise choose the Vertex of the minimum angle with the ray as connection Vertex
const Node* stop = m;
double tanMin = std::numeric_limits<double>::infinity();
double tanCur = 0;
p = m;
double mx = m->x;
double my = m->y;
do {
if (hx >= p->x && p->x >= mx && hx != p->x &&
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p->x, p->y)) {
tanCur = std::abs(hy - p->y) / (hx - p->x); // tangential
if (locallyInside(p, hole) &&
(tanCur < tanMin || (tanCur == tanMin && (p->x > m->x || sectorContainsSector(m, p))))) {
m = p;
tanMin = tanCur;
}
}
p = p->next;
} while (p != stop);
return m;
}
// whether sector in vertex m contains sector in vertex p in the same coordinates
template <typename N>
bool Earcut<N>::sectorContainsSector(const Node* m, const Node* p) {
return area(m->prev, m, p->prev) < 0 && area(p->next, m, m->next) < 0;
}
// interlink polygon nodes in z-order
template <typename N>
void Earcut<N>::indexCurve(Node* start) {
assert(start);
Node* p = start;
do {
p->z = p->z ? p->z : zOrder(p->x, p->y);
p->prevZ = p->prev;
p->nextZ = p->next;
p = p->next;
} while (p != start);
p->prevZ->nextZ = nullptr;
p->prevZ = nullptr;
sortLinked(p);
}
// Simon Tatham's linked list merge sort algorithm
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::sortLinked(Node* list) {
assert(list);
Node* p;
Node* q;
Node* e;
Node* tail;
int i, numMerges, pSize, qSize;
int inSize = 1;
for (;;) {
p = list;
list = nullptr;
tail = nullptr;
numMerges = 0;
while (p) {
numMerges++;
q = p;
pSize = 0;
for (i = 0; i < inSize; i++) {
pSize++;
q = q->nextZ;
if (!q) break;
}
qSize = inSize;
while (pSize > 0 || (qSize > 0 && q)) {
if (pSize == 0) {
e = q;
q = q->nextZ;
qSize--;
} else if (qSize == 0 || !q) {
e = p;
p = p->nextZ;
pSize--;
} else if (p->z <= q->z) {
e = p;
p = p->nextZ;
pSize--;
} else {
e = q;
q = q->nextZ;
qSize--;
}
if (tail) tail->nextZ = e;
else list = e;
e->prevZ = tail;
tail = e;
}
p = q;
}
tail->nextZ = nullptr;
if (numMerges <= 1) return list;
inSize *= 2;
}
}
// z-order of a Vertex given coords and size of the data bounding box
template <typename N>
int32_t Earcut<N>::zOrder(const double x_, const double y_) {
// coords are transformed into non-negative 15-bit integer range
int32_t x = static_cast<int32_t>((x_ - minX) * inv_size);
int32_t y = static_cast<int32_t>((y_ - minY) * inv_size);
x = (x | (x << 8)) & 0x00FF00FF;
x = (x | (x << 4)) & 0x0F0F0F0F;
x = (x | (x << 2)) & 0x33333333;
x = (x | (x << 1)) & 0x55555555;
y = (y | (y << 8)) & 0x00FF00FF;
y = (y | (y << 4)) & 0x0F0F0F0F;
y = (y | (y << 2)) & 0x33333333;
y = (y | (y << 1)) & 0x55555555;
return x | (y << 1);
}
// find the leftmost node of a polygon ring
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::getLeftmost(Node* start) {
Node* p = start;
Node* leftmost = start;
do {
if (p->x < leftmost->x || (p->x == leftmost->x && p->y < leftmost->y))
leftmost = p;
p = p->next;
} while (p != start);
return leftmost;
}
// check if a point lies within a convex triangle
template <typename N>
bool Earcut<N>::pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const {
return (cx - px) * (ay - py) >= (ax - px) * (cy - py) &&
(ax - px) * (by - py) >= (bx - px) * (ay - py) &&
(bx - px) * (cy - py) >= (cx - px) * (by - py);
}
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
template <typename N>
bool Earcut<N>::isValidDiagonal(Node* a, Node* b) {
return a->next->i != b->i && a->prev->i != b->i && !intersectsPolygon(a, b) && // dones't intersect other edges
((locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
(area(a->prev, a, b->prev) != 0.0 || area(a, b->prev, b) != 0.0)) || // does not create opposite-facing sectors
(equals(a, b) && area(a->prev, a, a->next) > 0 && area(b->prev, b, b->next) > 0)); // special zero-length case
}
// signed area of a triangle
template <typename N>
double Earcut<N>::area(const Node* p, const Node* q, const Node* r) const {
return (q->y - p->y) * (r->x - q->x) - (q->x - p->x) * (r->y - q->y);
}
// check if two points are equal
template <typename N>
bool Earcut<N>::equals(const Node* p1, const Node* p2) {
return p1->x == p2->x && p1->y == p2->y;
}
// check if two segments intersect
template <typename N>
bool Earcut<N>::intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2) {
int o1 = sign(area(p1, q1, p2));
int o2 = sign(area(p1, q1, q2));
int o3 = sign(area(p2, q2, p1));
int o4 = sign(area(p2, q2, q1));
if (o1 != o2 && o3 != o4) return true; // general case
if (o1 == 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1
if (o2 == 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1
if (o3 == 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2
if (o4 == 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2
return false;
}
// for collinear points p, q, r, check if point q lies on segment pr
template <typename N>
bool Earcut<N>::onSegment(const Node* p, const Node* q, const Node* r) {
return q->x <= std::max<double>(p->x, r->x) &&
q->x >= std::min<double>(p->x, r->x) &&
q->y <= std::max<double>(p->y, r->y) &&
q->y >= std::min<double>(p->y, r->y);
}
template <typename N>
int Earcut<N>::sign(double val) {
return (0.0 < val) - (val < 0.0);
}
// check if a polygon diagonal intersects any polygon segments
template <typename N>
bool Earcut<N>::intersectsPolygon(const Node* a, const Node* b) {
const Node* p = a;
do {
if (p->i != a->i && p->next->i != a->i && p->i != b->i && p->next->i != b->i &&
intersects(p, p->next, a, b)) return true;
p = p->next;
} while (p != a);
return false;
}
// check if a polygon diagonal is locally inside the polygon
template <typename N>
bool Earcut<N>::locallyInside(const Node* a, const Node* b) {
return area(a->prev, a, a->next) < 0 ?
area(a, b, a->next) >= 0 && area(a, a->prev, b) >= 0 :
area(a, b, a->prev) < 0 || area(a, a->next, b) < 0;
}
// check if the middle Vertex of a polygon diagonal is inside the polygon
template <typename N>
bool Earcut<N>::middleInside(const Node* a, const Node* b) {
const Node* p = a;
bool inside = false;
double px = (a->x + b->x) / 2;
double py = (a->y + b->y) / 2;
do {
if (((p->y > py) != (p->next->y > py)) && p->next->y != p->y &&
(px < (p->next->x - p->x) * (py - p->y) / (p->next->y - p->y) + p->x))
inside = !inside;
p = p->next;
} while (p != a);
return inside;
}
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits
// polygon into two; if one belongs to the outer ring and another to a hole, it merges it into a
// single ring
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::splitPolygon(Node* a, Node* b) {
Node* a2 = nodes.construct(a->i, a->x, a->y);
Node* b2 = nodes.construct(b->i, b->x, b->y);
Node* an = a->next;
Node* bp = b->prev;
a->next = b;
b->prev = a;
a2->next = an;
an->prev = a2;
b2->next = a2;
a2->prev = b2;
bp->next = b2;
b2->prev = bp;
return b2;
}
// create a node and util::optionally link it with previous one (in a circular doubly linked list)
template <typename N> template <typename Point>
typename Earcut<N>::Node*
Earcut<N>::insertNode(std::size_t i, const Point& pt, Node* last) {
Node* p = nodes.construct(static_cast<N>(i), util::nth<0, Point>::get(pt), util::nth<1, Point>::get(pt));
if (!last) {
p->prev = p;
p->next = p;
} else {
assert(last);
p->next = last->next;
p->prev = last;
last->next->prev = p;
last->next = p;
}
return p;
}
template <typename N>
void Earcut<N>::removeNode(Node* p) {
p->next->prev = p->prev;
p->prev->next = p->next;
if (p->prevZ) p->prevZ->nextZ = p->nextZ;
if (p->nextZ) p->nextZ->prevZ = p->prevZ;
}
}
template <typename N = uint32_t, typename Polygon>
std::vector<N> earcut(const Polygon& poly) {
mapbox::detail::Earcut<N> earcut;
earcut(poly);
return std::move(earcut.indices);
}
}

Some files were not shown because too many files have changed in this diff Show more