#pragma once #include "CesiumGltf/Class.h" #include "CesiumGltf/ClassProperty.h" #include "CesiumGltf/ExtensionModelExtStructuralMetadata.h" #include "CesiumGltf/PropertyAttribute.h" #include "CesiumGltf/PropertyAttributePropertyView.h" #include "Model.h" namespace CesiumGltf { /** * @brief Indicates the status of a property attribute view. * * The {@link PropertyAttributeView} constructor always completes successfully. * However it may not always reflect the actual content of the * {@link PropertyAttribute}. This enumeration provides the reason. */ enum class PropertyAttributeViewStatus { /** * @brief This property attribute view is valid and ready to use. */ Valid, /** * @brief The glTF is missing the EXT_structural_metadata extension. */ ErrorMissingMetadataExtension, /** * @brief The glTF EXT_structural_metadata extension doesn't contain a schema. */ ErrorMissingSchema, /** * @brief The property attribute's specified class could not be found in the * extension. */ ErrorClassNotFound }; /** * @brief Attempts to obtain a \ref PropertyType from the \ref Accessor::type * "type" field of the accessor. * * @param accessor The accessor whose type will be obtained. * @returns A \ref PropertyType equivalent to the accessor's \ref * AccessorSpec::type, or \ref PropertyType::Invalid if no conversion could be * made. */ PropertyType getAccessorTypeAsPropertyType(const Accessor& accessor); /** * @brief Attempts to obtain a \ref PropertyComponentType from the \ref * Accessor::componentType "componentType" field of the accessor. * * @param accessor The accessor whose componentType will be obtained. * @returns A \ref PropertyComponentType equivalent to the accessor's \ref * AccessorSpec::componentType, or \ref PropertyComponentType::None if no * conversion could be made. */ PropertyComponentType getAccessorComponentTypeAsPropertyComponentType(const Accessor& accessor); /** * @brief A view on a {@link PropertyAttribute}. * * This should be used to get a {@link PropertyAttributePropertyView} of a property * in the property attribute. It will validate the EXT_structural_metadata * format and ensure {@link PropertyAttributePropertyView} does not access data out * of bounds. */ class PropertyAttributeView { public: /** * @brief Construct a PropertyAttributeView. * * @param model The glTF that contains the property attribute's data. * @param propertyAttribute The {@link PropertyAttribute} from which * the view will retrieve data. */ PropertyAttributeView( const Model& model, const PropertyAttribute& propertyAttribute) noexcept; /** * @brief Gets the status of this property attribute view. * * Indicates whether the view accurately reflects the property attribute's * data, or whether an error occurred. */ PropertyAttributeViewStatus status() const noexcept { return this->_status; } /** * @brief Gets the name of the property attribute being viewed. Returns * std::nullopt if no name was specified. */ const std::optional& name() const noexcept { return _pPropertyAttribute->name; } /** * @brief Gets the {@link Class} that this property attribute conforms to. * * @return A pointer to the {@link Class}. Returns nullptr if the * PropertyAttribute did not specify a valid class. */ const Class* getClass() const noexcept { return _pClass; } /** * @brief Finds the {@link ClassProperty} that * describes the type information of the property with the specified id. * @param propertyId The id of the property to retrieve the class for. * @return A pointer to the {@link ClassProperty}. * Return nullptr if the PropertyAttributeView is invalid or if no class * property was found. */ const ClassProperty* getClassProperty(const std::string& propertyId) const; /** * @brief Gets a {@link PropertyAttributePropertyView} that views the data of a * property stored in the {@link PropertyAttribute}. * * This method will validate the EXT_structural_metadata format to ensure * {@link PropertyAttributePropertyView} retrieves the correct data. T must * be a scalar with a supported component type (int8_t, uint8_t, int16_t, * uint16_t, float), a glm vecN composed of one of the scalar types, * or a glm matN containing one of the scalar types. * * If T does not match the type specified by the class property, this returns * an invalid PropertyAttributePropertyView. Likewise, if the value of * Normalized does not match the value of {@link ClassProperty::normalized} for that * class property, this returns an invalid property view. Only types with * integer components may be normalized. * * @tparam T The C++ type corresponding to the type of the data retrieved. * @tparam Normalized Whether the property is normalized. Only applicable to * types with integer components. * @param primitive The target primitive * @param propertyId The id of the property to retrieve data from * @return A {@link PropertyAttributePropertyView} of the property. If no valid * property is found, the property view will be invalid. */ template PropertyAttributePropertyView getPropertyView( const MeshPrimitive& primitive, const std::string& propertyId) const { if (this->_status != PropertyAttributeViewStatus::Valid) { return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorInvalidPropertyAttribute); } const ClassProperty* pClassProperty = getClassProperty(propertyId); if (!pClassProperty) { return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorNonexistentProperty); } if constexpr ( IsMetadataArray::value || IsMetadataBoolean::value || IsMetadataString::value) { return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorUnsupportedProperty); } return getPropertyViewImpl( primitive, propertyId, *pClassProperty); } /** * @brief Gets a {@link PropertyAttributePropertyView} through a callback that accepts a * property id and a {@link PropertyAttributePropertyView} that views the data * of the property with the specified id. * * This method will validate the EXT_structural_metadata format to ensure * {@link PropertyAttributePropertyView} retrieves the correct data. T must * be a scalar with a supported component type (int8_t, uint8_t, int16_t, * uint16_t, float), a glm vecN composed of one of the scalar types, * or a glm matN containing one of the scalar types. * * If the property is somehow invalid, an empty {@link PropertyAttributePropertyView} * with an error status will be passed to the callback. Otherwise, a valid * property view will be passed to the callback. * * @param primitive The target primitive * @param propertyId The id of the property to retrieve data from * @param callback A callback function that accepts a property id and a * {@link PropertyAttributePropertyView} * @tparam Callback The type of the callback function. */ template void getPropertyView( const MeshPrimitive& primitive, const std::string& propertyId, Callback&& callback) const { if (this->_status != PropertyAttributeViewStatus::Valid) { callback( propertyId, PropertyAttributePropertyView( PropertyAttributePropertyViewStatus:: ErrorInvalidPropertyAttribute)); return; } const ClassProperty* pClassProperty = getClassProperty(propertyId); if (!pClassProperty) { callback( propertyId, PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorNonexistentProperty)); return; } if (pClassProperty->array) { callback( propertyId, PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorUnsupportedProperty)); return; } PropertyType type = convertStringToPropertyType(pClassProperty->type); PropertyComponentType componentType = PropertyComponentType::None; if (pClassProperty->componentType) { componentType = convertStringToPropertyComponentType(*pClassProperty->componentType); } bool normalized = pClassProperty->normalized; if (normalized && !isPropertyComponentTypeInteger(componentType)) { callback( propertyId, PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorInvalidNormalization)); return; } if (type == PropertyType::Scalar) { if (normalized) { getScalarPropertyViewImpl( primitive, propertyId, *pClassProperty, componentType, std::forward(callback)); } else { getScalarPropertyViewImpl( primitive, propertyId, *pClassProperty, componentType, std::forward(callback)); } return; } if (isPropertyTypeVecN(type)) { if (normalized) { getVecNPropertyViewImpl( primitive, propertyId, *pClassProperty, type, componentType, std::forward(callback)); } else { getVecNPropertyViewImpl( primitive, propertyId, *pClassProperty, type, componentType, std::forward(callback)); } return; } if (isPropertyTypeMatN(type)) { if (normalized) { getMatNPropertyViewImpl( primitive, propertyId, *pClassProperty, type, componentType, std::forward(callback)); } else { getMatNPropertyViewImpl( primitive, propertyId, *pClassProperty, type, componentType, std::forward(callback)); } return; } callback( propertyId, PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorUnsupportedProperty)); return; } /** * @brief Iterates over each property in the {@link PropertyAttribute} with a callback * that accepts a property id and a {@link PropertyAttributePropertyView} to view * the data stored in the {@link PropertyAttributeProperty}. * * This method will validate the EXT_structural_metadata format to ensure * {@link PropertyAttributePropertyView} retrieves the correct data. T must be * a scalar with a supported component type (int8_t, uint8_t, int16_t, * uint16_t, float), a glm vecN composed of one of the scalar types, * or a PropertyArrayView containing one of the scalar types. * * If the property is invalid, an empty {@link PropertyAttributePropertyView} with an * error status will be passed to the callback. Otherwise, a valid property * view will be passed to the callback. * * @param primitive The id of the property to retrieve data from * @param callback A callback function that accepts property id and * {@link PropertyAttributePropertyView} * @tparam Callback The type of the callback function. */ template void forEachProperty(const MeshPrimitive& primitive, Callback&& callback) const { for (const auto& property : this->_pClass->properties) { getPropertyView( primitive, property.first, std::forward(callback)); } } private: template PropertyAttributePropertyView getEmptyPropertyViewWithDefault( const MeshPrimitive& primitive, const ClassProperty& classProperty) const { // To make the view have a nonzero size, find the POSITION attribute and get // its accessor count. If it doesn't exist or is somehow erroneous, just // mark the property as nonexistent. if (primitive.attributes.find("POSITION") == primitive.attributes.end()) { return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorNonexistentProperty); } const Accessor* pAccessor = _pModel->getSafe( &_pModel->accessors, primitive.attributes.at("POSITION")); if (!pAccessor) { return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorNonexistentProperty); } return PropertyAttributePropertyView( classProperty, pAccessor->count); } template PropertyAttributePropertyView getPropertyViewImpl( const MeshPrimitive& primitive, const std::string& propertyId, const ClassProperty& classProperty) const { auto propertyAttributePropertyIter = _pPropertyAttribute->properties.find(propertyId); if (propertyAttributePropertyIter == _pPropertyAttribute->properties.end()) { if (!classProperty.required && classProperty.defaultProperty) { // If the property was omitted from the property attribute, it is still // technically valid if it specifies a default value. Try to create a // view that just returns the default value. return getEmptyPropertyViewWithDefault( primitive, classProperty); } // Otherwise, the property is erroneously nonexistent. return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorNonexistentProperty); } const PropertyAttributeProperty& propertyAttributeProperty = propertyAttributePropertyIter->second; return createPropertyView( primitive, classProperty, propertyAttributeProperty); } template void getScalarPropertyViewImpl( const MeshPrimitive& primitive, const std::string& propertyId, const ClassProperty& classProperty, PropertyComponentType componentType, Callback&& callback) const { switch (componentType) { case PropertyComponentType::Int8: callback( propertyId, getPropertyViewImpl( primitive, propertyId, classProperty)); return; case PropertyComponentType::Uint8: callback( propertyId, getPropertyViewImpl( primitive, propertyId, classProperty)); return; case PropertyComponentType::Int16: callback( propertyId, getPropertyViewImpl( primitive, propertyId, classProperty)); return; case PropertyComponentType::Uint16: callback( propertyId, getPropertyViewImpl( primitive, propertyId, classProperty)); break; case PropertyComponentType::Float32: callback( propertyId, getPropertyViewImpl( primitive, propertyId, classProperty)); break; default: callback( propertyId, PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorUnsupportedProperty)); break; } } template void getVecNPropertyViewImpl( const MeshPrimitive& primitive, const std::string& propertyId, const ClassProperty& classProperty, PropertyComponentType componentType, Callback&& callback) const { switch (componentType) { case PropertyComponentType::Int8: callback( propertyId, getPropertyViewImpl, Normalized>( primitive, propertyId, classProperty)); break; case PropertyComponentType::Uint8: callback( propertyId, getPropertyViewImpl, Normalized>( primitive, propertyId, classProperty)); break; case PropertyComponentType::Int16: callback( propertyId, getPropertyViewImpl, Normalized>( primitive, propertyId, classProperty)); break; case PropertyComponentType::Uint16: callback( propertyId, getPropertyViewImpl, Normalized>( primitive, propertyId, classProperty)); break; case PropertyComponentType::Float32: callback( propertyId, getPropertyViewImpl, false>( primitive, propertyId, classProperty)); break; default: callback( propertyId, PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorUnsupportedProperty)); break; } } template void getVecNPropertyViewImpl( const MeshPrimitive& primitive, const std::string& propertyId, const ClassProperty& classProperty, PropertyType type, PropertyComponentType componentType, Callback&& callback) const { const glm::length_t N = getDimensionsFromPropertyType(type); switch (N) { case 2: getVecNPropertyViewImpl( primitive, propertyId, classProperty, componentType, std::forward(callback)); break; case 3: getVecNPropertyViewImpl( primitive, propertyId, classProperty, componentType, std::forward(callback)); break; case 4: getVecNPropertyViewImpl( primitive, propertyId, classProperty, componentType, std::forward(callback)); break; default: callback( propertyId, PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorTypeMismatch)); break; } } template void getMatNPropertyViewImpl( const MeshPrimitive& primitive, const std::string& propertyId, const ClassProperty& classProperty, PropertyComponentType componentType, Callback&& callback) const { switch (componentType) { case PropertyComponentType::Int8: callback( propertyId, getPropertyViewImpl, Normalized>( primitive, propertyId, classProperty)); break; case PropertyComponentType::Uint8: callback( propertyId, getPropertyViewImpl, Normalized>( primitive, propertyId, classProperty)); break; case PropertyComponentType::Int16: callback( propertyId, getPropertyViewImpl, Normalized>( primitive, propertyId, classProperty)); break; case PropertyComponentType::Uint16: callback( propertyId, getPropertyViewImpl, Normalized>( primitive, propertyId, classProperty)); break; case PropertyComponentType::Float32: callback( propertyId, getPropertyViewImpl, false>( primitive, propertyId, classProperty)); break; default: callback( propertyId, PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorUnsupportedProperty)); break; } } template void getMatNPropertyViewImpl( const MeshPrimitive& primitive, const std::string& propertyId, const ClassProperty& classProperty, PropertyType type, PropertyComponentType componentType, Callback&& callback) const { glm::length_t N = getDimensionsFromPropertyType(type); switch (N) { case 2: getMatNPropertyViewImpl( primitive, propertyId, classProperty, componentType, std::forward(callback)); break; case 3: getMatNPropertyViewImpl( primitive, propertyId, classProperty, componentType, std::forward(callback)); break; case 4: getMatNPropertyViewImpl( primitive, propertyId, classProperty, componentType, std::forward(callback)); break; default: callback( propertyId, PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorTypeMismatch)); break; } } template PropertyAttributePropertyView createPropertyView( const MeshPrimitive& primitive, const ClassProperty& classProperty, const PropertyAttributeProperty& propertyAttributeProperty) const { const PropertyType type = convertStringToPropertyType(classProperty.type); if (TypeToPropertyType::value != type) { return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorTypeMismatch); } const PropertyComponentType componentType = convertStringToPropertyComponentType( classProperty.componentType.value_or("")); if (TypeToPropertyType::component != componentType) { return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorComponentTypeMismatch); } if (classProperty.normalized != Normalized) { return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorNormalizationMismatch); } if (primitive.attributes.find(propertyAttributeProperty.attribute) == primitive.attributes.end()) { return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorMissingAttribute); } const Accessor* pAccessor = _pModel->getSafe( &_pModel->accessors, primitive.attributes.at(propertyAttributeProperty.attribute)); if (!pAccessor) { return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorInvalidAccessor); } if (getAccessorTypeAsPropertyType(*pAccessor) != type) { return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorAccessorTypeMismatch); } if (getAccessorComponentTypeAsPropertyComponentType(*pAccessor) != componentType) { return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus:: ErrorAccessorComponentTypeMismatch); } if (pAccessor->normalized != Normalized) { return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus:: ErrorAccessorNormalizationMismatch); } AccessorView accessorView = AccessorView(*_pModel, *pAccessor); if (accessorView.status() != AccessorViewStatus::Valid) { switch (accessorView.status()) { case AccessorViewStatus::InvalidBufferViewIndex: return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorInvalidBufferView); case AccessorViewStatus::InvalidBufferIndex: return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorInvalidBuffer); case AccessorViewStatus::BufferViewTooSmall: return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorAccessorOutOfBounds); case AccessorViewStatus::BufferTooSmall: return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorBufferViewOutOfBounds); default: return PropertyAttributePropertyView( PropertyAttributePropertyViewStatus::ErrorInvalidAccessor); } } return PropertyAttributePropertyView( propertyAttributeProperty, classProperty, accessorView); } const Model* _pModel; const PropertyAttribute* _pPropertyAttribute; const Class* _pClass; PropertyAttributeViewStatus _status; }; } // namespace CesiumGltf