From 0ad2f2247aa5a4afdcb12ca8efc9d29905f0f1aa Mon Sep 17 00:00:00 2001 From: Alexandr Arutjunov Date: Thu, 11 Aug 2016 01:04:30 +0300 Subject: [PATCH] [+] glTF import/export. Support for Open3DGC-compression of mesh primitives. --- code/glTFAsset.h | 173 +++++++++--------- code/glTFAsset.inl | 458 ++++++++++++++++++++++++++--------------------- code/glTFAssetWriter.inl | 7 +- code/glTFExporter.cpp | 60 ++++--- code/glTFImporter.cpp | 46 +++-- 5 files changed, 416 insertions(+), 328 deletions(-) diff --git a/code/glTFAsset.h b/code/glTFAsset.h index 5f4f933..90335f5 100644 --- a/code/glTFAsset.h +++ b/code/glTFAsset.h @@ -450,88 +450,49 @@ namespace glTF //! A buffer points to binary geometry, animation, or skins. struct Buffer : public Object - { - public: - - enum Type - { - Type_arraybuffer, - Type_text - }; - - //std::string uri; //!< The uri of the buffer. Can be a filepath, a data uri, etc. (required) - size_t byteLength; //!< The length of the buffer in bytes. (default: 0) - //std::string type; //!< XMLHttpRequest responseType (default: "arraybuffer") - - Type type; - - private: - shared_ptr mData; //!< Pointer to the data - bool mIsSpecial; //!< Set to true for special cases (e.g. the body buffer) - - public: - Buffer(); - - void Read(Value& obj, Asset& r); - - bool LoadFromStream(IOStream& stream, size_t length = 0, size_t baseOffset = 0); + { + /********************* Types *********************/ + public: - /// \fn bool ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t* pReplace_Data, const size_t pReplace_Count) - /// Replace part of buffer data. For example: decoded/encoded data. - /// \param [in] pBufferData_Offset - index of first element in buffer from which new data will be placed. - /// \param [in] pBufferData_Count - count of bytes in buffer which will be replaced. - /// \param [in] pReplace_Data - pointer to array with new data for buffer. - /// \param [in] pReplace_Count - count of bytes in new data. - /// \return true - if successfully replaced, false if input arguments is out of range. - bool ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t* pReplace_Data, const size_t pReplace_Count); + enum Type + { + Type_arraybuffer, + Type_text + }; - size_t AppendData(uint8_t* data, size_t length); - void Grow(size_t amount); + /// \struct SEncodedRegion + /// Descriptor of encoded region in "bufferView". + struct SEncodedRegion + { + const size_t Offset;///< Offset from begin of "bufferView" to encoded region, in bytes. + const size_t EncodedData_Length;///< Size of encoded region, in bytes. + uint8_t* const DecodedData;///< Cached encoded data. + const size_t DecodedData_Length;///< Size of decoded region, in bytes. + const std::string ID;///< ID of the region. - uint8_t* GetPointer() - { return mData.get(); } + /// \fn SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t* pDecodedData, const size_t pDecodedData_Length, const std::string pID) + /// Constructor. + /// \param [in] pOffset - offset from begin of "bufferView" to encoded region, in bytes. + /// \param [in] pEncodedData_Length - size of encoded region, in bytes. + /// \param [in] pDecodedData - pointer to decoded data array. + /// \param [in] pDecodedData_Length - size of encoded region, in bytes. + /// \param [in] pID - ID of the region. + SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t* pDecodedData, const size_t pDecodedData_Length, const std::string pID) + : Offset(pOffset), EncodedData_Length(pEncodedData_Length), DecodedData(pDecodedData), DecodedData_Length(pDecodedData_Length), ID(pID) + {} - void MarkAsSpecial() - { mIsSpecial = true; } + /// \fn ~SEncodedRegion() + /// Destructor. + ~SEncodedRegion() { delete [] DecodedData; } + }; - bool IsSpecial() const - { return mIsSpecial; } + /******************* Variables *******************/ - static const char* TranslateId(Asset& r, const char* id); - }; + //std::string uri; //!< The uri of the buffer. Can be a filepath, a data uri, etc. (required) + size_t byteLength; //!< The length of the buffer in bytes. (default: 0) + //std::string type; //!< XMLHttpRequest responseType (default: "arraybuffer") - /// \struct SEncodedRegion - /// Descriptor of encoded region in "bufferView". - struct SEncodedRegion - { - const size_t Offset;///< Offset from begin of "bufferView" to encoded region, in bytes. - const size_t EncodedData_Length;///< Size of encoded region, in bytes. - uint8_t* const DecodedData;///< Cached encoded data. - const size_t DecodedData_Length;///< Size of decoded region, in bytes. - const std::string ID;///< ID of the region. - - /// \fn SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t* pDecodedData, const size_t pDecodedData_Length, const std::string pID) - /// Constructor. - /// \param [in] pOffset - offset from begin of "bufferView" to encoded region, in bytes. - /// \param [in] pEncodedData_Length - size of encoded region, in bytes. - /// \param [in] pDecodedData - pointer to decoded data array. - /// \param [in] pDecodedData_Length - size of encoded region, in bytes. - /// \param [in] pID - ID of the region. - SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t* pDecodedData, const size_t pDecodedData_Length, const std::string pID) - : Offset(pOffset), EncodedData_Length(pEncodedData_Length), DecodedData(pDecodedData), DecodedData_Length(pDecodedData_Length), ID(pID) - {} - - /// \fn ~SEncodedRegion() - /// Destructor. - ~SEncodedRegion() { delete [] DecodedData; } - }; - - //! A view into a buffer generally representing a subset of the buffer. - struct BufferView : public Object - { - Ref buffer; //! The ID of the buffer. (required) - size_t byteOffset; //! The offset into the buffer in bytes. (required) - size_t byteLength; //! The length of the bufferView in bytes. (default: 0) + Type type; /// \var EncodedRegion_Current /// Pointer to currently active encoded region. @@ -555,19 +516,25 @@ namespace glTF /// exporter and importer. And, thanks to such way, there is no need to load whole file into memory. SEncodedRegion* EncodedRegion_Current; + private: + + shared_ptr mData; //!< Pointer to the data + bool mIsSpecial; //!< Set to true for special cases (e.g. the body buffer) + /// \var EncodedRegion_List /// List of encoded regions. std::list EncodedRegion_List; - BufferViewTarget target; //! The target that the WebGL buffer should be bound to. + /******************* Functions *******************/ - BufferView() - : EncodedRegion_Current(nullptr) - {} + public: - ~BufferView() { for(SEncodedRegion* reg : EncodedRegion_List) delete reg; } + Buffer(); + ~Buffer(); - void Read(Value& obj, Asset& r); + void Read(Value& obj, Asset& r); + + bool LoadFromStream(IOStream& stream, size_t length = 0, size_t baseOffset = 0); /// \fn void EncodedRegion_Mark(const size_t pOffset, const size_t pEncodedData_Length, uint8_t* pDecodedData, const size_t pDecodedData_Length, const std::string& pID) /// Mark region of "bufferView" as encoded. When data is request from such region then "bufferView" use decoded data. @@ -582,8 +549,42 @@ namespace glTF /// Select current encoded region by ID. \sa EncodedRegion_Current. /// \param [in] pID - ID of the region. void EncodedRegion_SetCurrent(const std::string& pID); + + /// \fn bool ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t* pReplace_Data, const size_t pReplace_Count) + /// Replace part of buffer data. Pay attention that function work with original array of data (\ref mData) not with encoded regions. + /// \param [in] pBufferData_Offset - index of first element in buffer from which new data will be placed. + /// \param [in] pBufferData_Count - count of bytes in buffer which will be replaced. + /// \param [in] pReplace_Data - pointer to array with new data for buffer. + /// \param [in] pReplace_Count - count of bytes in new data. + /// \return true - if successfully replaced, false if input arguments is out of range. + bool ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t* pReplace_Data, const size_t pReplace_Count); + + size_t AppendData(uint8_t* data, size_t length); + void Grow(size_t amount); + + uint8_t* GetPointer() + { return mData.get(); } + + void MarkAsSpecial() + { mIsSpecial = true; } + + bool IsSpecial() const + { return mIsSpecial; } + + static const char* TranslateId(Asset& r, const char* id); }; + //! A view into a buffer generally representing a subset of the buffer. + struct BufferView : public Object + { + Ref buffer; //! The ID of the buffer. (required) + size_t byteOffset; //! The offset into the buffer in bytes. (required) + size_t byteLength; //! The length of the bufferView in bytes. (default: 0) + + BufferViewTarget target; //! The target that the WebGL buffer should be bound to. + + void Read(Value& obj, Asset& r); + }; struct Camera : public Object { @@ -739,6 +740,7 @@ namespace glTF std::string Buffer;///< ID of "buffer" used for storing compressed data. size_t Offset;///< Offset in "bufferView" where compressed data are stored. size_t Count;///< Count of elements in compressed data. Is always equivalent to size in bytes: look comments for "Type" and "Component_Type". + bool Binary;///< If true then "binary" mode is used for coding, if false - "ascii" mode. size_t IndicesCount;///< Count of indices in mesh. size_t VerticesCount;///< Count of vertices in mesh. // AttribType::Value Type;///< Is always "SCALAR". @@ -766,11 +768,11 @@ namespace glTF /// \param [out] pAsset_Root - reference to root assed where data will be stored. void Read(Value& pJSON_Object, Asset& pAsset_Root); - /// \fn void Decode_O3DGC(Value& pJSON_Object_CompressedData, Asset& pAsset_Root) - /// Decode part of "bufferView" which encoded with Open3DGC algorythm. - /// \param [in] pJSON_Object_CompressedData - reference to JSON-object which is "compressedData" block. + /// \fn void Decode_O3DGC(const SCompression_Open3DGC& pCompression_Open3DGC, Asset& pAsset_Root) + /// Decode part of "buffer" which encoded with Open3DGC algorithm. + /// \param [in] pCompression_Open3DGC - reference to structure which describe encoded region. /// \param [out] pAsset_Root - reference to root assed where data will be stored. - void Decode_O3DGC(Value& pJSON_Object_CompressedData, Asset& pAsset_Root); + void Decode_O3DGC(const SCompression_Open3DGC& pCompression_Open3DGC, Asset& pAsset_Root); }; struct Node : public Object @@ -941,6 +943,7 @@ namespace glTF Ref Get(const char* id); Ref Get(unsigned int i); + Ref Get(const std::string& pID) { return Get(pID.c_str()); } Ref Create(const char* id); Ref Create(const std::string& id) diff --git a/code/glTFAsset.inl b/code/glTFAsset.inl index de9c165..3397171 100644 --- a/code/glTFAsset.inl +++ b/code/glTFAsset.inl @@ -247,9 +247,14 @@ Ref LazyDict::Create(const char* id) inline Buffer::Buffer() -: byteLength(0), type(Type_arraybuffer), mIsSpecial(false) + : byteLength(0), type(Type_arraybuffer), EncodedRegion_Current(nullptr), mIsSpecial(false) { } +inline Buffer::~Buffer() +{ + for(SEncodedRegion* reg : EncodedRegion_List) delete reg; +} + inline const char* Buffer::TranslateId(Asset& r, const char* id) { // Compatibility with old spec @@ -330,6 +335,57 @@ inline bool Buffer::LoadFromStream(IOStream& stream, size_t length, size_t baseO return true; } +inline void Buffer::EncodedRegion_Mark(const size_t pOffset, const size_t pEncodedData_Length, uint8_t* pDecodedData, const size_t pDecodedData_Length, const std::string& pID) +{ + // Check pointer to data + if(pDecodedData == nullptr) throw DeadlyImportError("GLTF: for marking encoded region pointer to decoded data must be provided."); + + // Check offset + if(pOffset > byteLength) + { + constexpr uint8_t val_size = 32; + + char val[val_size]; + + ai_snprintf(val, val_size, "%llu", (long long)pOffset); + throw DeadlyImportError(std::string("GLTF: incorrect offset value (") + val + ") for marking encoded region."); + } + + // Check length + if((pOffset + pEncodedData_Length) > byteLength) + { + constexpr uint8_t val_size = 64; + + char val[val_size]; + + ai_snprintf(val, val_size, "%llu, %llu", (long long)pOffset, (long long)pEncodedData_Length); + throw DeadlyImportError(std::string("GLTF: encoded region with offset/length (") + val + ") is out of range."); + } + + // Add new region + EncodedRegion_List.push_back(new SEncodedRegion(pOffset, pEncodedData_Length, pDecodedData, pDecodedData_Length, pID)); + // And set new value for "byteLength" + byteLength += (pDecodedData_Length - pEncodedData_Length); +} + +inline void Buffer::EncodedRegion_SetCurrent(const std::string& pID) +{ + if((EncodedRegion_Current != nullptr) && (EncodedRegion_Current->ID == pID)) return; + + for(SEncodedRegion* reg : EncodedRegion_List) + { + if(reg->ID == pID) + { + EncodedRegion_Current = reg; + + return; + } + + } + + throw DeadlyImportError("GLTF: EncodedRegion with ID: \"" + pID + "\" not found."); +} + inline bool Buffer::ReplaceData(const size_t pBufferData_Offset, const size_t pBufferData_Count, const uint8_t* pReplace_Data, const size_t pReplace_Count) { const size_t new_data_size = byteLength + pReplace_Count - pBufferData_Count; @@ -384,58 +440,9 @@ inline void BufferView::Read(Value& obj, Asset& r) byteLength = MemberOrDefault(obj, "byteLength", 0u); } -inline void BufferView::EncodedRegion_Mark(const size_t pOffset, const size_t pEncodedData_Length, uint8_t* pDecodedData, const size_t pDecodedData_Length, const std::string& pID) -{ -const size_t last = byteOffset + byteLength; - - // Check pointer to data - if(pDecodedData == nullptr) throw DeadlyImportError("GLTF: for marking encoded region pointer to decoded data must be provided."); - - // Check offset - if((pOffset < byteOffset) || (pOffset > last)) - { - constexpr uint8_t val_size = 32; - - char val[val_size]; - - ai_snprintf(val, val_size, "%llu", (long long)pOffset); - throw DeadlyImportError(std::string("GLTF: incorrect offset value (") + val + ") for marking encoded region."); - } - - // Check length - if((pOffset + pEncodedData_Length) > last) - { - constexpr uint8_t val_size = 64; - - char val[val_size]; - - ai_snprintf(val, val_size, "%llu, %llu", (long long)pOffset, (long long)pEncodedData_Length); - throw DeadlyImportError(std::string("GLTF: encoded region with offset/length (") + val + ") is out of range."); - } - - // Add new region - EncodedRegion_List.push_back(new SEncodedRegion(pOffset, pEncodedData_Length, pDecodedData, pDecodedData_Length, pID)); - // And set new value for "byteLength" - byteLength += (pDecodedData_Length - pEncodedData_Length); -} - -inline void BufferView::EncodedRegion_SetCurrent(const std::string& pID) -{ - if((EncodedRegion_Current != nullptr) && (EncodedRegion_Current->ID == pID)) return; - - for(SEncodedRegion* reg : EncodedRegion_List) - { - if(reg->ID == pID) - { - EncodedRegion_Current = reg; - - return; - } - - } - - throw DeadlyImportError("GLTF: EncodedRegion with ID: \"" + pID + "\" not found."); -} +// +// struct Accessor +// inline void Accessor::Read(Value& obj, Asset& r) { @@ -477,12 +484,12 @@ inline uint8_t* Accessor::GetPointer() size_t offset = byteOffset + bufferView->byteOffset; // Check if region is encoded. - if(bufferView->EncodedRegion_Current != nullptr) + if(bufferView->buffer->EncodedRegion_Current != nullptr) { - const size_t begin = bufferView->EncodedRegion_Current->Offset; - const size_t end = bufferView->EncodedRegion_Current->Offset + bufferView->EncodedRegion_Current->DecodedData_Length; + const size_t begin = bufferView->buffer->EncodedRegion_Current->Offset; + const size_t end = bufferView->buffer->EncodedRegion_Current->Offset + bufferView->buffer->EncodedRegion_Current->DecodedData_Length; - if((offset >= begin) && (offset < end)) return &bufferView->EncodedRegion_Current->DecodedData[offset - begin]; + if((offset >= begin) && (offset < end)) return &bufferView->buffer->EncodedRegion_Current->DecodedData[offset - begin]; } return basePtr + offset; @@ -770,33 +777,7 @@ namespace { inline void Mesh::Read(Value& pJSON_Object, Asset& pAsset_Root) { - // - // Mesh extensions - // - // At first check for extensions. They can affect on interpretaion of mesh data. - Value* extensions = FindObject(pJSON_Object, "extensions"); - - if(extensions != nullptr) - { - // At first check if data of mesh is compressed. Because buffer data must be decoded before another get data from it. - // Only Open3DGC supported at now. - Value* o3dgc = FindObject(*extensions, "Open3DGC-compression"); - - if(o3dgc != nullptr) - { - // Search compressed data - Value* comp_data = FindObject(*o3dgc, "compressedData"); - - if(comp_data == nullptr) throw DeadlyImportError("GLTF: \"Open3DGC-compression\" must has \"compressedData\"."); - - Assimp::DefaultLogger::get()->info("GLTF: Decompressing Open3DGC data."); - Decode_O3DGC(*comp_data, pAsset_Root); - }// if(o3dgc == nullptr) - }// if(extensions != nullptr) - - // - // Mesh primitives. - // + /****************** Mesh primitives ******************/ if (Value* primitives = FindArray(pJSON_Object, "primitives")) { this->primitives.resize(primitives->Size()); for (unsigned int i = 0; i < primitives->Size(); ++i) { @@ -831,158 +812,229 @@ inline void Mesh::Read(Value& pJSON_Object, Asset& pAsset_Root) } } } -} -inline void Mesh::Decode_O3DGC(Value& pJSON_Object_CompressedData, Asset& pAsset_Root) -{ -// Compressed data contain description of part of "bufferView" which is encoded. This part must be decoded and -// new data must replace old encoded part. In fact \"compressedData\" is similar to "accessor" structure. -const char* bufview_id; -uint32_t byte_offset, count, count_indices, count_vertices; -const char* mode_str; -const char* type_str; -ComponentType component_type; -std::list float_attributes_indices;// See above about "floatAttributesIndexes". - - /**********************************************************/ - /************** Read data from JSON-document **************/ - /**********************************************************/ - - #define MESH_READ_COMPRESSEDDATA_MEMBER(pFieldName, pOut) \ - if(!ReadMember(pJSON_Object_CompressedData, pFieldName, pOut)) { throw DeadlyImportError(std::string("GLTF: \"compressedData\" must has \"") + pFieldName + "\"."); } - - MESH_READ_COMPRESSEDDATA_MEMBER("bufferView", bufview_id); - MESH_READ_COMPRESSEDDATA_MEMBER("byteOffset", byte_offset); - MESH_READ_COMPRESSEDDATA_MEMBER("componentType", component_type); - MESH_READ_COMPRESSEDDATA_MEMBER("count", count); - MESH_READ_COMPRESSEDDATA_MEMBER("indicesCount", count_indices); - MESH_READ_COMPRESSEDDATA_MEMBER("verticesCount", count_vertices); - MESH_READ_COMPRESSEDDATA_MEMBER("mode", mode_str); - MESH_READ_COMPRESSEDDATA_MEMBER("type", type_str); - - #undef MESH_READ_COMPRESSEDDATA_MEMBER + /****************** Mesh extensions ******************/ + Value* json_extensions = FindObject(pJSON_Object, "extensions"); - // - // Check for float attributes - // - // Object example: - // "floatAttributesIndexes": { - // "accessor_1356": 0, - // "accessor_1358": 1, - // "accessor_1360": 2 - // }, - // - // Find object "floatAttributesIndexes" - Value* float_attr_ind = FindObject(pJSON_Object_CompressedData, "floatAttributesIndexes"); + if(json_extensions == nullptr) goto mr_skip_extensions; - if(float_attr_ind != nullptr) + for(Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); it_memb++) { - size_t attr_num = 0; - - // Walk thru children and get accessors and numbers of attributes. - for(Value::MemberIterator memb_it = float_attr_ind->MemberBegin(); memb_it != float_attr_ind->MemberEnd(); ++memb_it) + if(it_memb->name.GetString() == std::string("Open3DGC-compression")) { - if(!memb_it->name.IsString()) throw DeadlyImportError("GLTF: name of the member of \"floatAttributesIndexes\" must be a string."); - if(!memb_it->value.IsUint()) throw DeadlyImportError(std::string("GLTF: value of the member (\"") + memb_it->name.GetString() + \ - "\") in \"floatAttributesIndexes\" must be an unsigned integer."); - /*if(attr_num != memb_it->value.GetUint()) throw DeadlyImportError(std::string("GLTF: invalid number of float attribute index. Member (\"") + \ - memb_it->name.GetString() + "\".");*/ + // Search for compressed data. + // Compressed data contain description of part of "buffer" which is encoded. This part must be decoded and + // new data will replace old encoded part by request. In fact \"compressedData\" is kind of "accessor" structure. + Value* comp_data = FindObject(it_memb->value, "compressedData"); - attr_num++; - // Checks passed, extract data. - Ref attr_cur_acc = pAsset_Root.accessors.Get(memb_it->name.GetString()); + if(comp_data == nullptr) throw DeadlyImportError("GLTF: \"Open3DGC-compression\" must has \"compressedData\"."); - float_attributes_indices.push_back((float*)attr_cur_acc->GetPointer()); + Assimp::DefaultLogger::get()->info("GLTF: Decompressing Open3DGC data."); + + /************** Read data from JSON-document **************/ + #define MESH_READ_COMPRESSEDDATA_MEMBER(pFieldName, pOut) \ + if(!ReadMember(*comp_data, pFieldName, pOut)) \ + { \ + throw DeadlyImportError(std::string("GLTF: \"compressedData\" must has \"") + pFieldName + "\"."); \ + } + + const char* mode_str; + const char* type_str; + ComponentType component_type; + SCompression_Open3DGC* ext_o3dgc = new SCompression_Open3DGC; + + MESH_READ_COMPRESSEDDATA_MEMBER("buffer", ext_o3dgc->Buffer); + MESH_READ_COMPRESSEDDATA_MEMBER("byteOffset", ext_o3dgc->Offset); + MESH_READ_COMPRESSEDDATA_MEMBER("componentType", component_type); + MESH_READ_COMPRESSEDDATA_MEMBER("type", type_str); + MESH_READ_COMPRESSEDDATA_MEMBER("count", ext_o3dgc->Count); + MESH_READ_COMPRESSEDDATA_MEMBER("mode", mode_str); + MESH_READ_COMPRESSEDDATA_MEMBER("indicesCount", ext_o3dgc->IndicesCount); + MESH_READ_COMPRESSEDDATA_MEMBER("verticesCount", ext_o3dgc->VerticesCount); + + #undef MESH_READ_COMPRESSEDDATA_MEMBER + + // Check some values + if(strcmp(type_str, "SCALAR")) throw DeadlyImportError("GLTF: only \"SCALAR\" type is supported for compressed data."); + if(component_type != ComponentType_UNSIGNED_BYTE) throw DeadlyImportError("GLTF: only \"UNSIGNED_BYTE\" component type is supported for compressed data."); + + // Set read/write data mode. + if(strcmp(mode_str, "binary") == 0) + ext_o3dgc->Binary = true; + else if(strcmp(mode_str, "ascii") == 0) + ext_o3dgc->Binary = false; + else + throw DeadlyImportError(std::string("GLTF: for compressed data supported modes is: \"ascii\", \"binary\". Not the: \"") + mode_str + "\"."); + + /************************ Decoding ************************/ + Decode_O3DGC(*ext_o3dgc, pAsset_Root); + Extension.push_back(ext_o3dgc);// store info in mesh extensions list. + }// if(it_memb->name.GetString() == "Open3DGC-compression") + else + { + throw DeadlyImportError(std::string("GLTF: Unknown mesh extension: \"") + it_memb->name.GetString() + "\"."); } - }// if(float_attr_ind != nullptr) + }// for(Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); json_extensions++) - // Check some values - if(strcmp(type_str, "SCALAR")) throw DeadlyImportError("GLTF: only \"SCALAR\" type is supported for compressed data."); - if(component_type != ComponentType_UNSIGNED_BYTE) throw DeadlyImportError("GLTF: only \"UNSIGNED_BYTE\" component type is supported for compressed data."); - if((strcmp(mode_str, "binary") != 0) && (strcmp(mode_str, "ascii") != 0)) - { - throw DeadlyImportError(std::string("GLTF: for compressed data supported modes is: \"ascii\", \"binary\". Not the: \"") + mode_str + "\"."); - } +mr_skip_extensions: - /**********************************************************/ - /********************* Decoding data **********************/ - /**********************************************************/ + return;// After label some operators must be present. +} - // Search for "bufferView" by ID. - Ref bufview = pAsset_Root.bufferViews.Get(bufview_id); +inline void Mesh::Decode_O3DGC(const SCompression_Open3DGC& pCompression_Open3DGC, Asset& pAsset_Root) +{ +using IndicesType = unsigned short;///< \sa glTFExporter::ExportMeshes. - // - // Decode data. Adapted piece of code from COLLADA2GLTF converter. - // - // void testDecode(shared_ptr mesh, BinaryStream &bstream) - o3dgc::SC3DMCDecoder decoder; - o3dgc::IndexedFaceSet ifs; - uint8_t* output_data; - size_t size_vertex, size_normal, size_indices, output_data_size; - o3dgc::BinaryStream bstream; - float* tarrays[6]; +o3dgc::SC3DMCDecoder decoder; +o3dgc::IndexedFaceSet ifs; +o3dgc::BinaryStream bstream; +uint8_t* decoded_data; +size_t decoded_data_size = 0; +Ref buf = pAsset_Root.buffers.Get(pCompression_Open3DGC.Buffer); // Read data from buffer and place it in BinaryStream for decoder. - bstream.LoadFromBuffer(&bufview->buffer->GetPointer()[bufview->byteOffset + byte_offset], count); + // Just "Count" because always is used type equivalent to uint8_t. + bstream.LoadFromBuffer(&buf->GetPointer()[pCompression_Open3DGC.Offset], pCompression_Open3DGC.Count); - // After decoding header we can get size of primitives + // After decoding header we can get size of primitives. if(decoder.DecodeHeader(ifs, bstream) != o3dgc::O3DGC_OK) throw DeadlyImportError("GLTF: can not decode Open3DGC header."); - size_indices = ifs.GetNCoordIndex() * 3 * sizeof(unsigned short); - size_vertex = ifs.GetNCoord() * 3 * sizeof(float); - size_normal = ifs.GetNNormal() * 3 * sizeof(float); + /****************** Get sizes of arrays and check sizes ******************/ + // Note. See "Limitations for meshes when using Open3DGC-compression". - for(size_t idx_attr = 0, idx_attr_end = ifs.GetNumFloatAttributes(); idx_attr < idx_attr_end; idx_attr++) - { - size_t qty_attr = ifs.GetNFloatAttribute(idx_attr); + // Indices + size_t size_coordindex = ifs.GetNCoordIndex() * 3;// See float attributes note. + + if(primitives[0].indices->count != size_coordindex) + throw DeadlyImportError("GLTF: Open3DGC. Compressed indices count (" + std::to_string(size_coordindex) + + ") not equal to uncompressed (" + std::to_string(primitives[0].indices->count) + ")."); + + size_coordindex *= sizeof(IndicesType); + // Coordinates + size_t size_coord = ifs.GetNCoord();// See float attributes note. + + if(primitives[0].attributes.position[0]->count != size_coord) + throw DeadlyImportError("GLTF: Open3DGC. Compressed positions count (" + std::to_string(size_coord) + + ") not equal to uncompressed (" + std::to_string(primitives[0].attributes.position[0]->count) + ")."); + + size_coord *= 3 * sizeof(float); + // Normals + size_t size_normal = ifs.GetNNormal();// See float attributes note. - if(qty_attr == 0) continue; + if(primitives[0].attributes.normal[0]->count != size_normal) + throw DeadlyImportError("GLTF: Open3DGC. Compressed normals count (" + std::to_string(size_normal) + + ") not equal to uncompressed (" + std::to_string(primitives[0].attributes.normal[0]->count) + ")."); - tarrays[idx_attr] = new float[qty_attr * ifs.GetFloatAttributeDim(idx_attr)]; - ifs.SetFloatAttribute(idx_attr, tarrays[idx_attr]); - /* - switch(ifs.GetFloatAttributeType(idx_attr)) + size_normal *= 3 * sizeof(float); + // Additional attributes. + std::vector size_floatattr; + std::vector size_intattr; + + size_floatattr.resize(ifs.GetNumFloatAttributes()); + size_intattr.resize(ifs.GetNumIntAttributes()); + + decoded_data_size = size_coordindex + size_coord + size_normal; + for(size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++) + { + // size = number_of_elements * components_per_element * size_of_component. + // Note. But as you can see above, at first we are use this variable in meaning "count". After checking count of objects... + size_t tval = ifs.GetNFloatAttribute(idx); + + switch(ifs.GetFloatAttributeType(idx)) { - // Unknown for Open3DGC, - // but not for COLLADA2GLTF - GLTF::Semantic::JOINT. What's mean "JOINT"? Good question, but lim(comments in COLLADA2GLTF) = 0. - case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_UNKOWN:// 0 - break; - case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_POSITION:// 1 - break; - case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_NORMAL:// 2 - break; - case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_COLOR:// 3 - break; - case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD:// 4 - break; - case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_WEIGHT:// 5 + case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD: + // Check situation when encoded data contain texture coordinates but primitive not. + if(idx_texcoord < primitives[0].attributes.texcoord.size()) + { + if(primitives[0].attributes.texcoord[idx]->count != tval) + throw DeadlyImportError("GLTF: Open3DGC. Compressed texture coordinates count (" + std::to_string(tval) + + ") not equal to uncompressed (" + std::to_string(primitives[0].attributes.texcoord[idx]->count) + ")."); + + idx_texcoord++; + } + else + { + ifs.SetNFloatAttribute(idx, 0);// Disable decoding this attribute. + } + break; default: - throw DeadlyImportError("GLTF: Unknown type of float attribute (" + std::to_string(idx_attr) + ") for Open3DGC encoding."); - }*/ + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: " + std::to_string(ifs.GetFloatAttributeType(idx))); + } + + tval *= ifs.GetFloatAttributeDim(idx) * sizeof(o3dgc::Real);// After checking count of objects we can get size of array. + size_floatattr[idx] = tval; + decoded_data_size += tval; + } + + for(size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++) + { + // size = number_of_elements * components_per_element * size_of_component. See float attributes note. + size_t tval = ifs.GetNIntAttribute(idx); + + switch(ifs.GetIntAttributeType(idx)) + { + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: " + std::to_string(ifs.GetIntAttributeType(idx))); + } + + tval *= ifs.GetIntAttributeDim(idx) * sizeof(long);// See float attributes note. + size_intattr[idx] = tval; + decoded_data_size += tval; } - //size_texcoord = ifs.GetNFloatAttribute(0) * 2 * sizeof(float); + // Create array for decoded data. + decoded_data = new uint8_t[decoded_data_size]; - output_data_size = size_vertex + size_normal + /*size_texcoord*/ + size_indices; - output_data = new uint8_t[output_data_size]; + /****************** Set right array regions for decoder ******************/ - float* uncompressed_vertices = (float* const)(output_data + size_indices);// size_indices => vertex offset + auto get_buf_offset = [](Ref& pAccessor) -> size_t { return pAccessor->byteOffset + pAccessor->bufferView->byteOffset; }; - ifs.SetCoordIndex((uint16_t* const)output_data); - ifs.SetCoord((float* const)uncompressed_vertices); + // Indices + ifs.SetCoordIndex((IndicesType* const)(decoded_data + get_buf_offset(primitives[0].indices))); + // Coordinates + ifs.SetCoord((o3dgc::Real* const)(decoded_data + get_buf_offset(primitives[0].attributes.position[0]))); + // Normals + if(size_normal) + { + ifs.SetNormal((o3dgc::Real* const)(decoded_data + get_buf_offset(primitives[0].attributes.normal[0]))); + } - if(ifs.GetNNormal() > 0) ifs.SetNormal((float* const)(output_data + size_indices + size_vertex)); + for(size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++) + { + switch(ifs.GetFloatAttributeType(idx)) + { + case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD: + if(idx_texcoord < primitives[0].attributes.texcoord.size()) + { + // See above about absent attributes. + ifs.SetFloatAttribute(idx, (o3dgc::Real* const)(decoded_data + get_buf_offset(primitives[0].attributes.texcoord[idx]))); + idx_texcoord++; + } - //if(ifs.GetNFloatAttribute(0)) ifs.SetFloatAttribute(0, (float* const)(output_data + size_indices + size_vertex + size_normal)); + break; + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: " + std::to_string(ifs.GetFloatAttributeType(idx))); + } + } + + for(size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++) + { + switch(ifs.GetIntAttributeType(idx)) + { + // ifs.SetIntAttribute(idx, (long* const)(decoded_data + get_buf_offset(primitives[0].attributes.joint))); + default: + throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: " + std::to_string(ifs.GetIntAttributeType(idx))); + } + } + // // Decode data - if(decoder.DecodePlayload(ifs, bstream) != o3dgc::O3DGC_OK) throw DeadlyImportError("GLTF: can not decode Open3DGC data."); + // + if(decoder.DecodePayload(ifs, bstream) != o3dgc::O3DGC_OK) throw DeadlyImportError("GLTF: can not decode Open3DGC data."); - // Set encoded region for bufferView. - bufview->EncodedRegion_Mark(byte_offset, count, output_data, output_data_size, name); - // Ans set is current - bufview->EncodedRegion_SetCurrent(name); + // Set encoded region for "buffer". + buf->EncodedRegion_Mark(pCompression_Open3DGC.Offset, pCompression_Open3DGC.Count, decoded_data, decoded_data_size, id); // No. Do not delete "output_data". After calling "EncodedRegion_Mark" bufferView is owner of "output_data". // "delete [] output_data;" } diff --git a/code/glTFAssetWriter.inl b/code/glTFAssetWriter.inl index f30e8cf..d76bb5d 100644 --- a/code/glTFAssetWriter.inl +++ b/code/glTFAssetWriter.inl @@ -224,6 +224,11 @@ namespace glTF { json_comp_data.AddMember("componentType", 5121, w.mAl); json_comp_data.AddMember("type", "SCALAR", w.mAl); json_comp_data.AddMember("count", ptr_ext_comp->Count, w.mAl); + if(ptr_ext_comp->Binary) + json_comp_data.AddMember("mode", "binary", w.mAl); + else + json_comp_data.AddMember("mode", "ascii", w.mAl); + json_comp_data.AddMember("indicesCount", ptr_ext_comp->IndicesCount, w.mAl); json_comp_data.AddMember("verticesCount", ptr_ext_comp->VerticesCount, w.mAl); // filling object "Open3DGC-compression" @@ -237,7 +242,7 @@ namespace glTF { break; default: - throw DeadlyImportError("GLTF: Unknown mesh extension, Only Open3DGC is supported."); + throw DeadlyImportError("GLTF: Can not write mesh: unknown mesh extension, only Open3DGC is supported."); }// switch(ptr_ext->Type) }// for(Mesh::SExtension* ptr_ext : m.Extension) diff --git a/code/glTFExporter.cpp b/code/glTFExporter.cpp index 1dc35b5..48ab099 100644 --- a/code/glTFExporter.cpp +++ b/code/glTFExporter.cpp @@ -261,7 +261,7 @@ void glTFExporter::ExportMeshes() // using IndicesType = decltype(aiFace::mNumIndices); // But yes for // using IndicesType = unsigned short; -// because "ComponentType_UNSIGNED_SHORT" used for indices. And its maximal type according to glTF specification. +// because "ComponentType_UNSIGNED_SHORT" used for indices. And it's a maximal type according to glTF specification. using IndicesType = unsigned short; // Variables needed for compression. BEGIN. @@ -273,18 +273,26 @@ size_t idx_srcdata_ind;// Index of begin of coordinates indices array in buffer. bool comp_allow;// Point that data of current mesh can be compressed. // Variables needed for compression. END. - for (unsigned int i = 0; i < mScene->mNumMeshes; ++i) { - const aiMesh* aim = mScene->mMeshes[i]; + for (unsigned int idx_mesh = 0; idx_mesh < mScene->mNumMeshes; ++idx_mesh) { + const aiMesh* aim = mScene->mMeshes[idx_mesh]; // Check if compressing requested and mesh can be encoded. - if((aim->mPrimitiveTypes == aiPrimitiveType_TRIANGLE) && (aim->mNumVertices > 0))///TODO: export properties if(compression is needed) + comp_allow = mProperties->GetPropertyBool("extensions.Open3DGC.use", false); + if(comp_allow && (aim->mPrimitiveTypes == aiPrimitiveType_TRIANGLE) && (aim->mNumVertices > 0) && (aim->mNumFaces > 0)) { - comp_allow = true; + idx_srcdata_tc.clear(); idx_srcdata_tc.reserve(AI_MAX_NUMBER_OF_TEXTURECOORDS); } else { - comp_allow = false; + std::string msg; + + if(aim->mPrimitiveTypes != aiPrimitiveType_TRIANGLE) + msg = "all primitives of the mesh must be a triangles."; + else + msg = "mesh must has vertices and faces."; + + DefaultLogger::get()->warn("GLTF: can not use Open3DGC-compression: " + msg); } std::string meshId = mAsset->FindUniqueID(aim->mName.C_Str(), "mesh"); @@ -367,28 +375,30 @@ bool comp_allow;// Point that data of current mesh can be compressed. // // Fill data for encoder. // - unsigned qcoord = 12;///TODO: dbg - unsigned qnormal = 10;///TODO: dbg - unsigned qtexCoord = 10;///TODO: dbg + // Quantization + unsigned quant_coord = mProperties->GetPropertyInteger("extensions.Open3DGC.quantization.POSITION", 12); + unsigned quant_normal = mProperties->GetPropertyInteger("extensions.Open3DGC.quantization.NORMAL", 10); + unsigned quant_texcoord = mProperties->GetPropertyInteger("extensions.Open3DGC.quantization.TEXCOORD", 10); - o3dgc::O3DGCSC3DMCPredictionMode positionPrediction = o3dgc::O3DGC_SC3DMC_PARALLELOGRAM_PREDICTION;///TODO: dbg - o3dgc::O3DGCSC3DMCPredictionMode normalPrediction = o3dgc::O3DGC_SC3DMC_SURF_NORMALS_PREDICTION;///TODO: dbg - o3dgc::O3DGCSC3DMCPredictionMode texcoordPrediction = o3dgc::O3DGC_SC3DMC_PARALLELOGRAM_PREDICTION;///TODO: dbg + // Prediction + o3dgc::O3DGCSC3DMCPredictionMode prediction_position = o3dgc::O3DGC_SC3DMC_PARALLELOGRAM_PREDICTION; + o3dgc::O3DGCSC3DMCPredictionMode prediction_normal = o3dgc::O3DGC_SC3DMC_SURF_NORMALS_PREDICTION; + o3dgc::O3DGCSC3DMCPredictionMode prediction_texcoord = o3dgc::O3DGC_SC3DMC_PARALLELOGRAM_PREDICTION; // IndexedFacesSet: "Crease angle", "solid", "convex" are set to default. comp_o3dgc_ifs.SetCCW(true); comp_o3dgc_ifs.SetIsTriangularMesh(true); comp_o3dgc_ifs.SetNumFloatAttributes(0); // Coordinates - comp_o3dgc_params.SetCoordQuantBits(qcoord);///TODO: IME - comp_o3dgc_params.SetCoordPredMode(positionPrediction);///TODO: IME + comp_o3dgc_params.SetCoordQuantBits(quant_coord); + comp_o3dgc_params.SetCoordPredMode(prediction_position); comp_o3dgc_ifs.SetNCoord(aim->mNumVertices); comp_o3dgc_ifs.SetCoord((o3dgc::Real* const)&b->GetPointer()[idx_srcdata_begin]); // Normals if(idx_srcdata_normal != SIZE_MAX) { - comp_o3dgc_params.SetNormalQuantBits(qnormal);///TODO: IME - comp_o3dgc_params.SetNormalPredMode(normalPrediction);///TODO: IME + comp_o3dgc_params.SetNormalQuantBits(quant_normal); + comp_o3dgc_params.SetNormalPredMode(prediction_normal); comp_o3dgc_ifs.SetNNormal(aim->mNumVertices); comp_o3dgc_ifs.SetNormal((o3dgc::Real* const)&b->GetPointer()[idx_srcdata_normal]); } @@ -398,10 +408,10 @@ bool comp_allow;// Point that data of current mesh can be compressed. { size_t num = comp_o3dgc_ifs.GetNumFloatAttributes(); - comp_o3dgc_params.SetFloatAttributeQuantBits(num, qtexCoord);///TODO: IME - comp_o3dgc_params.SetFloatAttributePredMode(num, texcoordPrediction);///TODO: IME + comp_o3dgc_params.SetFloatAttributeQuantBits(num, quant_texcoord); + comp_o3dgc_params.SetFloatAttributePredMode(num, prediction_texcoord); comp_o3dgc_ifs.SetNFloatAttribute(num, aim->mNumVertices);// number of elements. - comp_o3dgc_ifs.SetFloatAttributeDim(num, aim->mNumUVComponents[i]);// components per element: aiVector3D => x * float + comp_o3dgc_ifs.SetFloatAttributeDim(num, aim->mNumUVComponents[num_tc]);// components per element: aiVector3D => x * float comp_o3dgc_ifs.SetFloatAttributeType(num, o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD); comp_o3dgc_ifs.SetFloatAttribute(num, (o3dgc::Real* const)&b->GetPointer()[idx_srcdata_tc[num_tc]]); comp_o3dgc_ifs.SetNumFloatAttributes(num + 1); @@ -412,7 +422,12 @@ bool comp_allow;// Point that data of current mesh can be compressed. comp_o3dgc_ifs.SetCoordIndex((IndicesType* const)&b->GetPointer()[idx_srcdata_ind]); // Prepare to enconding comp_o3dgc_params.SetNumFloatAttributes(comp_o3dgc_ifs.GetNumFloatAttributes()); - comp_o3dgc_params.SetStreamType(o3dgc::O3DGC_STREAM_TYPE_BINARY);///TODO: exporter params + if(mProperties->GetPropertyBool("extensions.Open3DGC.binary", true)) + comp_o3dgc_params.SetStreamType(o3dgc::O3DGC_STREAM_TYPE_BINARY); + else + comp_o3dgc_params.SetStreamType(o3dgc::O3DGC_STREAM_TYPE_ASCII); + + comp_o3dgc_ifs.ComputeMinMax(o3dgc::O3DGC_SC3DMC_MAX_ALL_DIMS); // // Encoding // @@ -429,14 +444,13 @@ bool comp_allow;// Point that data of current mesh can be compressed. ext->Buffer = b->id; ext->Offset = idx_srcdata_begin; ext->Count = b->byteLength - idx_srcdata_begin; - ext->IndicesCount = comp_o3dgc_ifs.GetNCoordIndex(); + ext->Binary = mProperties->GetPropertyBool("extensions.Open3DGC.binary"); + ext->IndicesCount = comp_o3dgc_ifs.GetNCoordIndex() * 3; ext->VerticesCount = comp_o3dgc_ifs.GetNCoord(); // And assign to mesh. m->Extension.push_back(ext); }// if(comp_allow) }// for (unsigned int i = 0; i < mScene->mNumMeshes; ++i) { - - ///TODO: export properties if(compression is used) } unsigned int glTFExporter::ExportNode(const aiNode* n) diff --git a/code/glTFImporter.cpp b/code/glTFImporter.cpp index 5fdad12..5cdd856 100644 --- a/code/glTFImporter.cpp +++ b/code/glTFImporter.cpp @@ -259,7 +259,36 @@ void glTFImporter::ImportMeshes(glTF::Asset& r) for (unsigned int m = 0; m < r.meshes.Size(); ++m) { Mesh& mesh = r.meshes[m]; - meshOffsets.push_back(k); + // Check if mesh extensions is used + if(mesh.Extension.size() > 0) + { + for(Mesh::SExtension* cur_ext : mesh.Extension) + { + if(cur_ext->Type == Mesh::SExtension::EType::Compression_Open3DGC) + { + // Limitations for meshes when using Open3DGC-compression. + // It's a current limitation of sp... Specification have not this part still - about mesh compression. Why only one primitive? + // Because glTF is very flexibly. But in fact it ugly flexible. Every primitive can has own set of accessors and accessors can + // point to a-a-a-a-any part of buffer (thru bufferview ofcourse) and even to another buffer. We know that "Open3DGC-compression" + // is applicable only to part of buffer. As we can't guaranty continuity of the data for decoder, we will limit quantity of primitives. + // Yes indices, coordinates etc. still can br stored in different buffers, but with current specification it's a exporter problem. + // Also primitive can has only one of "POSITION", "NORMAL" and less then "AI_MAX_NUMBER_OF_TEXTURECOORDS" of "TEXCOORD". All accessor + // of primitive must point to one continuous region of the buffer. + if(mesh.primitives.size() > 2) throw DeadlyImportError("GLTF: When using Open3DGC compression then only one primitive per mesh are allowed."); + + Mesh::SCompression_Open3DGC* o3dgc_ext = (Mesh::SCompression_Open3DGC*)cur_ext; + Ref buf = r.buffers.Get(o3dgc_ext->Buffer); + + buf->EncodedRegion_SetCurrent(mesh.id); + } + else + { + throw DeadlyImportError("GLTF: Can not import mesh: unknown mesh extension, only Open3DGC is supported."); + } + } + }// if(mesh.Extension.size() > 0) + + meshOffsets.push_back(k); k += unsigned(mesh.primitives.size()); for (unsigned int p = 0; p < mesh.primitives.size(); ++p) { @@ -295,25 +324,13 @@ void glTFImporter::ImportMeshes(glTF::Asset& r) Mesh::Primitive::Attributes& attr = prim.attributes; - // if "bufferView" of current accessor is containing encoded data then set ID of region. - if(attr.position[0]->bufferView->EncodedRegion_List.size() > 0) attr.position[0]->bufferView->EncodedRegion_SetCurrent(mesh.name); - if (attr.position.size() > 0 && attr.position[0]) { aim->mNumVertices = attr.position[0]->count; attr.position[0]->ExtractData(aim->mVertices); } - // if "bufferView" of current accessor is containing encoded data then set ID of region. - if(attr.normal[0]->bufferView->EncodedRegion_List.size() > 0) attr.normal[0]->bufferView->EncodedRegion_SetCurrent(mesh.name); - if (attr.normal.size() > 0 && attr.normal[0]) attr.normal[0]->ExtractData(aim->mNormals); - // if "bufferView" of current accessor is containing encoded data then set ID of region. - if((attr.texcoord.size() > 0) && (attr.texcoord[0]->bufferView->EncodedRegion_List.size() > 0)) - { - attr.texcoord[0]->bufferView->EncodedRegion_SetCurrent(mesh.name); - } - for (size_t tc = 0; tc < attr.texcoord.size() && tc <= AI_MAX_NUMBER_OF_TEXTURECOORDS; ++tc) { attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc]); aim->mNumUVComponents[tc] = attr.texcoord[tc]->GetNumComponents(); @@ -326,9 +343,6 @@ void glTFImporter::ImportMeshes(glTF::Asset& r) if (prim.indices) { - // if "bufferView" of current accessor is containing encoded data then set ID of region. - if(prim.indices->bufferView->EncodedRegion_List.size() > 0) prim.indices->bufferView->EncodedRegion_SetCurrent(mesh.name); - aiFace* faces = 0; unsigned int nFaces = 0; -- 2.7.4