| /* |
| Open Asset Import Library (assimp) |
| ---------------------------------------------------------------------- |
| |
| Copyright (c) 2006-2017, assimp team |
| |
| All rights reserved. |
| |
| Redistribution and use of this software in source and binary forms, |
| with or without modification, are permitted provided that the |
| following conditions are met: |
| |
| * Redistributions of source code must retain the above |
| copyright notice, this list of conditions and the |
| following disclaimer. |
| |
| * Redistributions in binary form must reproduce the above |
| copyright notice, this list of conditions and the |
| following disclaimer in the documentation and/or other |
| materials provided with the distribution. |
| |
| * Neither the name of the assimp team, nor the names of its |
| contributors may be used to endorse or promote products |
| derived from this software without specific prior |
| written permission of the assimp team. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| ---------------------------------------------------------------------- |
| */ |
| |
| #include "StringUtils.h" |
| #include <iomanip> |
| |
| // Header files, Assimp |
| #include <assimp/DefaultLogger.hpp> |
| |
| #ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC |
| // Header files, Open3DGC. |
| # include <Open3DGC/o3dgcSC3DMCDecoder.h> |
| #endif |
| |
| using namespace Assimp; |
| |
| namespace glTF { |
| |
| namespace { |
| |
| // |
| // JSON Value reading helpers |
| // |
| |
| template<class T> |
| struct ReadHelper { static bool Read(Value& val, T& out) { |
| return val.IsInt() ? out = static_cast<T>(val.GetInt()), true : false; |
| }}; |
| |
| template<> struct ReadHelper<bool> { static bool Read(Value& val, bool& out) { |
| return val.IsBool() ? out = val.GetBool(), true : false; |
| }}; |
| |
| template<> struct ReadHelper<float> { static bool Read(Value& val, float& out) { |
| return val.IsNumber() ? out = static_cast<float>(val.GetDouble()), true : false; |
| }}; |
| |
| template<unsigned int N> struct ReadHelper<float[N]> { static bool Read(Value& val, float (&out)[N]) { |
| if (!val.IsArray() || val.Size() != N) return false; |
| for (unsigned int i = 0; i < N; ++i) { |
| if (val[i].IsNumber()) |
| out[i] = static_cast<float>(val[i].GetDouble()); |
| } |
| return true; |
| }}; |
| |
| template<> struct ReadHelper<const char*> { static bool Read(Value& val, const char*& out) { |
| return val.IsString() ? (out = val.GetString(), true) : false; |
| }}; |
| |
| template<> struct ReadHelper<std::string> { static bool Read(Value& val, std::string& out) { |
| return val.IsString() ? (out = std::string(val.GetString(), val.GetStringLength()), true) : false; |
| }}; |
| |
| template<class T> struct ReadHelper< Nullable<T> > { static bool Read(Value& val, Nullable<T>& out) { |
| return out.isPresent = ReadHelper<T>::Read(val, out.value); |
| }}; |
| |
| template<class T> |
| inline static bool ReadValue(Value& val, T& out) |
| { |
| return ReadHelper<T>::Read(val, out); |
| } |
| |
| template<class T> |
| inline static bool ReadMember(Value& obj, const char* id, T& out) |
| { |
| Value::MemberIterator it = obj.FindMember(id); |
| if (it != obj.MemberEnd()) { |
| return ReadHelper<T>::Read(it->value, out); |
| } |
| return false; |
| } |
| |
| template<class T> |
| inline static T MemberOrDefault(Value& obj, const char* id, T defaultValue) |
| { |
| T out; |
| return ReadMember(obj, id, out) ? out : defaultValue; |
| } |
| |
| inline Value* FindMember(Value& val, const char* id) |
| { |
| Value::MemberIterator it = val.FindMember(id); |
| return (it != val.MemberEnd()) ? &it->value : 0; |
| } |
| |
| inline Value* FindString(Value& val, const char* id) |
| { |
| Value::MemberIterator it = val.FindMember(id); |
| return (it != val.MemberEnd() && it->value.IsString()) ? &it->value : 0; |
| } |
| |
| inline Value* FindNumber(Value& val, const char* id) |
| { |
| Value::MemberIterator it = val.FindMember(id); |
| return (it != val.MemberEnd() && it->value.IsNumber()) ? &it->value : 0; |
| } |
| |
| inline Value* FindArray(Value& val, const char* id) |
| { |
| Value::MemberIterator it = val.FindMember(id); |
| return (it != val.MemberEnd() && it->value.IsArray()) ? &it->value : 0; |
| } |
| |
| inline Value* FindObject(Value& val, const char* id) |
| { |
| Value::MemberIterator it = val.FindMember(id); |
| return (it != val.MemberEnd() && it->value.IsObject()) ? &it->value : 0; |
| } |
| } |
| |
| // |
| // LazyDict methods |
| // |
| |
| template<class T> |
| inline LazyDict<T>::LazyDict(Asset& asset, const char* dictId, const char* extId) |
| : mDictId(dictId), mExtId(extId), mDict(0), mAsset(asset) |
| { |
| asset.mDicts.push_back(this); // register to the list of dictionaries |
| } |
| |
| template<class T> |
| inline LazyDict<T>::~LazyDict() |
| { |
| for (size_t i = 0; i < mObjs.size(); ++i) { |
| delete mObjs[i]; |
| } |
| } |
| |
| |
| template<class T> |
| inline void LazyDict<T>::AttachToDocument(Document& doc) |
| { |
| Value* container = 0; |
| |
| if (mExtId) { |
| if (Value* exts = FindObject(doc, "extensions")) { |
| container = FindObject(*exts, mExtId); |
| } |
| } |
| else { |
| container = &doc; |
| } |
| |
| if (container) { |
| mDict = FindObject(*container, mDictId); |
| } |
| } |
| |
| template<class T> |
| inline void LazyDict<T>::DetachFromDocument() |
| { |
| mDict = 0; |
| } |
| |
| template<class T> |
| Ref<T> LazyDict<T>::Get(unsigned int i) |
| { |
| return Ref<T>(mObjs, i); |
| } |
| |
| template<class T> |
| Ref<T> LazyDict<T>::Get(const char* id) |
| { |
| id = T::TranslateId(mAsset, id); |
| |
| typename Dict::iterator it = mObjsById.find(id); |
| if (it != mObjsById.end()) { // already created? |
| return Ref<T>(mObjs, it->second); |
| } |
| |
| // read it from the JSON object |
| if (!mDict) { |
| throw DeadlyImportError("GLTF: Missing section \"" + std::string(mDictId) + "\""); |
| } |
| |
| Value::MemberIterator obj = mDict->FindMember(id); |
| if (obj == mDict->MemberEnd()) { |
| throw DeadlyImportError("GLTF: Missing object with id \"" + std::string(id) + "\" in \"" + mDictId + "\""); |
| } |
| if (!obj->value.IsObject()) { |
| throw DeadlyImportError("GLTF: Object with id \"" + std::string(id) + "\" is not a JSON object"); |
| } |
| |
| // create an instance of the given type |
| T* inst = new T(); |
| inst->id = id; |
| ReadMember(obj->value, "name", inst->name); |
| inst->Read(obj->value, mAsset); |
| return Add(inst); |
| } |
| |
| template<class T> |
| Ref<T> LazyDict<T>::Add(T* obj) |
| { |
| unsigned int idx = unsigned(mObjs.size()); |
| mObjs.push_back(obj); |
| mObjsById[obj->id] = idx; |
| mAsset.mUsedIds[obj->id] = true; |
| return Ref<T>(mObjs, idx); |
| } |
| |
| template<class T> |
| Ref<T> LazyDict<T>::Create(const char* id) |
| { |
| Asset::IdMap::iterator it = mAsset.mUsedIds.find(id); |
| if (it != mAsset.mUsedIds.end()) { |
| throw DeadlyImportError("GLTF: two objects with the same ID exist"); |
| } |
| T* inst = new T(); |
| inst->id = id; |
| return Add(inst); |
| } |
| |
| |
| // |
| // glTF dictionary objects methods |
| // |
| |
| |
| inline Buffer::Buffer() |
| : 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 |
| if (r.extensionsUsed.KHR_binary_glTF && strcmp(id, "KHR_binary_glTF") == 0) { |
| return "binary_glTF"; |
| } |
| |
| return id; |
| } |
| |
| inline void Buffer::Read(Value& obj, Asset& r) |
| { |
| size_t statedLength = MemberOrDefault<size_t>(obj, "byteLength", 0); |
| byteLength = statedLength; |
| |
| Value* it = FindString(obj, "uri"); |
| if (!it) { |
| if (statedLength > 0) { |
| throw DeadlyImportError("GLTF: buffer with non-zero length missing the \"uri\" attribute"); |
| } |
| return; |
| } |
| |
| const char* uri = it->GetString(); |
| |
| Util::DataURI dataURI; |
| if (ParseDataURI(uri, it->GetStringLength(), dataURI)) { |
| if (dataURI.base64) { |
| uint8_t* data = 0; |
| this->byteLength = Util::DecodeBase64(dataURI.data, dataURI.dataLength, data); |
| this->mData.reset(data); |
| |
| if (statedLength > 0 && this->byteLength != statedLength) { |
| throw DeadlyImportError("GLTF: buffer \"" + id + "\", expected " + to_string(statedLength) + |
| " bytes, but found " + to_string(dataURI.dataLength)); |
| } |
| } |
| else { // assume raw data |
| if (statedLength != dataURI.dataLength) { |
| throw DeadlyImportError("GLTF: buffer \"" + id + "\", expected " + to_string(statedLength) + |
| " bytes, but found " + to_string(dataURI.dataLength)); |
| } |
| |
| this->mData.reset(new uint8_t[dataURI.dataLength]); |
| memcpy( this->mData.get(), dataURI.data, dataURI.dataLength ); |
| } |
| } |
| else { // Local file |
| if (byteLength > 0) { |
| std::string dir = !r.mCurrentAssetDir.empty() ? (r.mCurrentAssetDir + "/") : ""; |
| |
| IOStream* file = r.OpenFile(dir + uri, "rb"); |
| if (file) { |
| bool ok = LoadFromStream(*file, byteLength); |
| delete file; |
| |
| if (!ok) |
| throw DeadlyImportError("GLTF: error while reading referenced file \"" + std::string(uri) + "\"" ); |
| } |
| else { |
| throw DeadlyImportError("GLTF: could not open referenced file \"" + std::string(uri) + "\""); |
| } |
| } |
| } |
| } |
| |
| inline bool Buffer::LoadFromStream(IOStream& stream, size_t length, size_t baseOffset) |
| { |
| byteLength = length ? length : stream.FileSize(); |
| |
| if (baseOffset) { |
| stream.Seek(baseOffset, aiOrigin_SET); |
| } |
| |
| mData.reset(new uint8_t[byteLength], std::default_delete<uint8_t[]>()); |
| |
| if (stream.Read(mData.get(), byteLength, 1) != 1) { |
| return false; |
| } |
| 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) |
| { |
| const 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) |
| { |
| const 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; |
| |
| 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; |
| |
| return true; |
| } |
| |
| inline size_t Buffer::AppendData(uint8_t* data, size_t length) |
| { |
| size_t offset = this->byteLength; |
| Grow(length); |
| memcpy(mData.get() + offset, data, length); |
| return offset; |
| } |
| |
| inline void Buffer::Grow(size_t amount) |
| { |
| if (amount <= 0) return; |
| uint8_t* b = new uint8_t[byteLength + amount]; |
| if (mData) memcpy(b, mData.get(), byteLength); |
| mData.reset(b); |
| byteLength += amount; |
| } |
| |
| // |
| // struct BufferView |
| // |
| |
| inline void BufferView::Read(Value& obj, Asset& r) |
| { |
| const char* bufferId = MemberOrDefault<const char*>(obj, "buffer", 0); |
| if (bufferId) { |
| buffer = r.buffers.Get(bufferId); |
| } |
| |
| byteOffset = MemberOrDefault(obj, "byteOffset", 0u); |
| byteLength = MemberOrDefault(obj, "byteLength", 0u); |
| } |
| |
| // |
| // struct Accessor |
| // |
| |
| inline void Accessor::Read(Value& obj, Asset& r) |
| { |
| const char* bufferViewId = MemberOrDefault<const char*>(obj, "bufferView", 0); |
| if (bufferViewId) { |
| bufferView = r.bufferViews.Get(bufferViewId); |
| } |
| |
| byteOffset = MemberOrDefault(obj, "byteOffset", 0u); |
| byteStride = MemberOrDefault(obj, "byteStride", 0u); |
| componentType = MemberOrDefault(obj, "componentType", ComponentType_BYTE); |
| count = MemberOrDefault(obj, "count", 0u); |
| |
| const char* typestr; |
| type = ReadMember(obj, "type", typestr) ? AttribType::FromString(typestr) : AttribType::SCALAR; |
| } |
| |
| inline unsigned int Accessor::GetNumComponents() |
| { |
| return AttribType::GetNumComponents(type); |
| } |
| |
| inline unsigned int Accessor::GetBytesPerComponent() |
| { |
| return int(ComponentTypeSize(componentType)); |
| } |
| |
| inline unsigned int Accessor::GetElementSize() |
| { |
| return GetNumComponents() * GetBytesPerComponent(); |
| } |
| |
| inline uint8_t* Accessor::GetPointer() |
| { |
| if (!bufferView || !bufferView->buffer) return 0; |
| uint8_t* basePtr = bufferView->buffer->GetPointer(); |
| if (!basePtr) return 0; |
| |
| size_t offset = byteOffset + bufferView->byteOffset; |
| |
| // Check if region is encoded. |
| if(bufferView->buffer->EncodedRegion_Current != nullptr) |
| { |
| const size_t begin = bufferView->buffer->EncodedRegion_Current->Offset; |
| const size_t end = begin + bufferView->buffer->EncodedRegion_Current->DecodedData_Length; |
| |
| if((offset >= begin) && (offset < end)) |
| return &bufferView->buffer->EncodedRegion_Current->DecodedData[offset - begin]; |
| } |
| |
| return basePtr + offset; |
| } |
| |
| namespace { |
| inline void CopyData(size_t count, |
| const uint8_t* src, size_t src_stride, |
| uint8_t* dst, size_t dst_stride) |
| { |
| if (src_stride == dst_stride) { |
| memcpy(dst, src, count * src_stride); |
| } |
| else { |
| size_t sz = std::min(src_stride, dst_stride); |
| for (size_t i = 0; i < count; ++i) { |
| memcpy(dst, src, sz); |
| if (sz < dst_stride) { |
| memset(dst + sz, 0, dst_stride - sz); |
| } |
| src += src_stride; |
| dst += dst_stride; |
| } |
| } |
| } |
| } |
| |
| template<class T> |
| bool Accessor::ExtractData(T*& outData) |
| { |
| uint8_t* data = GetPointer(); |
| if (!data) return false; |
| |
| const size_t elemSize = GetElementSize(); |
| const size_t totalSize = elemSize * count; |
| |
| const size_t stride = byteStride ? byteStride : elemSize; |
| |
| const size_t targetElemSize = sizeof(T); |
| ai_assert(elemSize <= targetElemSize); |
| |
| ai_assert(count*stride <= bufferView->byteLength); |
| |
| outData = new T[count]; |
| if (stride == elemSize && targetElemSize == elemSize) { |
| memcpy(outData, data, totalSize); |
| } |
| else { |
| for (size_t i = 0; i < count; ++i) { |
| memcpy(outData + i, data + i*stride, elemSize); |
| } |
| } |
| |
| return true; |
| } |
| |
| inline void Accessor::WriteData(size_t count, const void* src_buffer, size_t src_stride) |
| { |
| uint8_t* buffer_ptr = bufferView->buffer->GetPointer(); |
| size_t offset = byteOffset + bufferView->byteOffset; |
| |
| size_t dst_stride = GetNumComponents() * GetBytesPerComponent(); |
| |
| const uint8_t* src = reinterpret_cast<const uint8_t*>(src_buffer); |
| uint8_t* dst = reinterpret_cast< uint8_t*>(buffer_ptr + offset); |
| |
| ai_assert(dst + count*dst_stride <= buffer_ptr + bufferView->buffer->byteLength); |
| CopyData(count, src, src_stride, dst, dst_stride); |
| } |
| |
| |
| |
| inline Accessor::Indexer::Indexer(Accessor& acc) |
| : accessor(acc) |
| , data(acc.GetPointer()) |
| , elemSize(acc.GetElementSize()) |
| , stride(acc.byteStride ? acc.byteStride : elemSize) |
| { |
| |
| } |
| |
| //! Accesses the i-th value as defined by the accessor |
| template<class T> |
| T Accessor::Indexer::GetValue(int i) |
| { |
| ai_assert(data); |
| ai_assert(i*stride < accessor.bufferView->byteLength); |
| T value = T(); |
| memcpy(&value, data + i*stride, elemSize); |
| //value >>= 8 * (sizeof(T) - elemSize); |
| return value; |
| } |
| |
| inline Image::Image() |
| : width(0) |
| , height(0) |
| , mData(0) |
| , mDataLength(0) |
| { |
| |
| } |
| |
| inline void Image::Read(Value& obj, Asset& r) |
| { |
| // Check for extensions first (to detect binary embedded data) |
| if (Value* extensions = FindObject(obj, "extensions")) { |
| if (r.extensionsUsed.KHR_binary_glTF) { |
| if (Value* ext = FindObject(*extensions, "KHR_binary_glTF")) { |
| |
| width = MemberOrDefault(*ext, "width", 0); |
| height = MemberOrDefault(*ext, "height", 0); |
| |
| ReadMember(*ext, "mimeType", mimeType); |
| |
| const char* bufferViewId; |
| if (ReadMember(*ext, "bufferView", bufferViewId)) { |
| Ref<BufferView> bv = r.bufferViews.Get(bufferViewId); |
| if (bv) { |
| mDataLength = bv->byteLength; |
| mData = new uint8_t[mDataLength]; |
| memcpy(mData, bv->buffer->GetPointer() + bv->byteOffset, mDataLength); |
| } |
| } |
| } |
| } |
| } |
| |
| if (!mDataLength) { |
| if (Value* uri = FindString(obj, "uri")) { |
| const char* uristr = uri->GetString(); |
| |
| Util::DataURI dataURI; |
| if (ParseDataURI(uristr, uri->GetStringLength(), dataURI)) { |
| mimeType = dataURI.mediaType; |
| if (dataURI.base64) { |
| mDataLength = Util::DecodeBase64(dataURI.data, dataURI.dataLength, mData); |
| } |
| } |
| else { |
| this->uri = uristr; |
| } |
| } |
| } |
| } |
| |
| inline uint8_t* Image::StealData() |
| { |
| uint8_t* data = mData; |
| mDataLength = 0; |
| mData = 0; |
| return data; |
| } |
| |
| inline void Image::SetData(uint8_t* data, size_t length, Asset& r) |
| { |
| Ref<Buffer> b = r.GetBodyBuffer(); |
| if (b) { // binary file: append to body |
| std::string bvId = r.FindUniqueID(this->id, "imgdata"); |
| bufferView = r.bufferViews.Create(bvId); |
| |
| bufferView->buffer = b; |
| bufferView->byteLength = length; |
| bufferView->byteOffset = b->AppendData(data, length); |
| } |
| else { // text file: will be stored as a data uri |
| this->mData = data; |
| this->mDataLength = length; |
| } |
| } |
| |
| inline void Sampler::Read(Value& obj, Asset& /*r*/) |
| { |
| SetDefaults(); |
| |
| ReadMember(obj, "magFilter", magFilter); |
| ReadMember(obj, "minFilter", minFilter); |
| ReadMember(obj, "wrapS", wrapS); |
| ReadMember(obj, "wrapT", wrapT); |
| } |
| |
| inline void Sampler::SetDefaults() |
| { |
| magFilter = SamplerMagFilter_Linear; |
| minFilter = SamplerMinFilter_Linear; |
| wrapS = SamplerWrap_Repeat; |
| wrapT = SamplerWrap_Repeat; |
| } |
| |
| inline void Texture::Read(Value& obj, Asset& r) |
| { |
| const char* sourcestr; |
| if (ReadMember(obj, "source", sourcestr)) { |
| source = r.images.Get(sourcestr); |
| } |
| |
| const char* samplerstr; |
| if (ReadMember(obj, "sampler", samplerstr)) { |
| sampler = r.samplers.Get(samplerstr); |
| } |
| } |
| |
| namespace { |
| inline void ReadMaterialProperty(Asset& r, Value& vals, const char* propName, TexProperty& out) |
| { |
| if (Value* prop = FindMember(vals, propName)) { |
| if (prop->IsString()) { |
| out.texture = r.textures.Get(prop->GetString()); |
| } |
| else { |
| ReadValue(*prop, out.color); |
| } |
| } |
| } |
| } |
| |
| inline void Material::Read(Value& material, Asset& r) |
| { |
| SetDefaults(); |
| |
| if (Value* values = FindObject(material, "values")) { |
| ReadMaterialProperty(r, *values, "ambient", this->ambient); |
| ReadMaterialProperty(r, *values, "diffuse", this->diffuse); |
| ReadMaterialProperty(r, *values, "specular", this->specular); |
| |
| ReadMember(*values, "transparency", transparency); |
| ReadMember(*values, "shininess", shininess); |
| } |
| |
| if (Value* extensions = FindObject(material, "extensions")) { |
| if (r.extensionsUsed.KHR_materials_common) { |
| if (Value* ext = FindObject(*extensions, "KHR_materials_common")) { |
| if (Value* tnq = FindString(*ext, "technique")) { |
| const char* t = tnq->GetString(); |
| if (strcmp(t, "BLINN") == 0) technique = Technique_BLINN; |
| else if (strcmp(t, "PHONG") == 0) technique = Technique_PHONG; |
| else if (strcmp(t, "LAMBERT") == 0) technique = Technique_LAMBERT; |
| else if (strcmp(t, "CONSTANT") == 0) technique = Technique_CONSTANT; |
| } |
| |
| if (Value* values = FindObject(*ext, "values")) { |
| ReadMaterialProperty(r, *values, "ambient", this->ambient); |
| ReadMaterialProperty(r, *values, "diffuse", this->diffuse); |
| ReadMaterialProperty(r, *values, "specular", this->specular); |
| |
| ReadMember(*values, "doubleSided", doubleSided); |
| ReadMember(*values, "transparent", transparent); |
| ReadMember(*values, "transparency", transparency); |
| ReadMember(*values, "shininess", shininess); |
| } |
| } |
| } |
| } |
| } |
| |
| namespace { |
| void SetVector(vec4& v, float x, float y, float z, float w) |
| { v[0] = x; v[1] = y; v[2] = z; v[3] = w; } |
| } |
| |
| inline void Material::SetDefaults() |
| { |
| SetVector(ambient.color, 0, 0, 0, 1); |
| SetVector(diffuse.color, 0, 0, 0, 1); |
| SetVector(specular.color, 0, 0, 0, 1); |
| SetVector(emission.color, 0, 0, 0, 1); |
| |
| doubleSided = false; |
| transparent = false; |
| transparency = 1.0; |
| shininess = 0.0; |
| |
| technique = Technique_undefined; |
| } |
| |
| namespace { |
| |
| template<int N> |
| inline int Compare(const char* attr, const char (&str)[N]) { |
| return (strncmp(attr, str, N - 1) == 0) ? N - 1 : 0; |
| } |
| |
| inline bool GetAttribVector(Mesh::Primitive& p, const char* attr, Mesh::AccessorList*& v, int& pos) |
| { |
| if ((pos = Compare(attr, "POSITION"))) { |
| v = &(p.attributes.position); |
| } |
| else if ((pos = Compare(attr, "NORMAL"))) { |
| v = &(p.attributes.normal); |
| } |
| else if ((pos = Compare(attr, "TEXCOORD"))) { |
| v = &(p.attributes.texcoord); |
| } |
| else if ((pos = Compare(attr, "COLOR"))) { |
| v = &(p.attributes.color); |
| } |
| else if ((pos = Compare(attr, "JOINT"))) { |
| v = &(p.attributes.joint); |
| } |
| else if ((pos = Compare(attr, "JOINTMATRIX"))) { |
| v = &(p.attributes.jointmatrix); |
| } |
| else if ((pos = Compare(attr, "WEIGHT"))) { |
| v = &(p.attributes.weight); |
| } |
| else return false; |
| return true; |
| } |
| } |
| |
| inline void Mesh::Read(Value& pJSON_Object, Asset& pAsset_Root) |
| { |
| /****************** Mesh primitives ******************/ |
| if (Value* primitives = FindArray(pJSON_Object, "primitives")) { |
| this->primitives.resize(primitives->Size()); |
| for (unsigned int i = 0; i < primitives->Size(); ++i) { |
| Value& primitive = (*primitives)[i]; |
| |
| Primitive& prim = this->primitives[i]; |
| prim.mode = MemberOrDefault(primitive, "mode", PrimitiveMode_TRIANGLES); |
| |
| if (Value* attrs = FindObject(primitive, "attributes")) { |
| for (Value::MemberIterator it = attrs->MemberBegin(); it != attrs->MemberEnd(); ++it) { |
| if (!it->value.IsString()) continue; |
| const char* attr = it->name.GetString(); |
| // Valid attribute semantics include POSITION, NORMAL, TEXCOORD, COLOR, JOINT, JOINTMATRIX, |
| // and WEIGHT.Attribute semantics can be of the form[semantic]_[set_index], e.g., TEXCOORD_0, TEXCOORD_1, etc. |
| |
| int undPos = 0; |
| Mesh::AccessorList* vec = 0; |
| if (GetAttribVector(prim, attr, vec, undPos)) { |
| size_t idx = (attr[undPos] == '_') ? atoi(attr + undPos + 1) : 0; |
| if ((*vec).size() <= idx) (*vec).resize(idx + 1); |
| (*vec)[idx] = pAsset_Root.accessors.Get(it->value.GetString()); |
| } |
| } |
| } |
| |
| if (Value* indices = FindString(primitive, "indices")) { |
| prim.indices = pAsset_Root.accessors.Get(indices->GetString()); |
| } |
| |
| if (Value* material = FindString(primitive, "material")) { |
| prim.material = pAsset_Root.materials.Get(material->GetString()); |
| } |
| } |
| } |
| |
| /****************** Mesh extensions ******************/ |
| Value* json_extensions = FindObject(pJSON_Object, "extensions"); |
| |
| if(json_extensions == nullptr) goto mr_skip_extensions; |
| |
| for(Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); it_memb++) |
| { |
| #ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC |
| if(it_memb->name.GetString() == std::string("Open3DGC-compression")) |
| { |
| // 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"); |
| |
| if(comp_data == nullptr) throw DeadlyImportError("GLTF: \"Open3DGC-compression\" must has \"compressedData\"."); |
| |
| 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 |
| #endif |
| { |
| throw DeadlyImportError(std::string("GLTF: Unknown mesh extension: \"") + it_memb->name.GetString() + "\"."); |
| } |
| }// for(Value::MemberIterator it_memb = json_extensions->MemberBegin(); it_memb != json_extensions->MemberEnd(); json_extensions++) |
| |
| mr_skip_extensions: |
| |
| return;// After label some operators must be present. |
| } |
| |
| #ifdef ASSIMP_IMPORTER_GLTF_USE_OPEN3DGC |
| inline void Mesh::Decode_O3DGC(const SCompression_Open3DGC& pCompression_Open3DGC, Asset& pAsset_Root) |
| { |
| typedef unsigned short IndicesType;///< \sa glTFExporter::ExportMeshes. |
| |
| o3dgc::SC3DMCDecoder<IndicesType> decoder; |
| o3dgc::IndexedFaceSet<IndicesType> ifs; |
| o3dgc::BinaryStream bstream; |
| uint8_t* decoded_data; |
| size_t decoded_data_size = 0; |
| Ref<Buffer> buf = pAsset_Root.buffers.Get(pCompression_Open3DGC.Buffer); |
| |
| // Read data from buffer and place it in BinaryStream for decoder. |
| // Just "Count" because always is used type equivalent to uint8_t. |
| bstream.LoadFromBuffer(&buf->GetPointer()[pCompression_Open3DGC.Offset], static_cast<unsigned long>(pCompression_Open3DGC.Count)); |
| |
| // 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."); |
| |
| /****************** Get sizes of arrays and check sizes ******************/ |
| // Note. See "Limitations for meshes when using Open3DGC-compression". |
| |
| // 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(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) + ")."); |
| |
| size_normal *= 3 * sizeof(float); |
| // Additional attributes. |
| std::vector<size_t> size_floatattr; |
| std::vector<size_t> 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(static_cast<unsigned long>(idx)); |
| |
| switch(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))) |
| { |
| 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(static_cast<unsigned long>(idx), 0ul);// Disable decoding this attribute. |
| } |
| |
| break; |
| default: |
| throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: " + to_string(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx)))); |
| } |
| |
| tval *= ifs.GetFloatAttributeDim(static_cast<unsigned long>(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(static_cast<unsigned long>(idx)); |
| switch( ifs.GetIntAttributeType(static_cast<unsigned long>(idx) ) ) |
| { |
| case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN: |
| case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX: |
| case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID: |
| case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID: |
| break; |
| |
| default: |
| throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: " + to_string(ifs.GetIntAttributeType(static_cast<unsigned long>(idx)))); |
| } |
| |
| tval *= ifs.GetIntAttributeDim(static_cast<unsigned long>(idx)) * sizeof(long);// See float attributes note. |
| size_intattr[idx] = tval; |
| decoded_data_size += tval; |
| } |
| |
| // Create array for decoded data. |
| decoded_data = new uint8_t[decoded_data_size]; |
| |
| /****************** Set right array regions for decoder ******************/ |
| |
| auto get_buf_offset = [](Ref<Accessor>& pAccessor) -> size_t { return pAccessor->byteOffset + pAccessor->bufferView->byteOffset; }; |
| |
| // 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]))); |
| } |
| |
| for(size_t idx = 0, idx_end = size_floatattr.size(), idx_texcoord = 0; idx < idx_end; idx++) |
| { |
| switch(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx))) |
| { |
| case o3dgc::O3DGC_IFS_FLOAT_ATTRIBUTE_TYPE_TEXCOORD: |
| if(idx_texcoord < primitives[0].attributes.texcoord.size()) |
| { |
| // See above about absent attributes. |
| ifs.SetFloatAttribute(static_cast<unsigned long>(idx), (o3dgc::Real* const)(decoded_data + get_buf_offset(primitives[0].attributes.texcoord[idx]))); |
| idx_texcoord++; |
| } |
| |
| break; |
| default: |
| throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of float attribute: " + to_string(ifs.GetFloatAttributeType(static_cast<unsigned long>(idx)))); |
| } |
| } |
| |
| for(size_t idx = 0, idx_end = size_intattr.size(); idx < idx_end; idx++) { |
| switch(ifs.GetIntAttributeType(static_cast<unsigned int>(idx))) { |
| case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_UNKOWN: |
| case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX: |
| case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_JOINT_ID: |
| case o3dgc::O3DGC_IFS_INT_ATTRIBUTE_TYPE_INDEX_BUFFER_ID: |
| break; |
| |
| // ifs.SetIntAttribute(idx, (long* const)(decoded_data + get_buf_offset(primitives[0].attributes.joint))); |
| default: |
| throw DeadlyImportError("GLTF: Open3DGC. Unsupported type of int attribute: " + to_string(ifs.GetIntAttributeType(static_cast<unsigned long>(idx)))); |
| } |
| } |
| |
| // |
| // Decode data |
| // |
| if ( decoder.DecodePayload( ifs, bstream ) != o3dgc::O3DGC_OK ) { |
| throw DeadlyImportError( "GLTF: can not decode Open3DGC data." ); |
| } |
| |
| // 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;" |
| } |
| #endif |
| |
| inline void Camera::Read(Value& obj, Asset& /*r*/) |
| { |
| type = MemberOrDefault(obj, "type", Camera::Perspective); |
| |
| const char* subobjId = (type == Camera::Orthographic) ? "ortographic" : "perspective"; |
| |
| Value* it = FindObject(obj, subobjId); |
| if (!it) throw DeadlyImportError("GLTF: Camera missing its parameters"); |
| |
| if (type == Camera::Perspective) { |
| perspective.aspectRatio = MemberOrDefault(*it, "aspectRatio", 0.f); |
| perspective.yfov = MemberOrDefault(*it, "yfov", 3.1415f/2.f); |
| perspective.zfar = MemberOrDefault(*it, "zfar", 100.f); |
| perspective.znear = MemberOrDefault(*it, "znear", 0.01f); |
| } |
| else { |
| ortographic.xmag = MemberOrDefault(obj, "xmag", 1.f); |
| ortographic.ymag = MemberOrDefault(obj, "ymag", 1.f); |
| ortographic.zfar = MemberOrDefault(obj, "zfar", 100.f); |
| ortographic.znear = MemberOrDefault(obj, "znear", 0.01f); |
| } |
| } |
| |
| inline void Light::Read(Value& obj, Asset& /*r*/) |
| { |
| SetDefaults(); |
| |
| if (Value* type = FindString(obj, "type")) { |
| const char* t = type->GetString(); |
| if (strcmp(t, "ambient") == 0) this->type = Type_ambient; |
| else if (strcmp(t, "directional") == 0) this->type = Type_directional; |
| else if (strcmp(t, "point") == 0) this->type = Type_point; |
| else if (strcmp(t, "spot") == 0) this->type = Type_spot; |
| |
| if (this->type != Type_undefined) { |
| if (Value* vals = FindString(obj, t)) { |
| ReadMember(*vals, "color", color); |
| |
| ReadMember(*vals, "constantAttenuation", constantAttenuation); |
| ReadMember(*vals, "linearAttenuation", linearAttenuation); |
| ReadMember(*vals, "quadraticAttenuation", quadraticAttenuation); |
| ReadMember(*vals, "distance", distance); |
| |
| ReadMember(*vals, "falloffAngle", falloffAngle); |
| ReadMember(*vals, "falloffExponent", falloffExponent); |
| } |
| } |
| } |
| } |
| |
| inline void Light::SetDefaults() |
| { |
| #ifndef M_PI |
| const float M_PI = 3.14159265358979323846f; |
| #endif |
| |
| type = Type_undefined; |
| |
| SetVector(color, 0.f, 0.f, 0.f, 1.f); |
| |
| constantAttenuation = 0.f; |
| linearAttenuation = 1.f; |
| quadraticAttenuation = 1.f; |
| distance = 0.f; |
| |
| falloffAngle = static_cast<float>(M_PI / 2.f); |
| falloffExponent = 0.f; |
| } |
| |
| inline void Node::Read(Value& obj, Asset& r) |
| { |
| if (Value* children = FindArray(obj, "children")) { |
| this->children.reserve(children->Size()); |
| for (unsigned int i = 0; i < children->Size(); ++i) { |
| Value& child = (*children)[i]; |
| if (child.IsString()) { |
| // get/create the child node |
| Ref<Node> chn = r.nodes.Get(child.GetString()); |
| if (chn) this->children.push_back(chn); |
| } |
| } |
| } |
| |
| |
| if (Value* matrix = FindArray(obj, "matrix")) { |
| ReadValue(*matrix, this->matrix); |
| } |
| else { |
| ReadMember(obj, "translation", translation); |
| ReadMember(obj, "scale", scale); |
| ReadMember(obj, "rotation", rotation); |
| } |
| |
| if (Value* meshes = FindArray(obj, "meshes")) { |
| unsigned numMeshes = (unsigned)meshes->Size(); |
| |
| std::vector<unsigned int> meshList; |
| |
| this->meshes.reserve(numMeshes); |
| for (unsigned i = 0; i < numMeshes; ++i) { |
| if ((*meshes)[i].IsString()) { |
| Ref<Mesh> mesh = r.meshes.Get((*meshes)[i].GetString()); |
| if (mesh) this->meshes.push_back(mesh); |
| } |
| } |
| } |
| |
| if (Value* camera = FindString(obj, "camera")) { |
| this->camera = r.cameras.Get(camera->GetString()); |
| if (this->camera) |
| this->camera->id = this->id; |
| } |
| |
| // TODO load "skeletons", "skin", "jointName" |
| |
| if (Value* extensions = FindObject(obj, "extensions")) { |
| if (r.extensionsUsed.KHR_materials_common) { |
| |
| if (Value* ext = FindObject(*extensions, "KHR_materials_common")) { |
| if (Value* light = FindString(*ext, "light")) { |
| this->light = r.lights.Get(light->GetString()); |
| } |
| } |
| |
| } |
| } |
| } |
| |
| inline void Scene::Read(Value& obj, Asset& r) |
| { |
| if (Value* array = FindArray(obj, "nodes")) { |
| for (unsigned int i = 0; i < array->Size(); ++i) { |
| if (!(*array)[i].IsString()) continue; |
| Ref<Node> node = r.nodes.Get((*array)[i].GetString()); |
| if (node) |
| this->nodes.push_back(node); |
| } |
| } |
| } |
| |
| |
| inline void AssetMetadata::Read(Document& doc) |
| { |
| // read the version, etc. |
| if (Value* obj = FindObject(doc, "asset")) { |
| ReadMember(*obj, "copyright", copyright); |
| ReadMember(*obj, "generator", generator); |
| |
| premultipliedAlpha = MemberOrDefault(*obj, "premultipliedAlpha", false); |
| |
| if (Value* versionString = FindString(*obj, "version")) { |
| version = versionString->GetString(); |
| } else if (Value* versionNumber = FindNumber (*obj, "version")) { |
| char buf[4]; |
| |
| ai_snprintf(buf, 4, "%.1f", versionNumber->GetDouble()); |
| |
| version = buf; |
| } |
| |
| if (Value* profile = FindObject(*obj, "profile")) { |
| ReadMember(*profile, "api", this->profile.api); |
| ReadMember(*profile, "version", this->profile.version); |
| } |
| } |
| |
| if (version.empty() || version[0] != '1') { |
| throw DeadlyImportError("GLTF: Unsupported glTF version: " + version); |
| } |
| } |
| |
| |
| |
| // |
| // Asset methods implementation |
| // |
| |
| inline void Asset::ReadBinaryHeader(IOStream& stream) |
| { |
| GLB_Header header; |
| if (stream.Read(&header, sizeof(header), 1) != 1) { |
| throw DeadlyImportError("GLTF: Unable to read the file header"); |
| } |
| |
| if (strncmp((char*)header.magic, AI_GLB_MAGIC_NUMBER, sizeof(header.magic)) != 0) { |
| throw DeadlyImportError("GLTF: Invalid binary glTF file"); |
| } |
| |
| AI_SWAP4(header.version); |
| asset.version = to_string(header.version); |
| if (header.version != 1) { |
| throw DeadlyImportError("GLTF: Unsupported binary glTF version"); |
| } |
| |
| AI_SWAP4(header.sceneFormat); |
| if (header.sceneFormat != SceneFormat_JSON) { |
| throw DeadlyImportError("GLTF: Unsupported binary glTF scene format"); |
| } |
| |
| AI_SWAP4(header.length); |
| AI_SWAP4(header.sceneLength); |
| |
| mSceneLength = static_cast<size_t>(header.sceneLength); |
| |
| mBodyOffset = sizeof(header)+mSceneLength; |
| mBodyOffset = (mBodyOffset + 3) & ~3; // Round up to next multiple of 4 |
| |
| mBodyLength = header.length - mBodyOffset; |
| } |
| |
| inline void Asset::Load(const std::string& pFile, bool isBinary) |
| { |
| mCurrentAssetDir.clear(); |
| int pos = std::max(int(pFile.rfind('/')), int(pFile.rfind('\\'))); |
| if (pos != int(std::string::npos)) mCurrentAssetDir = pFile.substr(0, pos + 1); |
| |
| shared_ptr<IOStream> stream(OpenFile(pFile.c_str(), "rb", true)); |
| if (!stream) { |
| throw DeadlyImportError("GLTF: Could not open file for reading"); |
| } |
| |
| // is binary? then read the header |
| if (isBinary) { |
| SetAsBinary(); // also creates the body buffer |
| ReadBinaryHeader(*stream); |
| } |
| else { |
| mSceneLength = stream->FileSize(); |
| mBodyLength = 0; |
| } |
| |
| |
| // read the scene data |
| |
| std::vector<char> sceneData(mSceneLength + 1); |
| sceneData[mSceneLength] = '\0'; |
| |
| if (stream->Read(&sceneData[0], 1, mSceneLength) != mSceneLength) { |
| throw DeadlyImportError("GLTF: Could not read the file contents"); |
| } |
| |
| |
| // parse the JSON document |
| |
| Document doc; |
| doc.ParseInsitu(&sceneData[0]); |
| |
| if (doc.HasParseError()) { |
| char buffer[32]; |
| ai_snprintf(buffer, 32, "%d", static_cast<int>(doc.GetErrorOffset())); |
| throw DeadlyImportError(std::string("GLTF: JSON parse error, offset ") + buffer + ": " |
| + GetParseError_En(doc.GetParseError())); |
| } |
| |
| if (!doc.IsObject()) { |
| throw DeadlyImportError("GLTF: JSON document root must be a JSON object"); |
| } |
| |
| // Fill the buffer instance for the current file embedded contents |
| if (mBodyLength > 0) { |
| if (!mBodyBuffer->LoadFromStream(*stream, mBodyLength, mBodyOffset)) { |
| throw DeadlyImportError("GLTF: Unable to read gltf file"); |
| } |
| } |
| |
| |
| // Load the metadata |
| asset.Read(doc); |
| ReadExtensionsUsed(doc); |
| |
| // Prepare the dictionaries |
| for (size_t i = 0; i < mDicts.size(); ++i) { |
| mDicts[i]->AttachToDocument(doc); |
| } |
| |
| |
| |
| // Read the "scene" property, which specifies which scene to load |
| // and recursively load everything referenced by it |
| if (Value* scene = FindString(doc, "scene")) { |
| this->scene = scenes.Get(scene->GetString()); |
| } |
| |
| // Clean up |
| for (size_t i = 0; i < mDicts.size(); ++i) { |
| mDicts[i]->DetachFromDocument(); |
| } |
| } |
| |
| inline void Asset::SetAsBinary() |
| { |
| if (!extensionsUsed.KHR_binary_glTF) { |
| extensionsUsed.KHR_binary_glTF = true; |
| mBodyBuffer = buffers.Create("binary_glTF"); |
| mBodyBuffer->MarkAsSpecial(); |
| } |
| } |
| |
| |
| inline void Asset::ReadExtensionsUsed(Document& doc) |
| { |
| Value* extsUsed = FindArray(doc, "extensionsUsed"); |
| if (!extsUsed) return; |
| |
| std::gltf_unordered_map<std::string, bool> exts; |
| |
| for (unsigned int i = 0; i < extsUsed->Size(); ++i) { |
| if ((*extsUsed)[i].IsString()) { |
| exts[(*extsUsed)[i].GetString()] = true; |
| } |
| } |
| |
| #define CHECK_EXT(EXT) \ |
| if (exts.find(#EXT) != exts.end()) extensionsUsed.EXT = true; |
| |
| CHECK_EXT(KHR_binary_glTF); |
| CHECK_EXT(KHR_materials_common); |
| |
| #undef CHECK_EXT |
| } |
| |
| inline IOStream* Asset::OpenFile(std::string path, const char* mode, bool /*absolute*/) |
| { |
| #ifdef ASSIMP_API |
| return mIOSystem->Open(path, mode); |
| #else |
| if (path.size() < 2) return 0; |
| if (!absolute && path[1] != ':' && path[0] != '/') { // relative? |
| path = mCurrentAssetDir + path; |
| } |
| FILE* f = fopen(path.c_str(), mode); |
| return f ? new IOStream(f) : 0; |
| #endif |
| } |
| |
| inline std::string Asset::FindUniqueID(const std::string& str, const char* suffix) |
| { |
| std::string id = str; |
| |
| if (!id.empty()) { |
| if (mUsedIds.find(id) == mUsedIds.end()) |
| return id; |
| |
| id += "_"; |
| } |
| |
| id += suffix; |
| |
| Asset::IdMap::iterator it = mUsedIds.find(id); |
| if (it == mUsedIds.end()) |
| return id; |
| |
| char buffer[256]; |
| int offset = ai_snprintf(buffer, sizeof(buffer), "%s_", id.c_str()); |
| for (int i = 0; it != mUsedIds.end(); ++i) { |
| ai_snprintf(buffer + offset, sizeof(buffer) - offset, "%d", i); |
| id = buffer; |
| it = mUsedIds.find(id); |
| } |
| |
| return id; |
| } |
| |
| namespace Util { |
| |
| inline |
| bool ParseDataURI(const char* const_uri, size_t uriLen, DataURI& out) { |
| if ( NULL == const_uri ) { |
| return false; |
| } |
| |
| if (const_uri[0] != 0x10) { // we already parsed this uri? |
| if (strncmp(const_uri, "data:", 5) != 0) // not a data uri? |
| return false; |
| } |
| |
| // set defaults |
| out.mediaType = "text/plain"; |
| out.charset = "US-ASCII"; |
| out.base64 = false; |
| |
| char* uri = const_cast<char*>(const_uri); |
| if (uri[0] != 0x10) { |
| uri[0] = 0x10; |
| uri[1] = uri[2] = uri[3] = uri[4] = 0; |
| |
| size_t i = 5, j; |
| if (uri[i] != ';' && uri[i] != ',') { // has media type? |
| uri[1] = char(i); |
| for (; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) { |
| // nothing to do! |
| } |
| } |
| while (uri[i] == ';' && i < uriLen) { |
| uri[i++] = '\0'; |
| for (j = i; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) { |
| // nothing to do! |
| } |
| |
| if ( strncmp( uri + j, "charset=", 8 ) == 0 ) { |
| uri[2] = char(j + 8); |
| } else if ( strncmp( uri + j, "base64", 6 ) == 0 ) { |
| uri[3] = char(j); |
| } |
| } |
| if (i < uriLen) { |
| uri[i++] = '\0'; |
| uri[4] = char(i); |
| } else { |
| uri[1] = uri[2] = uri[3] = 0; |
| uri[4] = 5; |
| } |
| } |
| |
| if ( uri[ 1 ] != 0 ) { |
| out.mediaType = uri + uri[ 1 ]; |
| } |
| if ( uri[ 2 ] != 0 ) { |
| out.charset = uri + uri[ 2 ]; |
| } |
| if ( uri[ 3 ] != 0 ) { |
| out.base64 = true; |
| } |
| out.data = uri + uri[4]; |
| out.dataLength = (uri + uriLen) - out.data; |
| |
| return true; |
| } |
| |
| template<bool B> |
| struct DATA |
| { |
| static const uint8_t tableDecodeBase64[128]; |
| }; |
| |
| template<bool B> |
| const uint8_t DATA<B>::tableDecodeBase64[128] = { |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, |
| 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 64, 0, 0, |
| 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
| 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, |
| 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, |
| 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0, 0, 0, 0, 0 |
| }; |
| |
| inline char EncodeCharBase64(uint8_t b) |
| { |
| return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[size_t(b)]; |
| } |
| |
| inline uint8_t DecodeCharBase64(char c) |
| { |
| return DATA<true>::tableDecodeBase64[size_t(c)]; // TODO faster with lookup table or ifs? |
| /*if (c >= 'A' && c <= 'Z') return c - 'A'; |
| if (c >= 'a' && c <= 'z') return c - 'a' + 26; |
| if (c >= '0' && c <= '9') return c - '0' + 52; |
| if (c == '+') return 62; |
| if (c == '/') return 63; |
| return 64; // '-' */ |
| } |
| |
| inline size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out) |
| { |
| ai_assert(inLength % 4 == 0); |
| |
| if (inLength < 4) { |
| out = 0; |
| return 0; |
| } |
| |
| int nEquals = int(in[inLength - 1] == '=') + |
| int(in[inLength - 2] == '='); |
| |
| size_t outLength = (inLength * 3) / 4 - nEquals; |
| out = new uint8_t[outLength]; |
| memset(out, 0, outLength); |
| |
| size_t i, j = 0; |
| |
| for (i = 0; i + 4 < inLength; i += 4) { |
| uint8_t b0 = DecodeCharBase64(in[i]); |
| uint8_t b1 = DecodeCharBase64(in[i + 1]); |
| uint8_t b2 = DecodeCharBase64(in[i + 2]); |
| uint8_t b3 = DecodeCharBase64(in[i + 3]); |
| |
| out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4)); |
| out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2)); |
| out[j++] = (uint8_t)((b2 << 6) | b3); |
| } |
| |
| { |
| uint8_t b0 = DecodeCharBase64(in[i]); |
| uint8_t b1 = DecodeCharBase64(in[i + 1]); |
| uint8_t b2 = DecodeCharBase64(in[i + 2]); |
| uint8_t b3 = DecodeCharBase64(in[i + 3]); |
| |
| out[j++] = (uint8_t)((b0 << 2) | (b1 >> 4)); |
| if (b2 < 64) out[j++] = (uint8_t)((b1 << 4) | (b2 >> 2)); |
| if (b3 < 64) out[j++] = (uint8_t)((b2 << 6) | b3); |
| } |
| |
| return outLength; |
| } |
| |
| |
| |
| inline void EncodeBase64( |
| const uint8_t* in, size_t inLength, |
| std::string& out) |
| { |
| size_t outLength = ((inLength + 2) / 3) * 4; |
| |
| size_t j = out.size(); |
| out.resize(j + outLength); |
| |
| for (size_t i = 0; i < inLength; i += 3) { |
| uint8_t b = (in[i] & 0xFC) >> 2; |
| out[j++] = EncodeCharBase64(b); |
| |
| b = (in[i] & 0x03) << 4; |
| if (i + 1 < inLength) { |
| b |= (in[i + 1] & 0xF0) >> 4; |
| out[j++] = EncodeCharBase64(b); |
| |
| b = (in[i + 1] & 0x0F) << 2; |
| if (i + 2 < inLength) { |
| b |= (in[i + 2] & 0xC0) >> 6; |
| out[j++] = EncodeCharBase64(b); |
| |
| b = in[i + 2] & 0x3F; |
| out[j++] = EncodeCharBase64(b); |
| } |
| else { |
| out[j++] = EncodeCharBase64(b); |
| out[j++] = '='; |
| } |
| } |
| else { |
| out[j++] = EncodeCharBase64(b); |
| out[j++] = '='; |
| out[j++] = '='; |
| } |
| } |
| } |
| |
| } |
| |
| } // ns glTF |