浏览代码

Merge branch 'master' into respect-cmake-output-variables

Kim Kulling 3 年之前
父节点
当前提交
6cc469a5ae
共有 67 个文件被更改,包括 1268 次插入735 次删除
  1. 3 14
      code/AssetLib/3DS/3DSLoader.cpp
  2. 5 17
      code/AssetLib/3MF/D3MFImporter.cpp
  3. 3 12
      code/AssetLib/AC/ACLoader.cpp
  4. 3 13
      code/AssetLib/AMF/AMFImporter.cpp
  5. 3 13
      code/AssetLib/ASE/ASELoader.cpp
  6. 2 1
      code/AssetLib/ASE/ASEParser.cpp
  7. 2 2
      code/AssetLib/B3D/B3DImporter.cpp
  8. 3 12
      code/AssetLib/BVH/BVHLoader.cpp
  9. 4 12
      code/AssetLib/Blender/BlenderLoader.cpp
  10. 13 2
      code/AssetLib/C4D/C4DImporter.cpp
  11. 3 11
      code/AssetLib/COB/COBLoader.cpp
  12. 3 12
      code/AssetLib/CSM/CSMLoader.cpp
  13. 7 29
      code/AssetLib/Collada/ColladaLoader.cpp
  14. 30 15
      code/AssetLib/Collada/ColladaParser.cpp
  15. 3 12
      code/AssetLib/DXF/DXFLoader.cpp
  16. 4 12
      code/AssetLib/FBX/FBXImporter.cpp
  17. 7 14
      code/AssetLib/HMP/HMPLoader.cpp
  18. 1 1
      code/AssetLib/IFC/IFCBoolean.cpp
  19. 61 16
      code/AssetLib/IFC/IFCGeometry.cpp
  20. 6 12
      code/AssetLib/IFC/IFCLoader.cpp
  21. 312 50
      code/AssetLib/IFC/IFCOpenings.cpp
  22. 0 1
      code/AssetLib/IFC/IFCUtil.h
  23. 322 0
      code/AssetLib/IQM/IQMImporter.cpp
  24. 78 0
      code/AssetLib/IQM/IQMImporter.h
  25. 134 0
      code/AssetLib/IQM/iqm.h
  26. 3 17
      code/AssetLib/Irr/IRRLoader.cpp
  27. 7 21
      code/AssetLib/Irr/IRRMeshLoader.cpp
  28. 7 15
      code/AssetLib/LWO/LWOLoader.cpp
  29. 6 14
      code/AssetLib/LWS/LWSLoader.cpp
  30. 12 27
      code/AssetLib/M3D/M3DImporter.cpp
  31. 3 12
      code/AssetLib/MD2/MD2Loader.cpp
  32. 3 12
      code/AssetLib/MD3/MD3Loader.cpp
  33. 3 14
      code/AssetLib/MD5/MD5Loader.cpp
  34. 3 13
      code/AssetLib/MDC/MDCLoader.cpp
  35. 12 17
      code/AssetLib/MDL/MDLLoader.cpp
  36. 3 8
      code/AssetLib/MMD/MMDImporter.cpp
  37. 3 17
      code/AssetLib/MS3D/MS3DLoader.cpp
  38. 3 14
      code/AssetLib/NDO/NDOLoader.cpp
  39. 1 3
      code/AssetLib/NFF/NFFLoader.cpp
  40. 3 12
      code/AssetLib/OFF/OFFLoader.cpp
  41. 4 10
      code/AssetLib/Obj/ObjFileImporter.cpp
  42. 3 7
      code/AssetLib/Ogre/OgreImporter.cpp
  43. 3 10
      code/AssetLib/OpenGEX/OpenGEXImporter.cpp
  44. 3 18
      code/AssetLib/Ply/PlyLoader.cpp
  45. 3 4
      code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp
  46. 3 12
      code/AssetLib/Q3D/Q3DLoader.cpp
  47. 2 2
      code/AssetLib/Raw/RawLoader.cpp
  48. 2 5
      code/AssetLib/SIB/SIBImporter.cpp
  49. 11 12
      code/AssetLib/SMD/SMDLoader.cpp
  50. 3 16
      code/AssetLib/STL/STLLoader.cpp
  51. 3 21
      code/AssetLib/Terragen/TerragenLoader.cpp
  52. 9 5
      code/AssetLib/Unreal/UnrealLoader.cpp
  53. 3 11
      code/AssetLib/X/XFileImporter.cpp
  54. 3 19
      code/AssetLib/XGL/XGLLoader.cpp
  55. 6 16
      code/AssetLib/glTF/glTFImporter.cpp
  56. 32 30
      code/AssetLib/glTF2/glTF2Importer.cpp
  57. 1 1
      code/AssetLib/glTF2/glTF2Importer.h
  58. 6 0
      code/CMakeLists.txt
  59. 4 3
      code/Common/BaseImporter.cpp
  60. 5 4
      code/Common/DefaultLogger.cpp
  61. 58 16
      code/Common/Importer.cpp
  62. 6 0
      code/Common/ImporterRegistry.cpp
  63. 9 14
      include/assimp/BaseImporter.h
  64. 2 0
      include/assimp/defs.h
  65. 二进制
      test/models/IQM/Body.jpg
  66. 二进制
      test/models/IQM/Head.jpg
  67. 二进制
      test/models/IQM/mrfixit.iqm

+ 3 - 14
code/AssetLib/3DS/3DSLoader.cpp

@@ -111,20 +111,9 @@ Discreet3DSImporter::~Discreet3DSImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool Discreet3DSImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    std::string extension = GetExtension(pFile);
-    if (extension == "3ds" || extension == "prj") {
-        return true;
-    }
-
-    if (!extension.length() || checkSig) {
-        uint16_t token[3];
-        token[0] = 0x4d4d;
-        token[1] = 0x3dc2;
-        //token[2] = 0x3daa;
-        return CheckMagicToken(pIOHandler, pFile, token, 2, 0, 2);
-    }
-    return false;
+bool Discreet3DSImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const uint16_t token[] = { 0x4d4d, 0x3dc2 /*, 0x3daa */ };
+    return CheckMagicToken(pIOHandler, pFile, token, AI_COUNT_OF(token), 0, sizeof token[0]);
 }
 
 // ------------------------------------------------------------------------------------------------

+ 5 - 17
code/AssetLib/3MF/D3MFImporter.cpp

@@ -90,24 +90,12 @@ D3MFImporter::~D3MFImporter() {
     // empty
 }
 
