diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index c790a1374..0be998de8 100644 --- a/code/AssetLib/glTF2/glTF2Asset.h +++ b/code/AssetLib/glTF2/glTF2Asset.h @@ -76,6 +76,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #if (__GNUC__ == 8 && __GNUC_MINOR__ >= 0) # pragma GCC diagnostic pop @@ -1092,6 +1093,7 @@ class Asset { private: IOSystem *mIOSystem; + rapidjson::IRemoteSchemaDocumentProvider *mSchemaDocumentProvider; std::string mCurrentAssetDir; @@ -1153,8 +1155,9 @@ public: Ref scene; public: - Asset(IOSystem *io = nullptr) : + Asset(IOSystem *io = nullptr, rapidjson::IRemoteSchemaDocumentProvider *schemaDocumentProvider = nullptr) : mIOSystem(io), + mSchemaDocumentProvider(schemaDocumentProvider), asset(), accessors(*this, "accessors"), animations(*this, "animations"), @@ -1177,6 +1180,9 @@ public: //! Main function void Load(const std::string &file, bool isBinary = false); + //! Parse the AssetMetadata and check that the version is 2. + bool CanRead(const std::string &pFile, bool isBinary = false); + //! Enables binary encoding on the asset void SetAsBinary(); @@ -1188,6 +1194,11 @@ public: private: void ReadBinaryHeader(IOStream &stream, std::vector &sceneData); + //! Obtain a JSON document from the stream. + // \param second argument is a buffer used by the document. It must be kept + // alive while the document is in use. + Document ReadDocument(IOStream& stream, bool isBinary, std::vector& sceneData); + void ReadExtensionsUsed(Document &doc); void ReadExtensionsRequired(Document &doc); diff --git a/code/AssetLib/glTF2/glTF2Asset.inl b/code/AssetLib/glTF2/glTF2Asset.inl index 3fc238208..b570fc8e6 100644 --- a/code/AssetLib/glTF2/glTF2Asset.inl +++ b/code/AssetLib/glTF2/glTF2Asset.inl @@ -90,7 +90,6 @@ namespace { // // JSON Value reading helpers // - inline CustomExtension ReadExtensions(const char *name, Value &obj) { CustomExtension ret; ret.name = name; @@ -127,6 +126,81 @@ inline CustomExtension ReadExtensions(const char *name, Value &obj) { return ret; } +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); + return; + } + + 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; + } +} + +void SetVector(vec4 &v, const float (&in)[4]) { + v[0] = in[0]; + v[1] = in[1]; + v[2] = in[2]; + v[3] = in[3]; +} + +void SetVector(vec3 &v, const float (&in)[3]) { + v[0] = in[0]; + v[1] = in[1]; + v[2] = in[2]; +} + +template +inline int Compare(const char *attr, const char (&str)[N]) { + return (strncmp(attr, str, N - 1) == 0) ? N - 1 : 0; +} + +#if _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4706) +#endif // _MSC_VER + +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, "TANGENT"))) { + v = &(p.attributes.tangent); + } 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 bool GetAttribTargetVector(Mesh::Primitive &p, const int targetIndex, const char *attr, Mesh::AccessorList *&v, int &pos) { + if ((pos = Compare(attr, "POSITION"))) { + v = &(p.targets[targetIndex].position); + } else if ((pos = Compare(attr, "NORMAL"))) { + v = &(p.targets[targetIndex].normal); + } else if ((pos = Compare(attr, "TANGENT"))) { + v = &(p.targets[targetIndex].tangent); + } else + return false; + return true; +} + } // namespace inline Value *Object::FindString(Value &val, const char *memberId) { @@ -456,7 +530,6 @@ Ref LazyDict::Create(const char *id) { // // glTF dictionary objects methods // - inline Buffer::Buffer() : byteLength(0), type(Type_arraybuffer), @@ -865,27 +938,6 @@ inline size_t Accessor::GetMaxByteSize() { return (bufferView ? bufferView->byteLength : sparse->data.size()); } -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; - } - } -} - -} // namespace - template void Accessor::ExtractData(T *&outData) { uint8_t *data = GetPointer(); @@ -959,11 +1011,12 @@ inline void Accessor::WriteSparseIndices(size_t _count, const void *src_idx, siz ai_assert(indices_dst + _count * indices_dst_stride <= indices_buffer_ptr + sparse->indices->buffer->byteLength); CopyData(_count, indices_src, src_idxStride, indices_dst, indices_dst_stride); } + inline Accessor::Indexer::Indexer(Accessor &acc) : - accessor(acc), - data(acc.GetPointer()), - elemSize(acc.GetElementSize()), - stride(acc.GetStride()) { + accessor(acc), + data(acc.GetPointer()), + elemSize(acc.GetElementSize()), + stride(acc.GetStride()) { } //! Accesses the i-th value as defined by the accessor @@ -1241,21 +1294,6 @@ inline void Material::Read(Value &material, Asset &r) { } } -namespace { -void SetVector(vec4 &v, const float (&in)[4]) { - v[0] = in[0]; - v[1] = in[1]; - v[2] = in[2]; - v[3] = in[3]; -} - -void SetVector(vec3 &v, const float (&in)[3]) { - v[0] = in[0]; - v[1] = in[1]; - v[2] = in[2]; -} -} // namespace - inline void Material::SetDefaults() { //pbr materials SetVector(pbrMetallicRoughness.baseColorFactor, defaultBaseColor); @@ -1294,53 +1332,6 @@ inline void MaterialIOR::SetDefaults() { ior = 1.5f; } -namespace { - -template -inline int Compare(const char *attr, const char (&str)[N]) { - return (strncmp(attr, str, N - 1) == 0) ? N - 1 : 0; -} - -#if _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4706) -#endif // _MSC_VER - -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, "TANGENT"))) { - v = &(p.attributes.tangent); - } 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 bool GetAttribTargetVector(Mesh::Primitive &p, const int targetIndex, const char *attr, Mesh::AccessorList *&v, int &pos) { - if ((pos = Compare(attr, "POSITION"))) { - v = &(p.targets[targetIndex].position); - } else if ((pos = Compare(attr, "NORMAL"))) { - v = &(p.targets[targetIndex].normal); - } else if ((pos = Compare(attr, "TANGENT"))) { - v = &(p.targets[targetIndex].tangent); - } else - return false; - return true; -} -} // namespace - inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) { Value *curName = FindMember(pJSON_Object, "name"); if (nullptr != curName && curName->IsString()) { @@ -1811,28 +1802,15 @@ inline void Asset::ReadBinaryHeader(IOStream &stream, std::vector &sceneDa } } -inline void Asset::Load(const std::string &pFile, bool isBinary) { +inline rapidjson::Document Asset::ReadDocument(IOStream &stream, bool isBinary, std::vector &sceneData) { ASSIMP_LOG_DEBUG("Loading GLTF2 asset"); - mCurrentAssetDir.clear(); - /*int pos = std::max(int(pFile.rfind('/')), int(pFile.rfind('\\'))); - if (pos != int(std::string::npos)) */ - - if (0 != strncmp(pFile.c_str(), AI_MEMORYIO_MAGIC_FILENAME, AI_MEMORYIO_MAGIC_FILENAME_LENGTH)) { - mCurrentAssetDir = glTFCommon::getCurrentAssetDir(pFile); - } - - shared_ptr stream(OpenFile(pFile.c_str(), "rb", true)); - if (!stream) { - throw DeadlyImportError("GLTF: Could not open file for reading"); - } // is binary? then read the header - std::vector sceneData; if (isBinary) { SetAsBinary(); // also creates the body buffer - ReadBinaryHeader(*stream, sceneData); + ReadBinaryHeader(stream, sceneData); } else { - mSceneLength = stream->FileSize(); + mSceneLength = stream.FileSize(); mBodyLength = 0; // Binary format only supports up to 4GB of JSON, use that as a maximum @@ -1844,7 +1822,7 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) { sceneData.resize(mSceneLength + 1); sceneData[mSceneLength] = '\0'; - if (stream->Read(&sceneData[0], 1, mSceneLength) != mSceneLength) { + if (stream.Read(&sceneData[0], 1, mSceneLength) != mSceneLength) { throw DeadlyImportError("GLTF: Could not read the file contents"); } } @@ -1869,6 +1847,40 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) { throw DeadlyImportError("GLTF: JSON document root must be a JSON object"); } + return doc; +} + +inline void Asset::Load(const std::string &pFile, bool isBinary) +{ + mCurrentAssetDir.clear(); + if (0 != strncmp(pFile.c_str(), AI_MEMORYIO_MAGIC_FILENAME, AI_MEMORYIO_MAGIC_FILENAME_LENGTH)) { + mCurrentAssetDir = glTFCommon::getCurrentAssetDir(pFile); + } + + shared_ptr stream(OpenFile(pFile.c_str(), "rb", true)); + if (!stream) { + throw DeadlyImportError("GLTF: Could not open file for reading"); + } + + std::vector sceneData; + rapidjson::Document doc = ReadDocument(*stream, isBinary, sceneData); + + // If a schemaDocumentProvider is available, see if the glTF schema is present. + // If so, use it to validate the document. + if (mSchemaDocumentProvider) { + if (const rapidjson::SchemaDocument *gltfSchema = mSchemaDocumentProvider->GetRemoteDocument("glTF.schema.json", 16)) { + // The schemas are found here: https://github.com/KhronosGroup/glTF/tree/main/specification/2.0/schema + rapidjson::SchemaValidator validator(*gltfSchema); + if (!doc.Accept(validator)) { + rapidjson::StringBuffer pathBuffer; + validator.GetInvalidSchemaPointer().StringifyUriFragment(pathBuffer); + rapidjson::StringBuffer argumentBuffer; + validator.GetInvalidDocumentPointer().StringifyUriFragment(argumentBuffer); + throw DeadlyImportError("GLTF: The JSON document did not satisfy the glTF2 schema. Schema keyword: ", validator.GetInvalidSchemaKeyword(), ", document path: ", pathBuffer.GetString(), ", argument: ", argumentBuffer.GetString()); + } + } + } + // Fill the buffer instance for the current file embedded contents if (mBodyLength > 0) { if (!mBodyBuffer->LoadFromStream(*stream, mBodyLength, mBodyOffset)) { @@ -1925,6 +1937,21 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) { } } +inline bool Asset::CanRead(const std::string &pFile, bool isBinary) { + try { + shared_ptr stream(OpenFile(pFile.c_str(), "rb", true)); + if (!stream) { + return false; + } + std::vector sceneData; + rapidjson::Document doc = ReadDocument(*stream, isBinary, sceneData); + asset.Read(doc); + } catch (...) { + return false; + } + return true; +} + inline void Asset::SetAsBinary() { if (!mBodyBuffer) { mBodyBuffer = buffers.Create("binary_glTF"); @@ -2025,7 +2052,7 @@ inline std::string Asset::FindUniqueID(const std::string &str, const char *suffi } #if _MSC_VER -#pragma warning(pop) +# pragma warning(pop) #endif // _MSC_VER } // namespace glTF2 diff --git a/code/AssetLib/glTF2/glTF2Importer.cpp b/code/AssetLib/glTF2/glTF2Importer.cpp index 8fe60820b..a723b571a 100644 --- a/code/AssetLib/glTF2/glTF2Importer.cpp +++ b/code/AssetLib/glTF2/glTF2Importer.cpp @@ -111,19 +111,16 @@ const aiImporterDesc *glTF2Importer::GetInfo() const { return &desc; } -bool glTF2Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /* checkSig */) const { - const std::string &extension = GetExtension(pFile); +bool glTF2Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig ) const { + const std::string &extension = GetExtension(pFile); - if (extension != "gltf" && extension != "glb") { - return false; - } + if (!checkSig && (extension != "gltf") && (extension != "glb")) + return false; - if (pIOHandler) { - glTF2::Asset asset(pIOHandler); - asset.Load(pFile, extension == "glb"); - std::string version = asset.asset.version; - return !version.empty() && version[0] == '2'; - } + if (pIOHandler) { + glTF2::Asset asset(pIOHandler); + return asset.CanRead(pFile, extension == "glb"); + } return false; } @@ -1604,7 +1601,7 @@ void glTF2Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IO this->mScene = pScene; // read the asset file - glTF2::Asset asset(pIOHandler); + glTF2::Asset asset(pIOHandler, static_cast(mSchemaDocumentProvider)); asset.Load(pFile, GetExtension(pFile) == "glb"); if (asset.scene) { pScene->mName = asset.scene->name; @@ -1630,4 +1627,8 @@ void glTF2Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IO } } +void glTF2Importer::SetupProperties(const Importer *pImp) { + mSchemaDocumentProvider = static_cast(pImp->GetPropertyPointer(AI_CONFIG_IMPORT_SCHEMA_DOCUMENT_PROVIDER)); +} + #endif // ASSIMP_BUILD_NO_GLTF_IMPORTER diff --git a/code/AssetLib/glTF2/glTF2Importer.h b/code/AssetLib/glTF2/glTF2Importer.h index e586dc6cc..23830c0eb 100644 --- a/code/AssetLib/glTF2/glTF2Importer.h +++ b/code/AssetLib/glTF2/glTF2Importer.h @@ -65,6 +65,7 @@ public: protected: const aiImporterDesc *GetInfo() const override; void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override; + virtual void SetupProperties(const Importer *pImp) override; private: void ImportEmbeddedTextures(glTF2::Asset &a); @@ -80,6 +81,9 @@ private: std::vector meshOffsets; std::vector embeddedTexIdxs; aiScene *mScene; + + /// An instance of rapidjson::IRemoteSchemaDocumentProvider + void *mSchemaDocumentProvider = nullptr; }; } // namespace Assimp diff --git a/code/Common/Importer.cpp b/code/Common/Importer.cpp index d0ed3c788..41b73fd12 100644 --- a/code/Common/Importer.cpp +++ b/code/Common/Importer.cpp @@ -1072,6 +1072,18 @@ bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value) { return existing; } +// ------------------------------------------------------------------------------------------------ +// Set a configuration property +bool Importer::SetPropertyPointer(const char* szName, void* value) { + ai_assert(nullptr != pimpl); + + bool existing; + ASSIMP_BEGIN_EXCEPTION_REGION(); + existing = SetGenericProperty(pimpl->mPointerProperties, szName,value); + ASSIMP_END_EXCEPTION_REGION(bool); + return existing; +} + // ------------------------------------------------------------------------------------------------ // Get a configuration property int Importer::GetPropertyInteger(const char* szName, int iErrorReturn /*= 0xffffffff*/) const { @@ -1104,6 +1116,14 @@ aiMatrix4x4 Importer::GetPropertyMatrix(const char* szName, const aiMatrix4x4& i return GetGenericProperty(pimpl->mMatrixProperties,szName,iErrorReturn); } +// ------------------------------------------------------------------------------------------------ +// Get a configuration property +void* Importer::GetPropertyPointer(const char* szName, void* iErrorReturn /*= nullptr*/) const { + ai_assert(nullptr != pimpl); + + return GetGenericProperty(pimpl->mPointerProperties,szName,iErrorReturn); +} + // ------------------------------------------------------------------------------------------------ // Get the memory requirements of a single node inline diff --git a/code/Common/Importer.h b/code/Common/Importer.h index 0e04f9452..f79b38214 100644 --- a/code/Common/Importer.h +++ b/code/Common/Importer.h @@ -73,12 +73,12 @@ public: // Data type to store the key hash typedef unsigned int KeyType; - // typedefs for our four configuration maps. - // We don't need more, so there is no need for a generic solution + // typedefs for our configuration maps. typedef std::map IntPropertyMap; typedef std::map FloatPropertyMap; typedef std::map StringPropertyMap; typedef std::map MatrixPropertyMap; + typedef std::map PointerPropertyMap; /** IO handler to use for all file accesses. */ IOSystem* mIOHandler; @@ -116,6 +116,9 @@ public: /** List of Matrix properties */ MatrixPropertyMap mMatrixProperties; + /** List of pointer properties */ + PointerPropertyMap mPointerProperties; + /** Used for testing - extra verbose mode causes the ValidateDataStructure-Step * to be executed before and after every single post-process step */ bool bExtraVerbose; @@ -142,6 +145,7 @@ ImporterPimpl::ImporterPimpl() AI_NO_EXCEPT : mFloatProperties(), mStringProperties(), mMatrixProperties(), + mPointerProperties(), bExtraVerbose( false ), mPPShared( nullptr ) { // empty diff --git a/include/assimp/Importer.hpp b/include/assimp/Importer.hpp index 09b5b6883..6ce327c4f 100644 --- a/include/assimp/Importer.hpp +++ b/include/assimp/Importer.hpp @@ -245,6 +245,12 @@ public: */ bool SetPropertyMatrix(const char *szName, const aiMatrix4x4 &sValue); + // ------------------------------------------------------------------- + /** Set a pointer configuration property. + * @see SetPropertyInteger() + */ + bool SetPropertyPointer(const char *szName, void *sValue); + // ------------------------------------------------------------------- /** Get a configuration property. * @param szName Name of the property. All supported properties @@ -297,6 +303,15 @@ public: aiMatrix4x4 GetPropertyMatrix(const char *szName, const aiMatrix4x4 &sErrorReturn = aiMatrix4x4()) const; + // ------------------------------------------------------------------- + /** Get a pointer configuration property + * + * The return value remains valid until the property is modified. + * @see GetPropertyInteger() + */ + void* GetPropertyPointer(const char *szName, + void *sErrorReturn = nullptr) const; + // ------------------------------------------------------------------- /** Supplies a custom IO handler to the importer to use to open and * access files. If you need the importer to use custom IO logic to diff --git a/include/assimp/config.h.in b/include/assimp/config.h.in index a89d4e837..bf0076572 100644 --- a/include/assimp/config.h.in +++ b/include/assimp/config.h.in @@ -547,6 +547,15 @@ enum aiComponent // Various stuff to fine-tune the behaviour of specific importer plugins. // ########################################################################### +// --------------------------------------------------------------------------- +/** @brief Importers which parse JSON may use this to obtain a pointer to a + * rapidjson::IRemoteSchemaDocumentProvider. + * + * The default value is nullptr + * Property type: void* + */ +#define AI_CONFIG_IMPORT_SCHEMA_DOCUMENT_PROVIDER \ + "IMPORT_SCHEMA_DOCUMENT_PROVIDER" // --------------------------------------------------------------------------- /** @brief Set whether the fbx importer will merge all geometry layers present diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5a0e2e92d..977c734f2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -224,6 +224,19 @@ else() target_sources(unit PUBLIC ${Assimp_SOURCE_DIR}/contrib/gtest/src/gtest-all.cc) endif() +# RapidJSON +IF(ASSIMP_HUNTER_ENABLED) + hunter_add_package(RapidJSON) + find_package(RapidJSON CONFIG REQUIRED) +ELSE() + INCLUDE_DIRECTORIES("../contrib/rapidjson/include") + ADD_DEFINITIONS( -DRAPIDJSON_HAS_STDSTRING=1) + option( ASSIMP_RAPIDJSON_NO_MEMBER_ITERATOR "Suppress rapidjson warning on MSVC (NOTE: breaks android build)" ON ) + if(ASSIMP_RAPIDJSON_NO_MEMBER_ITERATOR) + ADD_DEFINITIONS( -DRAPIDJSON_NOMEMBERITERATORCLASS ) + endif() +ENDIF() + IF (ASSIMP_BUILD_DRACO) ADD_DEFINITIONS( -DASSIMP_ENABLE_DRACO ) ENDIF() diff --git a/test/models/glTF2/SchemaFailures/CesiumLogoFlat.png b/test/models/glTF2/SchemaFailures/CesiumLogoFlat.png new file mode 100644 index 000000000..45d502ed2 Binary files /dev/null and b/test/models/glTF2/SchemaFailures/CesiumLogoFlat.png differ diff --git a/test/models/glTF2/SchemaFailures/sceneWrongType.gltf b/test/models/glTF2/SchemaFailures/sceneWrongType.gltf new file mode 100644 index 000000000..9a97c2b95 --- /dev/null +++ b/test/models/glTF2/SchemaFailures/sceneWrongType.gltf @@ -0,0 +1,181 @@ +{ + "asset": { + "generator": "COLLADA2GLTF", + "version": "2.0" + }, + "scene": "hello", + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "nodes": [ + { + "children": [ + 1 + ], + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "mesh": 0 + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "NORMAL": 1, + "POSITION": 2, + "TEXCOORD_0": 3 + }, + "indices": 0, + "mode": 4, + "material": 0 + } + ], + "name": "Mesh" + } + ], + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5123, + "count": 36, + "max": [ + 23 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "max": [ + 1.0, + 1.0, + 1.0 + ], + "min": [ + -1.0, + -1.0, + -1.0 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 288, + "componentType": 5126, + "count": 24, + "max": [ + 0.5, + 0.5, + 0.5 + ], + "min": [ + -0.5, + -0.5, + -0.5 + ], + "type": "VEC3" + }, + { + "bufferView": 2, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "max": [ + 6.0, + 1.0 + ], + "min": [ + 0.0, + 0.0 + ], + "type": "VEC2" + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 0 + }, + "metallicFactor": 0.0 + }, + "name": "Texture" + } + ], + "textures": [ + { + "sampler": 0, + "source": 0 + } + ], + "images": [ + { + "uri": "CesiumLogoFlat.png" + } + ], + "samplers": [ + { + "magFilter": 9729, + "minFilter": 9986, + "wrapS": 33648, + "wrapT": 33071 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 768, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 576, + "byteStride": 12, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 576, + "byteLength": 192, + "byteStride": 8, + "target": 34962 + } + ], + "buffers": [ + { + "byteLength": 840, + "uri": "BoxTextured0.bin" + } + ] +} diff --git a/test/unit/utglTF2ImportExport.cpp b/test/unit/utglTF2ImportExport.cpp index e29d09145..288fa01a6 100644 --- a/test/unit/utglTF2ImportExport.cpp +++ b/test/unit/utglTF2ImportExport.cpp @@ -49,6 +49,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include @@ -772,3 +773,60 @@ TEST_F(utglTF2ImportExport, wrongTypes) { } } +namespace { + /// This class provides a fake schema to the GLTF importer. + /// It just checks that the file has a top-level "scene" property which is an integer. + class FakeSchemaProvider : public rapidjson::IRemoteSchemaDocumentProvider + { + public: + FakeSchemaProvider(const char* schemaName) : + m_schemaName(schemaName) + { + rapidjson::Document schemaDoc; + schemaDoc.Parse(R"==({"properties":{"scene" : { "type" : "integer" }}, "required": [ "scene" ]})=="); + EXPECT_FALSE(schemaDoc.HasParseError()); + m_schema.reset(new rapidjson::SchemaDocument(schemaDoc, m_schemaName.c_str(), static_cast(m_schemaName.size()), this)); + } + + const rapidjson::SchemaDocument* GetRemoteDocument(const char* uri, rapidjson::SizeType) override { + if (m_schemaName == uri) { + return m_schema.get(); + } + return nullptr; + } + + private: + std::string m_schemaName; + std::unique_ptr m_schema; + }; +} + +TEST_F(utglTF2ImportExport, schemaCheckPass) { + FakeSchemaProvider schemaProvider("glTF.schema.json"); + Assimp::Importer importer; + importer.SetPropertyPointer(AI_CONFIG_IMPORT_SCHEMA_DOCUMENT_PROVIDER, &schemaProvider); + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", aiProcess_ValidateDataStructure); + EXPECT_NE(scene, nullptr); + EXPECT_STREQ(importer.GetErrorString(), ""); +} + +TEST_F(utglTF2ImportExport, schemaCheckFail) { + FakeSchemaProvider schemaProvider("glTF.schema.json"); + Assimp::Importer importer; + importer.SetPropertyPointer(AI_CONFIG_IMPORT_SCHEMA_DOCUMENT_PROVIDER, &schemaProvider); + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/SchemaFailures/sceneWrongType.gltf", aiProcess_ValidateDataStructure); + EXPECT_EQ(scene, nullptr); + const std::string errorString = importer.GetErrorString(); + EXPECT_NE(errorString.find("The JSON document did not satisfy the glTF2 schema"), std::string::npos); +} + +TEST_F(utglTF2ImportExport, noSchemaFound) { + // More than one importer might make use the provider, but not all schemas might be present. + // Check that the glTF importer handles the case when an non-null provider returns null when asked for schemas. + FakeSchemaProvider schemaProvider("missingSchema.json"); + Assimp::Importer importer; + importer.SetPropertyPointer(AI_CONFIG_IMPORT_SCHEMA_DOCUMENT_PROVIDER, &schemaProvider); + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxTextured-glTF/BoxTextured.gltf", aiProcess_ValidateDataStructure); + EXPECT_NE(scene, nullptr); + EXPECT_STREQ(importer.GetErrorString(), ""); +}