2
0
Эх сурвалжийг харах

Merge pull request #4111 from MalcolmTyrrell/MalcolmTyrrell/jsonSchemaSupport

Allow the gltf2 Importer to optionally use glTF 2.0 JSON schemas for initial validation
Kim Kulling 3 жил өмнө
parent
commit
fe73213420

+ 12 - 1
code/AssetLib/glTF2/glTF2Asset.h

@@ -76,6 +76,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <rapidjson/document.h>
 #include <rapidjson/error/en.h>
 #include <rapidjson/rapidjson.h>
+#include <rapidjson/schema.h>
 
 #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> 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<char> &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<char>& sceneData);
+
     void ReadExtensionsUsed(Document &doc);
     void ReadExtensionsRequired(Document &doc);
 

+ 134 - 107
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 <int N>
+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<T> LazyDict<T>::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 <class T>
 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 <int N>
-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<char> &sceneDa
     }
 }
 
-inline void Asset::Load(const std::string &pFile, bool isBinary) {
+inline rapidjson::Document Asset::ReadDocument(IOStream &stream, bool isBinary, std::vector<char> &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<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
-    std::vector<char> 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<IOStream> stream(OpenFile(pFile.c_str(), "rb", true));
+    if (!stream) {
+        throw DeadlyImportError("GLTF: Could not open file for reading");
+    }
+
+    std::vector<char> 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<IOStream> stream(OpenFile(pFile.c_str(), "rb", true));
+        if (!stream) {
+            return false;
+        }
+        std::vector<char> 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

+ 13 - 12
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<rapidjson::IRemoteSchemaDocumentProvider*>(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<rapidjson::IRemoteSchemaDocumentProvider*>(pImp->GetPropertyPointer(AI_CONFIG_IMPORT_SCHEMA_DOCUMENT_PROVIDER));
+}
+
 #endif // ASSIMP_BUILD_NO_GLTF_IMPORTER

+ 4 - 0
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<unsigned int> meshOffsets;
     std::vector<int> embeddedTexIdxs;
     aiScene *mScene;
+
+    /// An instance of rapidjson::IRemoteSchemaDocumentProvider
+    void *mSchemaDocumentProvider = nullptr;
 };
 
 } // namespace Assimp

+ 20 - 0
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<void*>(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<aiMatrix4x4>(pimpl->mMatrixProperties,szName,iErrorReturn);
 }
 
+// ------------------------------------------------------------------------------------------------
+// Get a configuration property
+void* Importer::GetPropertyPointer(const char* szName, void* iErrorReturn /*= nullptr*/) const {
+    ai_assert(nullptr != pimpl);
+    
+    return GetGenericProperty<void*>(pimpl->mPointerProperties,szName,iErrorReturn);
+}
+
 // ------------------------------------------------------------------------------------------------
 // Get the memory requirements of a single node
 inline

+ 6 - 2
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<KeyType, int> IntPropertyMap;
     typedef std::map<KeyType, ai_real> FloatPropertyMap;
     typedef std::map<KeyType, std::string> StringPropertyMap;
     typedef std::map<KeyType, aiMatrix4x4> MatrixPropertyMap;
+    typedef std::map<KeyType, void*> 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

+ 15 - 0
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

+ 9 - 0
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

+ 13 - 0
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()

BIN
test/models/glTF2/SchemaFailures/CesiumLogoFlat.png


+ 181 - 0
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"
+        }
+    ]
+}

+ 58 - 0
test/unit/utglTF2ImportExport.cpp

@@ -49,6 +49,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/LogStream.hpp>
 #include <assimp/DefaultLogger.hpp>
 
+#include <rapidjson/schema.h>
 
 #include <array>
 
@@ -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<rapidjson::SizeType>(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<const rapidjson::SchemaDocument> 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(), "");
+}