Browse Source

Allow schema checking of glTF2 file.

Malcolm Tyrrell 3 years ago
parent
commit
a283a255a5

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

@@ -74,6 +74,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
@@ -1066,6 +1067,7 @@ class Asset {
 
 private:
     IOSystem *mIOSystem;
+    rapidjson::IRemoteSchemaDocumentProvider *mSchemaDocumentProvider;
 
     std::string mCurrentAssetDir;
 
@@ -1125,8 +1127,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"),
@@ -1149,6 +1152,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();
 
@@ -1160,6 +1166,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);
 

+ 60 - 17
code/AssetLib/glTF2/glTF2Asset.inl

@@ -1777,28 +1777,16 @@ 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;
 
         // read the scene data
@@ -1806,7 +1794,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");
         }
     }
@@ -1826,6 +1814,42 @@ 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();
+    /*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");
+    }
+
+    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)) {
+            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)) {
@@ -1882,6 +1906,25 @@ 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");

+ 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;
 }
@@ -1587,7 +1584,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;
@@ -1613,4 +1610,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