From 29e982e1851e72c1feead2d9e591434c29cec889 Mon Sep 17 00:00:00 2001 From: Alexandr Arutjunov Date: Wed, 3 Aug 2016 18:06:38 +0300 Subject: [PATCH] [F] Fixed problem with more then one mesh in scene. More detaily read at line 529 in glTFAsset.inl. --- code/glTFAsset.h | 79 ++++++++++++++++++++++++++---- code/glTFAsset.inl | 111 +++++++++++++++++++++++++++--------------- code/glTFImporter.cpp | 30 +++++++++--- 3 files changed, 165 insertions(+), 55 deletions(-) diff --git a/code/glTFAsset.h b/code/glTFAsset.h index c5c7d1349..860ba8df0 100644 --- a/code/glTFAsset.h +++ b/code/glTFAsset.h @@ -50,6 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include #include #include @@ -477,15 +478,6 @@ namespace glTF bool LoadFromStream(IOStream& stream, size_t length = 0, size_t baseOffset = 0); - /// \fn void ReplaceData(uint8_t* pBufferData_Offset, size_t pBufferData_Count, uint8_t pPrepend_Data, size_t pPrepend_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); - size_t AppendData(uint8_t* data, size_t length); void Grow(size_t amount); @@ -501,6 +493,31 @@ namespace glTF static const char* TranslateId(Asset& r, const char* id); }; + /// \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 @@ -509,10 +526,52 @@ namespace glTF size_t byteOffset; //! The offset into the buffer in bytes. (required) size_t byteLength; //! The length of the bufferView in bytes. (default: 0) + /// \var EncodedRegion_Current + /// Pointer to currently active encoded region. + /// Why not decoding all regions at once and not to set one buffer with decoded data? + /// Yes, why not? Even "accessor" point to decoded data. I mean that fields "byteOffset", "byteStride" and "count" has values which describes decoded + /// data array. But only in range of mesh while is active parameters from "compressedData". For another mesh accessors point to decoded data too. But + /// offset is counted for another regions is encoded. + /// Example. You have two meshes. For every of it you have 4 bytes of data. That data compressed to 2 bytes. So, you have buffer with encoded data: + /// M1_E0, M1_E1, M2_E0, M2_E1. + /// After decoding you'll get: + /// M1_D0, M1_D1, M1_D2, M1_D3, M2_D0, M2_D1, M2_D2, M2_D3. + /// "accessors" must to use values that point to decoded data - obviously. So, you'll expect "accessors" like + /// "accessor_0" : { byteOffset: 0, byteLength: 4}, "accessor_1" : { byteOffset: 4, byteLength: 4} + /// but in real life you'll get: + /// "accessor_0" : { byteOffset: 0, byteLength: 4}, "accessor_1" : { byteOffset: 2, byteLength: 4} + /// Yes, accessor of next mesh has offset and length which mean: current mesh data is decoded, all other data is encoded. + /// And when before you start to read data of current mesh (with encoded data ofcourse) you must decode region of "bufferView", after read finished + /// delete encoding mark. And after that you can repeat process: decode data of mesh, read, delete decoded data. + SEncodedRegion* EncodedRegion_Current; + + /// \var EncodedRegion_List + /// List of encoded regions. + std::list EncodedRegion_List; + BufferViewTarget target; //! The target that the WebGL buffer should be bound to. - BufferView() {} + BufferView() + : EncodedRegion_Current(nullptr) + {} + + ~BufferView() { for(SEncodedRegion* reg : EncodedRegion_List) delete reg; } + void Read(Value& obj, Asset& r); + + /// \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. + /// \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. + void EncodedRegion_Mark(const size_t pOffset, const size_t pEncodedData_Length, uint8_t* pDecodedData, const size_t pDecodedData_Length, const std::string& pID); + + /// \fn void EncodedRegion_SetCurrent(const std::string& pID) + /// Select current encoded region by ID. \sa EncodedRegion_Current. + /// \param [in] pID - ID of the region. + void EncodedRegion_SetCurrent(const std::string& pID); }; diff --git a/code/glTFAsset.inl b/code/glTFAsset.inl index 3e644c5c3..b9e5c6e13 100644 --- a/code/glTFAsset.inl +++ b/code/glTFAsset.inl @@ -330,26 +330,6 @@ inline bool Buffer::LoadFromStream(IOStream& stream, size_t length, size_t baseO return true; } -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; - -uint8_t* new_data; - - if((pBufferData_Count == 0) || (pReplace_Count == 0) || (pReplace_Data == nullptr)) return false; - - new_data = new uint8_t[new_data_size]; - // Copy data which place before replacing part. - memcpy(new_data, mData.get(), pBufferData_Offset); - // Copy new data. - memcpy(&new_data[pBufferData_Offset], pReplace_Data, pReplace_Count); - // Copy data which place after replacing part. - memcpy(&new_data[pBufferData_Offset + pReplace_Count], &mData.get()[pBufferData_Offset + pBufferData_Count], pBufferData_Offset); - // Apply new data - mData.reset(new_data); - byteLength = new_data_size; -} - inline size_t Buffer::AppendData(uint8_t* data, size_t length) { size_t offset = this->byteLength; @@ -367,6 +347,9 @@ inline void Buffer::Grow(size_t amount) byteLength += amount; } +// +// struct BufferView +// inline void BufferView::Read(Value& obj, Asset& r) { @@ -379,7 +362,58 @@ 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."); +} inline void Accessor::Read(Value& obj, Asset& r) { @@ -419,7 +453,17 @@ inline uint8_t* Accessor::GetPointer() if (!basePtr) return 0; size_t offset = byteOffset + bufferView->byteOffset; - return basePtr + offset; + + // Check if region is encoded. + if(bufferView->EncodedRegion_Current != nullptr) + { + const size_t begin = bufferView->EncodedRegion_Current->Offset; + const size_t end = bufferView->EncodedRegion_Current->Offset + bufferView->EncodedRegion_Current->DecodedData_Length; + + if((offset >= begin) && (offset < end)) return &bufferView->EncodedRegion_Current->DecodedData[offset - begin]; + } + + return basePtr + offset; } namespace { @@ -777,8 +821,8 @@ const char* mode_str; const char* type_str; ComponentType component_type; - #define MESH_READ_COMPRESSEDDATA_MEMBER(pID, pOut) \ - if(!ReadMember(pJSON_Object_CompressedData, pID, pOut)) { throw DeadlyImportError(std::string("GLTF: \"compressedData\" must has \"") + pID + "\"."); } + #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); @@ -838,21 +882,12 @@ ComponentType component_type; // Decode data if(decoder.DecodePlayload(ifs, bstream) != o3dgc::O3DGC_OK) throw DeadlyImportError("GLTF: can not decode Open3DGC data."); - // Set/extend buffer - bufview->buffer->ReplaceData(byte_offset, count, output_data, output_data_size); - // Also correct size of current "bufferView" ... - bufview->byteLength = output_data_size; - // and offset for all other "bufferViews" which are placed after edited. - const size_t difference = output_data_size - count; - - for(size_t idx_bv = 0; idx_bv < pAsset_Root.bufferViews.Size(); idx_bv++) - { - size_t off = pAsset_Root.bufferViews[idx_bv].byteOffset; - - if(off > (byte_offset + count)) pAsset_Root.bufferViews[idx_bv].byteOffset += difference; - } - - delete [] output_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); + // No. Do not delete "output_data". After calling "EncodedRegion_Mark" bufferView is owner of "output_data". + // "delete [] output_data;" } inline void Camera::Read(Value& obj, Asset& r) diff --git a/code/glTFImporter.cpp b/code/glTFImporter.cpp index 6a6619b81..c2fcd488b 100644 --- a/code/glTFImporter.cpp +++ b/code/glTFImporter.cpp @@ -1,4 +1,4 @@ -/* +/* Open Asset Import Library (assimp) ---------------------------------------------------------------------- @@ -294,17 +294,30 @@ void glTFImporter::ImportMeshes(glTF::Asset& r) } Mesh::Primitive::Attributes& attr = prim.attributes; - if (attr.position.size() > 0 && attr.position[0]) { + + // 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 (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.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); } - for (size_t tc = 0; tc < attr.texcoord.size() && tc <= AI_MAX_NUMBER_OF_TEXTURECOORDS; ++tc) { - attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc]); + // 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(); aiVector3D* values = aim->mTextureCoords[tc]; @@ -315,7 +328,10 @@ void glTFImporter::ImportMeshes(glTF::Asset& r) if (prim.indices) { - aiFace* faces = 0; + // 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; unsigned int count = prim.indices->count;