#pragma once #include #include #include #include namespace CesiumGltf { /** * @brief Indicates the status of an accessor view. * * The {@link AccessorView} constructor always completes successfully. However, * it may not always reflect the actual content of the {@link Accessor}, but * instead indicate that its {@link AccessorView::size} is 0. This enumeration * provides the reason. */ enum class AccessorViewStatus { /** * @brief This accessor is valid and ready to use. */ Valid, /** * @brief The accessor index does not refer to a valid accessor. */ InvalidAccessorIndex, /** * @brief The accessor's bufferView index does not refer to a valid * bufferView. */ InvalidBufferViewIndex, /** * @brief The accessor's bufferView's buffer index does not refer to a valid * buffer. */ InvalidBufferIndex, /** * @brief The accessor is too large to fit in its bufferView. */ BufferViewTooSmall, /** * @brief The accessor's bufferView is too large to fit in its buffer. */ BufferTooSmall, /** * @brief The `sizeof(T)` does not match the accessor's * {@link Accessor::computeBytesPerVertex}. */ WrongSizeT, /** * @brief The `AccessorSpec:type` is invalid. */ InvalidType, /** * @brief The {@link AccessorSpec::componentType} is invalid. */ InvalidComponentType, /** * @brief The {@link BufferView::byteStride} is negative, which is invalid. * */ InvalidByteStride, }; /** * @brief A view on the data of one accessor of a glTF asset. * * It provides the actual accessor data like an array of elements. * The type of the accessor elements is determined by the template * parameter. Instances are usually from an {@link Accessor}, * and the {@link operator[]()} can be used to access the elements: * * @snippet TestAccessorView.cpp createFromAccessorAndRead * * @tparam T The type of the elements in the accessor. */ template class AccessorView final { private: const std::byte* _pData; int64_t _stride; int64_t _offset; int64_t _size; AccessorViewStatus _status; public: /** * @brief The type of the elements in the accessor. */ typedef T value_type; /** * @brief Construct a new instance not pointing to any data. * * The new instance will have a {@link size} of 0 and a {@link status} of * `AccessorViewStatus::InvalidAccessorIndex`. * * @param status The status of the new accessor. Defaults to * {@link AccessorViewStatus::InvalidAccessorIndex}. */ AccessorView( AccessorViewStatus status = AccessorViewStatus::InvalidAccessorIndex) : _pData(nullptr), _stride(0), _offset(0), _size(0), _status(status) {} /** * @brief Creates a new instance from low-level parameters. * * The provided parameters are not validated in any way, and so this overload * can easily be used to access invalid memory. * * @param pData The raw data buffer from which to read. * @param stride The stride, in bytes, between successive elements. * @param offset The offset from the start of the buffer to the first element. * @param size The total number of elements. */ AccessorView( const std::byte* pData, int64_t stride, int64_t offset, int64_t size) : _pData(pData), _stride(stride), _offset(offset), _size(size), _status(AccessorViewStatus::Valid) {} /** * @brief Creates a new instance from a given model and {@link Accessor}. * * If the accessor cannot be viewed, the construct will still complete * successfully without throwing an exception. However, {@link size} will * return 0 and * {@link status} will indicate what went wrong. * * @param model The model to access. * @param accessor The accessor to view. */ AccessorView(const Model& model, const Accessor& accessor) noexcept : AccessorView() { this->create(model, accessor); } /** * @brief Creates a new instance from a given model and accessor index. * * If the accessor cannot be viewed, the construct will still complete * successfully without throwing an exception. However, {@link size} will * return 0 and * {@link status} will indicate what went wrong. * * @param model The model to access. * @param accessorIndex The index of the accessor to view in the model's * {@link Model::accessors} list. */ AccessorView(const Model& model, int32_t accessorIndex) noexcept : AccessorView() { const Accessor* pAccessor = Model::getSafe(&model.accessors, accessorIndex); if (!pAccessor) { this->_status = AccessorViewStatus::InvalidAccessorIndex; return; } this->create(model, *pAccessor); } /** * @brief Provides the specified accessor element. * * @param i The index of the element. * @returns The constant reference to the accessor element. * @throws A `std::range_error` if the given index is negative * or not smaller than the {@link size} of this accessor. */ const T& operator[](int64_t i) const { if (i < 0 || i >= this->_size) { throw std::range_error("index out of range"); } return *reinterpret_cast( this->_pData + i * this->_stride + this->_offset); } /** * @brief Returns the size (number of elements) of this accessor. * * This is the number of elements of type `T` that this accessor contains. * * @returns The size. */ int64_t size() const noexcept { return this->_size; } /** * @brief Gets the status of this accessor view. * * Indicates whether the view accurately reflects the accessor's data, or * whether an error occurred. */ AccessorViewStatus status() const noexcept { return this->_status; } /** * @brief Returns the stride of this accessor, which is the number of bytes * from the start of one element to the start of the next. * * @returns The stride. */ int64_t stride() const noexcept { return this->_stride; } /** * @brief Returns the offset of this accessor, which is the number of bytes * from the start of the buffer to the first element. * * @returns The offset. */ int64_t offset() const noexcept { return this->_offset; } /** * @brief Returns a pointer to the first byte of this accessor view's data. * The elements are stored contiguously, so the next one starts {@link stride} bytes later. * * @returns The start of this view. */ const std::byte* data() const noexcept { return this->_pData + this->_offset; } private: void create(const Model& model, const Accessor& accessor) noexcept { const CesiumGltf::BufferView* pBufferView = Model::getSafe(&model.bufferViews, accessor.bufferView); if (!pBufferView) { this->_status = AccessorViewStatus::InvalidBufferViewIndex; return; } const CesiumGltf::Buffer* pBuffer = Model::getSafe(&model.buffers, pBufferView->buffer); if (!pBuffer) { this->_status = AccessorViewStatus::InvalidBufferIndex; return; } const std::vector& data = pBuffer->cesium.data; const int64_t bufferBytes = int64_t(data.size()); if (pBufferView->byteOffset + pBufferView->byteLength > bufferBytes) { this->_status = AccessorViewStatus::BufferTooSmall; return; } const int64_t accessorByteStride = accessor.computeByteStride(model); if (accessorByteStride < 0) { this->_status = AccessorViewStatus::InvalidByteStride; return; } const int64_t accessorComponentElements = accessor.computeNumberOfComponents(); const int64_t accessorComponentBytes = accessor.computeByteSizeOfComponent(); const int64_t accessorBytesPerStride = accessorComponentElements * accessorComponentBytes; if (sizeof(T) != accessorBytesPerStride) { this->_status = AccessorViewStatus::WrongSizeT; return; } const int64_t accessorBytes = accessorByteStride * accessor.count; const int64_t bytesRemainingInBufferView = pBufferView->byteLength - (accessor.byteOffset + accessorByteStride * (accessor.count - 1) + accessorBytesPerStride); if (accessorBytes > pBufferView->byteLength || bytesRemainingInBufferView < 0) { this->_status = AccessorViewStatus::BufferViewTooSmall; return; } this->_pData = pBuffer->cesium.data.data(); this->_stride = accessorByteStride; this->_offset = accessor.byteOffset + pBufferView->byteOffset; this->_size = accessor.count; this->_status = AccessorViewStatus::Valid; } }; /** * @brief Contains types that may optionally be used with {@link AccessorView} * for various {@link Accessor::componentType} values. */ struct AccessorTypes { #pragma pack(push, 1) /** * @brief A scalar element for an {@link AccessorView}. * * @tparam T The component type. */ template struct SCALAR { /** * @brief The component values of this element. */ T value[1]; }; /** * @brief A 2D vector element for an {@link AccessorView}. * * @tparam T The component type. */ template struct VEC2 { /** * @brief The component values of this element. */ T value[2]; }; /** * @brief A 3D vector element for an {@link AccessorView}. * * @tparam T The component type. */ template struct VEC3 { /** * @brief The component values of this element. */ T value[3]; }; /** * @brief A 4D vector element for an {@link AccessorView}. * * @tparam T The component type. */ template struct VEC4 { /** * @brief The component values of this element. */ T value[4]; }; /** * @brief A 2x2 matrix element for an {@link AccessorView}. * * @tparam T The component type. */ template struct MAT2 { /** * @brief The component values of this element. */ T value[4]; }; /** * @brief A 3x3 matrix element for an {@link AccessorView}. * * @tparam T The component type. */ template struct MAT3 { /** * @brief The component values of this element. */ T value[9]; }; /** * @brief A 4x4 matrix element for an {@link AccessorView}. * * @tparam T The component type. */ template struct MAT4 { /** * @brief The component values of this element. */ T value[16]; }; #pragma pack(pop) }; namespace CesiumImpl { template std::invoke_result_t>> createAccessorView( const Model& model, const Accessor& accessor, TCallback&& callback) { if (accessor.type == Accessor::Type::SCALAR) { return callback( AccessorView>(model, accessor)); } if (accessor.type == Accessor::Type::VEC2) { return callback( AccessorView>(model, accessor)); } if (accessor.type == Accessor::Type::VEC3) { return callback( AccessorView>(model, accessor)); } if (accessor.type == Accessor::Type::VEC4) { return callback( AccessorView>(model, accessor)); } if (accessor.type == Accessor::Type::MAT2) { return callback( AccessorView>(model, accessor)); } if (accessor.type == Accessor::Type::MAT3) { return callback( AccessorView>(model, accessor)); } if (accessor.type == Accessor::Type::MAT4) { return callback( AccessorView>(model, accessor)); } // TODO Print a warning here??? return callback(AccessorView>( AccessorViewStatus::InvalidType)); } } // namespace CesiumImpl /** * @brief Creates an appropriate {@link AccessorView} for a given accessor. * * The created accessor is provided via a callback, which is a function that can * be invoked with all possible {@link AccessorView} types. If an accessor * cannot be created, the callback will be invoked with * `AccessorView>` and the * {@link AccessorView::status} will indicate the reason. * * @tparam TCallback The callback. * @param model The model to access. * @param accessor The accessor to view. * @param callback The callback that receives the created accessor. * @return The value returned by the callback. */ template std::invoke_result_t>> createAccessorView( const Model& model, const Accessor& accessor, TCallback&& callback) { switch (accessor.componentType) { case Accessor::ComponentType::BYTE: return ::CesiumGltf::CesiumImpl::createAccessorView( model, accessor, std::forward(callback)); case Accessor::ComponentType::UNSIGNED_BYTE: return ::CesiumGltf::CesiumImpl::createAccessorView( model, accessor, std::forward(callback)); case Accessor::ComponentType::SHORT: return ::CesiumGltf::CesiumImpl::createAccessorView( model, accessor, std::forward(callback)); case Accessor::ComponentType::UNSIGNED_SHORT: return ::CesiumGltf::CesiumImpl::createAccessorView( model, accessor, std::forward(callback)); case Accessor::ComponentType::INT: return ::CesiumGltf::CesiumImpl::createAccessorView( model, accessor, std::forward(callback)); case Accessor::ComponentType::UNSIGNED_INT: return ::CesiumGltf::CesiumImpl::createAccessorView( model, accessor, std::forward(callback)); case Accessor::ComponentType::INT64: return ::CesiumGltf::CesiumImpl::createAccessorView( model, accessor, std::forward(callback)); case Accessor::ComponentType::UNSIGNED_INT64: return ::CesiumGltf::CesiumImpl::createAccessorView( model, accessor, std::forward(callback)); case Accessor::ComponentType::FLOAT: return ::CesiumGltf::CesiumImpl::createAccessorView( model, accessor, std::forward(callback)); case Accessor::ComponentType::DOUBLE: return ::CesiumGltf::CesiumImpl::createAccessorView( model, accessor, std::forward(callback)); default: return callback(AccessorView>( AccessorViewStatus::InvalidComponentType)); } } /** * @brief Creates an appropriate {@link AccessorView} for a given accessor. * * The created accessor is provided via a callback, which is a function that can * be invoked with all possible {@link AccessorView} types. If an accessor * cannot be created, the callback will be invoked with * `AccessorView>` and the * {@link AccessorView::status} will indicate the reason. * * @tparam TCallback The callback. * @param model The model to access. * @param accessorIndex The index of the accessor to view in * {@link Model::accessors}. * @param callback The callback that receives the created accessor. * @return The value returned by the callback. */ template std::invoke_result_t>> createAccessorView( const Model& model, int32_t accessorIndex, TCallback&& callback) { const Accessor* pAccessor = Model::getSafe(&model.accessors, accessorIndex); if (!pAccessor) { return callback(AccessorView>( AccessorViewStatus::InvalidComponentType)); } return createAccessorView(model, *pAccessor, callback); } } // namespace CesiumGltf