#pragma once #include "CesiumGltf/ImageAsset.h" #include "CesiumGltf/KhrTextureTransform.h" #include "CesiumGltf/PropertyTextureProperty.h" #include "CesiumGltf/PropertyTransformations.h" #include "CesiumGltf/PropertyTypeTraits.h" #include "CesiumGltf/PropertyView.h" #include "CesiumGltf/Sampler.h" #include "CesiumGltf/TextureView.h" #include #include #include #include #include namespace CesiumGltf { /** * @brief Indicates the status of a property texture property view. * * The {@link PropertyTexturePropertyView} constructor always completes * successfully. However it may not always reflect the actual content of the * corresponding property texture property. This enumeration provides the * reason. */ class PropertyTexturePropertyViewStatus : public PropertyViewStatus { public: /** * @brief This property view was initialized from an invalid * {@link PropertyTexture}. */ static const int ErrorInvalidPropertyTexture = 14; /** * @brief This property view is associated with a {@link ClassProperty} of an * unsupported type. */ static const int ErrorUnsupportedProperty = 15; /** * @brief This property view does not have a valid texture index. */ static const int ErrorInvalidTexture = 16; /** * @brief This property view does not have a valid sampler index. */ static const int ErrorInvalidSampler = 17; /** * @brief This property view does not have a valid image index. */ static const int ErrorInvalidImage = 18; /** * @brief This property is viewing an empty image. */ static const int ErrorEmptyImage = 19; /** * @brief This property uses an image with multi-byte channels. Only * single-byte channels are supported. */ static const int ErrorInvalidBytesPerChannel = 20; /** * @brief The channels of this property texture property are invalid. * Channels must be in the range 0-N, where N is the number of available * channels in the image. There must be a minimum of one channel. Although * more than four channels can be defined for specialized texture * formats, this implementation only supports four channels max. */ static const int ErrorInvalidChannels = 21; /** * @brief The channels of this property texture property do not provide * the exact number of bytes required by the property type. This may be * because an incorrect number of channels was provided, or because the * image itself has a different channel count / byte size than expected. */ static const int ErrorChannelsAndTypeMismatch = 22; }; /** * @brief Attempts to obtain a scalar value from the given span of bytes. * * @tparam ElementType The scalar value type to read from `bytes`. * @param bytes A span of bytes to convert into a scalar value. * @returns A value of `ElementType`. */ template ElementType assembleScalarValue(const std::span bytes) noexcept { if constexpr (std::is_same_v) { CESIUM_ASSERT( bytes.size() == sizeof(float) && "Not enough channel inputs to construct a float."); uint32_t resultAsUint = 0; for (size_t i = 0; i < bytes.size(); i++) { resultAsUint |= static_cast(bytes[i]) << i * 8; } // Reinterpret the bits as a float. return *reinterpret_cast(&resultAsUint); } if constexpr (IsMetadataInteger::value) { using UintType = std::make_unsigned_t; UintType resultAsUint = 0; for (size_t i = 0; i < bytes.size(); i++) { resultAsUint |= static_cast(bytes[i]) << i * 8; } // Reinterpret the bits with the correct signedness. return *reinterpret_cast(&resultAsUint); } } /** * @brief Attempts to obtain a vector value from the given span of bytes. * * @tparam ElementType The vector value type to read from `bytes`. * @param bytes A span of bytes to convert into a vector value. * @returns A value of `ElementType`. */ template ElementType assembleVecNValue(const std::span bytes) noexcept { ElementType result = ElementType(); const glm::length_t N = getDimensionsFromPropertyType(TypeToPropertyType::value); using T = typename ElementType::value_type; CESIUM_ASSERT( sizeof(T) <= 2 && "Components cannot be larger than two bytes in size."); if constexpr (std::is_same_v) { CESIUM_ASSERT( N == 2 && "Only vec2s can contain two-byte integer components."); uint16_t x = static_cast(bytes[0]) | (static_cast(bytes[1]) << 8); uint16_t y = static_cast(bytes[2]) | (static_cast(bytes[3]) << 8); result[0] = *reinterpret_cast(&x); result[1] = *reinterpret_cast(&y); } if constexpr (std::is_same_v) { CESIUM_ASSERT( N == 2 && "Only vec2s can contain two-byte integer components."); result[0] = static_cast(bytes[0]) | (static_cast(bytes[1]) << 8); result[1] = static_cast(bytes[2]) | (static_cast(bytes[3]) << 8); } if constexpr (std::is_same_v) { for (size_t i = 0; i < bytes.size(); i++) { result[i] = *reinterpret_cast(&bytes[i]); } } if constexpr (std::is_same_v) { for (size_t i = 0; i < bytes.size(); i++) { result[i] = bytes[i]; } } return result; } /** * @brief Attempts to obtain an array value from the given span of bytes. * * @tparam T The element type to read from `bytes`. * @param bytes A span of bytes to convert into an array value. * @returns A \ref PropertyArrayCopy containing the elements read. */ template PropertyArrayCopy assembleArrayValue(const std::span bytes) noexcept { std::vector result(bytes.size() / sizeof(T)); if constexpr (sizeof(T) == 2) { for (int i = 0, b = 0; i < result.size(); i++, b += 2) { using UintType = std::make_unsigned_t; UintType resultAsUint = static_cast(bytes[b]) | (static_cast(bytes[b + 1]) << 8); result[i] = *reinterpret_cast(&resultAsUint); } } else { for (size_t i = 0; i < bytes.size(); i++) { result[i] = *reinterpret_cast(&bytes[i]); } } return PropertyArrayCopy(std::move(result)); } /** * @brief Assembles the given type from the provided channel values of sampling * a texture. * * @tparam ElementType The type of element to assemble. * @param bytes The byte values of the sampled channels of the texture. * @returns The result of \ref assembleScalarValue, \ref assembleVecNValue, or * \ref assembleArrayValue depending on `ElementType`. */ template PropertyValueViewToCopy assembleValueFromChannels(const std::span bytes) noexcept { CESIUM_ASSERT( bytes.size() > 0 && "Channel input must have at least one value."); if constexpr (IsMetadataScalar::value) { return assembleScalarValue(bytes); } if constexpr (IsMetadataVecN::value) { return assembleVecNValue(bytes); } if constexpr (IsMetadataArray::value) { return assembleArrayValue::type>( bytes); } } #pragma region Non - normalized property /** * @brief A view of the data specified by a {@link PropertyTextureProperty}. * * Provides utilities to sample the property texture property using texture * coordinates. Property values are retrieved from the NEAREST texel without * additional filtering applied. * * @tparam ElementType The type of the elements represented in the property * view * @tparam Normalized Whether or not the property is normalized. If * normalized, the elements can be retrieved as normalized floating-point * numbers, as opposed to their integer values. */ template class PropertyTexturePropertyView; /** * @brief A view of the non-normalized data specified by a * {@link PropertyTextureProperty}. * * Provides utilities to sample the property texture property using texture * coordinates. * * @tparam ElementType The type of the elements represented in the property view */ template class PropertyTexturePropertyView : public PropertyView, public TextureView { public: /** * @brief Constructs an invalid instance for a non-existent property. */ PropertyTexturePropertyView() noexcept : PropertyView(), TextureView(), _channels(), _swizzle() {} /** * @brief Constructs an invalid instance for an erroneous property. * * @param status The code from {@link PropertyTexturePropertyViewStatus} indicating the error with the property. */ PropertyTexturePropertyView(PropertyViewStatusType status) noexcept : PropertyView(status), TextureView(), _channels(), _swizzle() { CESIUM_ASSERT( this->_status != PropertyTexturePropertyViewStatus::Valid && "An empty property view should not be constructed with a valid status"); } /** * @brief Constructs an instance of an empty property that specifies a default * value. Although this property has no data, it can return the default value * when \ref get is called. However, \ref getRaw cannot be used. * * @param classProperty The {@link ClassProperty} this property conforms to. */ PropertyTexturePropertyView(const ClassProperty& classProperty) noexcept : PropertyView(classProperty), TextureView(), _channels(), _swizzle() { if (this->_status != PropertyTexturePropertyViewStatus::Valid) { // Don't override the status / size if something is wrong with the class // property's definition. return; } if (!classProperty.defaultProperty) { // This constructor should only be called if the class property *has* a // default value. But in the case that it does not, this property view // becomes invalid. this->_status = PropertyTexturePropertyViewStatus::ErrorNonexistentProperty; return; } this->_status = PropertyTexturePropertyViewStatus::EmptyPropertyWithDefault; } /** * @brief Construct a view of the data specified by a {@link PropertyTextureProperty}. * * @param property The {@link PropertyTextureProperty} * @param classProperty The {@link ClassProperty} this property conforms to. * @param sampler The {@link Sampler} used by the property. * @param image The {@link ImageAsset} used by the property. * @param options The options for constructing the view. */ PropertyTexturePropertyView( const PropertyTextureProperty& property, const ClassProperty& classProperty, const Sampler& sampler, const ImageAsset& image, const TextureViewOptions& options = TextureViewOptions()) noexcept : PropertyView(classProperty, property), TextureView( sampler, image, property.texCoord, property.getExtension(), options), _channels(property.channels), _swizzle() { if (this->_status != PropertyTexturePropertyViewStatus::Valid) { return; } switch (this->getTextureViewStatus()) { case TextureViewStatus::Valid: break; case TextureViewStatus::ErrorInvalidSampler: this->_status = PropertyTexturePropertyViewStatus::ErrorInvalidSampler; return; case TextureViewStatus::ErrorInvalidImage: this->_status = PropertyTexturePropertyViewStatus::ErrorInvalidImage; return; case TextureViewStatus::ErrorEmptyImage: this->_status = PropertyTexturePropertyViewStatus::ErrorEmptyImage; return; case TextureViewStatus::ErrorInvalidBytesPerChannel: this->_status = PropertyTexturePropertyViewStatus::ErrorInvalidBytesPerChannel; return; case TextureViewStatus::ErrorUninitialized: case TextureViewStatus::ErrorInvalidTexture: default: this->_status = PropertyTexturePropertyViewStatus::ErrorInvalidTexture; return; } _swizzle.reserve(_channels.size()); for (size_t i = 0; i < _channels.size(); ++i) { switch (_channels[i]) { case 0: _swizzle += "r"; break; case 1: _swizzle += "g"; break; case 2: _swizzle += "b"; break; case 3: _swizzle += "a"; break; default: CESIUM_ASSERT( false && "A valid channels vector must be passed to the view."); } } } /** * @brief Gets the value of the property for the given texture coordinates * with all value transforms applied. That is, if the property specifies an * offset and scale, they will be applied to the value before the value is * returned. The sampler's wrapping mode will be used when sampling the * texture. * * If this property has a specified "no data" value, this will return the * property's default value for any elements that equal this "no data" value. * If the property did not specify a default value, this returns std::nullopt. * * @param u The u-component of the texture coordinates. * @param v The v-component of the texture coordinates. * * @return The value of the element, or std::nullopt if it matches the "no * data" value */ std::optional> get(double u, double v) const noexcept { if (this->_status == PropertyTexturePropertyViewStatus::EmptyPropertyWithDefault) { return propertyValueViewToCopy(this->defaultValue()); } PropertyValueViewToCopy value = getRaw(u, v); if (value == this->noData()) { return propertyValueViewToCopy(this->defaultValue()); } else if constexpr (IsMetadataNumeric::value) { return transformValue(value, this->offset(), this->scale()); } else if constexpr (IsMetadataNumericArray::value) { return transformArray( propertyValueCopyToView(value), this->offset(), this->scale()); } else { return value; } } /** * @brief Gets the raw value of the property for the given texture * coordinates. The sampler's wrapping mode will be used when sampling the * texture. * * If this property has a specified "no data" value, the raw value will still * be returned, even if it equals the "no data" value. * * @param u The u-component of the texture coordinates. * @param v The v-component of the texture coordinates. * * @return The value at the nearest pixel to the texture coordinates. */ PropertyValueViewToCopy getRaw(double u, double v) const noexcept { CESIUM_ASSERT( this->_status == PropertyTexturePropertyViewStatus::Valid && "Check the status() first to make sure view is valid"); std::vector sample = this->sampleNearestPixel(u, v, this->_channels); return assembleValueFromChannels( std::span(sample.data(), this->_channels.size())); } /** * @brief Gets the channels of this property texture property. */ const std::vector& getChannels() const noexcept { return this->_channels; } /** * @brief Gets this property's channels as a swizzle string. */ const std::string& getSwizzle() const noexcept { return this->_swizzle; } private: std::vector _channels; std::string _swizzle; }; #pragma endregion #pragma region Normalized property /** * @brief A view of the normalized data specified by a * {@link PropertyTextureProperty}. * * Provides utilities to sample the property texture property using texture * coordinates. */ template class PropertyTexturePropertyView : public PropertyView, public TextureView { private: using NormalizedType = typename TypeToNormalizedType::type; public: /** * @brief Constructs an invalid instance for a non-existent property. */ PropertyTexturePropertyView() noexcept : PropertyView(), TextureView(), _channels(), _swizzle() {} /** * @brief Constructs an invalid instance for an erroneous property. * * @param status The code from {@link PropertyTexturePropertyViewStatus} indicating the error with the property. */ PropertyTexturePropertyView(PropertyViewStatusType status) noexcept : PropertyView(status), TextureView(), _channels(), _swizzle() { CESIUM_ASSERT( this->_status != PropertyTexturePropertyViewStatus::Valid && "An empty property view should not be constructed with a valid " "status"); } /** * @brief Constructs an instance of an empty property that specifies a * default value. Although this property has no data, it can return the * default value when {@link PropertyTexturePropertyView::get} is called. * However, {@link PropertyTexturePropertyView::getRaw} cannot be used. * * @param classProperty The {@link ClassProperty} this property conforms to. */ PropertyTexturePropertyView(const ClassProperty& classProperty) noexcept : PropertyView(classProperty), TextureView(), _channels(), _swizzle() { if (this->_status != PropertyTexturePropertyViewStatus::Valid) { // Don't override the status / size if something is wrong with the class // property's definition. return; } if (!classProperty.defaultProperty) { // This constructor should only be called if the class property *has* a // default value. But in the case that it does not, this property view // becomes invalid. this->_status = PropertyTexturePropertyViewStatus::ErrorNonexistentProperty; return; } this->_status = PropertyTexturePropertyViewStatus::EmptyPropertyWithDefault; } /** * @brief Construct a view of the data specified by a {@link PropertyTextureProperty}. * * @param property The {@link PropertyTextureProperty} * @param classProperty The {@link ClassProperty} this property conforms to. * @param sampler The {@link Sampler} used by the property. * @param image The {@link ImageAsset} used by the property. * @param options The options for constructing the view. */ PropertyTexturePropertyView( const PropertyTextureProperty& property, const ClassProperty& classProperty, const Sampler& sampler, const ImageAsset& image, const TextureViewOptions& options = TextureViewOptions()) noexcept : PropertyView(classProperty, property), TextureView( sampler, image, property.texCoord, property.getExtension(), options), _channels(property.channels), _swizzle() { if (this->_status != PropertyTexturePropertyViewStatus::Valid) { return; } switch (this->getTextureViewStatus()) { case TextureViewStatus::Valid: break; case TextureViewStatus::ErrorInvalidSampler: this->_status = PropertyTexturePropertyViewStatus::ErrorInvalidSampler; return; case TextureViewStatus::ErrorInvalidImage: this->_status = PropertyTexturePropertyViewStatus::ErrorInvalidImage; return; case TextureViewStatus::ErrorEmptyImage: this->_status = PropertyTexturePropertyViewStatus::ErrorEmptyImage; return; case TextureViewStatus::ErrorInvalidBytesPerChannel: this->_status = PropertyTexturePropertyViewStatus::ErrorInvalidBytesPerChannel; return; case TextureViewStatus::ErrorUninitialized: case TextureViewStatus::ErrorInvalidTexture: default: this->_status = PropertyTexturePropertyViewStatus::ErrorInvalidTexture; return; } _swizzle.reserve(_channels.size()); for (size_t i = 0; i < _channels.size(); ++i) { switch (_channels[i]) { case 0: _swizzle += "r"; break; case 1: _swizzle += "g"; break; case 2: _swizzle += "b"; break; case 3: _swizzle += "a"; break; default: CESIUM_ASSERT( false && "A valid channels vector must be passed to the view."); } } } /** * @brief Gets the value of the property for the given texture coordinates * with all value transforms applied. That is, if the property specifies an * offset and scale, they will be applied to the value before the value is * returned. The sampler's wrapping mode will be used when sampling the * texture. * * If this property has a specified "no data" value, and the retrieved * element is equal to that value, then this will return the property's * specified default value. If the property did not provide a default value, * this returns std::nullopt. * * @param u The u-component of the texture coordinates. * @param v The v-component of the texture coordinates. * * @return The value of the element, or std::nullopt if it matches the "no * data" value */ std::optional> get(double u, double v) const noexcept { if (this->_status == PropertyTexturePropertyViewStatus::EmptyPropertyWithDefault) { return propertyValueViewToCopy(this->defaultValue()); } PropertyValueViewToCopy value = getRaw(u, v); if (value == this->noData()) { return propertyValueViewToCopy(this->defaultValue()); } else if constexpr (IsMetadataScalar::value) { return transformValue( normalize(value), this->offset(), this->scale()); } else if constexpr (IsMetadataVecN::value) { constexpr glm::length_t N = ElementType::length(); using T = typename ElementType::value_type; using NormalizedT = typename NormalizedType::value_type; return transformValue>( normalize(value), this->offset(), this->scale()); } else if constexpr (IsMetadataArray::value) { using ArrayElementType = typename MetadataArrayType::type; if constexpr (IsMetadataScalar::value) { return transformNormalizedArray( propertyValueCopyToView(value), this->offset(), this->scale()); } else if constexpr (IsMetadataVecN::value) { constexpr glm::length_t N = ArrayElementType::length(); using T = typename ArrayElementType::value_type; return transformNormalizedVecNArray( propertyValueCopyToView(value), this->offset(), this->scale()); } } } /** * @brief Gets the raw value of the property for the given texture * coordinates. The sampler's wrapping mode will be used when sampling the * texture. * * If this property has a specified "no data" value, the raw value will * still be returned, even if it equals the "no data" value. * * @param u The u-component of the texture coordinates. * @param v The v-component of the texture coordinates. * * @return The value at the nearest pixel to the texture coordinates. */ PropertyValueViewToCopy getRaw(double u, double v) const noexcept { CESIUM_ASSERT( this->_status == PropertyTexturePropertyViewStatus::Valid && "Check the status() first to make sure view is valid"); std::vector sample = this->sampleNearestPixel(u, v, this->_channels); return assembleValueFromChannels( std::span(sample.data(), this->_channels.size())); } /** * @brief Gets the channels of this property texture property. */ const std::vector& getChannels() const noexcept { return this->_channels; } /** * @brief Gets this property's channels as a swizzle string. */ const std::string& getSwizzle() const noexcept { return this->_swizzle; } private: std::vector _channels; std::string _swizzle; }; #pragma endregion } // namespace CesiumGltf