-bool D3MFImporter::CanRead(const std::string &filename, IOSystem *pIOHandler, bool checkSig) const {
-    const std::string extension(GetExtension(filename));
-    if (extension == desc.mFileExtensions) {
-        return true;
-    } 
-
-    if (!extension.length() || checkSig) {
-        if (nullptr == pIOHandler) {
-            return false;
-        }
-        if (!ZipArchiveIOSystem::isZipArchive(pIOHandler, filename)) {
-            return false;
-        }
-        D3MFOpcPackage opcPackage(pIOHandler, filename);
-        return opcPackage.validate();
+bool D3MFImporter::CanRead(const std::string &filename, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    if (!ZipArchiveIOSystem::isZipArchive(pIOHandler, filename)) {
+        return false;
     }
-
-    return false;
+    D3MF::D3MFOpcPackage opcPackage(pIOHandler, filename);
+    return opcPackage.validate();
 }
 
 void D3MFImporter::SetupProperties(const Importer*) {

+ 3 - 12
code/AssetLib/AC/ACLoader.cpp

@@ -152,18 +152,9 @@ AC3DImporter::~AC3DImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool AC3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    std::string extension = GetExtension(pFile);
-
-    // fixme: are acc and ac3d *really* used? Some sources say they are
-    if (extension == "ac" || extension == "ac3d" || extension == "acc") {
-        return true;
-    }
-    if (!extension.length() || checkSig) {
-        uint32_t token = AI_MAKE_MAGIC("AC3D");
-        return CheckMagicToken(pIOHandler, pFile, &token, 1, 0);
-    }
-    return false;
+bool AC3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const uint32_t tokens[] = { AI_MAKE_MAGIC("AC3D") };
+    return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 3 - 13
code/AssetLib/AMF/AMFImporter.cpp

@@ -503,19 +503,9 @@ void AMFImporter::ParseNode_Metadata(XmlNode &node) {
     mNodeElement_List.push_back(ne); // and to node element list because its a new object in graph.
 }
 
-bool AMFImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool pCheckSig) const {
-    const std::string extension = GetExtension(pFile);
-
-    if (extension == "amf") {
-        return true;
-    }
-
-    if (extension.empty() || pCheckSig) {
-        static const char * const tokens[] = { "<amf" };
-        return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
-    }
-
-    return false;
+bool AMFImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*pCheckSig*/) const {
+    static const char *tokens[] = { "<amf" };
+    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 const aiImporterDesc *AMFImporter::GetInfo() const {

+ 3 - 13
code/AssetLib/ASE/ASELoader.cpp

@@ -95,19 +95,9 @@ ASEImporter::~ASEImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool ASEImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool cs) const {
-    // check file extension
-    const std::string extension = GetExtension(pFile);
-
-    if (extension == "ase" || extension == "ask") {
-        return true;
-    }
-
-    if ((!extension.length() || cs) && pIOHandler) {
-        static const char * const tokens[] = { "*3dsmax_asciiexport" };
-        return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
-    }
-    return false;
+bool ASEImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const char *tokens[] = { "*3dsmax_asciiexport" };
+    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 2 - 1
code/AssetLib/ASE/ASEParser.cpp

@@ -486,8 +486,9 @@ void Parser::ParseLV1MaterialListBlock() {
                 ParseLV4MeshLong(iIndex);
 
                 if (iIndex >= iMaterialCount) {
-                    LogWarning("Out of range: material index is too large");
+                    LogError("Out of range: material index is too large");
                     iIndex = iMaterialCount - 1;
+                    return;
                 }
 
                 // get a reference to the material

+ 2 - 2
code/AssetLib/B3D/B3DImporter.cpp

@@ -81,7 +81,7 @@ static const aiImporterDesc desc = {
 
 //#define DEBUG_B3D
 
-template <typename T>
+template<typename T>
 void DeleteAllBarePointers(std::vector<T> &x) {
     for (auto p : x) {
         delete p;
@@ -89,11 +89,11 @@ void DeleteAllBarePointers(std::vector<T> &x) {
 }
 
 B3DImporter::~B3DImporter() {
+    // empty
 }
 
 // ------------------------------------------------------------------------------------------------
 bool B3DImporter::CanRead(const std::string &pFile, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const {
-
     size_t pos = pFile.find_last_of('.');
     if (pos == string::npos) {
         return false;

+ 3 - 12
code/AssetLib/BVH/BVHLoader.cpp

@@ -92,18 +92,9 @@ BVHLoader::~BVHLoader() {}
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool BVHLoader::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool cs) const {
-    // check file extension
-    const std::string extension = GetExtension(pFile);
-
-    if (extension == "bvh")
-        return true;
-
-    if ((!extension.length() || cs) && pIOHandler) {
-        static const char * const tokens[] = { "HIERARCHY" };
-        return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
-    }
-    return false;
+bool BVHLoader::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const char *tokens[] = { "HIERARCHY" };
+    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 4 - 12
code/AssetLib/Blender/BlenderLoader.cpp

@@ -114,22 +114,14 @@ BlenderImporter::~BlenderImporter() {
 }
 
 static const char * const Tokens[] = { "BLENDER" };
-static const char * const TokensForSearch[] = { "blender" };
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool BlenderImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    const std::string &extension = GetExtension(pFile);
-    if (extension == "blend") {
-        return true;
-    }
-
-    if ((!extension.length() || checkSig) && pIOHandler) {
-        // note: this won't catch compressed files
-        return SearchFileHeaderForToken(pIOHandler, pFile, TokensForSearch, 1);
-    }
+bool BlenderImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    // note: this won't catch compressed files
+    static const char *tokens[] = { "<BLENDER", "blender" };
 
-    return false;
+    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 13 - 2
code/AssetLib/C4D/C4DImporter.cpp

@@ -106,14 +106,25 @@ static const aiImporterDesc desc = {
 
 
 // ------------------------------------------------------------------------------------------------
-bool C4DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const {
+C4DImporter::C4DImporter()
+: BaseImporter() {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+C4DImporter::~C4DImporter() {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+bool C4DImporter::CanRead( const std::string& pFile, IOSystem* /*pIOHandler*/, bool /*checkSig*/) const {
     const std::string& extension = GetExtension(pFile);
     if (extension == "c4d") {
         return true;
     } else if ((!extension.length() || checkSig) && pIOHandler)   {
         // TODO
     }
-
+    
     return false;
 }
 

+ 3 - 11
code/AssetLib/COB/COBLoader.cpp

@@ -103,17 +103,9 @@ COBImporter::~COBImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool COBImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    const std::string &extension = GetExtension(pFile);
-    if (extension == "cob" || extension == "scn" || extension == "COB" || extension == "SCN") {
-        return true;
-    }
-
-    else if ((!extension.length() || checkSig) && pIOHandler) {
-        static const char * const tokens[] = { "Caligary" };
-        return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
-    }
-    return false;
+bool COBImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const char *tokens[] = { "Caligary" };
+    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 3 - 12
code/AssetLib/CSM/CSMLoader.cpp

@@ -90,19 +90,10 @@ CSMImporter::~CSMImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool CSMImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
+bool CSMImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const
 {
-    // check file extension
-    const std::string extension = GetExtension(pFile);
-
-    if( extension == "csm")
-        return true;
-
-    if ((checkSig || !extension.length()) && pIOHandler) {
-        static const char * const tokens[] = {"$Filename"};
-        return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
-    }
-    return false;
+    static const char* tokens[] = {"$Filename"};
+    return SearchFileHeaderForToken(pIOHandler,pFile,tokens,AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 7 - 29
code/AssetLib/Collada/ColladaLoader.cpp

@@ -116,37 +116,15 @@ ColladaLoader::~ColladaLoader() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool ColladaLoader::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    // check file extension
-    const std::string extension = GetExtension(pFile);
-    const bool readSig = checkSig && (pIOHandler != nullptr);
-    if (!readSig) {
-        if (extension == "dae" || extension == "zae") {
-            return true;
-        }
-    } else {
-        // Look for a DAE file inside, but don't extract it
-        ZipArchiveIOSystem zip_archive(pIOHandler, pFile);
-        if (zip_archive.isOpen()) {
-            return !ColladaParser::ReadZaeManifest(zip_archive).empty();
-        }
-    }
-
-    // XML - too generic, we need to open the file and search for typical keywords
-    if (extension == "xml" || !extension.length() || checkSig) {
-        //  If CanRead() is called in order to check whether we
-        //  support a specific file extension in general pIOHandler
-        //  might be nullptr and it's our duty to return true here.
-        if (nullptr == pIOHandler) {
-            return true;
-        }
-        static const char * const tokens[] = {
-            "<collada"
-        };
-        return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
+bool ColladaLoader::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    // Look for a DAE file inside, but don't extract it
+    ZipArchiveIOSystem zip_archive(pIOHandler, pFile);
+    if (zip_archive.isOpen()) {
+        return !ColladaParser::ReadZaeManifest(zip_archive).empty();
     }
 
-    return false;
+    static const char *tokens[] = { "<collada" };
+    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 30 - 15
code/AssetLib/Collada/ColladaParser.cpp

@@ -331,7 +331,16 @@ void ColladaParser::ReadAssetInfo(XmlNode &node) {
         const std::string &currentName = currentNode.name();
         if (currentName == "unit") {
             mUnitSize = 1.f;
-            XmlParser::getRealAttribute(currentNode, "meter", mUnitSize);
+            std::string tUnitSizeString;
+            if (XmlParser::getStdStrAttribute(currentNode, "meter", tUnitSizeString)) {
+                try {
+                    fast_atoreal_move<ai_real>(tUnitSizeString.data(), mUnitSize);
+                } catch (const DeadlyImportError& die) {
+                    std::string warning("Collada: Failed to parse meter parameter to real number. Exception:\n");
+                    warning.append(die.what());
+                    ASSIMP_LOG_WARN(warning.data());
+                }
+            }
         } else if (currentName == "up_axis") {
             std::string v;
             if (!XmlParser::getValueAsString(currentNode, v)) {
@@ -2242,20 +2251,26 @@ void ColladaParser::ReadNodeGeometry(XmlNode &node, Node *pNode) {
         if (currentName == "bind_material") {
             XmlNode techNode = currentNode.child("technique_common");
             if (techNode) {
-                XmlNode instanceMatNode = techNode.child("instance_material");
-                // read ID of the geometry subgroup and the target material
-                std::string group;
-                XmlParser::getStdStrAttribute(instanceMatNode, "symbol", group);
-                XmlParser::getStdStrAttribute(instanceMatNode, "target", url);
-                const char *urlMat = url.c_str();
-                Collada::SemanticMappingTable s;
-                if (urlMat[0] == '#')
-                    urlMat++;
-
-                s.mMatName = urlMat;
-                // store the association
-                instance.mMaterials[group] = s;
-                ReadMaterialVertexInputBinding(instanceMatNode, s);
+                for (XmlNode instanceMatNode = techNode.child("instance_material"); instanceMatNode; instanceMatNode = instanceMatNode.next_sibling())
+                {
+                    const std::string &instance_name = instanceMatNode.name();
+                    if (instance_name == "instance_material")
+                    {
+                        // read ID of the geometry subgroup and the target material
+                        std::string group;
+                        XmlParser::getStdStrAttribute(instanceMatNode, "symbol", group);
+                        XmlParser::getStdStrAttribute(instanceMatNode, "target", url);
+                        const char *urlMat = url.c_str();
+                        Collada::SemanticMappingTable s;
+                        if (urlMat[0] == '#')
+                            urlMat++;
+
+                        s.mMatName = urlMat;
+                        // store the association
+                        instance.mMaterials[group] = s;
+                        ReadMaterialVertexInputBinding(instanceMatNode, s);
+                    }
+                }
             }
         }
     }

+ 3 - 12
code/AssetLib/DXF/DXFLoader.cpp

@@ -123,18 +123,9 @@ DXFImporter::~DXFImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool DXFImporter::CanRead( const std::string& filename, IOSystem* pIOHandler, bool checkSig ) const {
-    const std::string& extension = GetExtension( filename );
-    if ( extension == desc.mFileExtensions ) {
-        return true;
-    }
-
-    if ( extension.empty() || checkSig ) {
-        static const char * const pTokens[] = { "SECTION", "HEADER", "ENDSEC", "BLOCKS" };
-        return SearchFileHeaderForToken(pIOHandler, filename, pTokens, 4, 32 );
-    }
-
-    return false;
+bool DXFImporter::CanRead( const std::string& filename, IOSystem* pIOHandler, bool /*checkSig*/ ) const {
+    static const char *tokens[] = { "SECTION", "HEADER", "ENDSEC", "BLOCKS" };
+    return SearchFileHeaderForToken(pIOHandler, filename, tokens, AI_COUNT_OF(tokens), 32);
 }
 
 // ------------------------------------------------------------------------------------------------

+ 4 - 12
code/AssetLib/FBX/FBXImporter.cpp

@@ -100,18 +100,10 @@ FBXImporter::~FBXImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool FBXImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-	const std::string &extension = GetExtension(pFile);
-	if (extension == std::string(desc.mFileExtensions)) {
-		return true;
-	}
-
-	else if ((!extension.length() || checkSig) && pIOHandler) {
-		// at least ASCII-FBX files usually have a 'FBX' somewhere in their head
-		static const char * const tokens[] = { "fbx" };
-		return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
-	}
-	return false;
+bool FBXImporter::CanRead(const std::string & pFile, IOSystem * pIOHandler, bool /*checkSig*/) const {
+	// at least ASCII-FBX files usually have a 'FBX' somewhere in their head
+	static const char *tokens[] = { "fbx" };
+	return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 7 - 14
code/AssetLib/HMP/HMPLoader.cpp

@@ -84,20 +84,13 @@ HMPImporter::~HMPImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool HMPImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool cs) const {
-    const std::string extension = GetExtension(pFile);
-    if (extension == "hmp")
-        return true;
-
-    // if check for extension is not enough, check for the magic tokens
-    if (!extension.length() || cs) {
-        uint32_t tokens[3];
-        tokens[0] = AI_HMP_MAGIC_NUMBER_LE_4;
-        tokens[1] = AI_HMP_MAGIC_NUMBER_LE_5;
-        tokens[2] = AI_HMP_MAGIC_NUMBER_LE_7;
-        return CheckMagicToken(pIOHandler, pFile, tokens, 3, 0);
-    }
-    return false;
+bool HMPImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const uint32_t tokens[] = {
+        AI_HMP_MAGIC_NUMBER_LE_4,
+        AI_HMP_MAGIC_NUMBER_LE_5,
+        AI_HMP_MAGIC_NUMBER_LE_7
+    };
+    return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 1 - 1
code/AssetLib/IFC/IFCBoolean.cpp

@@ -699,7 +699,7 @@ void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedArea
             continue;
         }
 
-        GenerateOpenings(openings, std::vector<IfcVector3>(1, IfcVector3(1, 0, 0)), temp, false, true);
+        GenerateOpenings(openings, temp, false, true);
         result.Append(temp);
 
         vit += pcount;

+ 61 - 16
code/AssetLib/IFC/IFCGeometry.cpp

@@ -190,7 +190,7 @@ void ProcessPolygonBoundaries(TempMesh& result, const TempMesh& inmesh, size_t m
     std::copy(outer_vit, outer_vit+outer_polygon_size,
         std::back_inserter(temp.mVerts));
 
-    GenerateOpenings(fake_openings, normals, temp, false, false);
+    GenerateOpenings(fake_openings, temp, false, false);
     result.Append(temp);
 }
 
@@ -529,6 +529,31 @@ IfcMatrix3 DerivePlaneCoordinateSpace(const TempMesh& curmesh, bool& ok, IfcVect
     return m;
 }
 
+const auto closeDistance = 1e-6;
+
+bool areClose(Schema_2x3::IfcCartesianPoint pt1,Schema_2x3::IfcCartesianPoint pt2) {
+    if(pt1.Coordinates.size() != pt2.Coordinates.size())
+    {
+        IFCImporter::LogWarn("unable to compare differently-dimensioned points");
+        return false;
+    }
+    auto coord1 = pt1.Coordinates.begin();
+    auto coord2 = pt2.Coordinates.begin();
+    // we're just testing each dimension separately rather than doing euclidean distance, as we're
+    // looking for very close coordinates
+    for(; coord1 != pt1.Coordinates.end(); coord1++,coord2++)
+    {
+        if(std::fabs(*coord1 - *coord2) > closeDistance)
+            return false;
+    }
+    return true;
+}
+
+bool areClose(IfcVector3 pt1,IfcVector3 pt2) {
+    return (std::fabs(pt1.x - pt2.x) < closeDistance &&
+        std::fabs(pt1.y - pt2.y) < closeDistance &&
+        std::fabs(pt1.z - pt2.z) < closeDistance);
+}
 // Extrudes the given polygon along the direction, converts it into an opening or applies all openings as necessary.
 void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const TempMesh& curve,
     const IfcVector3& extrusionDir, TempMesh& result, ConversionData &conv, bool collect_openings)
@@ -592,7 +617,21 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te
                 nors.push_back(IfcVector3());
                 continue;
             }
-            nors.push_back(((bounds.mVerts[2] - bounds.mVerts[0]) ^ (bounds.mVerts[1] - bounds.mVerts[0])).Normalize());
+            auto nor = ((bounds.mVerts[2] - bounds.mVerts[0]) ^ (bounds.mVerts[1] - bounds.mVerts[0])).Normalize();
+            auto vI0 = bounds.mVertcnt[0];
+            for(size_t faceI = 0; faceI < bounds.mVertcnt.size(); faceI++)
+            {
+                if(bounds.mVertcnt[faceI] >= 3) {
+                    // do a check that this is at least parallel to the base plane
+                    auto nor2 = ((bounds.mVerts[vI0 + 2] - bounds.mVerts[vI0]) ^ (bounds.mVerts[vI0 + 1] - bounds.mVerts[vI0])).Normalize();
+                    if(!areClose(nor,nor2)) {
+                        std::stringstream msg;
+                        msg << "Face " << faceI << " is not parallel with face 0 - opening on entity " << solid.GetID();
+                        IFCImporter::LogWarn(msg.str().c_str());
+                    }
+                }
+            }
+            nors.push_back(nor);
         }
     }
 
@@ -613,7 +652,7 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te
         out.push_back(in[i] + dir);
 
         if( openings ) {
-            if( (in[i] - in[next]).Length() > diag * 0.1 && GenerateOpenings(*conv.apply_openings, nors, temp, true, true, dir) ) {
+            if( (in[i] - in[next]).Length() > diag * 0.1 && GenerateOpenings(*conv.apply_openings, temp, true, true, dir) ) {
                 ++sides_with_openings;
             }
 
@@ -622,31 +661,33 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te
         }
     }
 
-    if( openings ) {
+    if(openings) {
         for(TempOpening& opening : *conv.apply_openings) {
-            if( !opening.wallPoints.empty() ) {
-                IFCImporter::LogError("failed to generate all window caps");
+            if(!opening.wallPoints.empty()) {
+                std::stringstream msg;
+                msg << "failed to generate all window caps on ID " << (int)solid.GetID();
+                IFCImporter::LogError(msg.str().c_str());
             }
             opening.wallPoints.clear();
         }
     }
 
     size_t sides_with_v_openings = 0;
-    if( has_area ) {
+    if(has_area) {
 
-        for( size_t n = 0; n < 2; ++n ) {
-            if( n > 0 ) {
-                for( size_t i = 0; i < in.size(); ++i )
+        for(size_t n = 0; n < 2; ++n) {
+            if(n > 0) {
+                for(size_t i = 0; i < in.size(); ++i)
                     out.push_back(in[i] + dir);
             }
             else {
-                for( size_t i = in.size(); i--; )
+                for(size_t i = in.size(); i--; )
                     out.push_back(in[i]);
             }
 
             curmesh.mVertcnt.push_back(static_cast<unsigned int>(in.size()));
-            if( openings && in.size() > 2 ) {
-                if( GenerateOpenings(*conv.apply_openings, nors, temp, true, true, dir) ) {
+            if(openings && in.size() > 2) {
+                if(GenerateOpenings(*conv.apply_openings,temp,true,true,dir)) {
                     ++sides_with_v_openings;
                 }
 
@@ -656,8 +697,10 @@ void ProcessExtrudedArea(const Schema_2x3::IfcExtrudedAreaSolid& solid, const Te
         }
     }
 
-    if( openings && (sides_with_openings == 1 || sides_with_v_openings == 2 ) ) {
-        IFCImporter::LogWarn("failed to resolve all openings, presumably their topology is not supported by Assimp");
+    if (openings && (sides_with_openings == 1 || sides_with_v_openings == 2)) {
+        std::stringstream msg;
+        msg << "failed to resolve all openings, presumably their topology is not supported by Assimp - ID " << solid.GetID() << " sides_with_openings " << sides_with_openings << " sides_with_v_openings " << sides_with_v_openings;
+        IFCImporter::LogWarn(msg.str().c_str());
     }
 
     IFCImporter::LogVerboseDebug("generate mesh procedurally by extrusion (IfcExtrudedAreaSolid)");
@@ -781,7 +824,9 @@ bool ProcessGeometricItem(const Schema_2x3::IfcRepresentationItem& geo, unsigned
         return false;
     }
     else {
-        IFCImporter::LogWarn("skipping unknown IfcGeometricRepresentationItem entity, type is ", geo.GetClassName());
+        std::stringstream toLog;
+        toLog << "skipping unknown IfcGeometricRepresentationItem entity, type is " << geo.GetClassName() << " id is " << geo.GetID();
+        IFCImporter::LogWarn(toLog.str().c_str());
         return false;
     }
 

+ 6 - 12
code/AssetLib/IFC/IFCLoader.cpp

@@ -129,18 +129,12 @@ IFCImporter::~IFCImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool IFCImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    const std::string &extension = GetExtension(pFile);
-    if (extension == "ifc" || extension == "ifczip") {
-        return true;
-    } else if ((!extension.length() || checkSig) && pIOHandler) {
-        // note: this is the common identification for STEP-encoded files, so
-        // it is only unambiguous as long as we don't support any further
-        // file formats with STEP as their encoding.
-        static const char * const tokens[] = { "ISO-10303-21" };
-        return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
-    }
-    return false;
+bool IFCImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    // note: this is the common identification for STEP-encoded files, so
+    // it is only unambiguous as long as we don't support any further
+    // file formats with STEP as their encoding.
+    static const char *tokens[] = { "ISO-10303-21" };
+    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 312 - 50
code/AssetLib/IFC/IFCOpenings.cpp

@@ -58,6 +58,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #endif
 
 #include <iterator>
+#include <forward_list>
+#include <deque>
 
 namespace Assimp {
     namespace IFC {
@@ -73,7 +75,7 @@ namespace Assimp {
 
 
         // fallback method to generate wall openings
-        bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,const std::vector<IfcVector3>& nors,
+        bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
             TempMesh& curmesh);
 
 
@@ -1140,7 +1142,6 @@ IfcMatrix4 ProjectOntoPlane(std::vector<IfcVector2>& out_contour, const TempMesh
 
 // ------------------------------------------------------------------------------------------------
 bool GenerateOpenings(std::vector<TempOpening>& openings,
-    const std::vector<IfcVector3>& nors,
     TempMesh& curmesh,
     bool check_intersection,
     bool generate_connection_geometry,
@@ -1340,7 +1341,7 @@ bool GenerateOpenings(std::vector<TempOpening>& openings,
                 MergeWindowContours(temp_contour, other, poly);
 
                 if (poly.size() > 1) {
-                    return TryAddOpenings_Poly2Tri(openings, nors, curmesh);
+                    return TryAddOpenings_Poly2Tri(openings, curmesh);
                 }
                 else if (poly.size() == 0) {
                     IFCImporter::LogWarn("ignoring duplicate opening");
@@ -1427,8 +1428,289 @@ bool GenerateOpenings(std::vector<TempOpening>& openings,
     return true;
 }
 
+std::vector<IfcVector2> GetContourInPlane2D(std::shared_ptr<TempMesh> mesh,IfcMatrix3 planeSpace,
+    IfcVector3 planeNor,IfcFloat planeOffset,
+    IfcVector3 extrusionDir,IfcVector3& wall_extrusion,bool& first,bool& ok) {
+    std::vector<IfcVector2> contour;
+
+    const auto outernor = ((mesh->mVerts[2] - mesh->mVerts[0]) ^ (mesh->mVerts[1] - mesh->mVerts[0])).Normalize();
+    const IfcFloat dot = planeNor * outernor;
+    if(std::fabs(dot) < 1.f - 1e-6f) {
+        std::stringstream msg;
+        msg << "Skipping: Unaligned opening (" << planeNor.x << ", " << planeNor.y << ", " << planeNor.z << ")";
+        msg << " . ( " << outernor.x << ", " << outernor.y << ", " << outernor.z << ") = " << dot;
+        IFCImporter::LogDebug(msg.str().c_str());
+        ok = false;
+        return contour;
+    }
+
+    const std::vector<IfcVector3>& va = mesh->mVerts;
+    if(va.size() <= 2) {
+        std::stringstream msg;
+        msg << "Skipping: Only " << va.size() << " verticies in opening mesh.";
+        IFCImporter::LogDebug(msg.str().c_str());
+        ok = false;
+        return contour;
+    }
+
+    for(const IfcVector3& xx : mesh->mVerts) {
+        IfcVector3 vv = planeSpace * xx,vv_extr = planeSpace * (xx + extrusionDir);
+
+        const bool is_extruded_side = std::fabs(vv.z - planeOffset) > std::fabs(vv_extr.z - planeOffset);
+        if(first) {
+            first = false;
+            if(dot > 0.f) {
+                wall_extrusion = extrusionDir;
+                if(is_extruded_side) {
+                    wall_extrusion = -wall_extrusion;
+                }
+            }
+        }
+
+        // XXX should not be necessary - but it is. Why? For precision reasons?
+        vv = is_extruded_side ? vv_extr : vv;
+        contour.push_back(IfcVector2(vv.x,vv.y));
+    }
+    ok = true;
+
+    return contour;
+}
+
+const float close { 1e-6f };
+
+static bool isClose(IfcVector2 first,IfcVector2 second) {
+    auto diff = (second - first);
+    return (std::fabs(diff.x) < close && std::fabs(diff.y) < close);
+}
+
+static void logSegment(std::pair<IfcVector2,IfcVector2> segment) {
+    std::stringstream msg2;
+    msg2 << " Segment: \n";
+    msg2 << "   " << segment.first.x << " " << segment.first.y << " \n";
+    msg2 << "   " << segment.second.x << " " << segment.second.y << " \n";
+    IFCImporter::LogInfo(msg2.str().c_str());
+}
+
+std::vector<std::vector<IfcVector2>> GetContoursInPlane3D(std::shared_ptr<TempMesh> mesh,IfcMatrix3 planeSpace,
+    IfcFloat planeOffset) {
+
+        {
+            std::stringstream msg;
+            msg << "GetContoursInPlane3D: planeSpace is \n";
+            msg << planeSpace.a1 << " " << planeSpace.a2 << " " << planeSpace.a3 << " " << "\n";
+            msg << planeSpace.b1 << " " << planeSpace.b2 << " " << planeSpace.b3 << " " << "\n";
+            msg << planeSpace.c1 << " " << planeSpace.c2 << " " << planeSpace.c3 << " " << "\n";
+            msg << "\n planeOffset is " << planeOffset;
+            IFCImporter::LogInfo(msg.str().c_str());
+        }
+
+        // we'll put our line segments in here, and then merge them together into contours later
+        std::deque<std::pair<IfcVector2,IfcVector2>> lineSegments;
+
+        // find the lines giving the intersection of the faces with the plane - we'll work in planeSpace throughout.
+        size_t vI0{ 0 }; // vertex index for first vertex in plane
+        for(auto nVertices : mesh->mVertcnt) { // iterate over faces
+            {
+                std::stringstream msg;
+                msg << "GetContoursInPlane3D: face (transformed) is  \n";
+                for(auto vI = vI0; vI < vI0 + nVertices; vI++) {
+                    auto v = planeSpace * mesh->mVerts[vI];
+                    msg << "   " << v.x << " " << v.y << " " << v.z << " " << "\n";
+                }
+                IFCImporter::LogInfo(msg.str().c_str());
+            }
+
+            if(nVertices <= 2) // not a plane, a point or line
+            {
+                std::stringstream msg;
+                msg << "GetContoursInPlane3D: found point or line when expecting plane (only " << nVertices << " vertices)";
+                IFCImporter::LogWarn(msg.str().c_str());
+                vI0 += nVertices;
+                continue;
+            }
+
+            auto v0 = planeSpace * mesh->mVerts[vI0];
+
+            // now calculate intersections between face and plane
+            IfcVector2 firstPoint;
+            bool gotFirstPoint(false);
+
+            if(std::fabs(v0.z - planeOffset) < close) {
+                // first point is on the plane
+                firstPoint.x = v0.x;
+                firstPoint.y = v0.y;
+                gotFirstPoint = true;
+            }
+
+            auto vn = v0;
+            for(auto vI = vI0 + 1; vI < vI0 + nVertices; vI++) {
+                auto vp = vn;
+                vn = planeSpace * mesh->mVerts[vI];
+                IfcVector3 intersection;
+
+                if(std::fabs(vn.z - planeOffset) < close) {
+                    // on the plane
+                    intersection = vn;
+                }
+                else if((vn.z > planeOffset) != (vp.z > planeOffset))
+                {
+                    // passes through the plane
+                    auto vdir = vn - vp;
+                    auto scale = (planeOffset - vp.z) / vdir.z;
+                    intersection = vp + scale * vdir;
+                }
+                else {
+                    // nowhere near - move on
+                    continue;
+                }
+
+                if(!gotFirstPoint) {
+                    if(std::fabs(vp.z - planeOffset) < close) {
+                        // just had a second line along the plane
+                        firstPoint.x = vp.x;
+                        firstPoint.y = vp.y;
+                        IfcVector2 secondPoint(intersection.x,intersection.y);
+                        auto s = std::pair<IfcVector2,IfcVector2>(firstPoint,secondPoint);
+                        logSegment(s);
+                        lineSegments.push_back(s);
+                        // next firstpoint should be this one
+                    }
+                    else {
+                        // store the first intersection point
+                        firstPoint.x = intersection.x;
+                        firstPoint.y = intersection.y;
+                        gotFirstPoint = true;
+                    }
+                }
+                else {
+                    // now got the second point, so store the pair
+                    IfcVector2 secondPoint(intersection.x,intersection.y);
+                    auto s = std::pair<IfcVector2,IfcVector2>(firstPoint,secondPoint);
+                    logSegment(s);
+                    lineSegments.push_back(s);
+
+                    // - note that we don't move onto the next face as a non-convex face can create two or more intersections with a plane
+                    gotFirstPoint = false;
+                }
+            }
+            if(gotFirstPoint) {
+                IFCImporter::LogWarn("GetContoursInPlane3D: odd number of intersections with plane");
+            }
+            vI0 += nVertices;
+        }
+
+        {
+            std::stringstream msg;
+            msg << "GetContoursInPlane3D: found " << lineSegments.size() << " line segments:\n";
+            IFCImporter::LogInfo(msg.str().c_str());
+
+            for(auto& s : lineSegments) {
+                logSegment(s);
+            }
+
+        }
+
+        // now merge contours until we have the best-looking polygons we can
+        std::vector<Contour> contours;
+        while(!lineSegments.empty()) {
+            // start with a polygon and make the best closed contour we can
+            const auto& firstSeg = lineSegments.front();
+            std::deque<IfcVector2> contour{ firstSeg.first, firstSeg.second };
+            lineSegments.pop_front();
+            bool foundNextPoint{ true };
+            bool closedContour{ false };
+            while(foundNextPoint) {
+                foundNextPoint = false;
+                for(auto nextSeg = lineSegments.begin(); nextSeg != lineSegments.end(); nextSeg++) {
+                    // see if we can match up both ends - in which case we've closed the contour
+                    if((isClose(contour.front(),nextSeg->first) && isClose(contour.back(),nextSeg->second)) ||
+                        (isClose(contour.back(),nextSeg->first) && isClose(contour.front(),nextSeg->second))
+                        ) {
+                        lineSegments.erase(nextSeg);
+                        closedContour = true;
+                        break;
+                    }
+
+                    // otherwise, see if we can match up either end
+                    foundNextPoint = true;
+                    if(isClose(contour.front(),nextSeg->first)) {
+                        contour.push_front(nextSeg->second);
+                    }
+                    else if(isClose(contour.front(),nextSeg->second)) {
+                        contour.push_front(nextSeg->first);
+                    }
+                    else if(isClose(contour.back(),nextSeg->first)) {
+                        contour.push_back(nextSeg->second);
+                    }
+                    else if(isClose(contour.back(),nextSeg->second)) {
+                        contour.push_back(nextSeg->first);
+                    }
+                    else {
+                        foundNextPoint = false;
+                    }
+                    if(foundNextPoint) {
+                        lineSegments.erase(nextSeg);
+                        break;
+                    }
+                }
+            }
+
+            if(!closedContour) {
+                IFCImporter::LogWarn("GetContoursInPlane3D: did not close contour");
+            }
+
+            // now add the contour if we can
+            if(contour.size() <= 2) {
+                IFCImporter::LogWarn("GetContoursInPlane3D: discarding line/point contour");
+                continue;
+            }
+            Contour c{};
+            for(auto p : contour)
+            {
+                c.push_back(p);
+            }
+            contours.push_back(c);
+        }
+
+        {
+            std::stringstream msg;
+            msg << "GetContoursInPlane3D: found " << contours.size() << " contours:\n";
+
+            for(auto c : contours) {
+                msg << " Contour: \n";
+                for(auto p : c) {
+                    msg << "   " << p.x << " " << p.y << " \n";
+                }
+            }
+
+            IFCImporter::LogInfo(msg.str().c_str());
+        }
+
+
+        return contours;
+}
+
+std::vector<std::vector<IfcVector2>> GetContoursInPlane(std::shared_ptr<TempMesh> mesh,IfcMatrix3 planeSpace,
+    IfcVector3 planeNor,IfcFloat planeOffset,
+    IfcVector3 extrusionDir,IfcVector3& wall_extrusion,bool& first) {
+
+    if(mesh->mVertcnt.size() == 1)
+    {
+        bool ok;
+        auto contour = GetContourInPlane2D(mesh,planeSpace,planeNor,planeOffset,extrusionDir,wall_extrusion,first,ok);
+        if(ok)
+            return std::vector<std::vector<IfcVector2>> {contour};
+        else
+            return std::vector<std::vector<IfcVector2>> {};
+    }
+    else
+    {
+        return GetContoursInPlane3D(mesh,planeSpace,planeOffset);
+    }
+}
+
 // ------------------------------------------------------------------------------------------------
-bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,const std::vector<IfcVector3>& nors,
+bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,
     TempMesh& curmesh)
 {
     IFCImporter::LogWarn("forced to use poly2tri fallback method to generate wall openings");
@@ -1498,61 +1780,41 @@ bool TryAddOpenings_Poly2Tri(const std::vector<TempOpening>& openings,const std:
     try {
 
         ClipperLib::Clipper clipper_holes;
-        size_t c = 0;
 
-        for(const TempOpening& t :openings) {
-            const IfcVector3& outernor = nors[c++];
-            const IfcFloat dot = nor * outernor;
-            if (std::fabs(dot)<1.f-1e-6f) {
-                continue;
-            }
-
-            const std::vector<IfcVector3>& va = t.profileMesh->mVerts;
-            if(va.size() <= 2) {
-                continue;
-            }
+        for(const TempOpening& t : openings) {
+            auto contours = GetContoursInPlane(t.profileMesh,m,nor,coord,t.extrusionDir,wall_extrusion,first);
 
-            std::vector<IfcVector2> contour;
+            for(auto& contour : contours) {
+                // scale to clipping space
+                ClipperLib::Polygon hole;
+                for(IfcVector2& pip : contour) {
+                    pip.x = (pip.x - vmin.x) / vmax.x;
+                    pip.y = (pip.y - vmin.y) / vmax.y;
 
-            for(const IfcVector3& xx : t.profileMesh->mVerts) {
-                IfcVector3 vv = m *  xx, vv_extr = m * (xx + t.extrusionDir);
+                    hole.push_back(ClipperLib::IntPoint(to_int64(pip.x),to_int64(pip.y)));
+                }
 
-                const bool is_extruded_side = std::fabs(vv.z - coord) > std::fabs(vv_extr.z - coord);
-                if (first) {
-                    first = false;
-                    if (dot > 0.f) {
-                        wall_extrusion = t.extrusionDir;
-                        if (is_extruded_side) {
-                            wall_extrusion = - wall_extrusion;
-                        }
-                    }
+                if(!ClipperLib::Orientation(hole)) {
+                    std::reverse(hole.begin(),hole.end());
+                    //  assert(ClipperLib::Orientation(hole));
                 }
 
-                // XXX should not be necessary - but it is. Why? For precision reasons?
-                vv = is_extruded_side ? vv_extr : vv;
-                contour.push_back(IfcVector2(vv.x,vv.y));
-            }
+                /*ClipperLib::Polygons pol_temp(1), pol_temp2(1);
+                pol_temp[0] = hole;
 
-            ClipperLib::Polygon hole;
-            for(IfcVector2& pip : contour) {
-                pip.x  = (pip.x - vmin.x) / vmax.x;
-                pip.y  = (pip.y - vmin.y) / vmax.y;
+                ClipperLib::OffsetPolygons(pol_temp,pol_temp2,5.0);
+                hole = pol_temp2[0];*/
 
-                hole.push_back(ClipperLib::IntPoint(  to_int64(pip.x), to_int64(pip.y) ));
-            }
-
-            if (!ClipperLib::Orientation(hole)) {
-                std::reverse(hole.begin(), hole.end());
-            //  assert(ClipperLib::Orientation(hole));
+                clipper_holes.AddPolygon(hole,ClipperLib::ptSubject);
+                {
+                    std::stringstream msg;
+                    msg << "- added polygon ";
+                    for(auto elem : hole) {
+                        msg << " (" << elem.X << ", " << elem.Y << ")";
+                    }
+                    IFCImporter::LogDebug(msg.str().c_str());
+                }
             }
-
-            /*ClipperLib::Polygons pol_temp(1), pol_temp2(1);
-            pol_temp[0] = hole;
-
-            ClipperLib::OffsetPolygons(pol_temp,pol_temp2,5.0);
-            hole = pol_temp2[0];*/
-
-            clipper_holes.AddPolygon(hole,ClipperLib::ptSubject);
         }
 
         clipper_holes.Execute(ClipperLib::ctUnion,holes_union,

+ 0 - 1
code/AssetLib/IFC/IFCUtil.h

@@ -307,7 +307,6 @@ void ProcessBooleanExtrudedAreaSolidDifference(const Schema_2x3::IfcExtrudedArea
 // IFCOpenings.cpp
 
 bool GenerateOpenings(std::vector<TempOpening>& openings,
-                      const std::vector<IfcVector3>& nors,
                       TempMesh& curmesh,
                       bool check_intersection,
                       bool generate_connection_geometry,

+ 322 - 0
code/AssetLib/IQM/IQMImporter.cpp

@@ -0,0 +1,322 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2021, 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.
+
+----------------------------------------------------------------------
+*/
+
+#ifndef ASSIMP_BUILD_NO_IQM_IMPORTER
+
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/IOStreamBuffer.h>
+#include <assimp/ai_assert.h>
+#include <assimp/importerdesc.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
+#include <assimp/ByteSwapper.h>
+#include <memory>
+#include <numeric>
+
+#include "IQMImporter.h"
+#include "iqm.h"
+
+// RESOURCES:
+// http://sauerbraten.org/iqm/
+// https://github.com/lsalzman/iqm
+
+
+inline void swap_block( uint32_t *block, size_t size ){
+    (void)block; // suppress 'unreferenced formal parameter' MSVC warning
+    size >>= 2;
+    for ( size_t i = 0; i < size; ++i )
+        AI_SWAP4( block[ i ] );
+}
+
+static const aiImporterDesc desc = {
+    "Inter-Quake Model Importer",
+    "",
+    "",
+    "",
+    aiImporterFlags_SupportBinaryFlavour,
+    0,
+    0,
+    0,
+    0,
+    "iqm"
+};
+
+namespace Assimp {
+
+// ------------------------------------------------------------------------------------------------
+//  Default constructor
+IQMImporter::IQMImporter() :
+        mScene(nullptr) {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Returns true, if file is a binary Inter-Quake Model file.
+bool IQMImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
+    const std::string extension = GetExtension(pFile);
+
+    if (extension == "iqm")
+        return true;
+    else if (!extension.length() || checkSig) {
+        if (!pIOHandler) {
+            return true;
+        }
+        /*
+         * don't use CheckMagicToken because that checks with swapped bytes too, leading to false
+         * positives. This magic is not uint32_t, but char[4], so memcmp is the best way
+
+        const char* tokens[] = {"3DMO", "3dmo"};
+        return CheckMagicToken(pIOHandler,pFile,tokens,2,0,4);
+        */
+        std::unique_ptr<IOStream> pStream(pIOHandler->Open(pFile, "rb"));
+        unsigned char data[15];
+        if (!pStream || 15 != pStream->Read(data, 1, 15)) {
+            return false;
+        }
+        return !memcmp(data, "INTERQUAKEMODEL", 15);
+    }
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiImporterDesc *IQMImporter::GetInfo() const {
+    return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Model 3D import implementation
+void IQMImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSystem *pIOHandler) {
+    // Read file into memory
+    std::unique_ptr<IOStream> pStream(pIOHandler->Open(file, "rb"));
+    if (!pStream.get()) {
+        throw DeadlyImportError("Failed to open file ", file, ".");
+    }
+
+    // Get the file-size and validate it, throwing an exception when fails
+    const size_t fileSize = pStream->FileSize();
+    if (fileSize < sizeof( iqmheader )) {
+        throw DeadlyImportError("IQM-file ", file, " is too small.");
+    }
+    std::vector<unsigned char> buffer(fileSize);
+    unsigned char *data = buffer.data();
+    if (fileSize != pStream->Read(data, 1, fileSize)) {
+        throw DeadlyImportError("Failed to read the file ", file, ".");
+    }
+
+    // get header
+    iqmheader &hdr = reinterpret_cast<iqmheader&>( *data );
+    swap_block( &hdr.version, sizeof( iqmheader ) - sizeof( iqmheader::magic ) );
+
+    // extra check for header
+    if (memcmp(data, IQM_MAGIC, sizeof( IQM_MAGIC ) )
+     || hdr.version != IQM_VERSION
+     || hdr.filesize != fileSize) {
+        throw DeadlyImportError("Bad binary header in file ", file, ".");
+    }
+
+    ASSIMP_LOG_DEBUG("IQM: loading ", file);
+
+    // create the root node
+    pScene->mRootNode = new aiNode( "<IQMRoot>" );
+    // Now rotate the whole scene 90 degrees around the x axis to convert to internal coordinate system
+    pScene->mRootNode->mTransformation = aiMatrix4x4(
+            1.f, 0.f, 0.f, 0.f,
+            0.f, 0.f, 1.f, 0.f,
+            0.f, -1.f, 0.f, 0.f,
+            0.f, 0.f, 0.f, 1.f);
+    pScene->mRootNode->mNumMeshes = hdr.num_meshes;
+    pScene->mRootNode->mMeshes = new unsigned int[hdr.num_meshes];
+    std::iota( pScene->mRootNode->mMeshes, pScene->mRootNode->mMeshes + pScene->mRootNode->mNumMeshes, 0 );
+
+    mScene = pScene;
+
+    // Allocate output storage
+    pScene->mNumMeshes = 0;
+    pScene->mMeshes = new aiMesh *[hdr.num_meshes](); // Set arrays to zero to ensue proper destruction if an exception is raised
+
+    pScene->mNumMaterials = 0;
+    pScene->mMaterials = new aiMaterial *[hdr.num_meshes]();
+
+    // swap vertex arrays beforehand...
+    for( auto array = reinterpret_cast<iqmvertexarray*>( data + hdr.ofs_vertexarrays ), end = array + hdr.num_vertexarrays; array != end; ++array )
+    {
+        swap_block( &array->type, sizeof( iqmvertexarray ) );
+    }
+
+    // Read all surfaces from the file
+    for( auto imesh = reinterpret_cast<iqmmesh*>( data + hdr.ofs_meshes ), end_ = imesh + hdr.num_meshes; imesh != end_; ++imesh )
+    {
+        swap_block( &imesh->name, sizeof( iqmmesh ) );
+        // Allocate output mesh & material
+        auto mesh = pScene->mMeshes[pScene->mNumMeshes++] = new aiMesh();
+        mesh->mMaterialIndex = pScene->mNumMaterials;
+        auto mat = pScene->mMaterials[pScene->mNumMaterials++] = new aiMaterial();
+
+        {
+            auto text = reinterpret_cast<char*>( data + hdr.ofs_text );
+            aiString name( text + imesh->material );
+            mat->AddProperty( &name, AI_MATKEY_NAME );
+            mat->AddProperty( &name, AI_MATKEY_TEXTURE_DIFFUSE(0) );
+        }
+
+        // Fill mesh information
+        mesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
+        mesh->mNumFaces = 0;
+        mesh->mFaces = new aiFace[imesh->num_triangles];
+
+        // Fill in all triangles
+        for( auto tri = reinterpret_cast<iqmtriangle*>( data + hdr.ofs_triangles ) + imesh->first_triangle, end = tri + imesh->num_triangles; tri != end; ++tri )
+        {
+            swap_block( tri->vertex, sizeof( tri->vertex ) );
+            auto& face = mesh->mFaces[mesh->mNumFaces++];
+            face.mNumIndices = 3;
+            face.mIndices = new unsigned int[3]{ tri->vertex[0] - imesh->first_vertex,
+                                                 tri->vertex[2] - imesh->first_vertex,
+                                                 tri->vertex[1] - imesh->first_vertex };
+        }
+
+        // Fill in all vertices
+        for( auto array = reinterpret_cast<const iqmvertexarray*>( data + hdr.ofs_vertexarrays ), end__ = array + hdr.num_vertexarrays; array != end__; ++array )
+        {
+            const unsigned int nVerts = imesh->num_vertexes;
+            const unsigned int step = array->size;
+
+            switch ( array->type )
+            {
+            case IQM_POSITION:
+                if( array->format == IQM_FLOAT && step >= 3 ){
+                    mesh->mNumVertices = nVerts;
+                    auto v = mesh->mVertices = new aiVector3D[nVerts];
+                    for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step,
+                        end = f + nVerts * step; f != end; f += step, ++v )
+                    {
+                        *v = { AI_BE( f[0] ),
+                               AI_BE( f[1] ),
+                               AI_BE( f[2] ) };
+                    }
+                }
+                break;
+               case IQM_TEXCOORD:
+                if( array->format == IQM_FLOAT && step >= 2)
+                {
+                    auto v = mesh->mTextureCoords[0] = new aiVector3D[nVerts];
+                    mesh->mNumUVComponents[0] = 2;
+                    for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step,
+                        end = f + nVerts * step; f != end; f += step, ++v )
+                    {
+                        *v = { AI_BE( f[0] ),
+                           1 - AI_BE( f[1] ), 0 };
+                    }
+                }
+                break;
+            case IQM_NORMAL:
+                if (array->format == IQM_FLOAT && step >= 3)
+                {
+                    auto v = mesh->mNormals = new aiVector3D[nVerts];
+                    for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step,
+                        end = f + nVerts * step; f != end; f += step, ++v )
+                    {
+                        *v = { AI_BE( f[0] ),
+                               AI_BE( f[1] ),
+                               AI_BE( f[2] ) };
+                    }
+                }
+                break;
+            case IQM_COLOR:
+                if (array->format == IQM_UBYTE && step >= 3)
+                {
+                    auto v = mesh->mColors[0] = new aiColor4D[nVerts];
+                    for( auto f = ( data + array->offset ) + imesh->first_vertex * step,
+                        end = f + nVerts * step; f != end; f += step, ++v )
+                    {
+                        *v = { ( f[0] ) / 255.f,
+                               ( f[1] ) / 255.f,
+                               ( f[2] ) / 255.f,
+                               step == 3? 1 : ( f[3] ) / 255.f };
+                    }
+                }
+                else if (array->format == IQM_FLOAT && step >= 3)
+                {
+                    auto v = mesh->mColors[0] = new aiColor4D[nVerts];
+                    for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step,
+                        end = f + nVerts * step; f != end; f += step, ++v )
+                    {
+                        *v = { AI_BE( f[0] ),
+                               AI_BE( f[1] ),
+                               AI_BE( f[2] ),
+                               step == 3? 1 : AI_BE( f[3] ) };
+                    }
+                }
+                break;
+            case IQM_TANGENT:
+#if 0
+                if (array->format == IQM_FLOAT && step >= 3)
+                {
+                    auto v = mesh->mTangents = new aiVector3D[nVerts];
+                    for( auto f = reinterpret_cast<const float*>( data + array->offset ) + imesh->first_vertex * step,
+                        end = f + nVerts * step; f != end; f += step, ++v )
+                    {
+                        *v = { AI_BE( f[0] ),
+                               AI_BE( f[1] ),
+                               AI_BE( f[2] ) };
+                    }
+                }
+#endif
+                break;
+            case IQM_BLENDINDEXES:
+            case IQM_BLENDWEIGHTS:
+            case IQM_CUSTOM:
+                break; // these attributes are not relevant.
+
+            default:
+                break;
+            }
+        }
+    }
+}
+
+
+// ------------------------------------------------------------------------------------------------
+
+} // Namespace Assimp
+
+#endif // !! ASSIMP_BUILD_NO_IQM_IMPORTER

+ 78 - 0
code/AssetLib/IQM/IQMImporter.h

@@ -0,0 +1,78 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2021, 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.
+
+----------------------------------------------------------------------
+*/
+
+/** @file IQMImporter.h
+*   @brief Declares the importer class to read a scene from an Inter-Quake Model file
+*/
+
+#pragma once
+
+#ifndef ASSIMP_BUILD_NO_IQM_IMPORTER
+
+#include <assimp/BaseImporter.h>
+#include <assimp/material.h>
+
+namespace Assimp {
+
+class IQMImporter : public BaseImporter {
+public:
+	/// \brief  Default constructor
+	IQMImporter();
+    ~IQMImporter() override {}
+
+	/// \brief  Returns whether the class can handle the format of the given file.
+	/// \remark See BaseImporter::CanRead() for details.
+	bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override;
+
+protected:
+    //! \brief  Appends the supported extension.
+    const aiImporterDesc *GetInfo() const override;
+
+    //! \brief  File import implementation.
+    void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
+
+private:
+    aiScene *mScene = nullptr; // the scene to import to
+};
+
+} // Namespace Assimp
+
+#endif // ASSIMP_BUILD_NO_IQM_IMPORTER

+ 134 - 0
code/AssetLib/IQM/iqm.h

@@ -0,0 +1,134 @@
+#ifndef __IQM_H__
+#define __IQM_H__
+
+#define IQM_MAGIC "INTERQUAKEMODEL"
+#define IQM_VERSION 2
+
+struct iqmheader
+{
+    char magic[16];
+    unsigned int version;
+    unsigned int filesize;
+    unsigned int flags;
+    unsigned int num_text, ofs_text;
+    unsigned int num_meshes, ofs_meshes;
+    unsigned int num_vertexarrays, num_vertexes, ofs_vertexarrays;
+    unsigned int num_triangles, ofs_triangles, ofs_adjacency;
+    unsigned int num_joints, ofs_joints;
+    unsigned int num_poses, ofs_poses;
+    unsigned int num_anims, ofs_anims;
+    unsigned int num_frames, num_framechannels, ofs_frames, ofs_bounds;
+    unsigned int num_comment, ofs_comment;
+    unsigned int num_extensions, ofs_extensions;
+};
+
+struct iqmmesh
+{
+    unsigned int name;
+    unsigned int material;
+    unsigned int first_vertex, num_vertexes;
+    unsigned int first_triangle, num_triangles;
+};
+
+enum
+{
+    IQM_POSITION     = 0,
+    IQM_TEXCOORD     = 1,
+    IQM_NORMAL       = 2,
+    IQM_TANGENT      = 3,
+    IQM_BLENDINDEXES = 4,
+    IQM_BLENDWEIGHTS = 5,
+    IQM_COLOR        = 6,
+    IQM_CUSTOM       = 0x10
+};
+
+enum
+{
+    IQM_BYTE   = 0,
+    IQM_UBYTE  = 1,
+    IQM_SHORT  = 2,
+    IQM_USHORT = 3,
+    IQM_INT    = 4,
+    IQM_UINT   = 5,
+    IQM_HALF   = 6,
+    IQM_FLOAT  = 7,
+    IQM_DOUBLE = 8
+};
+
+struct iqmtriangle
+{
+    unsigned int vertex[3];
+};
+
+struct iqmadjacency
+{
+    unsigned int triangle[3];
+};
+
+struct iqmjointv1
+{
+    unsigned int name;
+    int parent;
+    float translate[3], rotate[3], scale[3];
+};
+
+struct iqmjoint
+{
+    unsigned int name;
+    int parent;
+    float translate[3], rotate[4], scale[3];
+};
+
+struct iqmposev1
+{
+    int parent;
+    unsigned int mask;
+    float channeloffset[9];
+    float channelscale[9];
+};
+
+struct iqmpose
+{
+    int parent;
+    unsigned int mask;
+    float channeloffset[10];
+    float channelscale[10];
+};
+
+struct iqmanim
+{
+    unsigned int name;
+    unsigned int first_frame, num_frames;
+    float framerate;
+    unsigned int flags;
+};
+
+enum
+{
+    IQM_LOOP = 1<<0
+};
+
+struct iqmvertexarray
+{
+    unsigned int type;
+    unsigned int flags;
+    unsigned int format;
+    unsigned int size;
+    unsigned int offset;
+};
+
+struct iqmbounds
+{
+    float bbmin[3], bbmax[3];
+    float xyradius, radius;
+};
+
+struct iqmextension
+{
+    unsigned int name;
+    unsigned int num_data, ofs_data;
+    unsigned int ofs_extensions; // pointer to next extension
+};
+
+#endif
+

+ 3 - 17
code/AssetLib/Irr/IRRLoader.cpp

@@ -94,23 +94,9 @@ IRRImporter::~IRRImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool IRRImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-	const std::string extension = GetExtension(pFile);
-	if (extension == "irr") {
-		return true;
-	} else if (extension == "xml" || checkSig) {
-		/*  If CanRead() is called in order to check whether we
-         *  support a specific file extension in general pIOHandler
-         *  might be nullptr and it's our duty to return true here.
-         */
-		if (nullptr == pIOHandler) {
-			return true;
-		}
-		static const char * const tokens[] = { "irr_scene" };
-		return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
-	}
-
-	return false;
+bool IRRImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+	static const char *tokens[] = { "irr_scene" };
+	return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 7 - 21
code/AssetLib/Irr/IRRMeshLoader.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2022, assimp team
 
-
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -85,26 +83,14 @@ IRRMeshImporter::~IRRMeshImporter() {}
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool IRRMeshImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
+bool IRRMeshImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
 	/* NOTE: A simple check for the file extension is not enough
-     * here. Irrmesh and irr are easy, but xml is too generic
-     * and could be collada, too. So we need to open the file and
-     * search for typical tokens.
-     */
-	const std::string extension = GetExtension(pFile);
-
-	if (extension == "irrmesh")
-		return true;
-	else if (extension == "xml" || checkSig) {
-		/*  If CanRead() is called to check whether the loader
-         *  supports a specific file extension in general we
-         *  must return true here.
-         */
-		if (!pIOHandler) return true;
-		static const char * const tokens[] = { "irrmesh" };
-		return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
-	}
-	return false;
+	 * here. Irrmesh and irr are easy, but xml is too generic
+	 * and could be collada, too. So we need to open the file and
+	 * search for typical tokens.
+	 */
+	static const char *tokens[] = { "irrmesh" };
+	return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 7 - 15
code/AssetLib/LWO/LWOLoader.cpp

@@ -105,21 +105,13 @@ LWOImporter::~LWOImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool LWOImporter::CanRead(const std::string &file, IOSystem *pIOHandler, bool checkSig) const {
-    const std::string extension = GetExtension(file);
-    if (extension == "lwo" || extension == "lxo") {
-        return true;
-    }
-
-    // if check for extension is not enough, check for the magic tokens
-    if (!extension.length() || checkSig) {
-        uint32_t tokens[3];
-        tokens[0] = AI_LWO_FOURCC_LWOB;
-        tokens[1] = AI_LWO_FOURCC_LWO2;
-        tokens[2] = AI_LWO_FOURCC_LXOB;
-        return CheckMagicToken(pIOHandler, file, tokens, 3, 8);
-    }
-    return false;
+bool LWOImporter::CanRead(const std::string &file, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const uint32_t tokens[] = {
+        AI_LWO_FOURCC_LWOB,
+        AI_LWO_FOURCC_LWO2,
+        AI_LWO_FOURCC_LXOB
+    };
+    return CheckMagicToken(pIOHandler, file, tokens, AI_COUNT_OF(tokens), 8);
 }
 
 // ------------------------------------------------------------------------------------------------

+ 6 - 14
code/AssetLib/LWS/LWSLoader.cpp

@@ -147,20 +147,12 @@ LWSImporter::~LWSImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool LWSImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    const std::string extension = GetExtension(pFile);
-    if (extension == "lws" || extension == "mot") {
-        return true;
-    }
-
-    // if check for extension is not enough, check for the magic tokens LWSC and LWMO
-    if (!extension.length() || checkSig) {
-        uint32_t tokens[2];
-        tokens[0] = AI_MAKE_MAGIC("LWSC");
-        tokens[1] = AI_MAKE_MAGIC("LWMO");
-        return CheckMagicToken(pIOHandler, pFile, tokens, 2);
-    }
-    return false;
+bool LWSImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const uint32_t tokens[] = {
+        AI_MAKE_MAGIC("LWSC"),
+        AI_MAKE_MAGIC("LWMO")
+    };
+    return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 12 - 27
code/AssetLib/M3D/M3DImporter.cpp

@@ -111,34 +111,19 @@ M3DImporter::M3DImporter() :
 
 // ------------------------------------------------------------------------------------------------
 //  Returns true, if file is a binary or ASCII Model 3D file.
-bool M3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    const std::string extension = GetExtension(pFile);
-
-    if (extension == "m3d"
-            || extension == "a3d"
-    )
-        return true;
-    else if (!extension.length() || checkSig) {
-        if (!pIOHandler) {
-            return true;
-        }
-        /*
-         * don't use CheckMagicToken because that checks with swapped bytes too, leading to false
-         * positives. This magic is not uint32_t, but char[4], so memcmp is the best way
-
-        const char* tokens[] = {"3DMO", "3dmo"};
-        return CheckMagicToken(pIOHandler,pFile,tokens,2,0,4);
-        */
-        std::unique_ptr<IOStream> pStream(pIOHandler->Open(pFile, "rb"));
-        unsigned char data[4];
-        if (!pStream || 4 != pStream->Read(data, 1, 4)) {
-            return false;
-        }
-        return !memcmp(data, "3DMO", 4) /* bin */
-               || !memcmp(data, "3dmo", 4) /* ASCII */
-                ;
+bool M3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    // don't use CheckMagicToken because that checks with swapped bytes too, leading to false
+    // positives. This magic is not uint32_t, but char[4], so memcmp is the best way
+    std::unique_ptr<IOStream> pStream(pIOHandler->Open(pFile, "rb"));
+    unsigned char data[4];
+    if (4 != pStream->Read(data, 1, 4)) {
+        return false;
     }
-    return false;
+    return !memcmp(data, "3DMO", 4) /* bin */
+#ifdef M3D_ASCII
+        || !memcmp(data, "3dmo", 4) /* ASCII */
+#endif
+            ;
 }
 
 // ------------------------------------------------------------------------------------------------

+ 3 - 12
code/AssetLib/MD2/MD2Loader.cpp

@@ -107,19 +107,10 @@ MD2Importer::~MD2Importer()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool MD2Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
+bool MD2Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const
 {
-    const std::string extension = GetExtension(pFile);
-    if (extension == "md2")
-        return true;
-
-    // if check for extension is not enough, check for the magic tokens
-    if (!extension.length() || checkSig) {
-        uint32_t tokens[1];
-        tokens[0] = AI_MD2_MAGIC_NUMBER_LE;
-        return CheckMagicToken(pIOHandler,pFile,tokens,1);
-    }
-    return false;
+    static const uint32_t tokens[] = { AI_MD2_MAGIC_NUMBER_LE };
+    return CheckMagicToken(pIOHandler,pFile,tokens,AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 3 - 12
code/AssetLib/MD3/MD3Loader.cpp

@@ -349,18 +349,9 @@ MD3Importer::~MD3Importer() {}
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool MD3Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    const std::string extension = GetExtension(pFile);
-    if (extension == "md3")
-        return true;
-
-    // if check for extension is not enough, check for the magic tokens
-    if (!extension.length() || checkSig) {
-        uint32_t tokens[1];
-        tokens[0] = AI_MD3_MAGIC_NUMBER_LE;
-        return CheckMagicToken(pIOHandler, pFile, tokens, 1);
-    }
-    return false;
+bool MD3Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const uint32_t tokens[] = { AI_MD3_MAGIC_NUMBER_LE };
+    return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 3 - 14
code/AssetLib/MD5/MD5Loader.cpp

@@ -100,20 +100,9 @@ MD5Importer::~MD5Importer() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool MD5Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    const std::string extension = GetExtension(pFile);
-
-    if (extension == "md5anim" || extension == "md5mesh" || extension == "md5camera")
-        return true;
-    else if (!extension.length() || checkSig) {
-        if (!pIOHandler) {
-            return true;
-        }
-        static const char * const tokens[] = { "MD5Version" };
-        return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
-    }
-
-    return false;
+bool MD5Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const char *tokens[] = { "MD5Version" };
+    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 3 - 13
code/AssetLib/MDC/MDCLoader.cpp

@@ -111,19 +111,9 @@ MDCImporter::~MDCImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool MDCImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    const std::string extension = GetExtension(pFile);
-    if (extension == "mdc") {
-        return true;
-    }
-
-    // if check for extension is not enough, check for the magic tokens
-    if (!extension.length() || checkSig) {
-        uint32_t tokens[1];
-        tokens[0] = AI_MDC_MAGIC_NUMBER_LE;
-        return CheckMagicToken(pIOHandler, pFile, tokens, 1);
-    }
-    return false;
+bool MDCImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const uint32_t tokens[] = { AI_MDC_MAGIC_NUMBER_LE };
+    return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 12 - 17
code/AssetLib/MDL/MDLLoader.cpp

@@ -104,23 +104,18 @@ MDLImporter::~MDLImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool MDLImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    const std::string extension = GetExtension(pFile);
-
-    // if check for extension is not enough, check for the magic tokens
-    if (extension == "mdl" || !extension.length() || checkSig) {
-        uint32_t tokens[8];
-        tokens[0] = AI_MDL_MAGIC_NUMBER_LE_HL2a;
-        tokens[1] = AI_MDL_MAGIC_NUMBER_LE_HL2b;
-        tokens[2] = AI_MDL_MAGIC_NUMBER_LE_GS7;
-        tokens[3] = AI_MDL_MAGIC_NUMBER_LE_GS5b;
-        tokens[4] = AI_MDL_MAGIC_NUMBER_LE_GS5a;
-        tokens[5] = AI_MDL_MAGIC_NUMBER_LE_GS4;
-        tokens[6] = AI_MDL_MAGIC_NUMBER_LE_GS3;
-        tokens[7] = AI_MDL_MAGIC_NUMBER_LE;
-        return CheckMagicToken(pIOHandler, pFile, tokens, 8, 0);
-    }
-    return false;
+bool MDLImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const uint32_t tokens[] = {
+        AI_MDL_MAGIC_NUMBER_LE_HL2a,
+        AI_MDL_MAGIC_NUMBER_LE_HL2b,
+        AI_MDL_MAGIC_NUMBER_LE_GS7,
+        AI_MDL_MAGIC_NUMBER_LE_GS5b,
+        AI_MDL_MAGIC_NUMBER_LE_GS5a,
+        AI_MDL_MAGIC_NUMBER_LE_GS4,
+        AI_MDL_MAGIC_NUMBER_LE_GS3,
+        AI_MDL_MAGIC_NUMBER_LE
+    };
+    return CheckMagicToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 3 - 8
code/AssetLib/MMD/MMDImporter.cpp

@@ -89,14 +89,9 @@ MMDImporter::~MMDImporter() {
 // ------------------------------------------------------------------------------------------------
 //  Returns true, if file is an pmx file.
 bool MMDImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler,
-        bool checkSig) const {
-    if (!checkSig) {
-        return SimpleExtensionCheck(pFile, "pmx");
-    } else {
-        // Check file Header
-        static const char * const pTokens[] = { "PMX " };
-        return SearchFileHeaderForToken(pIOHandler, pFile, pTokens, 1);
-    }
+        bool /*checkSig*/) const {
+    static const char *tokens[] = { "PMX " };
+    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 3 - 17
code/AssetLib/MS3D/MS3DLoader.cpp

@@ -88,26 +88,12 @@ MS3DImporter::MS3DImporter()
 // Destructor, private as well
 MS3DImporter::~MS3DImporter()
 {}
-
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool MS3DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
+bool MS3DImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const
 {
-    // first call - simple extension check
-    const std::string extension = GetExtension(pFile);
-    if (extension == "ms3d") {
-        return true;
-    }
-
-    // second call - check for magic identifiers
-    else if (!extension.length() || checkSig)   {
-        if (!pIOHandler) {
-            return true;
-        }
-        static const char * const tokens[] = {"MS3D000000"};
-        return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
-    }
-    return false;
+    static const char* tokens[] = { "MS3D000000" };
+    return SearchFileHeaderForToken(pIOHandler,pFile,tokens,AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 3 - 14
code/AssetLib/NDO/NDOLoader.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2022, assimp team
 
-
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -82,19 +80,10 @@ NDOImporter::~NDOImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool NDOImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
+bool NDOImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const
 {
-    // check file extension
-    const std::string extension = GetExtension(pFile);
-
-    if( extension == "ndo")
-        return true;
-
-    if ((checkSig || !extension.length()) && pIOHandler) {
-        static const char * const tokens[] = {"nendo"};
-        return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1,5);
-    }
-    return false;
+    static const char* tokens[] = {"nendo"};
+    return SearchFileHeaderForToken(pIOHandler,pFile,tokens,AI_COUNT_OF(tokens),5);
 }
 
 // ------------------------------------------------------------------------------------------------

+ 1 - 3
code/AssetLib/NFF/NFFLoader.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2022, assimp team
 
-
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -83,7 +81,7 @@ NFFImporter::~NFFImporter() {}
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool NFFImporter::CanRead(const std::string &pFile, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const {
+bool NFFImporter::CanRead(const std::string & pFile, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const {
     return SimpleExtensionCheck(pFile, "nff", "enff");
 }
 

+ 3 - 12
code/AssetLib/OFF/OFFLoader.cpp

@@ -83,19 +83,10 @@ OFFImporter::~OFFImporter()
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool OFFImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
+bool OFFImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const
 {
-    const std::string extension = GetExtension(pFile);
-
-    if (extension == "off")
-        return true;
-    else if (!extension.length() || checkSig)
-    {
-        if (!pIOHandler)return true;
-        static const char * const tokens[] = {"off"};
-        return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1,3);
-    }
-    return false;
+    static const char* tokens[] = { "off" };
+    return SearchFileHeaderForToken(pIOHandler,pFile,tokens,AI_COUNT_OF(tokens),3);
 }
 
 // ------------------------------------------------------------------------------------------------

+ 4 - 10
code/AssetLib/Obj/ObjFileImporter.cpp

@@ -87,16 +87,10 @@ ObjFileImporter::~ObjFileImporter() {
 }
 
 // ------------------------------------------------------------------------------------------------
-//  Returns true, if file is an obj file.
-bool ObjFileImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    if (!checkSig) {
-        //Check File Extension
-        return SimpleExtensionCheck(pFile, "obj");
-    } else {
-        // Check file Header
-        static const char *pTokens[] = { "mtllib", "usemtl", "v ", "vt ", "vn ", "o ", "g ", "s ", "f " };
-        return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, pTokens, 9, 200, false, true);
-    }
+//  Returns true if file is an obj file.
+bool ObjFileImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const char *tokens[] = { "mtllib", "usemtl", "v ", "vt ", "vn ", "o ", "g ", "s ", "f " };
+    return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens), 200, false, true);
 }
 
 // ------------------------------------------------------------------------------------------------

+ 3 - 7
code/AssetLib/Ogre/OgreImporter.cpp

@@ -73,14 +73,10 @@ void OgreImporter::SetupProperties(const Importer *pImp) {
     m_detectTextureTypeFromFilename = pImp->GetPropertyBool(AI_CONFIG_IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME, false);
 }
 
-bool OgreImporter::CanRead(const std::string &pFile, Assimp::IOSystem *pIOHandler, bool checkSig) const {
-    if (!checkSig) {
-        return EndsWith(pFile, ".mesh.xml", false) || EndsWith(pFile, ".mesh", false);
-    }
-
+bool OgreImporter::CanRead(const std::string &pFile, Assimp::IOSystem *pIOHandler, bool /*checkSig*/) const {
     if (EndsWith(pFile, ".mesh.xml", false)) {
-        static const char * const tokens[] = { "<mesh>" };
-        return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
+        static const char *tokens[] = { "<mesh>" };
+        return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
     }
 
     /// @todo Read and validate first header chunk?

+ 3 - 10
code/AssetLib/OpenGEX/OpenGEXImporter.cpp

@@ -290,16 +290,9 @@ OpenGEXImporter::~OpenGEXImporter() {
 }
 
 //------------------------------------------------------------------------------------------------
-bool OpenGEXImporter::CanRead(const std::string &file, IOSystem *pIOHandler, bool checkSig) const {
-    bool canRead(false);
-    if (!checkSig) {
-        canRead = SimpleExtensionCheck(file, "ogex");
-    } else {
-        static const char * const token[] = { "Metric", "GeometryNode", "VertexArray (attrib", "IndexArray" };
-        canRead = SearchFileHeaderForToken(pIOHandler, file, token, 4);
-    }
-
-    return canRead;
+bool OpenGEXImporter::CanRead(const std::string &file, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const char *tokens[] = { "Metric", "GeometryNode", "VertexArray (attrib", "IndexArray" };
+    return SearchFileHeaderForToken(pIOHandler, file, tokens, AI_COUNT_OF(tokens));
 }
 
 //------------------------------------------------------------------------------------------------

+ 3 - 18
code/AssetLib/Ply/PlyLoader.cpp

@@ -100,24 +100,9 @@ PLYImporter::~PLYImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool PLYImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    const std::string extension = GetExtension(pFile);
-
-    if (extension == "ply") {
-        return true;
-    }
-
-    if (!extension.length() || checkSig) {
-        if (!pIOHandler) {
-            return true;
-        }
-        static const char * const tokens[] = {
-            "ply"
-        };
-        return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
-    }
-
-    return false;
+bool PLYImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const char *tokens[] = { "ply" };
+    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 3 - 4
code/AssetLib/Q3BSP/Q3BSPFileImporter.cpp

@@ -156,12 +156,11 @@ Q3BSPFileImporter::~Q3BSPFileImporter() {
 }
 
 // ------------------------------------------------------------------------------------------------
-//  Returns true, if the loader can read this.
-bool Q3BSPFileImporter::CanRead(const std::string &rFile, IOSystem * /*pIOHandler*/, bool checkSig) const {
+//  Returns true if the loader can read this.
+bool Q3BSPFileImporter::CanRead(const std::string &filename, IOSystem * /*pIOHandler*/, bool checkSig) const {
     if (!checkSig) {
-        return SimpleExtensionCheck(rFile, "pk3", "bsp");
+        return SimpleExtensionCheck(filename, "pk3", "bsp");
     }
-
     return false;
 }
 

+ 3 - 12
code/AssetLib/Q3D/Q3DLoader.cpp

@@ -84,18 +84,9 @@ Q3DImporter::~Q3DImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool Q3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    const std::string extension = GetExtension(pFile);
-
-    if (extension == "q3s" || extension == "q3o")
-        return true;
-    else if (!extension.length() || checkSig) {
-        if (!pIOHandler)
-            return true;
-        static const char * const tokens[] = { "quick3Do", "quick3Ds" };
-        return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 2);
-    }
-    return false;
+bool Q3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const char *tokens[] = { "quick3Do", "quick3Ds" };
+    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 2 - 2
code/AssetLib/Raw/RawLoader.cpp

@@ -84,8 +84,8 @@ RAWImporter::~RAWImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool RAWImporter::CanRead(const std::string &pFile, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const {
-    return SimpleExtensionCheck(pFile, "raw");
+bool RAWImporter::CanRead(const std::string &filename, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const {
+    return SimpleExtensionCheck(filename, "raw");
 }
 
 // ------------------------------------------------------------------------------------------------

+ 2 - 5
code/AssetLib/SIB/SIBImporter.cpp

@@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
 
 Copyright (c) 2006-2022, assimp team
 
-
-
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -61,7 +59,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #ifdef ASSIMP_USE_HUNTER
 #include <utf8.h>
 #else
-//#  include "../contrib/ConvertUTF/ConvertUTF.h"
 #include "../contrib/utf8cpp/source/utf8.h"
 #endif
 #include <assimp/importerdesc.h>
@@ -217,8 +214,8 @@ SIBImporter::~SIBImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool SIBImporter::CanRead(const std::string &pFile, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const {
-    return SimpleExtensionCheck(pFile, "sib");
+bool SIBImporter::CanRead(const std::string &filename, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const {
+    return SimpleExtensionCheck(filename, "sib");
 }
 
 // ------------------------------------------------------------------------------------------------

+ 11 - 12
code/AssetLib/SMD/SMDLoader.cpp

@@ -81,15 +81,15 @@ static const aiImporterDesc desc = {
 
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
-SMDImporter::SMDImporter()
-: configFrameID()
-, mBuffer()
-, pScene( nullptr )
-, iFileSize( 0 )
-, iSmallestFrame( INT_MAX )
-, dLengthOfAnim( 0.0 )
-, bHasUVs(false )
-, iLineNumber((unsigned int)-1)  {
+SMDImporter::SMDImporter() :
+        configFrameID(), 
+        mBuffer(), 
+        pScene( nullptr ), 
+        iFileSize( 0 ), 
+        iSmallestFrame( INT_MAX ),
+        dLengthOfAnim( 0.0 ),
+        bHasUVs(false ), 
+        iLineNumber((unsigned int)-1)  {
     // empty
 }
 
@@ -101,9 +101,8 @@ SMDImporter::~SMDImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool SMDImporter::CanRead( const std::string& pFile, IOSystem* /*pIOHandler*/, bool) const {
-    // fixme: auto format detection
-    return SimpleExtensionCheck(pFile,"smd","vta");
+bool SMDImporter::CanRead( const std::string& filename, IOSystem* /*pIOHandler*/, bool) const {
+    return SimpleExtensionCheck(filename, "smd", "vta");
 }
 
 // ------------------------------------------------------------------------------------------------

+ 3 - 16
code/AssetLib/STL/STLLoader.cpp

@@ -140,22 +140,9 @@ STLImporter::~STLImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool STLImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    const std::string extension = GetExtension(pFile);
-
-    if (extension == "stl") {
-        return true;
-    }
-
-    if (!extension.length() || checkSig) {
-        if (!pIOHandler) {
-            return true;
-        }
-        static const char * const tokens[] = { "STL", "solid" };
-        return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 2);
-    }
-
-    return false;
+bool STLImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const char *tokens[] = { "STL", "solid" };
+    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 3 - 21
code/AssetLib/Terragen/TerragenLoader.cpp

@@ -81,27 +81,9 @@ TerragenImporter::~TerragenImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool TerragenImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-    // check file extension
-    std::string extension = GetExtension(pFile);
-
-    if (extension == "ter")
-        return true;
-
-    if (!extension.length() || checkSig) {
-        /*  If CanRead() is called in order to check whether we
-         *  support a specific file extension in general pIOHandler
-         *  might be nullptr and it's our duty to return true here.
-         */
-        if (!pIOHandler) {
-            return true;
-        }
-
-        static const char * const tokens[] = { "terragen" };
-        return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
-    }
-
-    return false;
+bool TerragenImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+    static const char *tokens[] = { "terragen" };
+    return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 9 - 5
code/AssetLib/Unreal/UnrealLoader.cpp

@@ -74,7 +74,7 @@ namespace Unreal {
     3 = Masked two-sided
     4 = Modulation blended two-sided
     8 = Placeholder triangle for weapon positioning (invisible)
-    */
+*/
 enum MeshFlags {
     MF_NORMAL_OS = 0,
     MF_NORMAL_TS = 1,
@@ -168,16 +168,20 @@ static const aiImporterDesc desc = {
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by Importer
 UnrealImporter::UnrealImporter() :
-        mConfigFrameID(0), mConfigHandleFlags(true) {}
+        mConfigFrameID(0), mConfigHandleFlags(true) {
+    // empty
+}
 
 // ------------------------------------------------------------------------------------------------
 // Destructor, private as well
-UnrealImporter::~UnrealImporter() {}
+UnrealImporter::~UnrealImporter() {
+    // empty
+}
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool UnrealImporter::CanRead(const std::string &pFile, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const {
-    return SimpleExtensionCheck(pFile, "3d", "uc");
+bool UnrealImporter::CanRead(const std::string & filename, IOSystem * /*pIOHandler*/, bool /*checkSig*/) const {
+    return SimpleExtensionCheck(filename, "3d", "uc");
 }
 
 // ------------------------------------------------------------------------------------------------

+ 3 - 11
code/AssetLib/X/XFileImporter.cpp

@@ -88,17 +88,9 @@ XFileImporter::~XFileImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool XFileImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const {
-    std::string extension = GetExtension(pFile);
-    if(extension == "x") {
-        return true;
-    }
-    if (!extension.length() || checkSig) {
-        uint32_t token[1];
-        token[0] = AI_MAKE_MAGIC("xof ");
-        return CheckMagicToken(pIOHandler,pFile,token,1,0);
-    }
-    return false;
+bool XFileImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool /*checkSig*/) const {
+    static const uint32_t token[] = { AI_MAKE_MAGIC("xof ") };
+    return CheckMagicToken(pIOHandler,pFile,token,AI_COUNT_OF(token));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 3 - 19
code/AssetLib/XGL/XGLLoader.cpp

@@ -104,25 +104,9 @@ XGLImporter::~XGLImporter() {
 
 // ------------------------------------------------------------------------------------------------
 // Returns whether the class can handle the format of the given file.
-bool XGLImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
-	/* NOTE: A simple check for the file extension is not enough
-     * here. XGL and ZGL are ok, but xml is too generic
-     * and might be collada as well. So open the file and
-     * look for typical signal tokens.
-     */
-	const std::string extension = GetExtension(pFile);
-
-	if (extension == "xgl" || extension == "zgl") {
-		return true;
-	}
-
-  if (extension == "xml" || checkSig) {
-		ai_assert(pIOHandler != nullptr);
-		static const char * const tokens[] = { "<world>", "<World>", "<WORLD>" };
-		return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 3);
-	}
-
-    return false;
+bool XGLImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+	static const char *tokens[] = { "<world>", "<World>", "<WORLD>" };
+	return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
 }
 
 // ------------------------------------------------------------------------------------------------

+ 6 - 16
code/AssetLib/glTF/glTFImporter.cpp

@@ -93,24 +93,14 @@ const aiImporterDesc *glTFImporter::GetInfo() const {
 }
 
 bool glTFImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /* checkSig */) const {
-    const std::string &extension = GetExtension(pFile);
-
-    if (extension != "gltf" && extension != "glb") {
+    glTF::Asset asset(pIOHandler);
+    try {
+        asset.Load(pFile, GetExtension(pFile) == "glb");
+        std::string version = asset.asset.version;
+        return !version.empty() && version[0] == '1';
+    } catch (...) {
         return false;
     }
-
-    if (pIOHandler) {
-        glTF::Asset asset(pIOHandler);
-        try {
-            asset.Load(pFile, extension == "glb");
-            std::string version = asset.asset.version;
-            return !version.empty() && version[0] == '1';
-        } catch (...) {
-            return false;
-        }
-    }
-
-    return false;
 }
 
 inline void SetMaterialColorProperty(std::vector<int> &embeddedTexIdxs, Asset & /*r*/, glTF::TexProperty prop, aiMaterial *mat,

+ 32 - 30
code/AssetLib/glTF2/glTF2Importer.cpp

@@ -98,7 +98,7 @@ static const aiImporterDesc desc = {
 glTF2Importer::glTF2Importer() :
         BaseImporter(),
         meshOffsets(),
-        embeddedTexIdxs(),
+        mEmbeddedTexIdxs(),
         mScene(nullptr) {
     // empty
 }
@@ -111,21 +111,21 @@ 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);
-
-	if (!checkSig && (extension != "gltf") && (extension != "glb"))
-		return false;
+bool glTF2Importer::CanRead(const std::string &filename, IOSystem *pIOHandler, bool checkSig ) const {
+    const std::string extension = GetExtension(filename);
+    if (!checkSig && (extension != "gltf") && (extension != "glb")) {
+        return false;
+    }
 
-	if (pIOHandler) {
-		glTF2::Asset asset(pIOHandler);
-		return asset.CanRead(pFile, extension == "glb");
-	}
+    if (pIOHandler) {
+        glTF2::Asset asset(pIOHandler);
+        return asset.CanRead(filename, extension == "glb");
+    }
 
     return false;
 }
 
-static aiTextureMapMode ConvertWrappingMode(SamplerWrap gltfWrapMode) {
+static inline aiTextureMapMode ConvertWrappingMode(SamplerWrap gltfWrapMode) {
     switch (gltfWrapMode) {
         case SamplerWrap::Mirrored_Repeat:
             return aiTextureMapMode_Mirror;
@@ -140,21 +140,21 @@ static aiTextureMapMode ConvertWrappingMode(SamplerWrap gltfWrapMode) {
     }
 }
 
-inline void SetMaterialColorProperty(Asset & /*r*/, vec4 &prop, aiMaterial *mat,
+static inline void SetMaterialColorProperty(Asset & /*r*/, vec4 &prop, aiMaterial *mat,
         const char *pKey, unsigned int type, unsigned int idx) {
     aiColor4D col;
     CopyValue(prop, col);
     mat->AddProperty(&col, 1, pKey, type, idx);
 }
 
-inline void SetMaterialColorProperty(Asset & /*r*/, vec3 &prop, aiMaterial *mat,
+static inline void SetMaterialColorProperty(Asset & /*r*/, vec3 &prop, aiMaterial *mat,
         const char *pKey, unsigned int type, unsigned int idx) {
     aiColor4D col;
     glTFCommon::CopyValue(prop, col);
     mat->AddProperty(&col, 1, pKey, type, idx);
 }
 
-inline void SetMaterialTextureProperty(std::vector<int> &embeddedTexIdxs, Asset & /*r*/,
+static void SetMaterialTextureProperty(std::vector<int> &embeddedTexIdxs, Asset & /*r*/,
         glTF2::TextureInfo prop, aiMaterial *mat, aiTextureType texType,
         unsigned int texSlot = 0) {
     if (prop.texture && prop.texture->source) {
@@ -371,10 +371,10 @@ void glTF2Importer::ImportMaterials(Asset &r) {
     mScene->mNumMaterials = numImportedMaterials + 1;
     mScene->mMaterials = new aiMaterial *[mScene->mNumMaterials];
     std::fill(mScene->mMaterials, mScene->mMaterials + mScene->mNumMaterials, nullptr);
-    mScene->mMaterials[numImportedMaterials] = ImportMaterial(embeddedTexIdxs, r, defaultMaterial);
+    mScene->mMaterials[numImportedMaterials] = ImportMaterial(mEmbeddedTexIdxs, r, defaultMaterial);
 
     for (unsigned int i = 0; i < numImportedMaterials; ++i) {
-        mScene->mMaterials[i] = ImportMaterial(embeddedTexIdxs, r, r.materials[i]);
+        mScene->mMaterials[i] = ImportMaterial(mEmbeddedTexIdxs, r, r.materials[i]);
     }
 }
 
@@ -802,8 +802,7 @@ void glTF2Importer::ImportMeshes(glTF2::Asset &r) {
                 if (actualNumFaces < nFaces) {
                     ASSIMP_LOG_WARN("Some faces had out-of-range indices. Those faces were dropped.");
                 }
-                if (actualNumFaces == 0)
-                {
+                if (actualNumFaces == 0) {
                     throw DeadlyImportError("Mesh \"", aim->mName.C_Str(), "\" has no faces");
                 }
                 aim->mNumFaces = actualNumFaces;
@@ -843,7 +842,6 @@ void glTF2Importer::ImportCameras(glTF2::Asset &r) {
         aicam->mLookAt = aiVector3D(0.f, 0.f, -1.f);
 
         if (cam.type == Camera::Perspective) {
-
             aicam->mAspect = cam.cameraProperties.perspective.aspectRatio;
             aicam->mHorizontalFOV = cam.cameraProperties.perspective.yfov * ((aicam->mAspect == 0.f) ? 1.f : aicam->mAspect);
             aicam->mClipPlaneFar = cam.cameraProperties.perspective.zfar;
@@ -862,8 +860,9 @@ void glTF2Importer::ImportCameras(glTF2::Asset &r) {
 }
 
 void glTF2Importer::ImportLights(glTF2::Asset &r) {
-    if (!r.lights.Size())
+    if (!r.lights.Size()) {
         return;
+    }
 
     const unsigned int numLights = r.lights.Size();
     ASSIMP_LOG_DEBUG("Importing ", numLights, " lights");
@@ -1125,8 +1124,8 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector<unsigned int> &
                         bone->mNumWeights = static_cast<uint32_t>(weights.size());
 
                         if (bone->mNumWeights > 0) {
-                          bone->mWeights = new aiVertexWeight[bone->mNumWeights];
-                          memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight));
+                            bone->mWeights = new aiVertexWeight[bone->mNumWeights];
+                            memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight));
                         } else {
                             // Assimp expects all bones to have at least 1 weight.
                             bone->mWeights = new aiVertexWeight[1];
@@ -1167,8 +1166,7 @@ aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector<unsigned int> &
                 if (!ainode->mMetaData) {
                     ainode->mMetaData = aiMetadata::Alloc(1);
                     ainode->mMetaData->Set(0, "PBR_LightRange", node.light->range.value);
-                }
-                else {
+                } else {
                     ainode->mMetaData->Add("PBR_LightRange", node.light->range.value);
                 }
             }
@@ -1509,16 +1507,20 @@ void glTF2Importer::ImportAnimations(glTF2::Asset &r) {
     }
 }
 
-void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset &r) {
-    embeddedTexIdxs.resize(r.images.Size(), -1);
-
-    int numEmbeddedTexs = 0;
+static unsigned int countEmbeddedTextures(glTF2::Asset &r) {
+    unsigned int numEmbeddedTexs = 0;
     for (size_t i = 0; i < r.images.Size(); ++i) {
         if (r.images[i].HasData()) {
             numEmbeddedTexs += 1;
         }
     }
 
+    return numEmbeddedTexs;
+}
+
+void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset &r) {
+    mEmbeddedTexIdxs.resize(r.images.Size(), -1);
+    const unsigned int numEmbeddedTexs = countEmbeddedTextures(r);
     if (numEmbeddedTexs == 0) {
         return;
     }
@@ -1536,7 +1538,7 @@ void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset &r) {
         }
 
         int idx = mScene->mNumTextures++;
-        embeddedTexIdxs[i] = idx;
+        mEmbeddedTexIdxs[i] = idx;
 
         aiTexture *tex = mScene->mTextures[idx] = new aiTexture();
 
@@ -1597,7 +1599,7 @@ void glTF2Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IO
 
     // clean all member arrays
     meshOffsets.clear();
-    embeddedTexIdxs.clear();
+    mEmbeddedTexIdxs.clear();
 
     this->mScene = pScene;
 

+ 1 - 1
code/AssetLib/glTF2/glTF2Importer.h

@@ -79,7 +79,7 @@ private:
 
 private:
     std::vector<unsigned int> meshOffsets;
-    std::vector<int> embeddedTexIdxs;
+    std::vector<int> mEmbeddedTexIdxs;
     aiScene *mScene;
 
     /// An instance of rapidjson::IRemoteSchemaDocumentProvider

+ 6 - 0
code/CMakeLists.txt

@@ -376,6 +376,12 @@ ADD_ASSIMP_IMPORTER( IRRMESH
   AssetLib/Irr/IRRShared.h
 )
 
+ADD_ASSIMP_IMPORTER( IQM
+  AssetLib/IQM/IQMImporter.cpp
+  AssetLib/IQM/iqm.h
+  AssetLib/IQM/IQMImporter.h
+)
+
 ADD_ASSIMP_IMPORTER( IRR
   AssetLib/Irr/IRRLoader.cpp
   AssetLib/Irr/IRRLoader.h

+ 4 - 3
code/Common/BaseImporter.cpp

@@ -156,8 +156,8 @@ void BaseImporter::GetExtensionList(std::set<std::string> &extensions) {
 // ------------------------------------------------------------------------------------------------
 /*static*/ bool BaseImporter::SearchFileHeaderForToken(IOSystem *pIOHandler,
         const std::string &pFile,
-        const char * const *tokens,
-        unsigned int numTokens,
+        const char **tokens,
+        std::size_t numTokens,
         unsigned int searchBytes /* = 200 */,
         bool tokensSol /* false */,
         bool noAlphaBeforeTokens /* false */) {
@@ -268,10 +268,11 @@ std::string BaseImporter::GetExtension(const std::string &file) {
     return ret;
 }
 
+
 // ------------------------------------------------------------------------------------------------
 // Check for magic bytes at the beginning of the file.
 /* static */ bool BaseImporter::CheckMagicToken(IOSystem *pIOHandler, const std::string &pFile,
-        const void *_magic, unsigned int num, unsigned int offset, unsigned int size) {
+        const void *_magic, std::size_t num, unsigned int offset, unsigned int size) {
     ai_assert(size <= 16);
     ai_assert(_magic);
 

+ 5 - 4
code/Common/DefaultLogger.cpp

@@ -388,15 +388,16 @@ void DefaultLogger::WriteToStreams(const char *message, ErrorSeverity ErrorSev)
     ai_assert(nullptr != message);
 
     // Check whether this is a repeated message
-    if (!::strncmp(message, lastMsg, lastLen - 1)) {
+    auto thisLen = ::strlen(message);
+    if (thisLen == lastLen - 1 && !::strncmp(message, lastMsg, lastLen - 1)) {
         if (!noRepeatMsg) {
             noRepeatMsg = true;
             message = "Skipping one or more lines with the same contents\n";
-        } else
-            return;
+        }
+        return;
     } else {
         // append a new-line character to the message to be printed
-        lastLen = ::strlen(message);
+        lastLen = thisLen;
         ::memcpy(lastMsg, message, lastLen + 1);
         ::strcat(lastMsg + lastLen, "\n");
 

+ 58 - 16
code/Common/Importer.cpp

@@ -617,29 +617,71 @@ const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags) {
             profiler->BeginRegion("total");
         }
 
-        // Find an worker class which can handle the file
-        BaseImporter* imp = nullptr;
+        // Find an worker class which can handle the file extension.
+        // Multiple importers may be able to handle the same extension (.xml!); gather them all.
         SetPropertyInteger("importerIndex", -1);
-        for( unsigned int a = 0; a < pimpl->mImporter.size(); a++)  {
+        struct ImporterAndIndex {
+            BaseImporter * importer;
+            unsigned int   index;
+        };
+        std::vector<ImporterAndIndex> possibleImporters;
+        for (unsigned int a = 0; a < pimpl->mImporter.size(); a++)  {
+
+            // Every importer has a list of supported extensions.
+            std::set<std::string> extensions;
+            pimpl->mImporter[a]->GetExtensionList(extensions);
+
+            // CAUTION: Do not just search for the extension!
+            // GetExtension() returns the part after the *last* dot, but some extensions have dots
+            // inside them, e.g. ogre.mesh.xml. Compare the entire end of the string.
+            for (std::set<std::string>::const_iterator it = extensions.cbegin(); it != extensions.cend(); ++it) {
+
+                // Yay for C++<20 not having std::string::ends_with()
+                std::string extension = "." + *it;
+                if (extension.length() <= pFile.length()) {
+                    // Possible optimization: Fetch the lowercase filename!
+                    if (0 == ASSIMP_stricmp(pFile.c_str() + pFile.length() - extension.length(), extension.c_str())) {
+                        ImporterAndIndex candidate = { pimpl->mImporter[a], a };
+                        possibleImporters.push_back(candidate);
+                        break;
+                    }
+                }
+
+            }
+
+        }
+
+        // If just one importer supports this extension, pick it and close the case.
+        BaseImporter* imp = nullptr;
+        if (1 == possibleImporters.size()) {
+            imp = possibleImporters[0].importer;
+            SetPropertyInteger("importerIndex", possibleImporters[0].index);
+        }
+        // If multiple importers claim this file extension, ask them to look at the actual file data to decide.
+        // This can happen e.g. with XML (COLLADA vs. Irrlicht).
+        else {
+            for (std::vector<ImporterAndIndex>::const_iterator it = possibleImporters.begin(); it < possibleImporters.end(); ++it) {
+                BaseImporter & importer = *it->importer;
+
+                ASSIMP_LOG_INFO("Found a possible importer: " + std::string(importer.GetInfo()->mName) + "; trying signature-based detection");
+                if (importer.CanRead( pFile, pimpl->mIOHandler, true)) {
+                    imp = &importer;
+                    SetPropertyInteger("importerIndex", it->index);
+                    break;
+                }
 
-            if( pimpl->mImporter[a]->CanRead( pFile, pimpl->mIOHandler, false)) {
-                imp = pimpl->mImporter[a];
-                SetPropertyInteger("importerIndex", a);
-                break;
             }
+
         }
 
         if (!imp)   {
             // not so bad yet ... try format auto detection.
-            const std::string::size_type s = pFile.find_last_of('.');
-            if (s != std::string::npos) {
-                ASSIMP_LOG_INFO("File extension not known, trying signature-based detection");
-                for( unsigned int a = 0; a < pimpl->mImporter.size(); a++)  {
-                    if( pimpl->mImporter[a]->CanRead( pFile, pimpl->mIOHandler, true)) {
-                        imp = pimpl->mImporter[a];
-                        SetPropertyInteger("importerIndex", a);
-                        break;
-                    }
+            ASSIMP_LOG_INFO("File extension not known, trying signature-based detection");
+            for( unsigned int a = 0; a < pimpl->mImporter.size(); a++)  {
+                if( pimpl->mImporter[a]->CanRead( pFile, pimpl->mIOHandler, true)) {
+                    imp = pimpl->mImporter[a];
+                    SetPropertyInteger("importerIndex", a);
+                    break;
                 }
             }
             // Put a proper error message if no suitable importer was found

+ 6 - 0
code/Common/ImporterRegistry.cpp

@@ -202,6 +202,9 @@ corresponding preprocessor flag to selectively disable formats.
 #ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
 #include "AssetLib/M3D/M3DImporter.h"
 #endif
+#ifndef ASSIMP_BUILD_NO_IQM_IMPORTER
+#include "AssetLib/IQM/IQMImporter.h"
+#endif
 
 namespace Assimp {
 
@@ -370,6 +373,9 @@ void GetImporterInstanceList(std::vector<BaseImporter *> &out) {
 #endif
 #ifndef ASSIMP_BUILD_NO_MMD_IMPORTER
     out.push_back(new MMDImporter());
+#endif
+#ifndef ASSIMP_BUILD_NO_IQM_IMPORTER
+    out.push_back(new IQMImporter());
 #endif
     //#ifndef ASSIMP_BUILD_NO_STEP_IMPORTER
     //    out.push_back(new StepFile::StepFileImporter());

+ 9 - 14
include/assimp/BaseImporter.h

@@ -95,20 +95,15 @@ public:
     // -------------------------------------------------------------------
     /** Returns whether the class can handle the format of the given file.
      *
-     * The implementation should be as quick as possible. A check for
-     * the file extension is enough. If no suitable loader is found with
-     * this strategy, CanRead() is called again, the 'checkSig' parameter
-     * set to true this time. Now the implementation is expected to
-     * perform a full check of the file structure, possibly searching the
-     * first bytes of the file for magic identifiers or keywords.
+     * The implementation is expected to perform a full check of the file
+     * structure, possibly searching the first bytes of the file for magic
+     * identifiers or keywords.
      *
      * @param pFile Path and file name of the file to be examined.
      * @param pIOHandler The IO handler to use for accessing any file.
-     * @param checkSig Set to true if this method is called a second time.
-     *   This time, the implementation may take more time to examine the
-     *   contents of the file to be loaded for magic bytes, keywords, etc
-     *   to be able to load files with unknown/not existent file extensions.
-     * @return true if the class can read this file, false if not.
+     * @param checkSig Legacy; do not use.
+     * @return true if the class can read this file, false if not or if
+     * unsure.
      */
     virtual bool CanRead(
             const std::string &pFile,
@@ -259,8 +254,8 @@ public: // static utilities
     static bool SearchFileHeaderForToken(
             IOSystem *pIOSystem,
             const std::string &file,
-            const char * const *tokens,
-            unsigned int numTokens,
+            const char **tokens,
+            std::size_t numTokens,
             unsigned int searchBytes = 200,
             bool tokensSol = false,
             bool noAlphaBeforeTokens = false);
@@ -305,7 +300,7 @@ public: // static utilities
             IOSystem *pIOHandler,
             const std::string &pFile,
             const void *magic,
-            unsigned int num,
+            std::size_t num,
             unsigned int offset = 0,
             unsigned int size = 4);
 

+ 2 - 0
include/assimp/defs.h

@@ -331,4 +331,6 @@ static const ai_real ai_epsilon = (ai_real)0.00001;
 #define AI_DEBUG_INVALIDATE_PTR(x)
 #endif
 
+#define AI_COUNT_OF(X) (sizeof(X) / sizeof((X)[0]))
+
 #endif // !! AI_DEFINES_H_INC

二进制
test/models/IQM/Body.jpg


二进制
test/models/IQM/Head.jpg


二进制
test/models/IQM/mrfixit.iqm