#pragma once #include #include #include #include #include #include #include #include #include #include #include #include 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 PropertyViewStatusType validatePropertyType( const ClassProperty& classProperty, const CesiumGltf::Enum* pEnumDefinition = nullptr) { if (!canRepresentPropertyType( 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::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 PropertyViewStatusType validateArrayPropertyType( const ClassProperty& classProperty, const CesiumGltf::Enum* pEnumDefinition = nullptr) { using ElementType = typename MetadataArrayType::type; if (!canRepresentPropertyType( 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::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 static std::optional getScalar(const CesiumUtility::JsonValue& jsonValue) { return jsonValue.getSafeNumber(); } /** * @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 static std::optional 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 value = getScalar(array[static_cast(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 static std::optional 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(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 value = getScalar(array[static_cast(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 int64_t getCount(std::optional>& buffer) { if (!buffer) { return 0; } return static_cast(buffer->size() / sizeof(ElementType)); } /** * @brief Represents a metadata property in EXT_structural_metadata. */ template 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 class PropertyView { 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(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::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::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& 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& 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& 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 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 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 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 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 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 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 _name; std::optional _semantic; std::optional _description; std::optional _offset; std::optional _scale; std::optional _max; std::optional _min; bool _required; std::optional _noData; std::optional _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 getValue(const CesiumUtility::JsonValue& jsonValue) { if constexpr (IsMetadataScalar::value) { return getScalar(jsonValue); } if constexpr (IsMetadataVecN::value) { return getVecN(jsonValue); } if constexpr (IsMetadataMatN::value) { return getMatN(jsonValue); } } static std::optional 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(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::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::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 class PropertyView { private: using NormalizedType = typename TypeToNormalizedType::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(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(*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(*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::status */ PropertyViewStatusType status() const noexcept { return _status; } /** * @copydoc PropertyView::name */ const std::optional& name() const noexcept { return _name; } /** * @copydoc PropertyView::semantic */ const std::optional& semantic() const noexcept { return _semantic; } /** * @copydoc PropertyView::description */ const std::optional& description() const noexcept { return _description; } /** * @copydoc PropertyView::arrayCount */ int64_t arrayCount() const noexcept { return 0; } /** * @copydoc PropertyView::normalized */ bool normalized() const noexcept { return true; } /** * @copydoc PropertyView::offset */ std::optional offset() const noexcept { return _offset; } /** * @copydoc PropertyView::scale */ std::optional scale() const noexcept { return _scale; } /** * @copydoc PropertyView::max */ std::optional max() const noexcept { return _max; } /** * @copydoc PropertyView::min */ std::optional min() const noexcept { return _min; } /** * @copydoc PropertyView::required */ bool required() const noexcept { return _required; } /** * @copydoc PropertyView::noData */ std::optional noData() const noexcept { return _noData; } /** * @copydoc PropertyView::defaultValue */ std::optional 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 _name; std::optional _semantic; std::optional _description; std::optional _offset; std::optional _scale; std::optional _max; std::optional _min; bool _required; std::optional _noData; std::optional _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 static std::optional getValue(const CesiumUtility::JsonValue& jsonValue) { if constexpr (IsMetadataScalar::value) { return getScalar(jsonValue); } if constexpr (IsMetadataVecN::value) { return getVecN(jsonValue); } if constexpr (IsMetadataMatN::value) { return getMatN(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(*property.offset); if (!_offset) { // The value was specified but something went wrong. _status = PropertyViewStatus::ErrorInvalidOffset; return; } } if (property.scale) { _scale = getValue(*property.scale); if (!_scale) { // The value was specified but something went wrong. _status = PropertyViewStatus::ErrorInvalidScale; return; } } if (property.max) { _max = getValue(*property.max); if (!_scale) { // The value was specified but something went wrong. _status = PropertyViewStatus::ErrorInvalidMax; return; } } if (property.min) { _min = getValue(*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 { 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(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::status */ PropertyViewStatusType status() const noexcept { return _status; } /** * @copydoc PropertyView::name */ const std::optional& name() const noexcept { return _name; } /** * @copydoc PropertyView::semantic */ const std::optional& semantic() const noexcept { return _semantic; } /** * @copydoc PropertyView::description */ const std::optional& description() const noexcept { return _description; } /** * @copydoc PropertyView::arrayCount */ int64_t arrayCount() const noexcept { return 0; } /** * @copydoc PropertyView::normalized */ bool normalized() const noexcept { return false; } /** * @copydoc PropertyView::offset */ std::optional offset() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::scale */ std::optional scale() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::max */ std::optional max() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::min */ std::optional min() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::required */ bool required() const noexcept { return _required; } /** * @copydoc PropertyView::noData */ std::optional noData() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::defaultValue */ std::optional 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 _name; std::optional _semantic; std::optional _description; bool _required; std::optional _defaultValue; static std::optional 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 { 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(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::status */ PropertyViewStatusType status() const noexcept { return _status; } /** * @copydoc PropertyView::name */ const std::optional& name() const noexcept { return _name; } /** * @copydoc PropertyView::semantic */ const std::optional& semantic() const noexcept { return _semantic; } /** * @copydoc PropertyView::description */ const std::optional& description() const noexcept { return _description; } /** * @copydoc PropertyView::arrayCount */ int64_t arrayCount() const noexcept { return 0; } /** * @copydoc PropertyView::normalized */ bool normalized() const noexcept { return false; } /** * @copydoc PropertyView::offset */ std::optional offset() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::scale */ std::optional scale() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::max */ std::optional max() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::min */ std::optional min() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::required */ bool required() const noexcept { return _required; } /** * @copydoc PropertyView::noData */ std::optional noData() const noexcept { if (_noData) return std::string_view(*_noData); return std::nullopt; } /** * @copydoc PropertyView::defaultValue */ std::optional 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 _name; std::optional _semantic; std::optional _description; bool _required; std::optional _noData; std::optional _defaultValue; static std::optional 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 class PropertyView, 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>( 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::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::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::status */ PropertyViewStatusType status() const noexcept { return _status; } /** * @copydoc PropertyView::name */ const std::optional& name() const noexcept { return _name; } /** * @copydoc PropertyView::semantic */ const std::optional& semantic() const noexcept { return _semantic; } /** * @copydoc PropertyView::description */ const std::optional& description() const noexcept { return _description; } /** * @copydoc PropertyView::arrayCount */ int64_t arrayCount() const noexcept { return _count; } /** * @copydoc PropertyView::normalized */ bool normalized() const noexcept { return false; } /** * @copydoc PropertyView::offset */ std::optional> offset() const noexcept { return this->_offset.size() > 0 ? std::make_optional(this->_offset.view()) : std::nullopt; } /** * @copydoc PropertyView::scale */ std::optional> scale() const noexcept { return this->_scale.size() > 0 ? std::make_optional(this->_scale.view()) : std::nullopt; } /** * @copydoc PropertyView::max */ std::optional> max() const noexcept { return this->_max.size() > 0 ? std::make_optional(this->_max.view()) : std::nullopt; } /** * @copydoc PropertyView::min */ std::optional> min() const noexcept { return this->_min.size() > 0 ? std::make_optional(this->_min.view()) : std::nullopt; } /** * @copydoc PropertyView::required */ bool required() const noexcept { return _required; } /** * @copydoc PropertyView::noData */ std::optional> noData() const noexcept { return this->_noData.size() > 0 ? std::make_optional(this->_noData.view()) : std::nullopt; } /** * @copydoc PropertyView::defaultValue */ std::optional> 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 _name; std::optional _semantic; std::optional _description; int64_t _count; PropertyArrayCopy _offset; PropertyArrayCopy _scale; PropertyArrayCopy _max; PropertyArrayCopy _min; bool _required; PropertyArrayCopy _noData; PropertyArrayCopy _defaultValue; PropertyType _propertyType; using PropertyDefinitionType = std:: variant; 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::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::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 getArrayValue(const CesiumUtility::JsonValue& jsonValue) { if (!jsonValue.isArray()) { return PropertyArrayCopy(); } const CesiumUtility::JsonValue::Array& array = jsonValue.getArray(); std::vector values(array.size()); if constexpr (IsMetadataScalar::value) { for (size_t i = 0; i < array.size(); i++) { std::optional maybeValue = getScalar(array[i]); if (!maybeValue.has_value()) { return PropertyArrayCopy(); } values[i] = *maybeValue; } } if constexpr (IsMetadataVecN::value) { for (size_t i = 0; i < array.size(); i++) { std::optional maybeValue = getVecN(array[i]); if (!maybeValue.has_value()) { return PropertyArrayCopy(); } values[i] = *maybeValue; } } if constexpr (IsMetadataMatN::value) { for (size_t i = 0; i < array.size(); i++) { std::optional maybeValue = getMatN(array[i]); if (!maybeValue.has_value()) { return PropertyArrayCopy(); } values[i] = *maybeValue; } } return PropertyArrayCopy(values); } static PropertyArrayCopy getEnumArrayValue( const CesiumUtility::JsonValue& jsonValue, const CesiumGltf::Enum& enumDefinition) { if (!jsonValue.isArray()) { return PropertyArrayCopy(); } const CesiumUtility::JsonValue::Array& array = jsonValue.getArray(); std::vector values(array.size()); for (size_t i = 0; i < array.size(); i++) { if (!array[i].isString()) { return PropertyArrayCopy(); } // 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(); } values[i] = static_cast(foundValue->value); } return PropertyArrayCopy(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 class PropertyView, true> { private: using NormalizedType = typename TypeToNormalizedType::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>( 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(*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(*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::status */ PropertyViewStatusType status() const noexcept { return _status; } /** * @copydoc PropertyView::name */ const std::optional& name() const noexcept { return _name; } /** * @copydoc PropertyView::semantic */ const std::optional& semantic() const noexcept { return _semantic; } /** * @copydoc PropertyView::description */ const std::optional& description() const noexcept { return _description; } /** * @copydoc PropertyView::arrayCount */ int64_t arrayCount() const noexcept { return _count; } /** * @copydoc PropertyView::normalized */ bool normalized() const noexcept { return true; } /** * @copydoc PropertyView::offset */ std::optional> offset() const noexcept { return this->_offset.size() > 0 ? std::make_optional(this->_offset.view()) : std::nullopt; } /** * @copydoc PropertyView::scale */ std::optional> scale() const noexcept { return this->_scale.size() > 0 ? std::make_optional(this->_scale.view()) : std::nullopt; } /** * @copydoc PropertyView::max */ std::optional> max() const noexcept { return this->_max.size() > 0 ? std::make_optional(this->_max.view()) : std::nullopt; } /** * @copydoc PropertyView::min */ std::optional> min() const noexcept { return this->_min.size() > 0 ? std::make_optional(this->_min.view()) : std::nullopt; } /** * @copydoc PropertyView::required */ bool required() const noexcept { return _required; } /** * @copydoc PropertyView::noData */ std::optional> noData() const noexcept { return this->_noData.size() > 0 ? std::make_optional(this->_noData.view()) : std::nullopt; } /** * @copydoc PropertyView::defaultValue */ std::optional> 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 _name; std::optional _semantic; std::optional _description; int64_t _count; PropertyArrayCopy _offset; PropertyArrayCopy _scale; PropertyArrayCopy _max; PropertyArrayCopy _min; bool _required; PropertyArrayCopy _noData; PropertyArrayCopy _defaultValue; PropertyType _propertyType; using PropertyDefinitionType = std:: variant; void getNumericPropertyValues(const PropertyDefinitionType& inProperty) { std::visit( [this](auto property) { bool isFixedSizeArray = this->_count > 0; if (property.offset) { if (isFixedSizeArray) { this->_offset = getArrayValue(*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(*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(*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); } template static PropertyArrayCopy getArrayValue(const CesiumUtility::JsonValue& jsonValue) { if (!jsonValue.isArray()) { return PropertyArrayCopy(); } const CesiumUtility::JsonValue::Array& array = jsonValue.getArray(); std::vector values(array.size()); if constexpr (IsMetadataScalar::value) { for (size_t i = 0; i < array.size(); i++) { std::optional maybeElement = getScalar(array[i]); if (!maybeElement.has_value()) { return PropertyArrayCopy(); } values[i] = *maybeElement; } } if constexpr (IsMetadataVecN::value) { for (size_t i = 0; i < array.size(); i++) { std::optional maybeElement = getVecN(array[i]); if (!maybeElement.has_value()) { return PropertyArrayCopy(); } values[i] = *maybeElement; } } if constexpr (IsMetadataMatN::value) { for (size_t i = 0; i < array.size(); i++) { std::optional maybeElement = getMatN(array[i]); if (!maybeElement.has_value()) { return PropertyArrayCopy(); } values[i] = *maybeElement; } } return PropertyArrayCopy(values); } }; /** * @brief Represents a boolean array metadata property in * EXT_structural_metadata. */ template <> class PropertyView> { 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>(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::status */ PropertyViewStatusType status() const noexcept { return _status; } /** * @copydoc PropertyView::name */ const std::optional& name() const noexcept { return _name; } /** * @copydoc PropertyView::semantic */ const std::optional& semantic() const noexcept { return _semantic; } /** * @copydoc PropertyView::description */ const std::optional& description() const noexcept { return _description; } /** * @copydoc PropertyView::arrayCount */ int64_t arrayCount() const noexcept { return _count; } /** * @copydoc PropertyView::normalized */ bool normalized() const noexcept { return false; } /** * @copydoc PropertyView::offset */ std::optional> offset() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::scale */ std::optional> scale() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::max */ std::optional> max() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::min */ std::optional> min() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::required */ bool required() const noexcept { return _required; } /** * @copydoc PropertyView::noData */ std::optional> noData() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::defaultValue */ std::optional> 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 _name; std::optional _semantic; std::optional _description; int64_t _count; bool _required; PropertyArrayCopy _defaultValue; static PropertyArrayCopy getBooleanArrayValue(const CesiumUtility::JsonValue& jsonValue) { if (!jsonValue.isArray()) { return PropertyArrayCopy(); } const CesiumUtility::JsonValue::Array& array = jsonValue.getArray(); std::vector values(array.size()); for (size_t i = 0; i < array.size(); i++) { if (!array[i].isBool()) { // The entire array is invalidated; return. return PropertyArrayCopy(); } values[i] = array[i].getBool(); } return values; } }; /** * @brief Represents a string array metadata property in * EXT_structural_metadata. */ template <> class PropertyView> { 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>( 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::status */ PropertyViewStatusType status() const noexcept { return _status; } /** * @copydoc PropertyView::name */ const std::optional& name() const noexcept { return _name; } /** * @copydoc PropertyView::semantic */ const std::optional& semantic() const noexcept { return _semantic; } /** * @copydoc PropertyView::description */ const std::optional& description() const noexcept { return _description; } /** * @copydoc PropertyView::arrayCount */ int64_t arrayCount() const noexcept { return _count; } /** * @copydoc PropertyView::normalized */ bool normalized() const noexcept { return false; } /** * @copydoc PropertyView::offset */ std::optional> offset() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::scale */ std::optional> scale() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::max */ std::optional> max() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::min */ std::optional> min() const noexcept { return std::nullopt; } /** * @copydoc PropertyView::required */ bool required() const noexcept { return _required; } /** * @copydoc PropertyView::noData */ std::optional> noData() const noexcept { return this->_noData.size() > 0 ? std::make_optional(this->_noData.view()) : std::nullopt; } /** * @copydoc PropertyView::defaultValue */ std::optional> 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 _name; std::optional _semantic; std::optional _description; int64_t _count; bool _required; PropertyArrayCopy _noData; PropertyArrayCopy _defaultValue; static PropertyArrayCopy getStringArrayValue(const CesiumUtility::JsonValue& jsonValue) { if (!jsonValue.isArray()) { return PropertyArrayCopy(); } const CesiumUtility::JsonValue::Array& array = jsonValue.getArray(); std::vector strings(array.size()); for (size_t i = 0; i < array.size(); i++) { if (!array[i].isString()) { // The entire array is invalidated; return. return PropertyArrayCopy(); } strings[i] = array[i].getString(); } return PropertyArrayCopy(strings); } }; } // namespace CesiumGltf