|
@@ -15,12 +15,18 @@
|
|
|
#ifndef DRACO_ATTRIBUTES_GEOMETRY_ATTRIBUTE_H_
|
|
|
#define DRACO_ATTRIBUTES_GEOMETRY_ATTRIBUTE_H_
|
|
|
|
|
|
+#include <algorithm>
|
|
|
#include <array>
|
|
|
+#include <cmath>
|
|
|
#include <limits>
|
|
|
|
|
|
#include "draco/attributes/geometry_indices.h"
|
|
|
#include "draco/core/data_buffer.h"
|
|
|
#include "draco/core/hash_utils.h"
|
|
|
+#include "draco/draco_features.h"
|
|
|
+#ifdef DRACO_TRANSCODER_SUPPORTED
|
|
|
+#include "draco/core/status.h"
|
|
|
+#endif
|
|
|
|
|
|
namespace draco {
|
|
|
|
|
@@ -51,6 +57,16 @@ class GeometryAttribute {
|
|
|
// predefined use case. Such attributes are often used for a shader specific
|
|
|
// data.
|
|
|
GENERIC,
|
|
|
+#ifdef DRACO_TRANSCODER_SUPPORTED
|
|
|
+ // TODO(ostava): Adding a new attribute would be bit-stream change for GLTF.
|
|
|
+ // Older decoders wouldn't know what to do with this attribute type. This
|
|
|
+ // should be open-sourced only when we are ready to increase our bit-stream
|
|
|
+ // version.
|
|
|
+ TANGENT,
|
|
|
+ MATERIAL,
|
|
|
+ JOINTS,
|
|
|
+ WEIGHTS,
|
|
|
+#endif
|
|
|
// Total number of different attribute types.
|
|
|
// Always keep behind all named attributes.
|
|
|
NAMED_ATTRIBUTES_COUNT,
|
|
@@ -58,7 +74,7 @@ class GeometryAttribute {
|
|
|
|
|
|
GeometryAttribute();
|
|
|
// Initializes and enables the attribute.
|
|
|
- void Init(Type attribute_type, DataBuffer *buffer, int8_t num_components,
|
|
|
+ void Init(Type attribute_type, DataBuffer *buffer, uint8_t num_components,
|
|
|
DataType data_type, bool normalized, int64_t byte_stride,
|
|
|
int64_t byte_offset);
|
|
|
bool IsValid() const { return buffer_ != nullptr; }
|
|
@@ -129,6 +145,17 @@ class GeometryAttribute {
|
|
|
buffer_->Write(byte_pos, value, byte_stride());
|
|
|
}
|
|
|
|
|
|
+#ifdef DRACO_TRANSCODER_SUPPORTED
|
|
|
+ // Sets a value of an attribute entry. The input |value| must have
|
|
|
+ // |input_num_components| entries and it will be automatically converted to
|
|
|
+ // the internal format used by the geometry attribute. If the conversion is
|
|
|
+ // not possible, an error status will be returned.
|
|
|
+ template <typename InputT>
|
|
|
+ Status ConvertAndSetAttributeValue(AttributeValueIndex avi,
|
|
|
+ int input_num_components,
|
|
|
+ const InputT *value);
|
|
|
+#endif
|
|
|
+
|
|
|
// DEPRECATED: Use
|
|
|
// ConvertValue(AttributeValueIndex att_id,
|
|
|
// int out_num_components,
|
|
@@ -233,10 +260,11 @@ class GeometryAttribute {
|
|
|
// Returns the number of components that are stored for each entry.
|
|
|
// For position attribute this is usually three (x,y,z),
|
|
|
// while texture coordinates have two components (u,v).
|
|
|
- int8_t num_components() const { return num_components_; }
|
|
|
+ uint8_t num_components() const { return num_components_; }
|
|
|
// Indicates whether the data type should be normalized before interpretation,
|
|
|
// that is, it should be divided by the max value of the data type.
|
|
|
bool normalized() const { return normalized_; }
|
|
|
+ void set_normalized(bool normalized) { normalized_ = normalized; }
|
|
|
// The buffer storing the entire data of the attribute.
|
|
|
const DataBuffer *buffer() const { return buffer_; }
|
|
|
// Returns the number of bytes between two attribute entries, this is, at
|
|
@@ -260,7 +288,7 @@ class GeometryAttribute {
|
|
|
// T is the stored attribute data type.
|
|
|
// OutT is the desired data type of the attribute.
|
|
|
template <typename T, typename OutT>
|
|
|
- bool ConvertTypedValue(AttributeValueIndex att_id, int8_t out_num_components,
|
|
|
+ bool ConvertTypedValue(AttributeValueIndex att_id, uint8_t out_num_components,
|
|
|
OutT *out_value) const {
|
|
|
const uint8_t *src_address = GetAddress(att_id);
|
|
|
|
|
@@ -270,35 +298,132 @@ class GeometryAttribute {
|
|
|
return false;
|
|
|
}
|
|
|
const T in_value = *reinterpret_cast<const T *>(src_address);
|
|
|
+ if (!ConvertComponentValue<T, OutT>(in_value, normalized_,
|
|
|
+ out_value + i)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ src_address += sizeof(T);
|
|
|
+ }
|
|
|
+ // Fill empty data for unused output components if needed.
|
|
|
+ for (int i = num_components_; i < out_num_components; ++i) {
|
|
|
+ out_value[i] = static_cast<OutT>(0);
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+#ifdef DRACO_TRANSCODER_SUPPORTED
|
|
|
+ // Function that converts input |value| from type T to the internal attribute
|
|
|
+ // representation defined by OutT and |num_components_|.
|
|
|
+ template <typename T, typename OutT>
|
|
|
+ Status ConvertAndSetAttributeTypedValue(AttributeValueIndex avi,
|
|
|
+ int8_t input_num_components,
|
|
|
+ const T *value) {
|
|
|
+ uint8_t *address = GetAddress(avi);
|
|
|
+
|
|
|
+ // Convert all components available in both the original and output formats.
|
|
|
+ for (int i = 0; i < num_components_; ++i) {
|
|
|
+ if (!IsAddressValid(address)) {
|
|
|
+ return ErrorStatus("GeometryAttribute: Invalid address.");
|
|
|
+ }
|
|
|
+ OutT *const out_value = reinterpret_cast<OutT *>(address);
|
|
|
+ if (i < input_num_components) {
|
|
|
+ if (!ConvertComponentValue<T, OutT>(*(value + i), normalized_,
|
|
|
+ out_value)) {
|
|
|
+ return ErrorStatus(
|
|
|
+ "GeometryAttribute: Failed to convert component value.");
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ *out_value = static_cast<OutT>(0);
|
|
|
+ }
|
|
|
+ address += sizeof(OutT);
|
|
|
+ }
|
|
|
+ return OkStatus();
|
|
|
+ }
|
|
|
+#endif // DRACO_TRANSCODER_SUPPORTED
|
|
|
|
|
|
- // Make sure the in_value fits within the range of values that OutT
|
|
|
+ // Converts |in_value| of type T into |out_value| of type OutT. If
|
|
|
+ // |normalized| is true, any conversion between floating point and integer
|
|
|
+ // values will be treating integers as normalized types (the entire integer
|
|
|
+ // range will be used to represent 0-1 floating point range).
|
|
|
+ template <typename T, typename OutT>
|
|
|
+ static bool ConvertComponentValue(const T &in_value, bool normalized,
|
|
|
+ OutT *out_value) {
|
|
|
+ // Make sure the |in_value| can be represented as an integral type OutT.
|
|
|
+ if (std::is_integral<OutT>::value) {
|
|
|
+ // Make sure the |in_value| fits within the range of values that OutT
|
|
|
// is able to represent. Perform the check only for integral types.
|
|
|
- if (std::is_integral<T>::value && std::is_integral<OutT>::value) {
|
|
|
+ if (!std::is_same<T, bool>::value && std::is_integral<T>::value) {
|
|
|
static constexpr OutT kOutMin =
|
|
|
- std::is_signed<T>::value ? std::numeric_limits<OutT>::lowest() : 0;
|
|
|
+ std::is_signed<T>::value ? std::numeric_limits<OutT>::min() : 0;
|
|
|
if (in_value < kOutMin || in_value > std::numeric_limits<OutT>::max()) {
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- out_value[i] = static_cast<OutT>(in_value);
|
|
|
+ // Check conversion of floating point |in_value| to integral value OutT.
|
|
|
+ if (std::is_floating_point<T>::value) {
|
|
|
+ // Make sure the floating point |in_value| is not NaN and not Inf as
|
|
|
+ // integral type OutT is unable to represent these values.
|
|
|
+ if (sizeof(in_value) > sizeof(double)) {
|
|
|
+ if (std::isnan(static_cast<long double>(in_value)) ||
|
|
|
+ std::isinf(static_cast<long double>(in_value))) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } else if (sizeof(in_value) > sizeof(float)) {
|
|
|
+ if (std::isnan(static_cast<double>(in_value)) ||
|
|
|
+ std::isinf(static_cast<double>(in_value))) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (std::isnan(static_cast<float>(in_value)) ||
|
|
|
+ std::isinf(static_cast<float>(in_value))) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Make sure the floating point |in_value| fits within the range of
|
|
|
+ // values that integral type OutT is able to represent.
|
|
|
+ if (in_value < std::numeric_limits<OutT>::min() ||
|
|
|
+ in_value >= std::numeric_limits<OutT>::max()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (std::is_integral<T>::value && std::is_floating_point<OutT>::value &&
|
|
|
+ normalized) {
|
|
|
// When converting integer to floating point, normalize the value if
|
|
|
// necessary.
|
|
|
- if (std::is_integral<T>::value && std::is_floating_point<OutT>::value &&
|
|
|
- normalized_) {
|
|
|
- out_value[i] /= static_cast<OutT>(std::numeric_limits<T>::max());
|
|
|
+ *out_value = static_cast<OutT>(in_value);
|
|
|
+ *out_value /= static_cast<OutT>(std::numeric_limits<T>::max());
|
|
|
+ } else if (std::is_floating_point<T>::value &&
|
|
|
+ std::is_integral<OutT>::value && normalized) {
|
|
|
+ // Converting from floating point to a normalized integer.
|
|
|
+ if (in_value > 1 || in_value < 0) {
|
|
|
+ // Normalized float values need to be between 0 and 1.
|
|
|
+ return false;
|
|
|
}
|
|
|
- // TODO(ostava): Add handling of normalized attributes when converting
|
|
|
- // between different integer representations. If the attribute is
|
|
|
- // normalized, integer values should be converted as if they represent 0-1
|
|
|
- // range. E.g. when we convert uint16 to uint8, the range <0, 2^16 - 1>
|
|
|
- // should be converted to range <0, 2^8 - 1>.
|
|
|
- src_address += sizeof(T);
|
|
|
- }
|
|
|
- // Fill empty data for unused output components if needed.
|
|
|
- for (int i = num_components_; i < out_num_components; ++i) {
|
|
|
- out_value[i] = static_cast<OutT>(0);
|
|
|
+ // TODO(ostava): Consider allowing float to normalized integer conversion
|
|
|
+ // for 64-bit integer types. Currently it doesn't work because we don't
|
|
|
+ // have a floating point type that could store all 64 bit integers.
|
|
|
+ if (sizeof(OutT) > 4) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // Expand the float to the range of the output integer and round it to the
|
|
|
+ // nearest representable value. Use doubles for the math to ensure the
|
|
|
+ // integer values are represented properly during the conversion process.
|
|
|
+ *out_value = static_cast<OutT>(std::floor(
|
|
|
+ in_value * static_cast<double>(std::numeric_limits<OutT>::max()) +
|
|
|
+ 0.5));
|
|
|
+ } else {
|
|
|
+ *out_value = static_cast<OutT>(in_value);
|
|
|
}
|
|
|
+
|
|
|
+ // TODO(ostava): Add handling of normalized attributes when converting
|
|
|
+ // between different integer representations. If the attribute is
|
|
|
+ // normalized, integer values should be converted as if they represent 0-1
|
|
|
+ // range. E.g. when we convert uint16 to uint8, the range <0, 2^16 - 1>
|
|
|
+ // should be converted to range <0, 2^8 - 1>.
|
|
|
return true;
|
|
|
}
|
|
|
|
|
@@ -307,7 +432,7 @@ class GeometryAttribute {
|
|
|
// attribute. The purpose is to detect if any changes happened to the buffer
|
|
|
// since the time it was attached.
|
|
|
DataBufferDescriptor buffer_descriptor_;
|
|
|
- int8_t num_components_;
|
|
|
+ uint8_t num_components_;
|
|
|
DataType data_type_;
|
|
|
bool normalized_;
|
|
|
int64_t byte_stride_;
|
|
@@ -323,6 +448,54 @@ class GeometryAttribute {
|
|
|
friend struct GeometryAttributeHasher;
|
|
|
};
|
|
|
|
|
|
+#ifdef DRACO_TRANSCODER_SUPPORTED
|
|
|
+template <typename InputT>
|
|
|
+Status GeometryAttribute::ConvertAndSetAttributeValue(AttributeValueIndex avi,
|
|
|
+ int input_num_components,
|
|
|
+ const InputT *value) {
|
|
|
+ switch (this->data_type()) {
|
|
|
+ case DT_INT8:
|
|
|
+ return ConvertAndSetAttributeTypedValue<InputT, int8_t>(
|
|
|
+ avi, input_num_components, value);
|
|
|
+ case DT_UINT8:
|
|
|
+ return ConvertAndSetAttributeTypedValue<InputT, uint8_t>(
|
|
|
+ avi, input_num_components, value);
|
|
|
+ case DT_INT16:
|
|
|
+ return ConvertAndSetAttributeTypedValue<InputT, int16_t>(
|
|
|
+ avi, input_num_components, value);
|
|
|
+ case DT_UINT16:
|
|
|
+ return ConvertAndSetAttributeTypedValue<InputT, uint16_t>(
|
|
|
+ avi, input_num_components, value);
|
|
|
+ case DT_INT32:
|
|
|
+ return ConvertAndSetAttributeTypedValue<InputT, int32_t>(
|
|
|
+ avi, input_num_components, value);
|
|
|
+ case DT_UINT32:
|
|
|
+ return ConvertAndSetAttributeTypedValue<InputT, uint32_t>(
|
|
|
+ avi, input_num_components, value);
|
|
|
+ case DT_INT64:
|
|
|
+ return ConvertAndSetAttributeTypedValue<InputT, int64_t>(
|
|
|
+ avi, input_num_components, value);
|
|
|
+ case DT_UINT64:
|
|
|
+ return ConvertAndSetAttributeTypedValue<InputT, uint64_t>(
|
|
|
+ avi, input_num_components, value);
|
|
|
+ case DT_FLOAT32:
|
|
|
+ return ConvertAndSetAttributeTypedValue<InputT, float>(
|
|
|
+ avi, input_num_components, value);
|
|
|
+ case DT_FLOAT64:
|
|
|
+ return ConvertAndSetAttributeTypedValue<InputT, double>(
|
|
|
+ avi, input_num_components, value);
|
|
|
+ case DT_BOOL:
|
|
|
+ return ConvertAndSetAttributeTypedValue<InputT, bool>(
|
|
|
+ avi, input_num_components, value);
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return ErrorStatus(
|
|
|
+ "GeometryAttribute::SetAndConvertAttributeValue: Unsupported "
|
|
|
+ "attribute type.");
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
// Hashing support
|
|
|
|
|
|
// Function object for using Attribute as a hash key.
|