ralpha-assets/Plugins/CesiumForUnreal/Source/ThirdParty/include/CesiumGltf/PropertyView.h

2709 lines
83 KiB
C++

#pragma once
#include <CesiumGltf/ClassProperty.h>
#include <CesiumGltf/Enum.h>
#include <CesiumGltf/PropertyArrayView.h>
#include <CesiumGltf/PropertyAttributeProperty.h>
#include <CesiumGltf/PropertyTableProperty.h>
#include <CesiumGltf/PropertyTextureProperty.h>
#include <CesiumGltf/PropertyType.h>
#include <CesiumGltf/PropertyTypeTraits.h>
#include <algorithm>
#include <cstddef>
#include <cstring>
#include <optional>
namespace CesiumGltf {
/**
* @brief The type used for fields of \ref PropertyViewStatus.
*/
typedef int32_t PropertyViewStatusType;
/**
* @brief Indicates the status of a property view.
*
* The {@link PropertyView} constructor always completes successfully.
* However, there may be fundamental errors with the property definition. In
* such cases, this enumeration provides the reason.
*
* This is defined with a class of static consts as opposed to an enum, so that
* derived property view classes can extend the statuses with their own specific
* errors.
*/
class PropertyViewStatus {
public:
/**
* @brief This property view is valid and ready to use.
*/
static const PropertyViewStatusType Valid = 0;
/**
* @brief This property view does not contain any data, but specifies a
* default value. This happens when a class property is defined with a default
* value and omitted from an instance of the class's collective properties. In
* this case, it is not possible to retrieve the raw data from a property, but
* its default value will be accessible.
*/
static const PropertyViewStatusType EmptyPropertyWithDefault = 1;
/**
* @brief This property view is trying to view a property that does not
* exist.
*/
static const PropertyViewStatusType ErrorNonexistentProperty = 2;
/**
* @brief This property view's type does not match what is
* specified in {@link ClassProperty::type}.
*/
static const PropertyViewStatusType ErrorTypeMismatch = 3;
/**
* @brief This property view's component type does not match what
* is specified in {@link ClassProperty::componentType}.
*/
static const PropertyViewStatusType ErrorComponentTypeMismatch = 4;
/**
* @brief This property view differs from what is specified in
* {@link ClassProperty::array}.
*/
static const PropertyViewStatusType ErrorArrayTypeMismatch = 5;
/**
* @brief This property says it is normalized, but it does not have an integer
* component type.
*/
static const PropertyViewStatusType ErrorInvalidNormalization = 6;
/**
* @brief This property view's normalization differs from what
* is specified in {@link ClassProperty::normalized}
*/
static const PropertyViewStatusType ErrorNormalizationMismatch = 7;
/**
* @brief The property provided an invalid offset value.
*/
static const PropertyViewStatusType ErrorInvalidOffset = 8;
/**
* @brief The property provided an invalid scale value.
*/
static const PropertyViewStatusType ErrorInvalidScale = 9;
/**
* @brief The property provided an invalid maximum value.
*/
static const PropertyViewStatusType ErrorInvalidMax = 10;
/**
* @brief The property provided an invalid minimum value.
*/
static const PropertyViewStatusType ErrorInvalidMin = 11;
/**
* @brief The property provided an invalid "no data" value.
*/
static const PropertyViewStatusType ErrorInvalidNoDataValue = 12;
/**
* @brief The property provided an invalid default value.
*/
static const PropertyViewStatusType ErrorInvalidDefaultValue = 13;
/**
* @brief The property provided an invalid enum value.
*/
static const PropertyViewStatusType ErrorInvalidEnum = 14;
};
/**
* @brief Validates a \ref ClassProperty representing a property, checking for
* any type mismatches.
*
* @tparam T The value type of the PropertyView that was constructed for this
* ClassProperty.
* @param classProperty The class property to validate.
* @param pEnumDefinition If the class property is an enum, this should be the
* enum definition. If not, this should be nullptr.
*
* @returns A \ref PropertyViewStatus value representing the error found while
* validating, or \ref PropertyViewStatus::Valid if no errors were found.
*/
template <typename T>
PropertyViewStatusType validatePropertyType(
const ClassProperty& classProperty,
const CesiumGltf::Enum* pEnumDefinition = nullptr) {
if (!canRepresentPropertyType<T>(
convertStringToPropertyType(classProperty.type))) {
return PropertyViewStatus::ErrorTypeMismatch;
}
const bool isEnum = classProperty.type == ClassProperty::Type::ENUM;
const bool hasEnumDefinition = pEnumDefinition != nullptr;
if (isEnum != hasEnumDefinition) {
return PropertyViewStatus::ErrorInvalidEnum;
}
PropertyComponentType expectedComponentType =
TypeToPropertyType<T>::component;
if (isEnum) {
if (expectedComponentType !=
convertStringToPropertyComponentType(pEnumDefinition->valueType)) {
return PropertyViewStatus::ErrorComponentTypeMismatch;
}
} else {
if (!classProperty.componentType &&
expectedComponentType != PropertyComponentType::None) {
return PropertyViewStatus::ErrorComponentTypeMismatch;
}
if (classProperty.componentType &&
expectedComponentType != convertStringToPropertyComponentType(
*classProperty.componentType)) {
return PropertyViewStatus::ErrorComponentTypeMismatch;
}
}
if (classProperty.array) {
return PropertyViewStatus::ErrorArrayTypeMismatch;
}
return PropertyViewStatus::Valid;
}
/**
* @brief Validates a \ref ClassProperty representing an array of values,
* checking for any type mismatches.
*
* @tparam T The array type of the PropertyView that was constructed for this
* ClassProperty.
* @param classProperty The class property to validate.
* @param pEnumDefinition If the class property is an enum array, this should be
* the enum definition. If not, this should be nullptr.
*
* @returns A \ref PropertyViewStatus value representing the error found while
* validating, or \ref PropertyViewStatus::Valid if no errors were found.
*/
template <typename T>
PropertyViewStatusType validateArrayPropertyType(
const ClassProperty& classProperty,
const CesiumGltf::Enum* pEnumDefinition = nullptr) {
using ElementType = typename MetadataArrayType<T>::type;
if (!canRepresentPropertyType<ElementType>(
convertStringToPropertyType(classProperty.type))) {
return PropertyViewStatus::ErrorTypeMismatch;
}
const bool isEnum = classProperty.type == ClassProperty::Type::ENUM;
const bool hasEnumDefinition = pEnumDefinition != nullptr;
if (isEnum != hasEnumDefinition) {
return PropertyViewStatus::ErrorInvalidEnum;
}
PropertyComponentType expectedComponentType =
TypeToPropertyType<ElementType>::component;
if (isEnum) {
if (expectedComponentType !=
convertStringToPropertyComponentType(pEnumDefinition->valueType)) {
return PropertyViewStatus::ErrorComponentTypeMismatch;
}
} else {
if (!classProperty.componentType &&
expectedComponentType != PropertyComponentType::None) {
return PropertyViewStatus::ErrorComponentTypeMismatch;
}
if (classProperty.componentType &&
expectedComponentType != convertStringToPropertyComponentType(
*classProperty.componentType)) {
return PropertyViewStatus::ErrorComponentTypeMismatch;
}
}
if (!classProperty.array) {
return PropertyViewStatus::ErrorArrayTypeMismatch;
}
return PropertyViewStatus::Valid;
}
/**
* @brief Attempts to get a scalar value from the provided \ref
* CesiumUtility::JsonValue "JsonValue".
*
* @param jsonValue The value to attempt to get as a scalar.
* @returns A scalar of type `T` if successful, or `std::nullopt` if not.
*/
template <typename T>
static std::optional<T> getScalar(const CesiumUtility::JsonValue& jsonValue) {
return jsonValue.getSafeNumber<T>();
}
/**
* @brief Attempts to obtain a vector of type `VecType` from the provided \ref
* CesiumUtility::JsonValue "JsonValue".
*
* @param jsonValue The value to attempt to get as a vector. To be successful,
* this \ref CesiumUtility::JsonValue "JsonValue" must be an array with the same
* number of elements as `VecType`.
* @returns A vector of type `VecType` if successful, or `std::nullopt` if not.
*/
template <typename VecType>
static std::optional<VecType>
getVecN(const CesiumUtility::JsonValue& jsonValue) {
if (!jsonValue.isArray()) {
return std::nullopt;
}
const CesiumUtility::JsonValue::Array& array = jsonValue.getArray();
constexpr glm::length_t N = VecType::length();
if (array.size() != N) {
return std::nullopt;
}
using T = typename VecType::value_type;
VecType result;
for (glm::length_t i = 0; i < N; i++) {
std::optional<T> value = getScalar<T>(array[static_cast<size_t>(i)]);
if (!value) {
return std::nullopt;
}
result[i] = *value;
}
return result;
}
/**
* @brief Attempts to obtain a matrix of type `MatType` from the provided \ref
* CesiumUtility::JsonValue "JsonValue".
*
* @param jsonValue The value to attempt to get as a matrix. To be successful,
* this \ref CesiumUtility::JsonValue "JsonValue" must be an array with the same
* number of elements as `MatType`. For example, to read a 4x4 matrix, the \ref
* CesiumUtility::JsonValue "JsonValue" must be an array with 16 elements.
* @returns A matrix of type `MatType` if successful, or `std::nullopt` if not.
*/
template <typename MatType>
static std::optional<MatType>
getMatN(const CesiumUtility::JsonValue& jsonValue) {
if (!jsonValue.isArray()) {
return std::nullopt;
}
const CesiumUtility::JsonValue::Array& array = jsonValue.getArray();
constexpr glm::length_t N = MatType::length();
if (array.size() != static_cast<size_t>(N * N)) {
return std::nullopt;
}
using T = typename MatType::value_type;
MatType result;
for (glm::length_t i = 0; i < N; i++) {
// Try to parse each value in the column.
for (glm::length_t j = 0; j < N; j++) {
std::optional<T> value =
getScalar<T>(array[static_cast<size_t>(i * N + j)]);
if (!value) {
return std::nullopt;
}
result[i][j] = *value;
}
}
return result;
}
/**
* @brief Obtains the number of values of type `ElementType` that could fit in
* the buffer.
*
* @param buffer The buffer whose size will be used for this calculation.
* @returns The number of values of type `ElementType` that could fit in
* `buffer`. This value will be equivalent to `floor(buffer->size() /
* sizeof(ElementType))`.
*/
template <typename ElementType>
int64_t getCount(std::optional<std::vector<std::byte>>& buffer) {
if (!buffer) {
return 0;
}
return static_cast<int64_t>(buffer->size() / sizeof(ElementType));
}
/**
* @brief Represents a metadata property in EXT_structural_metadata.
*/
template <typename ElementType, bool Normalized = false> class PropertyView;
/**
* @brief Represents a non-normalized metadata property in
* EXT_structural_metadata.
*
* Whether they belong to property tables, property textures, or property
* attributes, properties have their own sub-properties affecting the actual
* property values. Although they are typically defined via class property, they
* may be overridden by individual instances of the property themselves. The
* constructor is responsible for resolving those differences.
*
* @tparam ElementType The C++ type of the values in this property
*/
template <typename ElementType> class PropertyView<ElementType, false> {
public:
/**
* @brief Constructs an empty property instance.
*/
PropertyView()
: _status(PropertyViewStatus::ErrorNonexistentProperty),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_offset(std::nullopt),
_scale(std::nullopt),
_max(std::nullopt),
_min(std::nullopt),
_required(false),
_noData(std::nullopt),
_defaultValue(std::nullopt),
_propertyType(PropertyType::Invalid) {}
/**
* @brief Constructs a property instance from a class definition only.
*/
PropertyView(const ClassProperty& classProperty)
: PropertyView(classProperty, nullptr) {}
/**
* @brief Constructs a property instance from a class definition and enum
* definition.
*/
PropertyView(
const ClassProperty& classProperty,
const CesiumGltf::Enum* pEnumDefinition)
: _status(
validatePropertyType<ElementType>(classProperty, pEnumDefinition)),
_name(classProperty.name),
_semantic(classProperty.semantic),
_description(classProperty.description),
_offset(std::nullopt),
_scale(std::nullopt),
_max(std::nullopt),
_min(std::nullopt),
_required(classProperty.required),
_noData(std::nullopt),
_defaultValue(std::nullopt),
_propertyType(convertStringToPropertyType(classProperty.type)) {
if (_status != PropertyViewStatus::Valid) {
return;
}
if (classProperty.normalized) {
_status = PropertyViewStatus::ErrorNormalizationMismatch;
return;
}
if (classProperty.type != ClassProperty::Type::ENUM) {
getNumericPropertyValues(classProperty);
}
if (_status != PropertyViewStatus::Valid) {
return;
}
if (classProperty.noData) {
if (!_required && _propertyType == PropertyType::Enum) {
CESIUM_ASSERT(pEnumDefinition != nullptr);
if constexpr (IsMetadataInteger<ElementType>::value) {
// "noData" can only be defined if the property is not required.
_noData = getEnumValue(*classProperty.noData, *pEnumDefinition);
}
} else if (!_required && _propertyType != PropertyType::Enum) {
_noData = getValue(*classProperty.noData);
}
if (!_noData) {
// The value was specified but something went wrong.
_status = PropertyViewStatus::ErrorInvalidNoDataValue;
return;
}
}
if (classProperty.defaultProperty) {
if (!_required && _propertyType == PropertyType::Enum) {
CESIUM_ASSERT(pEnumDefinition != nullptr);
if constexpr (IsMetadataInteger<ElementType>::value) {
// "default" can only be defined if the property is not required.
_defaultValue =
getEnumValue(*classProperty.defaultProperty, *pEnumDefinition);
}
} else if (!_required && _propertyType != PropertyType::Enum) {
_defaultValue = getValue(*classProperty.defaultProperty);
}
if (!_defaultValue) {
// The value was specified but something went wrong.
_status = PropertyViewStatus::ErrorInvalidDefaultValue;
return;
}
}
}
protected:
/**
* @brief Constructs an invalid instance for an erroneous property.
*
* @param status The value of {@link PropertyViewStatus} indicating the error
* with the property.
*/
PropertyView(PropertyViewStatusType status)
: _status(status),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_offset(std::nullopt),
_scale(std::nullopt),
_max(std::nullopt),
_min(std::nullopt),
_required(false),
_noData(std::nullopt),
_defaultValue(std::nullopt),
_propertyType(PropertyType::Invalid) {}
/**
* @brief Constructs a property instance from a property table property and
* its class definition, with an optional associated enum definition.
*/
PropertyView(
const ClassProperty& classProperty,
const PropertyTableProperty& property,
const CesiumGltf::Enum* pEnumDefinition = nullptr)
: PropertyView(classProperty, pEnumDefinition) {
if (_status != PropertyViewStatus::Valid) {
return;
}
// If the property has its own values, override the class-provided values.
if (classProperty.type != ClassProperty::Type::ENUM) {
getNumericPropertyValues(property);
}
}
/**
* @brief Constructs a property instance from a property texture property,
* class definition, and an optional associated enum definition.
*/
PropertyView(
const ClassProperty& classProperty,
const PropertyTextureProperty& property,
const CesiumGltf::Enum* pEnumDefinition = nullptr)
: PropertyView(classProperty, pEnumDefinition) {
if (_status != PropertyViewStatus::Valid) {
return;
}
if (classProperty.type != ClassProperty::Type::ENUM) {
getNumericPropertyValues(property);
}
}
/**
* @brief Constructs a property instance from a property attribute property
* and its class definition, along with an optional enum definition.
*/
PropertyView(
const ClassProperty& classProperty,
const PropertyAttributeProperty& property,
const CesiumGltf::Enum* pEnumDefinition = nullptr)
: PropertyView(classProperty, pEnumDefinition) {
if (_status != PropertyViewStatus::Valid) {
return;
}
// If the property has its own values, override the class-provided values.
if (classProperty.type != ClassProperty::Type::ENUM) {
getNumericPropertyValues(property);
}
}
public:
/**
* @brief Gets the status of this property view, indicating whether an error
* occurred.
*
* @return The status of this property view.
*/
PropertyViewStatusType status() const noexcept { return _status; }
/**
* @brief Gets the name of the property being viewed. Returns std::nullopt if
* no name was specified.
*/
const std::optional<std::string>& name() const noexcept { return _name; }
/**
* @brief Gets the semantic of the property being viewed. The semantic is an
* identifier that describes how this property should be interpreted, and
* cannot be used by other properties in the class. Returns std::nullopt if no
* semantic was specified.
*/
const std::optional<std::string>& semantic() const noexcept {
return _semantic;
}
/**
* @brief Gets the description of the property being viewed. Returns
* std::nullopt if no description was specified.
*/
const std::optional<std::string>& description() const noexcept {
return _description;
}
/**
* @brief Get the element count of the fixed-length arrays in this property.
* Only applicable when the property is an array type.
*
* @return The count of this property.
*/
int64_t arrayCount() const noexcept { return 0; }
/**
* @brief Whether this property has a normalized integer type.
*/
bool normalized() const noexcept { return false; }
/**
* @brief Gets the offset to apply to property values. Only applicable to
* SCALAR, VECN, and MATN types when the component type is FLOAT32 or
* FLOAT64, or when the property is normalized.
*
* @returns The property's offset, or std::nullopt if it was not specified.
*/
std::optional<ElementType> offset() const noexcept { return _offset; }
/**
* @brief Gets the scale to apply to property values. Only applicable to
* SCALAR, VECN, and MATN types when the component type is FLOAT32 or
* FLOAT64, or when the property is normalized.
*
* @returns The property's scale, or std::nullopt if it was not specified.
*/
std::optional<ElementType> scale() const noexcept { return _scale; }
/**
* @brief Gets the maximum allowed value for the property. Only applicable to
* SCALAR, VECN, and MATN types. This is the maximum of all property
* values, after the transforms based on the normalized, offset, and
* scale properties have been applied.
*
* @returns The property's maximum value, or std::nullopt if it was not
* specified.
*/
std::optional<ElementType> max() const noexcept { return _max; }
/**
* @brief Gets the minimum allowed value for the property. Only applicable to
* SCALAR, VECN, and MATN types. This is the minimum of all property
* values, after the transforms based on the normalized, offset, and
* scale properties have been applied.
*
* @returns The property's minimum value, or std::nullopt if it was not
* specified.
*/
std::optional<ElementType> min() const noexcept { return _min; }
/**
* @brief Whether the property must be present in every entity conforming to
* the class. If not required, instances of the property may include "no data"
* values, or the entire property may be omitted.
*/
bool required() const noexcept { return _required; }
/**
* @brief Gets the "no data" value, i.e., the value representing missing data
* in the property wherever it appears. Also known as a sentinel value. This
* is given as the plain property value, without the transforms from the
* normalized, offset, and scale properties.
*
* @returns The property's "no data" value, or std::nullopt if it was not
* specified.
*/
std::optional<ElementType> noData() const noexcept { return _noData; }
/**
* @brief Gets the default value to use when encountering a "no data" value or
* an omitted property. The value is given in its final form, taking the
* effect of normalized, offset, and scale properties into account.
*
* @returns The property's default value, or std::nullopt if it was not
* specified.
*/
std::optional<ElementType> defaultValue() const noexcept {
return _defaultValue;
}
/**
* @brief Returns the \ref PropertyType of the property this view is
* accessing.
*/
PropertyType propertyType() const noexcept { return _propertyType; }
protected:
/** @copydoc PropertyViewStatus */
PropertyViewStatusType _status;
private:
std::optional<std::string> _name;
std::optional<std::string> _semantic;
std::optional<std::string> _description;
std::optional<ElementType> _offset;
std::optional<ElementType> _scale;
std::optional<ElementType> _max;
std::optional<ElementType> _min;
bool _required;
std::optional<ElementType> _noData;
std::optional<ElementType> _defaultValue;
PropertyType _propertyType;
/**
* @brief Attempts to parse an ElementType from the given json value.
*
* If ElementType is a type with multiple components, e.g. a VECN or MATN
* type, this will return std::nullopt if one or more components could not be
* parsed.
*
* @return The value as an instance of ElementType, or std::nullopt if it
* could not be parsed.
*/
static std::optional<ElementType>
getValue(const CesiumUtility::JsonValue& jsonValue) {
if constexpr (IsMetadataScalar<ElementType>::value) {
return getScalar<ElementType>(jsonValue);
}
if constexpr (IsMetadataVecN<ElementType>::value) {
return getVecN<ElementType>(jsonValue);
}
if constexpr (IsMetadataMatN<ElementType>::value) {
return getMatN<ElementType>(jsonValue);
}
}
static std::optional<ElementType> getEnumValue(
const CesiumUtility::JsonValue& value,
const CesiumGltf::Enum& enumDefinition) {
if (!value.isString()) {
return std::nullopt;
}
const CesiumUtility::JsonValue::String& valueStr = value.getString();
const auto foundValue = std::find_if(
enumDefinition.values.begin(),
enumDefinition.values.end(),
[&valueStr](const CesiumGltf::EnumValue& enumValue) {
return enumValue.name == valueStr;
});
if (foundValue == enumDefinition.values.end()) {
return std::nullopt;
}
return static_cast<ElementType>(foundValue->value);
}
using PropertyDefinitionType = std::variant<
ClassProperty,
PropertyTableProperty,
PropertyTextureProperty,
PropertyAttributeProperty>;
/**
* @brief Attempts to parse offset, scale, min, and max properties from the
* given property type.
*/
void getNumericPropertyValues(const PropertyDefinitionType& inProperty) {
std::visit(
[this](auto property) {
if (property.offset) {
// Only floating point types can specify an offset.
switch (TypeToPropertyType<ElementType>::component) {
case PropertyComponentType::Float32:
case PropertyComponentType::Float64:
this->_offset = getValue(*property.offset);
if (this->_offset) {
break;
}
// If it does not break here, something went wrong.
[[fallthrough]];
default:
this->_status = PropertyViewStatus::ErrorInvalidOffset;
return;
}
}
if (property.scale) {
// Only floating point types can specify a scale.
switch (TypeToPropertyType<ElementType>::component) {
case PropertyComponentType::Float32:
case PropertyComponentType::Float64:
this->_scale = getValue(*property.scale);
if (this->_scale) {
break;
}
// If it does not break here, something went wrong.
[[fallthrough]];
default:
this->_status = PropertyViewStatus::ErrorInvalidScale;
return;
}
}
if (property.max) {
this->_max = getValue(*property.max);
if (!this->_max) {
// The value was specified but something went wrong.
this->_status = PropertyViewStatus::ErrorInvalidMax;
return;
}
}
if (property.min) {
this->_min = getValue(*property.min);
if (!this->_min) {
// The value was specified but something went wrong.
this->_status = PropertyViewStatus::ErrorInvalidMin;
return;
}
}
},
inProperty);
}
};
/**
* @brief Represents a normalized metadata property in
* EXT_structural_metadata.
*
* Whether they belong to property tables, property textures, or property
* attributes, properties have their own sub-properties affecting the actual
* property values. Although they are typically defined via class property, they
* may be overridden by individual instances of the property themselves. The
* constructor is responsible for resolving those differences.
*
* @tparam ElementType The C++ type of the values in this property. Must have an
* integer component type.
*/
template <typename ElementType> class PropertyView<ElementType, true> {
private:
using NormalizedType = typename TypeToNormalizedType<ElementType>::type;
public:
/**
* @brief Constructs an empty property instance.
*/
PropertyView()
: _status(PropertyViewStatus::ErrorNonexistentProperty),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_offset(std::nullopt),
_scale(std::nullopt),
_max(std::nullopt),
_min(std::nullopt),
_required(false),
_noData(std::nullopt),
_defaultValue(std::nullopt),
_propertyType(PropertyType::Invalid) {}
/**
* @brief Constructs a property instance from a class definition only.
*/
PropertyView(const ClassProperty& classProperty)
: _status(validatePropertyType<ElementType>(classProperty)),
_name(classProperty.name),
_semantic(classProperty.semantic),
_description(classProperty.description),
_offset(std::nullopt),
_scale(std::nullopt),
_max(std::nullopt),
_min(std::nullopt),
_required(classProperty.required),
_noData(std::nullopt),
_defaultValue(std::nullopt),
_propertyType(convertStringToPropertyType(classProperty.type)) {
if (_status != PropertyViewStatus::Valid) {
return;
}
if (!classProperty.normalized) {
_status = PropertyViewStatus::ErrorNormalizationMismatch;
}
getNumericPropertyValues(classProperty);
if (_status != PropertyViewStatus::Valid) {
return;
}
if (classProperty.noData) {
if (!_required) {
// "noData" should not be defined if the property is required.
_noData = getValue<ElementType>(*classProperty.noData);
}
if (!_noData) {
// The value was specified but something went wrong.
_status = PropertyViewStatus::ErrorInvalidNoDataValue;
return;
}
}
if (classProperty.defaultProperty) {
// default value should not be defined if the property is required.
if (!_required) {
_defaultValue =
getValue<NormalizedType>(*classProperty.defaultProperty);
}
if (!_defaultValue) {
// The value was specified but something went wrong.
_status = PropertyViewStatus::ErrorInvalidDefaultValue;
return;
}
}
}
protected:
/**
* @brief Constructs an invalid instance for an erroneous property.
*
* @param status The value of {@link PropertyViewStatus} indicating the error with the property.
*/
PropertyView(PropertyViewStatusType status)
: _status(status),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_offset(std::nullopt),
_scale(std::nullopt),
_max(std::nullopt),
_min(std::nullopt),
_required(false),
_noData(std::nullopt),
_defaultValue(std::nullopt),
_propertyType(PropertyType::Invalid) {}
/**
* @brief Constructs a property instance from a property table property and
* its class definition.
*/
PropertyView(
const ClassProperty& classProperty,
const PropertyTableProperty& property)
: PropertyView(classProperty) {
if (_status != PropertyViewStatus::Valid) {
return;
}
// If the property has its own values, override the class-provided values.
getNumericPropertyValues(property);
}
/**
* @brief Constructs a property instance from a property texture property and
* its class definition.
*/
PropertyView(
const ClassProperty& classProperty,
const PropertyTextureProperty& property)
: PropertyView(classProperty) {
if (_status != PropertyViewStatus::Valid) {
return;
}
// If the property has its own values, override the class-provided values.
getNumericPropertyValues(property);
}
/**
* @brief Constructs a property instance from a property attribute property
* and its class definition.
*/
PropertyView(
const ClassProperty& classProperty,
const PropertyAttributeProperty& property)
: PropertyView(classProperty) {
if (_status != PropertyViewStatus::Valid) {
return;
}
// If the property has its own values, override the class-provided values.
getNumericPropertyValues(property);
}
public:
/**
* @copydoc PropertyView<ElementType, false>::status
*/
PropertyViewStatusType status() const noexcept { return _status; }
/**
* @copydoc PropertyView<ElementType, false>::name
*/
const std::optional<std::string>& name() const noexcept { return _name; }
/**
* @copydoc PropertyView<ElementType, false>::semantic
*/
const std::optional<std::string>& semantic() const noexcept {
return _semantic;
}
/**
* @copydoc PropertyView<ElementType, false>::description
*/
const std::optional<std::string>& description() const noexcept {
return _description;
}
/**
* @copydoc PropertyView<ElementType, false>::arrayCount
*/
int64_t arrayCount() const noexcept { return 0; }
/**
* @copydoc PropertyView<ElementType, false>::normalized
*/
bool normalized() const noexcept { return true; }
/**
* @copydoc PropertyView<ElementType, false>::offset
*/
std::optional<NormalizedType> offset() const noexcept { return _offset; }
/**
* @copydoc PropertyView<ElementType, false>::scale
*/
std::optional<NormalizedType> scale() const noexcept { return _scale; }
/**
* @copydoc PropertyView<ElementType, false>::max
*/
std::optional<NormalizedType> max() const noexcept { return _max; }
/**
* @copydoc PropertyView<ElementType, false>::min
*/
std::optional<NormalizedType> min() const noexcept { return _min; }
/**
* @copydoc PropertyView<ElementType, false>::required
*/
bool required() const noexcept { return _required; }
/**
* @copydoc PropertyView<ElementType, false>::noData
*/
std::optional<ElementType> noData() const noexcept { return _noData; }
/**
* @copydoc PropertyView<ElementType, false>::defaultValue
*/
std::optional<NormalizedType> defaultValue() const noexcept {
return _defaultValue;
}
/**
* @brief Returns the \ref PropertyType of the property this view is
* accessing.
*/
PropertyType propertyType() const noexcept { return _propertyType; }
protected:
/** @copydoc PropertyViewStatus */
PropertyViewStatusType _status;
private:
std::optional<std::string> _name;
std::optional<std::string> _semantic;
std::optional<std::string> _description;
std::optional<NormalizedType> _offset;
std::optional<NormalizedType> _scale;
std::optional<NormalizedType> _max;
std::optional<NormalizedType> _min;
bool _required;
std::optional<ElementType> _noData;
std::optional<NormalizedType> _defaultValue;
PropertyType _propertyType;
/**
* @brief Attempts to parse from the given json value.
*
* If T is a type with multiple components, e.g. a VECN or MATN type, this
* will return std::nullopt if one or more components could not be parsed.
*
* @return The value as an instance of T, or std::nullopt if it could not be
* parsed.
*/
template <typename T>
static std::optional<T> getValue(const CesiumUtility::JsonValue& jsonValue) {
if constexpr (IsMetadataScalar<T>::value) {
return getScalar<T>(jsonValue);
}
if constexpr (IsMetadataVecN<T>::value) {
return getVecN<T>(jsonValue);
}
if constexpr (IsMetadataMatN<T>::value) {
return getMatN<T>(jsonValue);
}
}
using PropertyDefinitionType = std::variant<
ClassProperty,
PropertyTableProperty,
PropertyTextureProperty,
PropertyAttributeProperty>;
/**
* @brief Attempts to parse offset, scale, min, and max properties from the
* given property type.
*/
void getNumericPropertyValues(const PropertyDefinitionType& inProperty) {
std::visit(
[this](auto property) {
if (property.offset) {
_offset = getValue<NormalizedType>(*property.offset);
if (!_offset) {
// The value was specified but something went wrong.
_status = PropertyViewStatus::ErrorInvalidOffset;
return;
}
}
if (property.scale) {
_scale = getValue<NormalizedType>(*property.scale);
if (!_scale) {
// The value was specified but something went wrong.
_status = PropertyViewStatus::ErrorInvalidScale;
return;
}
}
if (property.max) {
_max = getValue<NormalizedType>(*property.max);
if (!_scale) {
// The value was specified but something went wrong.
_status = PropertyViewStatus::ErrorInvalidMax;
return;
}
}
if (property.min) {
_min = getValue<NormalizedType>(*property.min);
if (!_scale) {
// The value was specified but something went wrong.
_status = PropertyViewStatus::ErrorInvalidMin;
return;
}
}
},
inProperty);
}
};
/**
* @brief Represents a boolean metadata property in
* EXT_structural_metadata.
*/
template <> class PropertyView<bool> {
public:
/**
* @brief Constructs an empty property instance.
*/
PropertyView()
: _status(PropertyViewStatus::ErrorNonexistentProperty),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_required(false),
_defaultValue(std::nullopt) {}
/**
* @brief Constructs a property instance from a class definition only.
*/
PropertyView(const ClassProperty& classProperty)
: _status(validatePropertyType<bool>(classProperty)),
_name(classProperty.name),
_semantic(classProperty.semantic),
_description(classProperty.description),
_required(classProperty.required),
_defaultValue(std::nullopt) {
if (_status != PropertyViewStatus::Valid) {
return;
}
if (classProperty.defaultProperty) {
if (!_required) {
_defaultValue = getBooleanValue(*classProperty.defaultProperty);
}
if (!_defaultValue) {
// The value was specified but something went wrong.
_status = PropertyViewStatus::ErrorInvalidDefaultValue;
return;
}
}
}
protected:
/**
* @brief Constructs an invalid instance for an erroneous property.
*
* @param status The value of {@link PropertyViewStatus} indicating the error with the property.
*/
PropertyView(PropertyViewStatusType status)
: _status(status),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_required(false),
_defaultValue(std::nullopt) {}
/**
* @brief Constructs a property instance from a property table property and
* its class definition.
*/
PropertyView(
const ClassProperty& classProperty,
const PropertyTableProperty& /*property*/,
const CesiumGltf::Enum* /*pEnumDefinition*/)
: PropertyView(classProperty) {}
public:
/**
* @copydoc PropertyView<ElementType, false>::status
*/
PropertyViewStatusType status() const noexcept { return _status; }
/**
* @copydoc PropertyView<ElementType, false>::name
*/
const std::optional<std::string>& name() const noexcept { return _name; }
/**
* @copydoc PropertyView<ElementType, false>::semantic
*/
const std::optional<std::string>& semantic() const noexcept {
return _semantic;
}
/**
* @copydoc PropertyView<ElementType, false>::description
*/
const std::optional<std::string>& description() const noexcept {
return _description;
}
/**
* @copydoc PropertyView<ElementType, false>::arrayCount
*/
int64_t arrayCount() const noexcept { return 0; }
/**
* @copydoc PropertyView<ElementType, false>::normalized
*/
bool normalized() const noexcept { return false; }
/**
* @copydoc PropertyView<ElementType, false>::offset
*/
std::optional<bool> offset() const noexcept { return std::nullopt; }
/**
* @copydoc PropertyView<ElementType, false>::scale
*/
std::optional<bool> scale() const noexcept { return std::nullopt; }
/**
* @copydoc PropertyView<ElementType, false>::max
*/
std::optional<bool> max() const noexcept { return std::nullopt; }
/**
* @copydoc PropertyView<ElementType, false>::min
*/
std::optional<bool> min() const noexcept { return std::nullopt; }
/**
* @copydoc PropertyView<ElementType, false>::required
*/
bool required() const noexcept { return _required; }
/**
* @copydoc PropertyView<ElementType, false>::noData
*/
std::optional<bool> noData() const noexcept { return std::nullopt; }
/**
* @copydoc PropertyView<ElementType, false>::defaultValue
*/
std::optional<bool> defaultValue() const noexcept { return _defaultValue; }
/**
* @brief Returns the \ref PropertyType of the property this view is
* accessing.
*/
PropertyType propertyType() const noexcept { return PropertyType::Boolean; }
protected:
/** @copydoc PropertyViewStatus */
PropertyViewStatusType _status;
private:
std::optional<std::string> _name;
std::optional<std::string> _semantic;
std::optional<std::string> _description;
bool _required;
std::optional<bool> _defaultValue;
static std::optional<bool>
getBooleanValue(const CesiumUtility::JsonValue& value) {
if (!value.isBool()) {
return std::nullopt;
}
return value.getBool();
}
};
/**
* @brief Represents a string metadata property in
* EXT_structural_metadata.
*/
template <> class PropertyView<std::string_view> {
public:
/**
* @brief Constructs an empty property instance.
*/
PropertyView()
: _status(PropertyViewStatus::ErrorNonexistentProperty),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_required(false),
_noData(std::nullopt),
_defaultValue(std::nullopt) {}
/**
* @brief Constructs a property instance from a class definition only.
*/
PropertyView(const ClassProperty& classProperty)
: _status(validatePropertyType<std::string_view>(classProperty)),
_name(classProperty.name),
_semantic(classProperty.semantic),
_description(classProperty.description),
_required(classProperty.required),
_noData(std::nullopt),
_defaultValue(std::nullopt) {
if (_status != PropertyViewStatus::Valid) {
return;
}
if (classProperty.noData) {
if (!_required) {
_noData = getStringValue(*classProperty.noData);
}
if (!_noData) {
// The value was specified but something went wrong.
_status = PropertyViewStatus::ErrorInvalidNoDataValue;
return;
}
}
if (classProperty.defaultProperty) {
if (!_required) {
_defaultValue = getStringValue(*classProperty.defaultProperty);
}
if (!_defaultValue) {
// The value was specified but something went wrong.
_status = PropertyViewStatus::ErrorInvalidDefaultValue;
return;
}
}
}
protected:
/**
* @brief Constructs an invalid instance for an erroneous property.
*
* @param status The value of {@link PropertyViewStatus} indicating the error with the property.
*/
PropertyView(PropertyViewStatusType status)
: _status(status),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_required(false),
_noData(std::nullopt),
_defaultValue(std::nullopt) {}
/**
* @brief Constructs a property instance from a property table property and
* its class definition.
*/
PropertyView(
const ClassProperty& classProperty,
const PropertyTableProperty& /*property*/,
const CesiumGltf::Enum* /*pEnumDefinition*/)
: PropertyView(classProperty) {}
public:
/**
* @copydoc PropertyView<ElementType, false>::status
*/
PropertyViewStatusType status() const noexcept { return _status; }
/**
* @copydoc PropertyView<ElementType, false>::name
*/
const std::optional<std::string>& name() const noexcept { return _name; }
/**
* @copydoc PropertyView<ElementType, false>::semantic
*/
const std::optional<std::string>& semantic() const noexcept {
return _semantic;
}
/**
* @copydoc PropertyView<ElementType, false>::description
*/
const std::optional<std::string>& description() const noexcept {
return _description;
}
/**
* @copydoc PropertyView<ElementType, false>::arrayCount
*/
int64_t arrayCount() const noexcept { return 0; }
/**
* @copydoc PropertyView<ElementType, false>::normalized
*/
bool normalized() const noexcept { return false; }
/**
* @copydoc PropertyView<ElementType, false>::offset
*/
std::optional<std::string_view> offset() const noexcept {
return std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::scale
*/
std::optional<std::string_view> scale() const noexcept {
return std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::max
*/
std::optional<std::string_view> max() const noexcept { return std::nullopt; }
/**
* @copydoc PropertyView<ElementType, false>::min
*/
std::optional<std::string_view> min() const noexcept { return std::nullopt; }
/**
* @copydoc PropertyView<ElementType, false>::required
*/
bool required() const noexcept { return _required; }
/**
* @copydoc PropertyView<ElementType, false>::noData
*/
std::optional<std::string_view> noData() const noexcept {
if (_noData)
return std::string_view(*_noData);
return std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::defaultValue
*/
std::optional<std::string_view> defaultValue() const noexcept {
if (_defaultValue)
return std::string_view(*_defaultValue);
return std::nullopt;
}
/**
* @brief Returns the \ref PropertyType of the property this view is
* accessing.
*/
PropertyType propertyType() const noexcept { return PropertyType::String; }
protected:
/** @copydoc PropertyViewStatus */
PropertyViewStatusType _status;
private:
std::optional<std::string> _name;
std::optional<std::string> _semantic;
std::optional<std::string> _description;
bool _required;
std::optional<std::string> _noData;
std::optional<std::string> _defaultValue;
static std::optional<std::string>
getStringValue(const CesiumUtility::JsonValue& value) {
if (!value.isString()) {
return std::nullopt;
}
return std::string(value.getString().c_str());
}
};
/**
* @brief Represents a non-normalized array metadata property in
* EXT_structural_metadata.
*
* Whether they belong to property tables, property textures, or property
* attributes, properties have their own sub-properties affecting the actual
* property values. Although they are typically defined via class property, they
* may be overridden by individual instances of the property themselves. The
* constructor is responsible for resolving those differences.
*
* @tparam ElementType The C++ type of the elements in the array values for this
* property.
*/
template <typename ElementType>
class PropertyView<PropertyArrayView<ElementType>, false> {
public:
/**
* @brief Constructs an empty property instance.
*/
PropertyView()
: _status(PropertyViewStatus::ErrorNonexistentProperty),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_count(0),
_offset(),
_scale(),
_max(),
_min(),
_required(false),
_noData(),
_defaultValue(),
_propertyType(PropertyType::Invalid) {}
/**
* @brief Constructs a property instance from a class definition only.
*/
PropertyView(const ClassProperty& classProperty)
: PropertyView(classProperty, nullptr) {}
/**
* @brief Constructs a property instance from a class definition and enum
* definition.
*/
PropertyView(
const ClassProperty& classProperty,
const CesiumGltf::Enum* pEnumDefinition)
: _status(validateArrayPropertyType<PropertyArrayView<ElementType>>(
classProperty,
pEnumDefinition)),
_name(classProperty.name),
_semantic(classProperty.semantic),
_description(classProperty.description),
_count(classProperty.count ? *classProperty.count : 0),
_offset(),
_scale(),
_max(),
_min(),
_required(classProperty.required),
_noData(),
_defaultValue(),
_propertyType(convertStringToPropertyType(classProperty.type)) {
if (_status != PropertyViewStatus::Valid) {
return;
}
if (classProperty.normalized) {
_status = PropertyViewStatus::ErrorNormalizationMismatch;
return;
}
if (classProperty.type != ClassProperty::Type::ENUM) {
getNumericPropertyValues(classProperty);
}
if (_status != PropertyViewStatus::Valid) {
return;
}
if (classProperty.noData) {
if (!this->_required && this->_propertyType == PropertyType::Enum) {
CESIUM_ASSERT(pEnumDefinition != nullptr);
if constexpr (IsMetadataInteger<ElementType>::value) {
this->_noData =
getEnumArrayValue(*classProperty.noData, *pEnumDefinition);
}
} else if (!this->_required) {
this->_noData = getArrayValue(*classProperty.noData);
}
if (this->_noData.size() == 0) {
// The value was specified but something went wrong.
this->_status = PropertyViewStatus::ErrorInvalidNoDataValue;
return;
}
}
if (classProperty.defaultProperty) {
if (!this->_required && this->_propertyType == PropertyType::Enum) {
CESIUM_ASSERT(pEnumDefinition != nullptr);
if constexpr (IsMetadataInteger<ElementType>::value) {
this->_defaultValue = getEnumArrayValue(
*classProperty.defaultProperty,
*pEnumDefinition);
}
} else if (!this->_required) {
this->_defaultValue = getArrayValue(*classProperty.defaultProperty);
}
if (this->_defaultValue.size() == 0) {
// The value was specified but something went wrong.
this->_status = PropertyViewStatus::ErrorInvalidDefaultValue;
return;
}
}
}
protected:
/**
* @brief Constructs an invalid instance for an erroneous property.
*
* @param status The value of {@link PropertyViewStatus} indicating the error with the property.
*/
PropertyView(PropertyViewStatusType status)
: _status(status),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_count(0),
_offset(),
_scale(),
_max(),
_min(),
_required(false),
_noData(),
_defaultValue(),
_propertyType(PropertyType::Invalid) {}
/**
* @brief Constructs a property instance from a property table property,
* its class definition, and an optional enum definition.
*/
PropertyView(
const ClassProperty& classProperty,
const PropertyTableProperty& property,
const CesiumGltf::Enum* pEnumDefinition = nullptr)
: PropertyView(classProperty, pEnumDefinition) {
if (_status != PropertyViewStatus::Valid) {
return;
}
// If the property has its own values, override the class-provided values.
if (classProperty.type != ClassProperty::Type::ENUM) {
getNumericPropertyValues(property);
}
}
/**
* @brief Constructs a property instance from a property texture property,
* its class definition, and an optional enum definition.
*/
PropertyView(
const ClassProperty& classProperty,
const PropertyTextureProperty& property,
const CesiumGltf::Enum* pEnumDefinition = nullptr)
: PropertyView(classProperty, pEnumDefinition) {
if (_status != PropertyViewStatus::Valid) {
return;
}
// If the property has its own values, override the class-provided values.
if (classProperty.type != ClassProperty::Type::ENUM) {
getNumericPropertyValues(property);
}
}
public:
/**
* @copydoc PropertyView<ElementType, false>::status
*/
PropertyViewStatusType status() const noexcept { return _status; }
/**
* @copydoc PropertyView<ElementType, false>::name
*/
const std::optional<std::string>& name() const noexcept { return _name; }
/**
* @copydoc PropertyView<ElementType, false>::semantic
*/
const std::optional<std::string>& semantic() const noexcept {
return _semantic;
}
/**
* @copydoc PropertyView<ElementType, false>::description
*/
const std::optional<std::string>& description() const noexcept {
return _description;
}
/**
* @copydoc PropertyView<ElementType, false>::arrayCount
*/
int64_t arrayCount() const noexcept { return _count; }
/**
* @copydoc PropertyView<ElementType, false>::normalized
*/
bool normalized() const noexcept { return false; }
/**
* @copydoc PropertyView<ElementType, false>::offset
*/
std::optional<PropertyArrayView<ElementType>> offset() const noexcept {
return this->_offset.size() > 0 ? std::make_optional(this->_offset.view())
: std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::scale
*/
std::optional<PropertyArrayView<ElementType>> scale() const noexcept {
return this->_scale.size() > 0 ? std::make_optional(this->_scale.view())
: std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::max
*/
std::optional<PropertyArrayView<ElementType>> max() const noexcept {
return this->_max.size() > 0 ? std::make_optional(this->_max.view())
: std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::min
*/
std::optional<PropertyArrayView<ElementType>> min() const noexcept {
return this->_min.size() > 0 ? std::make_optional(this->_min.view())
: std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::required
*/
bool required() const noexcept { return _required; }
/**
* @copydoc PropertyView<ElementType, false>::noData
*/
std::optional<PropertyArrayView<ElementType>> noData() const noexcept {
return this->_noData.size() > 0 ? std::make_optional(this->_noData.view())
: std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::defaultValue
*/
std::optional<PropertyArrayView<ElementType>> defaultValue() const noexcept {
return this->_defaultValue.size() > 0
? std::make_optional(this->_defaultValue.view())
: std::nullopt;
}
/**
* @brief Returns the \ref PropertyType of the property that this view is
* accessing.
*/
PropertyType propertyType() const noexcept { return _propertyType; }
protected:
/** @copydoc PropertyViewStatus */
PropertyViewStatusType _status;
private:
std::optional<std::string> _name;
std::optional<std::string> _semantic;
std::optional<std::string> _description;
int64_t _count;
PropertyArrayCopy<ElementType> _offset;
PropertyArrayCopy<ElementType> _scale;
PropertyArrayCopy<ElementType> _max;
PropertyArrayCopy<ElementType> _min;
bool _required;
PropertyArrayCopy<ElementType> _noData;
PropertyArrayCopy<ElementType> _defaultValue;
PropertyType _propertyType;
using PropertyDefinitionType = std::
variant<ClassProperty, PropertyTableProperty, PropertyTextureProperty>;
void getNumericPropertyValues(const PropertyDefinitionType& inProperty) {
std::visit(
[this](auto property) {
bool isFixedSizeArray = this->_count > 0;
if (property.offset) {
// Only floating point types can specify an offset.
switch (TypeToPropertyType<ElementType>::component) {
case PropertyComponentType::Float32:
case PropertyComponentType::Float64:
if (isFixedSizeArray) {
this->_offset = getArrayValue(*property.offset);
if (this->_offset.size() == this->_count) {
break;
}
}
// If it does not break here, something went wrong.
[[fallthrough]];
default:
this->_status = PropertyViewStatus::ErrorInvalidOffset;
return;
}
}
if (property.scale) {
// Only floating point types can specify a scale.
switch (TypeToPropertyType<ElementType>::component) {
case PropertyComponentType::Float32:
case PropertyComponentType::Float64:
if (isFixedSizeArray) {
this->_scale = getArrayValue(*property.scale);
if (this->_scale.size() == this->_count) {
break;
}
}
// If it does not break here, something went wrong.
[[fallthrough]];
default:
this->_status = PropertyViewStatus::ErrorInvalidScale;
return;
}
}
if (property.max) {
if (isFixedSizeArray) {
this->_max = getArrayValue(*property.max);
}
if (!isFixedSizeArray || this->_max.size() != this->_count) {
// The value was specified but something went wrong.
this->_status = PropertyViewStatus::ErrorInvalidMax;
return;
}
}
if (property.min) {
if (isFixedSizeArray) {
this->_min = getArrayValue(*property.min);
}
if (!isFixedSizeArray || this->_min.size() != this->_count) {
// The value was specified but something went wrong.
this->_status = PropertyViewStatus::ErrorInvalidMin;
return;
}
}
},
inProperty);
}
static PropertyArrayCopy<ElementType>
getArrayValue(const CesiumUtility::JsonValue& jsonValue) {
if (!jsonValue.isArray()) {
return PropertyArrayCopy<ElementType>();
}
const CesiumUtility::JsonValue::Array& array = jsonValue.getArray();
std::vector<ElementType> values(array.size());
if constexpr (IsMetadataScalar<ElementType>::value) {
for (size_t i = 0; i < array.size(); i++) {
std::optional<ElementType> maybeValue =
getScalar<ElementType>(array[i]);
if (!maybeValue.has_value()) {
return PropertyArrayCopy<ElementType>();
}
values[i] = *maybeValue;
}
}
if constexpr (IsMetadataVecN<ElementType>::value) {
for (size_t i = 0; i < array.size(); i++) {
std::optional<ElementType> maybeValue = getVecN<ElementType>(array[i]);
if (!maybeValue.has_value()) {
return PropertyArrayCopy<ElementType>();
}
values[i] = *maybeValue;
}
}
if constexpr (IsMetadataMatN<ElementType>::value) {
for (size_t i = 0; i < array.size(); i++) {
std::optional<ElementType> maybeValue = getMatN<ElementType>(array[i]);
if (!maybeValue.has_value()) {
return PropertyArrayCopy<ElementType>();
}
values[i] = *maybeValue;
}
}
return PropertyArrayCopy<ElementType>(values);
}
static PropertyArrayCopy<ElementType> getEnumArrayValue(
const CesiumUtility::JsonValue& jsonValue,
const CesiumGltf::Enum& enumDefinition) {
if (!jsonValue.isArray()) {
return PropertyArrayCopy<ElementType>();
}
const CesiumUtility::JsonValue::Array& array = jsonValue.getArray();
std::vector<ElementType> values(array.size());
for (size_t i = 0; i < array.size(); i++) {
if (!array[i].isString()) {
return PropertyArrayCopy<ElementType>();
}
// default and noData values for enums contain the name of the enum as a
// string
CesiumUtility::JsonValue::String str = array[i].getString();
auto foundValue = std::find_if(
enumDefinition.values.begin(),
enumDefinition.values.end(),
[&str](const CesiumGltf::EnumValue& enumValue) {
return enumValue.name == str;
});
if (foundValue == enumDefinition.values.end()) {
return PropertyArrayCopy<ElementType>();
}
values[i] = static_cast<ElementType>(foundValue->value);
}
return PropertyArrayCopy<ElementType>(values);
}
};
/**
* @brief Represents a normalized array metadata property in
* EXT_structural_metadata.
*
* Whether they belong to property tables, property textures, or property
* attributes, properties have their own sub-properties affecting the actual
* property values. Although they are typically defined via class property,
* they may be overridden by individual instances of the property themselves.
* The constructor is responsible for resolving those differences.
*
* @tparam ElementType The C++ type of the elements in the array values for
* this property. Must have an integer component type.
*/
template <typename ElementType>
class PropertyView<PropertyArrayView<ElementType>, true> {
private:
using NormalizedType = typename TypeToNormalizedType<ElementType>::type;
public:
/**
* @brief Constructs an empty property instance.
*/
PropertyView()
: _status(PropertyViewStatus::ErrorNonexistentProperty),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_count(0),
_offset(),
_scale(),
_max(),
_min(),
_required(false),
_noData(),
_defaultValue(),
_propertyType(PropertyType::Invalid) {}
/**
* @brief Constructs a property instance from a class definition only.
*/
PropertyView(const ClassProperty& classProperty)
: _status(validateArrayPropertyType<PropertyArrayView<ElementType>>(
classProperty)),
_name(classProperty.name),
_semantic(classProperty.semantic),
_description(classProperty.description),
_count(classProperty.count ? *classProperty.count : 0),
_offset(),
_scale(),
_max(),
_min(),
_required(classProperty.required),
_noData(),
_defaultValue(),
_propertyType(convertStringToPropertyType(classProperty.type)) {
if (_status != PropertyViewStatus::Valid) {
return;
}
if (!classProperty.normalized) {
_status = PropertyViewStatus::ErrorNormalizationMismatch;
return;
}
getNumericPropertyValues(classProperty);
if (_status != PropertyViewStatus::Valid) {
return;
}
if (classProperty.noData) {
if (!this->_required) {
this->_noData = getArrayValue<ElementType>(*classProperty.noData);
}
if (this->_noData.size() == 0) {
// The value was specified but something went wrong.
this->_status = PropertyViewStatus::ErrorInvalidNoDataValue;
return;
}
}
if (classProperty.defaultProperty) {
if (!this->_required) {
this->_defaultValue =
getArrayValue<NormalizedType>(*classProperty.defaultProperty);
}
if (this->_defaultValue.size() == 0) {
// The value was specified but something went wrong.
this->_status = PropertyViewStatus::ErrorInvalidDefaultValue;
return;
}
}
}
protected:
/**
* @brief Constructs an invalid instance for an erroneous property.
*
* @param status The value of {@link PropertyViewStatus} indicating the error with the property.
*/
PropertyView(PropertyViewStatusType status)
: _status(status),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_count(0),
_offset(),
_scale(),
_max(),
_min(),
_required(false),
_noData(),
_defaultValue(),
_propertyType(PropertyType::Invalid) {}
/**
* @brief Constructs a property instance from a property table property and
* its class definition.
*/
PropertyView(
const ClassProperty& classProperty,
const PropertyTableProperty& property)
: PropertyView(classProperty) {
if (_status != PropertyViewStatus::Valid) {
return;
}
// If the property has its own values, override the class-provided values.
getNumericPropertyValues(property);
}
/**
* @brief Constructs a property instance from a property texture property
* and its class definition.
*/
PropertyView(
const ClassProperty& classProperty,
const PropertyTextureProperty& property)
: PropertyView(classProperty) {
if (_status != PropertyViewStatus::Valid) {
return;
}
// If the property has its own values, override the class-provided values.
getNumericPropertyValues(property);
}
public:
/**
* @copydoc PropertyView<ElementType, false>::status
*/
PropertyViewStatusType status() const noexcept { return _status; }
/**
* @copydoc PropertyView<ElementType, false>::name
*/
const std::optional<std::string>& name() const noexcept { return _name; }
/**
* @copydoc PropertyView<ElementType, false>::semantic
*/
const std::optional<std::string>& semantic() const noexcept {
return _semantic;
}
/**
* @copydoc PropertyView<ElementType, false>::description
*/
const std::optional<std::string>& description() const noexcept {
return _description;
}
/**
* @copydoc PropertyView<ElementType, false>::arrayCount
*/
int64_t arrayCount() const noexcept { return _count; }
/**
* @copydoc PropertyView<ElementType, false>::normalized
*/
bool normalized() const noexcept { return true; }
/**
* @copydoc PropertyView<ElementType, false>::offset
*/
std::optional<PropertyArrayView<NormalizedType>> offset() const noexcept {
return this->_offset.size() > 0 ? std::make_optional(this->_offset.view())
: std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::scale
*/
std::optional<PropertyArrayView<NormalizedType>> scale() const noexcept {
return this->_scale.size() > 0 ? std::make_optional(this->_scale.view())
: std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::max
*/
std::optional<PropertyArrayView<NormalizedType>> max() const noexcept {
return this->_max.size() > 0 ? std::make_optional(this->_max.view())
: std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::min
*/
std::optional<PropertyArrayView<NormalizedType>> min() const noexcept {
return this->_min.size() > 0 ? std::make_optional(this->_min.view())
: std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::required
*/
bool required() const noexcept { return _required; }
/**
* @copydoc PropertyView<ElementType, false>::noData
*/
std::optional<PropertyArrayView<ElementType>> noData() const noexcept {
return this->_noData.size() > 0 ? std::make_optional(this->_noData.view())
: std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::defaultValue
*/
std::optional<PropertyArrayView<NormalizedType>>
defaultValue() const noexcept {
return this->_defaultValue.size() > 0
? std::make_optional(this->_defaultValue.view())
: std::nullopt;
}
/**
* @brief Returns the \ref PropertyType of the property this view is
* accessing.
*/
PropertyType propertyType() const noexcept { return _propertyType; }
protected:
/** @copydoc PropertyViewStatus */
PropertyViewStatusType _status;
private:
std::optional<std::string> _name;
std::optional<std::string> _semantic;
std::optional<std::string> _description;
int64_t _count;
PropertyArrayCopy<NormalizedType> _offset;
PropertyArrayCopy<NormalizedType> _scale;
PropertyArrayCopy<NormalizedType> _max;
PropertyArrayCopy<NormalizedType> _min;
bool _required;
PropertyArrayCopy<ElementType> _noData;
PropertyArrayCopy<NormalizedType> _defaultValue;
PropertyType _propertyType;
using PropertyDefinitionType = std::
variant<ClassProperty, PropertyTableProperty, PropertyTextureProperty>;
void getNumericPropertyValues(const PropertyDefinitionType& inProperty) {
std::visit(
[this](auto property) {
bool isFixedSizeArray = this->_count > 0;
if (property.offset) {
if (isFixedSizeArray) {
this->_offset = getArrayValue<NormalizedType>(*property.offset);
}
if (!isFixedSizeArray || this->_offset.size() != this->_count) {
// The value was specified but something went wrong.
_status = PropertyViewStatus::ErrorInvalidOffset;
return;
}
}
if (property.scale) {
if (isFixedSizeArray) {
this->_scale = getArrayValue<NormalizedType>(*property.scale);
}
if (!isFixedSizeArray || this->_scale.size() != this->_count) {
// The value was specified but something went wrong.
_status = PropertyViewStatus::ErrorInvalidScale;
return;
}
}
if (property.max) {
if (isFixedSizeArray) {
this->_max = getArrayValue<NormalizedType>(*property.max);
}
if (!isFixedSizeArray || this->_max.size() != this->_count) {
// The value was specified but something went wrong.
this->_status = PropertyViewStatus::ErrorInvalidMax;
return;
}
}
if (property.min) {
if (isFixedSizeArray) {
this->_min = getArrayValue<NormalizedType>(*property.min);
}
if (!isFixedSizeArray || this->_min.size() != this->_count) {
// The value was specified but something went wrong.
this->_status = PropertyViewStatus::ErrorInvalidMin;
return;
}
}
},
inProperty);
}
template <typename T>
static PropertyArrayCopy<T>
getArrayValue(const CesiumUtility::JsonValue& jsonValue) {
if (!jsonValue.isArray()) {
return PropertyArrayCopy<T>();
}
const CesiumUtility::JsonValue::Array& array = jsonValue.getArray();
std::vector<T> values(array.size());
if constexpr (IsMetadataScalar<T>::value) {
for (size_t i = 0; i < array.size(); i++) {
std::optional<T> maybeElement = getScalar<T>(array[i]);
if (!maybeElement.has_value()) {
return PropertyArrayCopy<T>();
}
values[i] = *maybeElement;
}
}
if constexpr (IsMetadataVecN<T>::value) {
for (size_t i = 0; i < array.size(); i++) {
std::optional<T> maybeElement = getVecN<T>(array[i]);
if (!maybeElement.has_value()) {
return PropertyArrayCopy<T>();
}
values[i] = *maybeElement;
}
}
if constexpr (IsMetadataMatN<T>::value) {
for (size_t i = 0; i < array.size(); i++) {
std::optional<T> maybeElement = getMatN<T>(array[i]);
if (!maybeElement.has_value()) {
return PropertyArrayCopy<T>();
}
values[i] = *maybeElement;
}
}
return PropertyArrayCopy<T>(values);
}
};
/**
* @brief Represents a boolean array metadata property in
* EXT_structural_metadata.
*/
template <> class PropertyView<PropertyArrayView<bool>> {
public:
/**
* @brief Constructs an empty property instance.
*/
PropertyView()
: _status(PropertyViewStatus::ErrorNonexistentProperty),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_count(0),
_required(false),
_defaultValue() {}
/**
* @brief Constructs a property instance from a class definition only.
*/
PropertyView(const ClassProperty& classProperty)
: _status(
validateArrayPropertyType<PropertyArrayView<bool>>(classProperty)),
_name(classProperty.name),
_semantic(classProperty.semantic),
_description(classProperty.description),
_count(classProperty.count ? *classProperty.count : 0),
_required(classProperty.required),
_defaultValue() {
if (_status != PropertyViewStatus::Valid) {
return;
}
if (classProperty.defaultProperty) {
if (!this->_required) {
this->_defaultValue =
getBooleanArrayValue(*classProperty.defaultProperty);
}
if (this->_defaultValue.size() == 0 ||
(this->_count > 0 && this->_defaultValue.size() != this->_count)) {
this->_status = PropertyViewStatus::ErrorInvalidDefaultValue;
return;
}
}
}
protected:
/**
* @brief Constructs an invalid instance for an erroneous property.
*
* @param status The value of {@link PropertyViewStatus} indicating the error with the property.
*/
PropertyView(PropertyViewStatusType status)
: _status(status),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_count(0),
_required(false),
_defaultValue() {}
/**
* @brief Constructs a property instance from a property table property and
* its class definition.
*/
PropertyView(
const ClassProperty& classProperty,
const PropertyTableProperty& /*property*/,
const CesiumGltf::Enum* /*pEnumDefinition*/)
: PropertyView(classProperty) {}
public:
/**
* @copydoc PropertyView<ElementType, false>::status
*/
PropertyViewStatusType status() const noexcept { return _status; }
/**
* @copydoc PropertyView<ElementType, false>::name
*/
const std::optional<std::string>& name() const noexcept { return _name; }
/**
* @copydoc PropertyView<ElementType, false>::semantic
*/
const std::optional<std::string>& semantic() const noexcept {
return _semantic;
}
/**
* @copydoc PropertyView<ElementType, false>::description
*/
const std::optional<std::string>& description() const noexcept {
return _description;
}
/**
* @copydoc PropertyView<ElementType, false>::arrayCount
*/
int64_t arrayCount() const noexcept { return _count; }
/**
* @copydoc PropertyView<ElementType, false>::normalized
*/
bool normalized() const noexcept { return false; }
/**
* @copydoc PropertyView<ElementType, false>::offset
*/
std::optional<PropertyArrayView<bool>> offset() const noexcept {
return std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::scale
*/
std::optional<PropertyArrayView<bool>> scale() const noexcept {
return std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::max
*/
std::optional<PropertyArrayView<bool>> max() const noexcept {
return std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::min
*/
std::optional<PropertyArrayView<bool>> min() const noexcept {
return std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::required
*/
bool required() const noexcept { return _required; }
/**
* @copydoc PropertyView<ElementType, false>::noData
*/
std::optional<PropertyArrayView<bool>> noData() const noexcept {
return std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::defaultValue
*/
std::optional<PropertyArrayView<bool>> defaultValue() const noexcept {
return this->_defaultValue.size() > 0
? std::make_optional(this->_defaultValue.view())
: std::nullopt;
}
/**
* @brief Returns the \ref PropertyType of the property this view is
* accessing.
*/
PropertyType propertyType() const noexcept { return PropertyType::Boolean; }
protected:
/** @copydoc PropertyViewStatus */
PropertyViewStatusType _status;
private:
std::optional<std::string> _name;
std::optional<std::string> _semantic;
std::optional<std::string> _description;
int64_t _count;
bool _required;
PropertyArrayCopy<bool> _defaultValue;
static PropertyArrayCopy<bool>
getBooleanArrayValue(const CesiumUtility::JsonValue& jsonValue) {
if (!jsonValue.isArray()) {
return PropertyArrayCopy<bool>();
}
const CesiumUtility::JsonValue::Array& array = jsonValue.getArray();
std::vector<bool> values(array.size());
for (size_t i = 0; i < array.size(); i++) {
if (!array[i].isBool()) {
// The entire array is invalidated; return.
return PropertyArrayCopy<bool>();
}
values[i] = array[i].getBool();
}
return values;
}
};
/**
* @brief Represents a string array metadata property in
* EXT_structural_metadata.
*/
template <> class PropertyView<PropertyArrayView<std::string_view>> {
public:
/**
* @brief Constructs an empty property instance.
*/
PropertyView()
: _status(PropertyViewStatus::ErrorNonexistentProperty),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_count(0),
_required(false),
_noData(),
_defaultValue() {}
/**
* @brief Constructs a property instance from a class definition only.
*/
PropertyView(const ClassProperty& classProperty)
: _status(validateArrayPropertyType<PropertyArrayView<std::string_view>>(
classProperty)),
_name(classProperty.name),
_semantic(classProperty.semantic),
_description(classProperty.description),
_count(classProperty.count ? *classProperty.count : 0),
_required(classProperty.required),
_noData(),
_defaultValue() {
if (_status != PropertyViewStatus::Valid) {
return;
}
if (classProperty.noData) {
if (!this->_required) {
this->_noData = getStringArrayValue(*classProperty.noData);
}
if (this->_noData.size() == 0 ||
(this->_count > 0 && this->_noData.size() != this->_count)) {
this->_status = PropertyViewStatus::ErrorInvalidNoDataValue;
return;
}
}
if (classProperty.defaultProperty) {
if (!this->_required) {
this->_defaultValue =
getStringArrayValue(*classProperty.defaultProperty);
}
if (this->_defaultValue.size() == 0 ||
(this->_count > 0 && this->_defaultValue.size() != this->_count)) {
// The value was specified but something went wrong.
this->_status = PropertyViewStatus::ErrorInvalidDefaultValue;
return;
}
}
}
protected:
/**
* @brief Constructs an invalid instance for an erroneous property.
*
* @param status The value of {@link PropertyViewStatus} indicating the error with the property.
*/
PropertyView(PropertyViewStatusType status)
: _status(status),
_name(std::nullopt),
_semantic(std::nullopt),
_description(std::nullopt),
_count(0),
_required(false),
_noData(),
_defaultValue() {}
/**
* @brief Constructs a property instance from a property table property
* and its class definition.
*/
PropertyView(
const ClassProperty& classProperty,
const PropertyTableProperty& /*property*/,
const CesiumGltf::Enum* /*pEnumDefinition*/)
: PropertyView(classProperty) {}
public:
/**
* @copydoc PropertyView<ElementType, false>::status
*/
PropertyViewStatusType status() const noexcept { return _status; }
/**
* @copydoc PropertyView<ElementType, false>::name
*/
const std::optional<std::string>& name() const noexcept { return _name; }
/**
* @copydoc PropertyView<ElementType, false>::semantic
*/
const std::optional<std::string>& semantic() const noexcept {
return _semantic;
}
/**
* @copydoc PropertyView<ElementType, false>::description
*/
const std::optional<std::string>& description() const noexcept {
return _description;
}
/**
* @copydoc PropertyView<ElementType, false>::arrayCount
*/
int64_t arrayCount() const noexcept { return _count; }
/**
* @copydoc PropertyView<ElementType, false>::normalized
*/
bool normalized() const noexcept { return false; }
/**
* @copydoc PropertyView<ElementType, false>::offset
*/
std::optional<PropertyArrayView<std::string_view>> offset() const noexcept {
return std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::scale
*/
std::optional<PropertyArrayView<std::string_view>> scale() const noexcept {
return std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::max
*/
std::optional<PropertyArrayView<std::string_view>> max() const noexcept {
return std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::min
*/
std::optional<PropertyArrayView<std::string_view>> min() const noexcept {
return std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::required
*/
bool required() const noexcept { return _required; }
/**
* @copydoc PropertyView<ElementType, false>::noData
*/
std::optional<PropertyArrayView<std::string_view>> noData() const noexcept {
return this->_noData.size() > 0 ? std::make_optional(this->_noData.view())
: std::nullopt;
}
/**
* @copydoc PropertyView<ElementType, false>::defaultValue
*/
std::optional<PropertyArrayView<std::string_view>>
defaultValue() const noexcept {
return this->_defaultValue.size() > 0
? std::make_optional(this->_defaultValue.view())
: std::nullopt;
}
/**
* @brief Returns the \ref PropertyType of the property this view is
* accessing.
*/
PropertyType propertyType() const noexcept { return PropertyType::String; }
protected:
/** @copydoc PropertyViewStatus */
PropertyViewStatusType _status;
private:
std::optional<std::string> _name;
std::optional<std::string> _semantic;
std::optional<std::string> _description;
int64_t _count;
bool _required;
PropertyArrayCopy<std::string_view> _noData;
PropertyArrayCopy<std::string_view> _defaultValue;
static PropertyArrayCopy<std::string_view>
getStringArrayValue(const CesiumUtility::JsonValue& jsonValue) {
if (!jsonValue.isArray()) {
return PropertyArrayCopy<std::string_view>();
}
const CesiumUtility::JsonValue::Array& array = jsonValue.getArray();
std::vector<std::string> strings(array.size());
for (size_t i = 0; i < array.size(); i++) {
if (!array[i].isString()) {
// The entire array is invalidated; return.
return PropertyArrayCopy<std::string_view>();
}
strings[i] = array[i].getString();
}
return PropertyArrayCopy<std::string_view>(strings);
}
};
} // namespace CesiumGltf