#pragma once #include "CesiumGltf/ClassProperty.h" #include "CesiumGltf/PropertyAttributeProperty.h" #include "CesiumGltf/PropertyTableProperty.h" #include "CesiumGltf/PropertyTextureProperty.h" #include "CesiumGltf/PropertyTypeTraits.h" #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 Validates a \ref ClassProperty, checking for any type mismatches. * * @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) { if (TypeToPropertyType::value != convertStringToPropertyType(classProperty.type)) { return PropertyViewStatus::ErrorTypeMismatch; } PropertyComponentType expectedComponentType = TypeToPropertyType::component; 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, checking for any * type mismatches. * * @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) { using ElementType = typename MetadataArrayType::type; if (TypeToPropertyType::value != convertStringToPropertyType(classProperty.type)) { return PropertyViewStatus::ErrorTypeMismatch; } PropertyComponentType expectedComponentType = TypeToPropertyType::component; 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[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() != 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[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) {} /** * @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) { if (_status != PropertyViewStatus::Valid) { return; } if (classProperty.normalized) { _status = PropertyViewStatus::ErrorNormalizationMismatch; return; } getNumericPropertyValues(classProperty); if (_status != PropertyViewStatus::Valid) { return; } if (classProperty.noData) { if (!_required) { // "noData" can only be defined if the property is not required. _noData = getValue(*classProperty.noData); } if (!_noData) { // The value was specified but something went wrong. _status = PropertyViewStatus::ErrorInvalidNoDataValue; return; } } if (classProperty.defaultProperty) { if (!_required) { // "default" can only be defined if the property is not 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) {} /** * @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: /** * @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; } 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; /** * @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); } } 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) {} /** * @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) { 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) {} /** * @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; } 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; /** * @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*/) : 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; } 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*/) : 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; } 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(std::nullopt), _scale(std::nullopt), _max(std::nullopt), _min(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(validateArrayPropertyType>( classProperty)), _name(classProperty.name), _semantic(classProperty.semantic), _description(classProperty.description), _count(_count = classProperty.count ? *classProperty.count : 0), _offset(std::nullopt), _scale(std::nullopt), _max(std::nullopt), _min(std::nullopt), _required(classProperty.required), _noData(std::nullopt), _defaultValue(std::nullopt) { if (_status != PropertyViewStatus::Valid) { return; } if (classProperty.normalized) { _status = PropertyViewStatus::ErrorNormalizationMismatch; return; } getNumericPropertyValues(classProperty); if (_status != PropertyViewStatus::Valid) { return; } if (classProperty.noData) { if (!_required) { _noData = getArrayValue(*classProperty.noData); } if (!_noData) { _status = PropertyViewStatus::ErrorInvalidNoDataValue; return; } } if (classProperty.defaultProperty) { if (!_required) { _defaultValue = getArrayValue(*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), _count(0), _offset(std::nullopt), _scale(std::nullopt), _max(std::nullopt), _min(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) : 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 false; } /** * @copydoc PropertyView::offset */ std::optional> offset() const noexcept { if (!_offset) { return std::nullopt; } return PropertyArrayView( std::span(_offset->data(), _offset->size())); } /** * @copydoc PropertyView::scale */ std::optional> scale() const noexcept { if (!_scale) { return std::nullopt; } return PropertyArrayView( std::span(_scale->data(), _scale->size())); } /** * @copydoc PropertyView::max */ std::optional> max() const noexcept { if (!_max) { return std::nullopt; } return PropertyArrayView( std::span(_max->data(), _max->size())); } /** * @copydoc PropertyView::min */ std::optional> min() const noexcept { if (!_min) { return std::nullopt; } return PropertyArrayView( std::span(_min->data(), _min->size())); } /** * @copydoc PropertyView::required */ bool required() const noexcept { return _required; } /** * @copydoc PropertyView::noData */ std::optional> noData() const noexcept { if (!_noData) { return std::nullopt; } return PropertyArrayView( std::span(_noData->data(), _noData->size())); } /** * @copydoc PropertyView::defaultValue */ std::optional> defaultValue() const noexcept { if (!_defaultValue) { return std::nullopt; } return PropertyArrayView(std::span( _defaultValue->data(), _defaultValue->size())); } protected: /** @copydoc PropertyViewStatus */ PropertyViewStatusType _status; private: std::optional _name; std::optional _semantic; std::optional _description; int64_t _count; std::optional> _offset; std::optional> _scale; std::optional> _max; std::optional> _min; bool _required; std::optional> _noData; std::optional> _defaultValue; using PropertyDefinitionType = std:: variant; 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: if (this->_count > 0) { this->_offset = getArrayValue(*property.offset); } if (this->_offset && getCount(this->_offset) == 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 (_count > 0) { this->_scale = getArrayValue(*property.scale); } if (this->_scale && getCount(this->_scale) == this->_count) { break; } // If it does not break here, something went wrong. [[fallthrough]]; default: this->_status = PropertyViewStatus::ErrorInvalidScale; return; } } if (property.max) { if (this->_count > 0) { this->_max = getArrayValue(*property.max); } if (!this->_max || getCount(this->_max) != this->_count) { // The value was specified but something went wrong. this->_status = PropertyViewStatus::ErrorInvalidMax; return; } } if (property.min) { if (this->_count > 0) { this->_min = getArrayValue(*property.min); } if (!this->_min || getCount(this->_min) != this->_count) { // The value was specified but something went wrong. this->_status = PropertyViewStatus::ErrorInvalidMin; return; } } }, inProperty); } static std::optional> getArrayValue(const CesiumUtility::JsonValue& jsonValue) { if (!jsonValue.isArray()) { return std::nullopt; } const CesiumUtility::JsonValue::Array& array = jsonValue.getArray(); std::vector values; values.reserve(array.size()); if constexpr (IsMetadataScalar::value) { for (size_t i = 0; i < array.size(); i++) { std::optional element = getScalar(array[i]); if (!element) { return std::nullopt; } values.push_back(*element); } } if constexpr (IsMetadataVecN::value) { for (size_t i = 0; i < array.size(); i++) { std::optional element = getVecN(array[i]); if (!element) { return std::nullopt; } values.push_back(*element); } } if constexpr (IsMetadataMatN::value) { for (size_t i = 0; i < array.size(); i++) { std::optional element = getMatN(array[i]); if (!element) { return std::nullopt; } values.push_back(*element); } } std::vector result(values.size() * sizeof(ElementType)); std::memcpy(result.data(), values.data(), result.size()); return result; } }; /** * @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(std::nullopt), _scale(std::nullopt), _max(std::nullopt), _min(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(validateArrayPropertyType>( classProperty)), _name(classProperty.name), _semantic(classProperty.semantic), _description(classProperty.description), _count(_count = classProperty.count ? *classProperty.count : 0), _offset(std::nullopt), _scale(std::nullopt), _max(std::nullopt), _min(std::nullopt), _required(classProperty.required), _noData(std::nullopt), _defaultValue(std::nullopt) { if (_status != PropertyViewStatus::Valid) { return; } if (!classProperty.normalized) { _status = PropertyViewStatus::ErrorNormalizationMismatch; return; } getNumericPropertyValues(classProperty); if (_status != PropertyViewStatus::Valid) { return; } if (classProperty.noData) { if (!_required) { _noData = getArrayValue(*classProperty.noData); } if (!_noData) { _status = PropertyViewStatus::ErrorInvalidNoDataValue; return; } } if (classProperty.defaultProperty) { if (!_required) { _defaultValue = getArrayValue(*classProperty.defaultProperty); } if (!_defaultValue) { _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(std::nullopt), _scale(std::nullopt), _max(std::nullopt), _min(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) : 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 { if (!_offset) { return std::nullopt; } return PropertyArrayView( std::span(_offset->data(), _offset->size())); } /** * @copydoc PropertyView::scale */ std::optional> scale() const noexcept { if (!_scale) { return std::nullopt; } return PropertyArrayView( std::span(_scale->data(), _scale->size())); } /** * @copydoc PropertyView::max */ std::optional> max() const noexcept { if (!_max) { return std::nullopt; } return PropertyArrayView( std::span(_max->data(), _max->size())); } /** * @copydoc PropertyView::min */ std::optional> min() const noexcept { if (!_min) { return std::nullopt; } return PropertyArrayView( std::span(_min->data(), _min->size())); } /** * @copydoc PropertyView::required */ bool required() const noexcept { return _required; } /** * @copydoc PropertyView::noData */ std::optional> noData() const noexcept { if (!_noData) { return std::nullopt; } return PropertyArrayView( std::span(_noData->data(), _noData->size())); } /** * @copydoc PropertyView::defaultValue */ std::optional> defaultValue() const noexcept { if (!_defaultValue) { return std::nullopt; } return PropertyArrayView(std::span( _defaultValue->data(), _defaultValue->size())); } protected: /** @copydoc PropertyViewStatus */ PropertyViewStatusType _status; private: std::optional _name; std::optional _semantic; std::optional _description; int64_t _count; std::optional> _offset; std::optional> _scale; std::optional> _max; std::optional> _min; bool _required; std::optional> _noData; std::optional> _defaultValue; using PropertyDefinitionType = std:: variant; void getNumericPropertyValues(const PropertyDefinitionType& inProperty) { std::visit( [this](auto property) { if (property.offset) { if (_count > 0) { _offset = getArrayValue(*property.offset); } if (!_offset || getCount(_offset) != _count) { // The value was specified but something went wrong. _status = PropertyViewStatus::ErrorInvalidOffset; return; } } if (property.scale) { if (_count > 0) { _scale = getArrayValue(*property.scale); } if (!_scale || getCount(_scale) != _count) { // The value was specified but something went wrong. _status = PropertyViewStatus::ErrorInvalidScale; return; } } if (property.max) { if (_count > 0) { _max = getArrayValue(*property.max); } if (!_max || getCount(_max) != _count) { // The value was specified but something went wrong. _status = PropertyViewStatus::ErrorInvalidMax; return; } } if (property.min) { if (_count > 0) { _min = getArrayValue(*property.min); } if (!_min || getCount(_min) != _count) { // The value was specified but something went wrong. _status = PropertyViewStatus::ErrorInvalidMin; return; } } }, inProperty); } template static std::optional> getArrayValue(const CesiumUtility::JsonValue& jsonValue) { if (!jsonValue.isArray()) { return std::nullopt; } const CesiumUtility::JsonValue::Array& array = jsonValue.getArray(); std::vector values; values.reserve(array.size()); if constexpr (IsMetadataScalar::value) { for (size_t i = 0; i < array.size(); i++) { std::optional element = getScalar(array[i]); if (!element) { return std::nullopt; } values.push_back(*element); } } if constexpr (IsMetadataVecN::value) { for (size_t i = 0; i < array.size(); i++) { std::optional element = getVecN(array[i]); if (!element) { return std::nullopt; } values.push_back(*element); } } if constexpr (IsMetadataMatN::value) { for (size_t i = 0; i < array.size(); i++) { std::optional element = getMatN(array[i]); if (!element) { return std::nullopt; } values.push_back(*element); } } std::vector result(values.size() * sizeof(T)); std::memcpy(result.data(), values.data(), result.size()); return result; } }; /** * @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(), _size(0) {} /** * @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(), _size(0) { if (_status != PropertyViewStatus::Valid) { return; } if (classProperty.defaultProperty) { if (!_required) { _defaultValue = getBooleanArrayValue(*classProperty.defaultProperty, _size); } if (_size == 0 || (_count > 0 && _size != _count)) { _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(), _size(0) {} /** * @brief Constructs a property instance from a property table property and * its class definition. */ PropertyView( const ClassProperty& classProperty, const PropertyTableProperty& /*property*/) : 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 { if (_size > 0) { return PropertyArrayView( std::span( _defaultValue.data(), _defaultValue.size()), /* bitOffset = */ 0, _size); } return std::nullopt; } protected: /** @copydoc PropertyViewStatus */ PropertyViewStatusType _status; private: std::optional _name; std::optional _semantic; std::optional _description; int64_t _count; bool _required; std::vector _defaultValue; int64_t _size; static std::vector getBooleanArrayValue( const CesiumUtility::JsonValue& jsonValue, int64_t& size) { if (!jsonValue.isArray()) { return std::vector(); } const CesiumUtility::JsonValue::Array& array = jsonValue.getArray(); std::vector values; values.reserve((array.size() / 8) + 1); size_t byteIndex = 0; uint8_t bitIndex = 0; for (size_t i = 0; i < array.size(); i++, size++) { if (!array[i].isBool()) { size = 0; return values; } if (values.size() < byteIndex - 1) { values.push_back(std::byte(0)); } std::byte value = std::byte(array[i].getBool() ? 1 : 0); value = value << bitIndex; values[byteIndex] |= value; bitIndex++; if (bitIndex > 7) { byteIndex++; bitIndex = 0; } } 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 (!_required) { _noData = getStringArrayValue(*classProperty.noData); } if (_noData.size == 0 || (_count > 0 && _noData.size != _count)) { _status = PropertyViewStatus::ErrorInvalidNoDataValue; return; } } if (classProperty.defaultProperty) { if (!_required) { _defaultValue = getStringArrayValue(*classProperty.defaultProperty); } if (_defaultValue.size == 0 || (_count > 0 && _defaultValue.size != _count)) { // 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), _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*/) : 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 { if (_noData.size > 0) { return PropertyArrayView( std::span(_noData.data.data(), _noData.data.size()), std::span( _noData.offsets.data(), _noData.offsets.size()), _noData.offsetType, _noData.size); } return std::nullopt; } /** * @copydoc PropertyView::defaultValue */ std::optional> defaultValue() const noexcept { if (_defaultValue.size > 0) { return PropertyArrayView( std::span( _defaultValue.data.data(), _defaultValue.data.size()), std::span( _defaultValue.offsets.data(), _defaultValue.offsets.size()), _defaultValue.offsetType, _defaultValue.size); } return std::nullopt; } protected: /** @copydoc PropertyViewStatus */ PropertyViewStatusType _status; private: std::optional _name; std::optional _semantic; std::optional _description; int64_t _count; bool _required; struct StringArrayValue { std::vector data; std::vector offsets; PropertyComponentType offsetType = PropertyComponentType::None; int64_t size = 0; }; StringArrayValue _noData; StringArrayValue _defaultValue; static StringArrayValue getStringArrayValue(const CesiumUtility::JsonValue& jsonValue) { StringArrayValue result; if (!jsonValue.isArray()) { return result; } std::vector strings; std::vector stringOffsets; const auto array = jsonValue.getArray(); strings.reserve(array.size()); stringOffsets.reserve(array.size() + 1); stringOffsets.push_back(static_cast(0)); for (size_t i = 0; i < array.size(); i++) { if (!array[i].isString()) { // The entire array is invalidated; return. return result; } const std::string& string = array[i].getString(); strings.push_back(string); stringOffsets.push_back(stringOffsets[i] + string.size()); } uint64_t totalLength = stringOffsets.back(); result.data.resize(totalLength); for (size_t i = 0; i < strings.size(); ++i) { std::memcpy( result.data.data() + stringOffsets[i], strings[i].data(), strings[i].size()); }; if (totalLength <= std::numeric_limits::max()) { result.offsets = narrowOffsetsBuffer(stringOffsets); result.offsetType = PropertyComponentType::Uint8; } else if (totalLength <= std::numeric_limits::max()) { result.offsets = narrowOffsetsBuffer(stringOffsets); result.offsetType = PropertyComponentType::Uint16; } else if (totalLength <= std::numeric_limits::max()) { result.offsets = narrowOffsetsBuffer(stringOffsets); result.offsetType = PropertyComponentType::Uint32; } else { result.offsets.resize(stringOffsets.size() * sizeof(uint64_t)); std::memcpy( result.offsets.data(), stringOffsets.data(), result.offsets.size()); result.offsetType = PropertyComponentType::Uint64; } result.size = static_cast(strings.size()); return result; } template static std::vector narrowOffsetsBuffer(std::vector offsets) { std::vector result(offsets.size() * sizeof(T)); size_t bufferOffset = 0; for (size_t i = 0; i < offsets.size(); i++, bufferOffset += sizeof(T)) { T offset = static_cast(offsets[i]); std::memcpy(result.data() + bufferOffset, &offset, sizeof(T)); } return result; } }; } // namespace CesiumGltf