瀏覽代碼

Merge branch 'master' into Dependabot-GitHub-Actions

Kim Kulling 3 年之前
父節點
當前提交
4a5ad50828
共有 51 個文件被更改,包括 999 次插入608 次删除
  1. 2 1
      Build.md
  2. 1 1
      CMakeLists.txt
  3. 16 0
      SECURITY.md
  4. 2 0
      code/AssetLib/3MF/3MFXmlTags.h
  5. 20 5
      code/AssetLib/3MF/XmlSerializer.cpp
  6. 5 0
      code/AssetLib/Collada/ColladaHelper.h
  7. 6 3
      code/AssetLib/Collada/ColladaLoader.cpp
  8. 1 0
      code/AssetLib/Collada/ColladaLoader.h
  9. 0 21
      code/AssetLib/FBX/FBXAnimation.cpp
  10. 2 1
      code/AssetLib/FBX/FBXCommon.h
  11. 0 1
      code/AssetLib/FBX/FBXCompileConfig.h
  12. 114 52
      code/AssetLib/FBX/FBXConverter.cpp
  13. 26 8
      code/AssetLib/FBX/FBXConverter.h
  14. 2 5
      code/AssetLib/FBX/FBXDeformer.cpp
  15. 2 2
      code/AssetLib/FBX/FBXDocument.cpp
  16. 10 10
      code/AssetLib/FBX/FBXDocument.h
  17. 3 6
      code/AssetLib/FBX/FBXDocumentUtil.cpp
  18. 5 7
      code/AssetLib/FBX/FBXDocumentUtil.h
  19. 1 1
      code/AssetLib/FBX/FBXExporter.cpp
  20. 6 0
      code/AssetLib/FBX/FBXImportSettings.h
  21. 22 25
      code/AssetLib/FBX/FBXImporter.cpp
  22. 7 6
      code/AssetLib/FBX/FBXImporter.h
  23. 4 20
      code/AssetLib/FBX/FBXMeshGeometry.cpp
  24. 12 9
      code/AssetLib/FBX/FBXMeshGeometry.h
  25. 29 78
      code/AssetLib/FBX/FBXNodeAttribute.cpp
  26. 1 15
      code/AssetLib/FBX/FBXParser.cpp
  27. 2 2
      code/AssetLib/FBX/FBXParser.h
  28. 1 1
      code/AssetLib/Irr/IRRLoader.cpp
  29. 1 2
      code/AssetLib/LWO/LWOLoader.cpp
  30. 3 0
      code/AssetLib/MDL/MDLMaterialLoader.cpp
  31. 21 29
      code/AssetLib/OpenGEX/OpenGEXImporter.cpp
  32. 3 8
      code/AssetLib/OpenGEX/OpenGEXImporter.h
  33. 2 2
      code/AssetLib/glTF2/glTF2Exporter.cpp
  34. 10 3
      code/Common/DefaultIOStream.cpp
  35. 29 26
      code/Common/ScenePreprocessor.cpp
  36. 8 6
      code/Common/SkeletonMeshBuilder.cpp
  37. 5 1
      code/Common/Version.cpp
  38. 1 1
      code/PostProcessing/EmbedTexturesProcess.cpp
  39. 4 0
      code/res/assimp.rc
  40. 4 4
      contrib/unzip/crypt.c
  41. 4 2
      contrib/unzip/ioapi.c
  42. 4 4
      contrib/unzip/unzip.c
  43. 273 191
      include/assimp/XmlParser.h
  44. 9 0
      include/assimp/config.h.in
  45. 120 21
      include/assimp/mesh.h
  46. 15 2
      include/assimp/scene.h
  47. 132 0
      test/models/Collada/box_nested_animation.dae
  48. 二進制
      test/models/FBX/animation_with_skeleton.fbx
  49. 38 25
      test/unit/utColladaImportExport.cpp
  50. 7 0
      test/unit/utFBXImporterExporter.cpp
  51. 4 1
      test/unit/utOpenGEXImportExport.cpp

+ 2 - 1
Build.md

@@ -14,7 +14,8 @@ The assimp port in vcpkg is kept up to date by Microsoft team members and commun
 ## Install on Ubuntu
 You can install the Asset-Importer-Lib via apt:
 ```
-sudo apt-get install assimp
+sudp apt-get update
+sudo apt-get install libassimp-dev
 ```
 
 ## Install pyassimp

+ 1 - 1
CMakeLists.txt

@@ -90,7 +90,7 @@ OPTION( ASSIMP_BUILD_ZLIB
 )
 OPTION( ASSIMP_BUILD_ASSIMP_TOOLS
   "If the supplementary tools for Assimp are built in addition to the library."
-  ON
+  OFF
 )
 OPTION ( ASSIMP_BUILD_SAMPLES
   "If the official samples are built as well (needs Glut)."

+ 16 - 0
SECURITY.md

@@ -0,0 +1,16 @@
+# Security Policy
+
+## Supported Versions
+
+Use this section to tell people about which versions of your project are
+currently being supported with security updates.
+
+| Version | Supported          |
+| ------- | ------------------ |
+| 5.2.4   | :white_check_mark: |
+
+## Reporting a Vulnerability
+
+If you have found any security vulnerability you can contact us via 
[email protected]
+

+ 2 - 0
code/AssetLib/3MF/3MFXmlTags.h

@@ -74,6 +74,8 @@ namespace XmlTag {
     const char* const pid = "pid";
     const char* const pindex = "pindex";
     const char* const p1 = "p1";
+    const char *const p2 = "p2";
+    const char *const p3 = "p3";
     const char* const name = "name";
     const char* const type = "type";
     const char* const build = "build";

+ 20 - 5
code/AssetLib/3MF/XmlSerializer.cpp

@@ -64,7 +64,7 @@ bool validateColorString(const char *color) {
     return true;
 }
 
-aiFace ReadTriangle(XmlNode &node) {
+aiFace ReadTriangle(XmlNode &node, unsigned int &texId0, unsigned int &texId1, unsigned int &texId2) {
     aiFace face;
 
     face.mNumIndices = 3;
@@ -73,6 +73,10 @@ aiFace ReadTriangle(XmlNode &node) {
     face.mIndices[1] = static_cast<unsigned int>(std::atoi(node.attribute(XmlTag::v2).as_string()));
     face.mIndices[2] = static_cast<unsigned int>(std::atoi(node.attribute(XmlTag::v3).as_string()));
 
+    texId0 = static_cast<unsigned int>(std::atoi(node.attribute(XmlTag::p1).as_string()));
+    texId1 = static_cast<unsigned int>(std::atoi(node.attribute(XmlTag::p2).as_string()));
+    texId2 = static_cast<unsigned int>(std::atoi(node.attribute(XmlTag::p3).as_string()));
+
     return face;
 }
 
@@ -412,6 +416,8 @@ void XmlSerializer::ImportTriangles(XmlNode &node, aiMesh *mesh) {
             bool hasPid = getNodeAttribute(currentNode, D3MF::XmlTag::pid, pid);
             bool hasP1 = getNodeAttribute(currentNode, D3MF::XmlTag::p1, p1);
 
+            unsigned int texId[3];
+            aiFace face = ReadTriangle(currentNode, texId[0], texId[1], texId[2]);
             if (hasPid && hasP1) {
                 auto it = mResourcesDictionnary.find(pid);
                 if (it != mResourcesDictionnary.end()) {
@@ -420,6 +426,11 @@ void XmlSerializer::ImportTriangles(XmlNode &node, aiMesh *mesh) {
                         mesh->mMaterialIndex = baseMaterials->mMaterialIndex[p1];
                     } else if (it->second->getType() == ResourceType::RT_Texture2DGroup) {
                         if (mesh->mTextureCoords[0] == nullptr) {
+                            mesh->mNumUVComponents[0] = 2;
+                            for (unsigned int i = 1; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
+                                mesh->mNumUVComponents[i] = 0;
+                            }
+
                             Texture2DGroup *group = static_cast<Texture2DGroup *>(it->second);
                             const std::string name = ai_to_string(group->mTexId);
                             for (size_t i = 0; i < mMaterials.size(); ++i) {
@@ -427,8 +438,9 @@ void XmlSerializer::ImportTriangles(XmlNode &node, aiMesh *mesh) {
                                     mesh->mMaterialIndex = static_cast<unsigned int>(i);
                                 }
                             }
-                            mesh->mTextureCoords[0] = new aiVector3D[group->mTex2dCoords.size()];
-                            for (unsigned int i = 0; i < group->mTex2dCoords.size(); ++i) {
+                            mesh->mTextureCoords[0] = new aiVector3D[mesh->mNumVertices];
+                            for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+
                                 mesh->mTextureCoords[0][i] = aiVector3D(group->mTex2dCoords[i].x, group->mTex2dCoords[i].y, 0);
                             }
                         }
@@ -436,7 +448,6 @@ void XmlSerializer::ImportTriangles(XmlNode &node, aiMesh *mesh) {
                 }
             }
 
-            aiFace face = ReadTriangle(currentNode);
             faces.push_back(face);
         }
     }
@@ -578,11 +589,15 @@ aiMaterial *XmlSerializer::readMaterialDef(XmlNode &node, unsigned int basemater
 }
 
 void XmlSerializer::StoreMaterialsInScene(aiScene *scene) {
-    if (nullptr == scene || mMaterials.empty()) {
+    if (nullptr == scene) {
         return;
     }
 
     scene->mNumMaterials = static_cast<unsigned int>(mMaterials.size());
+    if (scene->mNumMaterials == 0) {
+        return;
+    }
+
     scene->mMaterials = new aiMaterial *[scene->mNumMaterials];
     for (size_t i = 0; i < mMaterials.size(); ++i) {
         scene->mMaterials[i] = mMaterials[i];

+ 5 - 0
code/AssetLib/Collada/ColladaHelper.h

@@ -621,6 +621,11 @@ struct Animation {
 
         for (std::vector<Animation *>::iterator it = pParent->mSubAnims.begin(); it != pParent->mSubAnims.end();) {
             Animation *anim = *it;
+            // Assign the first animation name to the parent if empty.
+            // This prevents the animation name from being lost when animations are combined
+            if (mName.empty()) {
+              mName = anim->mName;
+            }
             CombineSingleChannelAnimationsRecursively(anim);
 
             if (childrenAnimationsHaveDifferentChannels && anim->mChannels.size() == 1 &&

+ 6 - 3
code/AssetLib/Collada/ColladaLoader.cpp

@@ -102,6 +102,7 @@ ColladaLoader::ColladaLoader() :
         mTextures(),
         mAnims(),
         noSkeletonMesh(false),
+        removeEmptyBones(false),
         ignoreUpDirection(false),
         useColladaName(false),
         mNodeNameCounter(0) {
@@ -130,6 +131,7 @@ bool ColladaLoader::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool
 // ------------------------------------------------------------------------------------------------
 void ColladaLoader::SetupProperties(const Importer *pImp) {
     noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES, 0) != 0;
+    removeEmptyBones = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true) != 0;
     ignoreUpDirection = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION, 0) != 0;
     useColladaName = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES, 0) != 0;
 }
@@ -798,9 +800,10 @@ aiMesh *ColladaLoader::CreateMesh(const ColladaParser &pParser, const Mesh *pSrc
         // count the number of bones which influence vertices of the current submesh
         size_t numRemainingBones = 0;
         for (const auto & dstBone : dstBones) {
-            if (!dstBone.empty()) {
-                ++numRemainingBones;
+            if (dstBone.empty() && removeEmptyBones) {
+                continue;
             }
+            ++numRemainingBones;
         }
 
         // create bone array and copy bone weights one by one
@@ -809,7 +812,7 @@ aiMesh *ColladaLoader::CreateMesh(const ColladaParser &pParser, const Mesh *pSrc
         size_t boneCount = 0;
         for (size_t a = 0; a < numBones; ++a) {
             // omit bones without weights
-            if (dstBones[a].empty()) {
+            if (dstBones[a].empty() && removeEmptyBones) {
                 continue;
             }
 

+ 1 - 0
code/AssetLib/Collada/ColladaLoader.h

@@ -237,6 +237,7 @@ protected:
     std::vector<aiAnimation *> mAnims;
 
     bool noSkeletonMesh;
+    bool removeEmptyBones;
     bool ignoreUpDirection;
     bool useColladaName;
 

+ 0 - 21
code/AssetLib/FBX/FBXAnimation.cpp

@@ -4,7 +4,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,
@@ -87,11 +86,6 @@ AnimationCurve::AnimationCurve(uint64_t id, const Element &element, const std::s
     }
 }
 
-// ------------------------------------------------------------------------------------------------
-AnimationCurve::~AnimationCurve() {
-    // empty
-}
-
 // ------------------------------------------------------------------------------------------------
 AnimationCurveNode::AnimationCurveNode(uint64_t id, const Element &element, const std::string &name,
         const Document &doc, const char *const *target_prop_whitelist /*= nullptr*/,
@@ -147,11 +141,6 @@ AnimationCurveNode::AnimationCurveNode(uint64_t id, const Element &element, cons
     props = GetPropertyTable(doc, "AnimationCurveNode.FbxAnimCurveNode", element, sc, false);
 }
 
-// ------------------------------------------------------------------------------------------------
-AnimationCurveNode::~AnimationCurveNode() {
-    // empty
-}
-
 // ------------------------------------------------------------------------------------------------
 const AnimationCurveMap &AnimationCurveNode::Curves() const {
     if (curves.empty()) {
@@ -193,11 +182,6 @@ AnimationLayer::AnimationLayer(uint64_t id, const Element &element, const std::s
     props = GetPropertyTable(doc, "AnimationLayer.FbxAnimLayer", element, sc, true);
 }
 
-// ------------------------------------------------------------------------------------------------
-AnimationLayer::~AnimationLayer() {
-    // empty
-}
-
 // ------------------------------------------------------------------------------------------------
 AnimationCurveNodeList AnimationLayer::Nodes(const char *const *target_prop_whitelist /*= nullptr*/,
         size_t whitelist_size /*= 0*/) const {
@@ -279,11 +263,6 @@ AnimationStack::AnimationStack(uint64_t id, const Element &element, const std::s
     }
 }
 
-// ------------------------------------------------------------------------------------------------
-AnimationStack::~AnimationStack() {
-    // empty
-}
-
 } // namespace FBX
 } // namespace Assimp
 

+ 2 - 1
code/AssetLib/FBX/FBXCommon.h

@@ -50,7 +50,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 namespace Assimp {
 namespace FBX {
 
-const std::string NULL_RECORD = { // 25 null bytes in 64-bit and 13 null bytes in 32-bit
+static constexpr size_t NumNullRecords = 25;
+const char NULL_RECORD[NumNullRecords] = { // 25 null bytes in 64-bit and 13 null bytes in 32-bit
     '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0',
     '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'
 }; // who knows why, it looks like two integers 32/64 bit (compressed and uncompressed sizes?) + 1 byte (might be compression type?)

+ 0 - 1
code/AssetLib/FBX/FBXCompileConfig.h

@@ -4,7 +4,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,

+ 114 - 52
code/AssetLib/FBX/FBXConverter.cpp

@@ -65,12 +65,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <stdlib.h>
 #include <cstdint>
 #include <iomanip>
-#include <iostream>
 #include <iterator>
 #include <memory>
 #include <sstream>
-#include <tuple>
-#include <vector>
 
 namespace Assimp {
 namespace FBX {
@@ -187,8 +184,7 @@ std::string FBXConverter::MakeUniqueNodeName(const Model *const model, const aiN
 
 /// This struct manages nodes which may or may not end up in the node hierarchy.
 /// When a node becomes a child of another node, that node becomes its owner and mOwnership should be released.
-struct FBXConverter::PotentialNode
-{
+struct FBXConverter::PotentialNode {
     PotentialNode() : mOwnership(new aiNode), mNode(mOwnership.get()) {}
     PotentialNode(const std::string& name) : mOwnership(new aiNode(name)), mNode(mOwnership.get()) {}
     aiNode* operator->() { return mNode; }
@@ -231,7 +227,7 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node)
         if (nullptr != model) {
             nodes_chain.clear();
             post_nodes_chain.clear();
-
+            aiMatrix4x4 new_abs_transform = parent->mTransformation;
             std::string node_name = FixNodeName(model->Name());
             // even though there is only a single input node, the design of
             // assimp (or rather: the complicated transformation chain that
@@ -268,7 +264,7 @@ void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node)
             }
 
             // attach geometry
-            ConvertModel(*model, nodes_chain.back().mNode, root_node);
+            ConvertModel(*model, nodes_chain.back().mNode, root_node, new_abs_transform);
 
             // check if there will be any child nodes
             const std::vector<const Connection *> &child_conns = doc.GetConnectionsByDestinationSequenced(model->ID(), "Model");
@@ -447,7 +443,7 @@ void FBXConverter::GetUniqueName(const std::string &name, std::string &uniqueNam
     auto it_pair = mNodeNames.insert({ name, 0 }); // duplicate node name instance count
     unsigned int &i = it_pair.first->second;
     while (!it_pair.second) {
-        i++;
+        ++i;
         std::ostringstream ext;
         ext << name << std::setfill('0') << std::setw(3) << i;
         uniqueName = ext.str();
@@ -646,9 +642,8 @@ void FBXConverter::GetRotationMatrix(Model::RotOrder mode, const aiVector3D &rot
 
 bool FBXConverter::NeedsComplexTransformationChain(const Model &model) {
     const PropertyTable &props = model.Props();
-    bool ok;
 
-    const float zero_epsilon = ai_epsilon;
+    const auto zero_epsilon = ai_epsilon;
     const aiVector3D all_ones(1.0f, 1.0f, 1.0f);
     for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) {
         const TransformationComp comp = static_cast<TransformationComp>(i);
@@ -660,6 +655,7 @@ bool FBXConverter::NeedsComplexTransformationChain(const Model &model) {
 
         bool scale_compare = (comp == TransformationComp_GeometricScaling || comp == TransformationComp_Scaling);
 
+        bool ok = true;
         const aiVector3D &v = PropertyGet<aiVector3D>(props, NameTransformationCompProperty(comp), ok);
         if (ok && scale_compare) {
             if ((v - all_ones).SquareLength() > zero_epsilon) {
@@ -894,18 +890,17 @@ void FBXConverter::SetupNodeMetadata(const Model &model, aiNode &nd) {
     }
 }
 
-void FBXConverter::ConvertModel(const Model &model, aiNode *parent, aiNode *root_node) {
+void FBXConverter::ConvertModel(const Model &model, aiNode *parent, aiNode *root_node, const aiMatrix4x4 &absolute_transform) {
     const std::vector<const Geometry *> &geos = model.GetGeometry();
 
     std::vector<unsigned int> meshes;
     meshes.reserve(geos.size());
 
     for (const Geometry *geo : geos) {
-
         const MeshGeometry *const mesh = dynamic_cast<const MeshGeometry *>(geo);
         const LineGeometry *const line = dynamic_cast<const LineGeometry *>(geo);
         if (mesh) {
-            const std::vector<unsigned int> &indices = ConvertMesh(*mesh, model, parent, root_node);
+            const std::vector<unsigned int> &indices = ConvertMesh(*mesh, model, parent, root_node, absolute_transform);
             std::copy(indices.begin(), indices.end(), std::back_inserter(meshes));
         } else if (line) {
             const std::vector<unsigned int> &indices = ConvertLine(*line, root_node);
@@ -926,7 +921,7 @@ void FBXConverter::ConvertModel(const Model &model, aiNode *parent, aiNode *root
 }
 
 std::vector<unsigned int>
-FBXConverter::ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node) {
+FBXConverter::ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node, const aiMatrix4x4 &absolute_transform) {
     std::vector<unsigned int> temp;
 
     MeshMap::const_iterator it = meshes_converted.find(&mesh);
@@ -949,13 +944,13 @@ FBXConverter::ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *
         const MatIndexArray::value_type base = mindices[0];
         for (MatIndexArray::value_type index : mindices) {
             if (index != base) {
-                return ConvertMeshMultiMaterial(mesh, model, parent, root_node);
+                return ConvertMeshMultiMaterial(mesh, model, absolute_transform, parent, root_node);
             }
         }
     }
 
     // faster code-path, just copy the data
-    temp.push_back(ConvertMeshSingleMaterial(mesh, model, parent, root_node));
+    temp.push_back(ConvertMeshSingleMaterial(mesh, model, absolute_transform, parent, root_node));
     return temp;
 }
 
@@ -1023,7 +1018,35 @@ aiMesh *FBXConverter::SetupEmptyMesh(const Geometry &mesh, aiNode *parent) {
     return out_mesh;
 }
 
-unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model,
+static aiSkeleton *createAiSkeleton(SkeletonBoneContainer &sbc) {
+    if (sbc.MeshArray.empty() || sbc.SkeletonBoneToMeshLookup.empty()) {
+        return nullptr;
+    }
+
+    aiSkeleton *skeleton = new aiSkeleton;
+    for (auto *mesh : sbc.MeshArray) {
+        auto it = sbc.SkeletonBoneToMeshLookup.find(mesh);
+        if (it == sbc.SkeletonBoneToMeshLookup.end()) {
+            continue;
+        }
+        SkeletonBoneArray *ba = it->second;
+        if (ba == nullptr) {
+            continue;
+        }
+
+        skeleton->mNumBones = static_cast<unsigned int>(ba->size());
+        skeleton->mBones = new aiSkeletonBone*[skeleton->mNumBones];
+        size_t index = 0;
+        for (auto bone : (* ba)) {
+            skeleton->mBones[index] = bone;
+            ++index;
+        }
+    }
+
+    return skeleton;
+}
+
+unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model, const aiMatrix4x4 &absolute_transform,
         aiNode *parent, aiNode *) {
     const MatIndexArray &mindices = mesh.GetMaterialIndices();
     aiMesh *const out_mesh = SetupEmptyMesh(mesh, parent);
@@ -1142,8 +1165,15 @@ unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, c
         ConvertMaterialForMesh(out_mesh, model, mesh, mindices[0]);
     }
 
-    if (doc.Settings().readWeights && mesh.DeformerSkin() != nullptr) {
-        ConvertWeights(out_mesh, mesh, parent, NO_MATERIAL_SEPARATION, nullptr);
+    if (doc.Settings().readWeights && mesh.DeformerSkin() != nullptr && !doc.Settings().useSkeleton) {
+        ConvertWeights(out_mesh, mesh, absolute_transform, parent, NO_MATERIAL_SEPARATION, nullptr);
+    } else if (doc.Settings().readWeights && mesh.DeformerSkin() != nullptr && doc.Settings().useSkeleton) {
+        SkeletonBoneContainer sbc;
+        ConvertWeightsToSkeleton(out_mesh, mesh, absolute_transform, parent, NO_MATERIAL_SEPARATION, nullptr, sbc);
+        aiSkeleton *skeleton = createAiSkeleton(sbc);
+        if (skeleton != nullptr) {
+            mSkeletons.emplace_back(skeleton);
+        }
     }
 
     std::vector<aiAnimMesh *> animMeshes;
@@ -1190,7 +1220,7 @@ unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, c
 }
 
 std::vector<unsigned int>
-FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent,
+FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, const aiMatrix4x4 &absolute_transform, aiNode *parent,
         aiNode *root_node) {
     const MatIndexArray &mindices = mesh.GetMaterialIndices();
     ai_assert(mindices.size());
@@ -1201,7 +1231,7 @@ FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &mo
     for (MatIndexArray::value_type index : mindices) {
         if (had.find(index) == had.end()) {
 
-            indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, parent, root_node));
+            indices.push_back(ConvertMeshMultiMaterial(mesh, model, absolute_transform, index, parent, root_node));
             had.insert(index);
         }
     }
@@ -1209,9 +1239,8 @@ FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &mo
     return indices;
 }
 
-unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model,
-        MatIndexArray::value_type index,
-        aiNode *parent, aiNode *) {
+unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, const aiMatrix4x4 &absolute_transform,
+        MatIndexArray::value_type index, aiNode *parent, aiNode *) {
     aiMesh *const out_mesh = SetupEmptyMesh(mesh, parent);
 
     const MatIndexArray &mindices = mesh.GetMaterialIndices();
@@ -1374,7 +1403,7 @@ unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, co
     ConvertMaterialForMesh(out_mesh, model, mesh, index);
 
     if (process_weights) {
-        ConvertWeights(out_mesh, mesh, parent, index, &reverseMapping);
+        ConvertWeights(out_mesh, mesh, absolute_transform, parent, index, &reverseMapping);
     }
 
     std::vector<aiAnimMesh *> animMeshes;
@@ -1424,19 +1453,47 @@ unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, co
     return static_cast<unsigned int>(mMeshes.size() - 1);
 }
 
-void FBXConverter::ConvertWeights(aiMesh *out, const MeshGeometry &geo,
+static void copyBoneToSkeletonBone(aiMesh *mesh, aiBone *bone, aiSkeletonBone *skeletonBone ) {
+    skeletonBone->mNumnWeights = bone->mNumWeights;
+    skeletonBone->mWeights = bone->mWeights;
+    skeletonBone->mOffsetMatrix = bone->mOffsetMatrix;
+    skeletonBone->mMeshId = mesh;
+    skeletonBone->mNode = bone->mNode;
+    skeletonBone->mParent = -1;
+}
+
+void FBXConverter::ConvertWeightsToSkeleton(aiMesh *out, const MeshGeometry &geo, const aiMatrix4x4 &absolute_transform, aiNode *parent, unsigned int materialIndex,
+        std::vector<unsigned int> *outputVertStartIndices, SkeletonBoneContainer &skeletonContainer) {
+
+    if (skeletonContainer.SkeletonBoneToMeshLookup.find(out) != skeletonContainer.SkeletonBoneToMeshLookup.end()) {
+        return;
+    }
+
+    ConvertWeights(out, geo, absolute_transform, parent, materialIndex, outputVertStartIndices);
+    skeletonContainer.MeshArray.emplace_back(out);
+    SkeletonBoneArray *ba = new SkeletonBoneArray;
+    for (size_t i = 0; i < out->mNumBones; ++i) {
+        aiBone *bone = out->mBones[i];
+        if (bone == nullptr) {
+            continue;
+        }
+        aiSkeletonBone *skeletonBone = new aiSkeletonBone;
+        copyBoneToSkeletonBone(out, bone, skeletonBone);
+        ba->emplace_back(skeletonBone);
+    }
+    skeletonContainer.SkeletonBoneToMeshLookup[out] = ba;
+}
+
+void FBXConverter::ConvertWeights(aiMesh *out, const MeshGeometry &geo, const aiMatrix4x4 &absolute_transform,
         aiNode *parent, unsigned int materialIndex,
         std::vector<unsigned int> *outputVertStartIndices) {
     ai_assert(geo.DeformerSkin());
 
-    std::vector<size_t> out_indices;
-    std::vector<size_t> index_out_indices;
-    std::vector<size_t> count_out_indices;
+    std::vector<size_t> out_indices, index_out_indices, count_out_indices;
 
     const Skin &sk = *geo.DeformerSkin();
 
-    std::vector<aiBone *> bones;
-
+    std::vector<aiBone*> bones;
     const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION;
     ai_assert(no_mat_check || outputVertStartIndices);
 
@@ -1496,7 +1553,7 @@ void FBXConverter::ConvertWeights(aiMesh *out, const MeshGeometry &geo,
             // XXX this could be heavily simplified by collecting the bone
             // data in a single step.
             ConvertCluster(bones, cluster, out_indices, index_out_indices,
-                    count_out_indices, parent);
+                    count_out_indices, absolute_transform, parent);
         }
 
         bone_map.clear();
@@ -1509,25 +1566,20 @@ void FBXConverter::ConvertWeights(aiMesh *out, const MeshGeometry &geo,
         out->mBones = nullptr;
         out->mNumBones = 0;
         return;
-    } else {
-        out->mBones = new aiBone *[bones.size()]();
-        out->mNumBones = static_cast<unsigned int>(bones.size());
+    } 
 
-        std::swap_ranges(bones.begin(), bones.end(), out->mBones);
-    }
+    out->mBones = new aiBone *[bones.size()]();
+    out->mNumBones = static_cast<unsigned int>(bones.size());
+    std::swap_ranges(bones.begin(), bones.end(), out->mBones);
 }
 
-const aiNode *GetNodeByName(aiNode *current_node) {
-    aiNode *iter = current_node;
-    //printf("Child count: %d", iter->mNumChildren);
-    return iter;
-}
-
-void FBXConverter::ConvertCluster(std::vector<aiBone *> &local_mesh_bones, const Cluster *cl,
+void FBXConverter::ConvertCluster(std::vector<aiBone*> &local_mesh_bones, const Cluster *cluster,
         std::vector<size_t> &out_indices, std::vector<size_t> &index_out_indices,
-        std::vector<size_t> &count_out_indices, aiNode *) {
-    ai_assert(cl); // make sure cluster valid
-    std::string deformer_name = cl->TargetNode()->Name();
+        std::vector<size_t> &count_out_indices, const aiMatrix4x4 & /* absolute_transform*/,
+        aiNode *) {
+    ai_assert(cluster != nullptr); // make sure cluster valid
+
+    std::string deformer_name = cluster->TargetNode()->Name();
     aiString bone_name = aiString(FixNodeName(deformer_name));
 
     aiBone *bone = nullptr;
@@ -1540,10 +1592,10 @@ void FBXConverter::ConvertCluster(std::vector<aiBone *> &local_mesh_bones, const
         bone = new aiBone();
         bone->mName = bone_name;
 
-        bone->mOffsetMatrix = cl->Transform();
+        bone->mOffsetMatrix = cluster->Transform();
         // store local transform link for post processing
         /*
-        bone->mOffsetMatrix = cl->TransformLink();
+        bone->mOffsetMatrix = cluster->TransformLink();
         bone->mOffsetMatrix.Inverse();
 
         aiMatrix4x4 matrix = (aiMatrix4x4)absolute_transform;
@@ -1560,7 +1612,7 @@ void FBXConverter::ConvertCluster(std::vector<aiBone *> &local_mesh_bones, const
         cursor = bone->mWeights = new aiVertexWeight[out_indices.size()];
 
         const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
-        const WeightArray &weights = cl->GetWeights();
+        const WeightArray &weights = cluster->GetWeights();
 
         const size_t c = index_out_indices.size();
         for (size_t i = 0; i < c; ++i) {
@@ -2605,7 +2657,7 @@ void FBXConverter::ConvertAnimationStack(const AnimationStack &st) {
                 meshMorphAnim->mNumKeys = numKeys;
                 meshMorphAnim->mKeys = new aiMeshMorphKey[numKeys];
                 unsigned int j = 0;
-                for (auto animIt : *animData) {
+                for (auto &animIt : *animData) {
                     morphKeyData *keyData = animIt.second;
                     unsigned int numValuesAndWeights = static_cast<unsigned int>(keyData->values.size());
                     meshMorphAnim->mKeys[j].mNumValuesAndWeights = numValuesAndWeights;
@@ -3180,7 +3232,7 @@ aiNodeAnim* FBXConverter::GenerateSimpleNodeAnim(const std::string& name,
 
     bool ok = false;
     
-    const float zero_epsilon = ai_epsilon;
+    const auto zero_epsilon = ai_epsilon;
 
     const aiVector3D& preRotation = PropertyGet<aiVector3D>(props, "PreRotation", ok);
     if (ok && preRotation.SquareLength() > zero_epsilon) {
@@ -3325,6 +3377,10 @@ FBXConverter::KeyFrameListList FBXConverter::GetRotationKeyframeList(const std::
                             Keys->push_back(tnew);
                             Values->push_back(vnew);
                         }
+                        else {
+                            // Something broke
+                            break;
+                        }
                         tp = tnew;
                         vp = vnew;
                     }
@@ -3625,6 +3681,12 @@ void FBXConverter::TransferDataToScene() {
 
         std::swap_ranges(textures.begin(), textures.end(), mSceneOut->mTextures);
     }
+
+    if (!mSkeletons.empty()) {
+        mSceneOut->mSkeletons = new aiSkeleton *[mSkeletons.size()];
+        mSceneOut->mNumSkeletons = static_cast<unsigned int>(mSkeletons.size());
+        std::swap_ranges(mSkeletons.begin(), mSkeletons.end(), mSceneOut->mSkeletons);
+    }
 }
 
 void FBXConverter::ConvertOrphanedEmbeddedTextures() {

+ 26 - 8
code/AssetLib/FBX/FBXConverter.h

@@ -75,7 +75,18 @@ typedef std::map<int64_t, morphKeyData*> morphAnimData;
 namespace Assimp {
 namespace FBX {
 
+class MeshGeometry;
+
+using SkeletonBoneArray = std::vector<aiSkeletonBone *>;
+using SkeletonBoneToMesh = std::map<aiMesh*, SkeletonBoneArray*>;
+
+struct SkeletonBoneContainer {
+    std::vector<aiMesh *> MeshArray;
+    SkeletonBoneToMesh SkeletonBoneToMeshLookup;
+};
+
 class Document;
+
 /**
  *  Convert a FBX #Document to #aiScene
  *  @param out Empty scene to be populated
@@ -180,12 +191,12 @@ private:
     void SetupNodeMetadata(const Model& model, aiNode& nd);
 
     // ------------------------------------------------------------------------------------------------
-    void ConvertModel(const Model &model, aiNode *parent, aiNode *root_node);
+    void ConvertModel(const Model &model, aiNode *parent, aiNode *root_node, const aiMatrix4x4 &absolute_transform);
 
     // ------------------------------------------------------------------------------------------------
     // MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed
     std::vector<unsigned int>
-    ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node);
+    ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node, const aiMatrix4x4 &absolute_transform);
 
     // ------------------------------------------------------------------------------------------------
     std::vector<unsigned int> ConvertLine(const LineGeometry& line, aiNode *root_node);
@@ -194,15 +205,15 @@ private:
     aiMesh* SetupEmptyMesh(const Geometry& mesh, aiNode *parent);
 
     // ------------------------------------------------------------------------------------------------
-    unsigned int ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model,
+    unsigned int ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model, const aiMatrix4x4 &absolute_transform,
                                            aiNode *parent, aiNode *root_node);
 
     // ------------------------------------------------------------------------------------------------
     std::vector<unsigned int>
-    ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node);
+    ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, const aiMatrix4x4 &absolute_transform, aiNode *parent, aiNode *root_node);
 
     // ------------------------------------------------------------------------------------------------
-    unsigned int ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, MatIndexArray::value_type index,
+    unsigned int ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, const aiMatrix4x4 &absolute_transform, MatIndexArray::value_type index,
                                           aiNode *parent, aiNode *root_node);
 
     // ------------------------------------------------------------------------------------------------
@@ -216,14 +227,19 @@ private:
     *  - outputVertStartIndices is only used when a material index is specified, it gives for
     *    each output vertex the DOM index it maps to.
     */
-    void ConvertWeights(aiMesh *out, const MeshGeometry &geo, aiNode *parent = nullptr,
+    void ConvertWeights(aiMesh *out, const MeshGeometry &geo, const aiMatrix4x4 &absolute_transform, aiNode *parent = nullptr,
             unsigned int materialIndex = NO_MATERIAL_SEPARATION,
             std::vector<unsigned int> *outputVertStartIndices = nullptr);
 
+    // ------------------------------------------------------------------------------------------------
+    void ConvertWeightsToSkeleton(aiMesh *out, const MeshGeometry &geo, const aiMatrix4x4 &absolute_transform,
+            aiNode *parent, unsigned int materialIndex, std::vector<unsigned int> *outputVertStartIndices,
+            SkeletonBoneContainer &skeletonContainer);
+
     // ------------------------------------------------------------------------------------------------
     void ConvertCluster(std::vector<aiBone *> &local_mesh_bones, const Cluster *cl,
                         std::vector<size_t> &out_indices, std::vector<size_t> &index_out_indices,
-                        std::vector<size_t> &count_out_indices, aiNode *parent );
+            std::vector<size_t> &count_out_indices, const aiMatrix4x4 &absolute_transform, aiNode *parent);
 
     // ------------------------------------------------------------------------------------------------
     void ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo,
@@ -296,7 +312,8 @@ private:
     void ConvertAnimationStack(const AnimationStack& st);
 
     // ------------------------------------------------------------------------------------------------
-    void ProcessMorphAnimDatas(std::map<std::string, morphAnimData*>* morphAnimDatas, const BlendShapeChannel* bsc, const AnimationCurveNode* node);
+    void ProcessMorphAnimDatas(std::map<std::string, morphAnimData*>* morphAnimDatas,
+        const BlendShapeChannel* bsc, const AnimationCurveNode* node);
 
     // ------------------------------------------------------------------------------------------------
     void GenerateNodeAnimations(std::vector<aiNodeAnim*>& node_anims,
@@ -445,6 +462,7 @@ private:
 
     double anim_fps;
 
+    std::vector<aiSkeleton *> mSkeletons;
     aiScene* const mSceneOut;
     const FBX::Document& doc;
     bool mRemoveEmptyBones;

+ 2 - 5
code/AssetLib/FBX/FBXDeformer.cpp

@@ -4,7 +4,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,
@@ -58,16 +57,14 @@ namespace FBX {
 using namespace Util;
 
 // ------------------------------------------------------------------------------------------------
-Deformer::Deformer(uint64_t id, const Element& element, const Document& doc, const std::string& name)
-    : Object(id,element,name)
-{
+Deformer::Deformer(uint64_t id, const Element& element, const Document& doc, const std::string& name) :
+        Object(id,element,name) {
     const Scope& sc = GetRequiredScope(element);
 
     const std::string& classname = ParseTokenAsString(GetRequiredToken(element,2));
     props = GetPropertyTable(doc,"Deformer.Fbx" + classname,element,sc,true);
 }
 
-
 // ------------------------------------------------------------------------------------------------
 Deformer::~Deformer()
 {

+ 2 - 2
code/AssetLib/FBX/FBXDocument.cpp

@@ -67,7 +67,7 @@ namespace FBX {
 using namespace Util;
 
 // ------------------------------------------------------------------------------------------------
-LazyObject::LazyObject(uint64_t id, const Element& element, const Document& doc) : 
+LazyObject::LazyObject(uint64_t id, const Element& element, const Document& doc) :
         doc(doc), element(element), id(id), flags() {
     // empty
 }
@@ -544,7 +544,7 @@ std::vector<const Connection*> Document::GetConnectionsSequenced(uint64_t id, bo
     ai_assert( count != 0 );
     ai_assert( count <= MAX_CLASSNAMES);
 
-    size_t lengths[MAX_CLASSNAMES];
+    size_t lengths[MAX_CLASSNAMES] = {};
 
     const size_t c = count;
     for (size_t i = 0; i < c; ++i) {

+ 10 - 10
code/AssetLib/FBX/FBXDocument.h

@@ -164,7 +164,7 @@ class NodeAttribute : public Object {
 public:
     NodeAttribute(uint64_t id, const Element& element, const Document& doc, const std::string& name);
 
-    virtual ~NodeAttribute();
+    virtual ~NodeAttribute() = default;
 
     const PropertyTable& Props() const {
         ai_assert(props.get());
@@ -180,7 +180,7 @@ class CameraSwitcher : public NodeAttribute {
 public:
     CameraSwitcher(uint64_t id, const Element& element, const Document& doc, const std::string& name);
 
-    virtual ~CameraSwitcher();
+    virtual ~CameraSwitcher() = default;
 
     int CameraID() const {
         return cameraId;
@@ -225,7 +225,7 @@ class Camera : public NodeAttribute {
 public:
     Camera(uint64_t id, const Element& element, const Document& doc, const std::string& name);
 
-    virtual  ~Camera();
+    virtual  ~Camera() = default;
 
     fbx_simple_property(Position, aiVector3D, aiVector3D(0,0,0))
     fbx_simple_property(UpVector, aiVector3D, aiVector3D(0,1,0))
@@ -250,21 +250,21 @@ public:
 class Null : public NodeAttribute {
 public:
     Null(uint64_t id, const Element& element, const Document& doc, const std::string& name);
-    virtual ~Null();
+    virtual ~Null() = default;
 };
 
 /** DOM base class for FBX limb node markers attached to a node */
 class LimbNode : public NodeAttribute {
 public:
     LimbNode(uint64_t id, const Element& element, const Document& doc, const std::string& name);
-    virtual ~LimbNode();
+    virtual ~LimbNode() = default;
 };
 
 /** DOM base class for FBX lights attached to a node */
 class Light : public NodeAttribute {
 public:
     Light(uint64_t id, const Element& element, const Document& doc, const std::string& name);
-    virtual ~Light();
+    virtual ~Light() = default;
 
     enum Type {
         Type_Point,
@@ -690,7 +690,7 @@ using KeyValueList = std::vector<float>;
 class AnimationCurve : public Object {
 public:
     AnimationCurve(uint64_t id, const Element& element, const std::string& name, const Document& doc);
-    virtual ~AnimationCurve();
+    virtual ~AnimationCurve() = default;
 
     /** get list of keyframe positions (time).
      *  Invariant: |GetKeys()| > 0 */
@@ -731,7 +731,7 @@ public:
     AnimationCurveNode(uint64_t id, const Element& element, const std::string& name, const Document& doc,
             const char *const *target_prop_whitelist = nullptr, size_t whitelist_size = 0);
 
-    virtual ~AnimationCurveNode();
+    virtual ~AnimationCurveNode() = default;
 
     const PropertyTable& Props() const {
         ai_assert(props.get());
@@ -776,7 +776,7 @@ using AnimationCurveNodeList = std::vector<const AnimationCurveNode*>;
 class AnimationLayer : public Object {
 public:
     AnimationLayer(uint64_t id, const Element& element, const std::string& name, const Document& doc);
-    virtual ~AnimationLayer();
+    virtual ~AnimationLayer() = default;
 
     const PropertyTable& Props() const {
         ai_assert(props.get());
@@ -799,7 +799,7 @@ using AnimationLayerList = std::vector<const AnimationLayer*>;
 class AnimationStack : public Object {
 public:
     AnimationStack(uint64_t id, const Element& element, const std::string& name, const Document& doc);
-    virtual ~AnimationStack();
+    virtual ~AnimationStack() = default;
 
     fbx_simple_property(LocalStart, int64_t, 0L)
     fbx_simple_property(LocalStop, int64_t, 0L)

+ 3 - 6
code/AssetLib/FBX/FBXDocumentUtil.cpp

@@ -59,14 +59,12 @@ namespace Util {
 
 // ------------------------------------------------------------------------------------------------
 // signal DOM construction error, this is always unrecoverable. Throws DeadlyImportError.
-void DOMError(const std::string& message, const Token& token)
-{
+void DOMError(const std::string& message, const Token& token) {
     throw DeadlyImportError("FBX-DOM", Util::GetTokenText(&token), message);
 }
 
 // ------------------------------------------------------------------------------------------------
-void DOMError(const std::string& message, const Element* element /*= nullptr*/)
-{
+void DOMError(const std::string& message, const Element* element /*= nullptr*/) {
     if(element) {
         DOMError(message,element->KeyToken());
     }
@@ -76,8 +74,7 @@ void DOMError(const std::string& message, const Element* element /*= nullptr*/)
 
 // ------------------------------------------------------------------------------------------------
 // print warning, do return
-void DOMWarning(const std::string& message, const Token& token)
-{
+void DOMWarning(const std::string& message, const Token& token) {
     if(DefaultLogger::get()) {
         ASSIMP_LOG_WARN("FBX-DOM", Util::GetTokenText(&token), message);
     }

+ 5 - 7
code/AssetLib/FBX/FBXDocumentUtil.h

@@ -74,13 +74,11 @@ std::shared_ptr<const PropertyTable> GetPropertyTable(const Document& doc,
 
 // ------------------------------------------------------------------------------------------------
 template <typename T>
-inline
-const T* ProcessSimpleConnection(const Connection& con,
-    bool is_object_property_conn,
-    const char* name,
-    const Element& element,
-    const char** propNameOut = nullptr)
-{
+inline const T* ProcessSimpleConnection(const Connection& con,
+        bool is_object_property_conn,
+        const char* name,
+        const Element& element,
+        const char** propNameOut = nullptr) {
     if (is_object_property_conn && !con.PropertyName().length()) {
         DOMWarning("expected incoming " + std::string(name) +
             " link to be an object-object connection, ignoring",

+ 1 - 1
code/AssetLib/FBX/FBXExporter.cpp

@@ -255,7 +255,7 @@ void FBXExporter::WriteBinaryHeader()
 
 void FBXExporter::WriteBinaryFooter()
 {
-    outfile->Write(NULL_RECORD.c_str(), NULL_RECORD.size(), 1);
+    outfile->Write(NULL_RECORD, NumNullRecords, 1);
 
     outfile->Write(GENERIC_FOOTID.c_str(), GENERIC_FOOTID.size(), 1);
 

+ 6 - 0
code/AssetLib/FBX/FBXImportSettings.h

@@ -60,6 +60,7 @@ struct ImportSettings {
             readLights(true),
             readAnimations(true),
             readWeights(true),
+            useSkeleton(false),
             preservePivots(true),
             optimizeEmptyAnimationCurves(true),
             useLegacyEmbeddedTextureNaming(false),
@@ -112,6 +113,11 @@ struct ImportSettings {
      *  Default value is true. */
     bool readWeights;
 
+    /** will convert all animation data into a skeleton (experimental)
+     *  Default value is false.
+     */
+    bool useSkeleton;
+
     /** preserve transformation pivots and offsets. Since these can
      *  not directly be represented in assimp, additional dummy
      *  nodes will be generated. Note that settings this to false

+ 22 - 25
code/AssetLib/FBX/FBXImporter.cpp

@@ -90,12 +90,9 @@ static const aiImporterDesc desc = {
 
 // ------------------------------------------------------------------------------------------------
 // Constructor to be privately used by #Importer
-FBXImporter::FBXImporter() {
-}
-
-// ------------------------------------------------------------------------------------------------
-// Destructor, private as well
-FBXImporter::~FBXImporter() {
+FBXImporter::FBXImporter() :
+        mSettings() {
+    // empty
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -115,20 +112,21 @@ const aiImporterDesc *FBXImporter::GetInfo() const {
 // ------------------------------------------------------------------------------------------------
 // Setup configuration properties for the loader
 void FBXImporter::SetupProperties(const Importer *pImp) {
-	settings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true);
-	settings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false);
-	settings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true);
-	settings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true);
-	settings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true);
-	settings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true);
-	settings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true);
-	settings.readWeights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_WEIGHTS, true);
-	settings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false);
-	settings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true);
-	settings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
-	settings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false);
-	settings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true);
-	settings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false);
+    mSettings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true);
+    mSettings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false);
+    mSettings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true);
+    mSettings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true);
+    mSettings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true);
+    mSettings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true);
+    mSettings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true);
+    mSettings.readWeights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_WEIGHTS, true);
+    mSettings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false);
+    mSettings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true);
+    mSettings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
+    mSettings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false);
+    mSettings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true);
+    mSettings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false);
+    mSettings.useSkeleton = pImp->GetPropertyBool(AI_CONFIG_FBX_USE_SKELETON_BONE_CONTAINER, false);
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -155,7 +153,7 @@ void FBXImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
 	contents[contents.size() - 1] = 0;
 	const char *const begin = &*contents.begin();
 
-	// broadphase tokenizing pass in which we identify the core
+	// broad-phase tokenized pass in which we identify the core
 	// syntax elements of FBX (brackets, commas, key:value mappings)
 	TokenList tokens;
 	try {
@@ -173,15 +171,14 @@ void FBXImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
 		Parser parser(tokens, is_binary);
 
 		// take the raw parse-tree and convert it to a FBX DOM
-		Document doc(parser, settings);
+		Document doc(parser, mSettings);
 
 		// convert the FBX DOM to aiScene
-		ConvertToAssimpScene(pScene, doc, settings.removeEmptyBones);
+		ConvertToAssimpScene(pScene, doc, mSettings.removeEmptyBones);
 
 		// size relative to cm
 		float size_relative_to_cm = doc.GlobalSettings().UnitScaleFactor();
-        if (size_relative_to_cm == 0.0)
-        {
+        if (size_relative_to_cm == 0.0) {
 			// BaseImporter later asserts that fileScale is non-zero.
 			ThrowException("The UnitScaleFactor must be non-zero");
         }

+ 7 - 6
code/AssetLib/FBX/FBXImporter.h

@@ -69,13 +69,14 @@ typedef class basic_formatter<char, std::char_traits<char>, std::allocator<char>
 // -------------------------------------------------------------------------------------------
 class FBXImporter : public BaseImporter, public LogFunctions<FBXImporter> {
 public:
+    /// @brief The class constructor.
     FBXImporter();
-    ~FBXImporter() override;
 
-    // --------------------
-    bool CanRead(const std::string &pFile,
-            IOSystem *pIOHandler,
-            bool checkSig) const override;
+    ///	@brief The class destructor, default implementation.
+    ~FBXImporter() override = default;
+
+    /// @brief Will check the file for readability.
+    bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override;
 
 protected:
     // --------------------
@@ -90,7 +91,7 @@ protected:
             IOSystem *pIOHandler) override;
 
 private:
-    FBX::ImportSettings settings;
+    FBX::ImportSettings mSettings;
 }; // !class FBXImporter
 
 } // end of namespace Assimp

+ 4 - 20
code/AssetLib/FBX/FBXMeshGeometry.cpp

@@ -4,7 +4,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,
@@ -54,18 +53,15 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "FBXImportSettings.h"
 #include "FBXDocumentUtil.h"
 
-
 namespace Assimp {
 namespace FBX {
 
 using namespace Util;
 
 // ------------------------------------------------------------------------------------------------
-Geometry::Geometry(uint64_t id, const Element& element, const std::string& name, const Document& doc)
-    : Object(id, element, name)
-    , skin()
-{
-    const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID(),"Deformer");
+Geometry::Geometry(uint64_t id, const Element& element, const std::string& name, const Document& doc) :
+        Object(id, element, name), skin() {
+    const std::vector<const Connection*> &conns = doc.GetConnectionsByDestinationSequenced(ID(),"Deformer");
     for(const Connection* con : conns) {
         const Skin* const sk = ProcessSimpleConnection<Skin>(*con, false, "Skin -> Geometry", element);
         if(sk) {
@@ -78,12 +74,6 @@ Geometry::Geometry(uint64_t id, const Element& element, const std::string& name,
     }
 }
 
-// ------------------------------------------------------------------------------------------------
-Geometry::~Geometry()
-{
-    // empty
-}
-
 // ------------------------------------------------------------------------------------------------
 const std::vector<const BlendShape*>& Geometry::GetBlendShapes() const {
     return blendShapes;
@@ -183,18 +173,12 @@ MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::strin
         if(doc.Settings().readAllLayers || index == 0) {
             const Scope& layer = GetRequiredScope(*(*it).second);
             ReadLayer(layer);
-        }
-        else {
+        } else {
             FBXImporter::LogWarn("ignoring additional geometry layers");
         }
     }
 }
 
-// ------------------------------------------------------------------------------------------------
-MeshGeometry::~MeshGeometry() {
-    // empty
-}
-
 // ------------------------------------------------------------------------------------------------
 const std::vector<aiVector3D>& MeshGeometry::GetVertices() const {
     return m_vertices;

+ 12 - 9
code/AssetLib/FBX/FBXMeshGeometry.h

@@ -55,22 +55,25 @@ namespace FBX {
 /**
  *  DOM base class for all kinds of FBX geometry
  */
-class Geometry : public Object
-{
+class Geometry : public Object {
 public:
+    /// @brief The class constructor with all parameters.
+    /// @param id       The id.
+    /// @param element  
+    /// @param name 
+    /// @param doc 
     Geometry( uint64_t id, const Element& element, const std::string& name, const Document& doc );
-    virtual ~Geometry();
+    virtual ~Geometry() = default;
 
-    /** Get the Skin attached to this geometry or nullptr */
+    /// Get the Skin attached to this geometry or nullptr
     const Skin* DeformerSkin() const;
 
-    /** Get the BlendShape attached to this geometry or nullptr */
+    /// Get the BlendShape attached to this geometry or nullptr
     const std::vector<const BlendShape*>& GetBlendShapes() const;
 
 private:
     const Skin* skin;
     std::vector<const BlendShape*> blendShapes;
-
 };
 
 typedef std::vector<int> MatIndexArray;
@@ -79,14 +82,13 @@ typedef std::vector<int> MatIndexArray;
 /**
  *  DOM class for FBX geometry of type "Mesh"
  */
-class MeshGeometry : public Geometry
-{
+class MeshGeometry : public Geometry {
 public:
     /** The class constructor */
     MeshGeometry( uint64_t id, const Element& element, const std::string& name, const Document& doc );
 
     /** The class destructor */
-    virtual ~MeshGeometry();
+    virtual ~MeshGeometry() = default;
 
     /** Get a list of all vertex points, non-unique*/
     const std::vector<aiVector3D>& GetVertices() const;
@@ -130,6 +132,7 @@ public:
     /** Determine the face to which a particular output vertex index belongs.
     *  This mapping is always unique. */
     unsigned int FaceForVertexIndex( unsigned int in_index ) const;
+
 private:
     void ReadLayer( const Scope& layer );
     void ReadLayerElement( const Scope& layerElement );

+ 29 - 78
code/AssetLib/FBX/FBXNodeAttribute.cpp

@@ -57,114 +57,65 @@ namespace FBX {
 using namespace Util;
 
 // ------------------------------------------------------------------------------------------------
-NodeAttribute::NodeAttribute(uint64_t id, const Element& element, const Document& doc, const std::string& name)
-: Object(id,element,name)
-, props()
-{
-    const Scope& sc = GetRequiredScope(element);
+NodeAttribute::NodeAttribute(uint64_t id, const Element &element, const Document &doc, const std::string &name) :
+        Object(id, element, name), props() {
+    const Scope &sc = GetRequiredScope(element);
 
-    const std::string& classname = ParseTokenAsString(GetRequiredToken(element,2));
+    const std::string &classname = ParseTokenAsString(GetRequiredToken(element, 2));
 
     // hack on the deriving type but Null/LimbNode attributes are the only case in which
     // the property table is by design absent and no warning should be generated
     // for it.
     const bool is_null_or_limb = !strcmp(classname.c_str(), "Null") || !strcmp(classname.c_str(), "LimbNode");
-    props = GetPropertyTable(doc,"NodeAttribute.Fbx" + classname,element,sc, is_null_or_limb);
+    props = GetPropertyTable(doc, "NodeAttribute.Fbx" + classname, element, sc, is_null_or_limb);
 }
 
-
-// ------------------------------------------------------------------------------------------------
-NodeAttribute::~NodeAttribute()
-{
-    // empty
-}
-
-
 // ------------------------------------------------------------------------------------------------
-CameraSwitcher::CameraSwitcher(uint64_t id, const Element& element, const Document& doc, const std::string& name)
-    : NodeAttribute(id,element,doc,name)
-{
-    const Scope& sc = GetRequiredScope(element);
-    const Element* const CameraId = sc["CameraId"];
-    const Element* const CameraName = sc["CameraName"];
-    const Element* const CameraIndexName = sc["CameraIndexName"];
-
-    if(CameraId) {
-        cameraId = ParseTokenAsInt(GetRequiredToken(*CameraId,0));
+CameraSwitcher::CameraSwitcher(uint64_t id, const Element &element, const Document &doc, const std::string &name) :
+        NodeAttribute(id, element, doc, name) {
+    const Scope &sc = GetRequiredScope(element);
+    const Element *const CameraId = sc["CameraId"];
+    const Element *const CameraName = sc["CameraName"];
+    const Element *const CameraIndexName = sc["CameraIndexName"];
+
+    if (CameraId) {
+        cameraId = ParseTokenAsInt(GetRequiredToken(*CameraId, 0));
     }
 
-    if(CameraName) {
-        cameraName = GetRequiredToken(*CameraName,0).StringContents();
+    if (CameraName) {
+        cameraName = GetRequiredToken(*CameraName, 0).StringContents();
     }
 
-    if(CameraIndexName && CameraIndexName->Tokens().size()) {
-        cameraIndexName = GetRequiredToken(*CameraIndexName,0).StringContents();
+    if (CameraIndexName && CameraIndexName->Tokens().size()) {
+        cameraIndexName = GetRequiredToken(*CameraIndexName, 0).StringContents();
     }
 }
 
 // ------------------------------------------------------------------------------------------------
-CameraSwitcher::~CameraSwitcher()
-{
+Camera::Camera(uint64_t id, const Element &element, const Document &doc, const std::string &name) :
+        NodeAttribute(id, element, doc, name) {
     // empty
 }
 
 // ------------------------------------------------------------------------------------------------
-Camera::Camera(uint64_t id, const Element& element, const Document& doc, const std::string& name)
-: NodeAttribute(id,element,doc,name)
-{
+Light::Light(uint64_t id, const Element &element, const Document &doc, const std::string &name) :
+        NodeAttribute(id, element, doc, name) {
     // empty
 }
 
 // ------------------------------------------------------------------------------------------------
-Camera::~Camera()
-{
+Null::Null(uint64_t id, const Element &element, const Document &doc, const std::string &name) :
+        NodeAttribute(id, element, doc, name) {
     // empty
 }
 
 // ------------------------------------------------------------------------------------------------
-Light::Light(uint64_t id, const Element& element, const Document& doc, const std::string& name)
-: NodeAttribute(id,element,doc,name)
-{
+LimbNode::LimbNode(uint64_t id, const Element &element, const Document &doc, const std::string &name) :
+        NodeAttribute(id, element, doc, name) {
     // empty
 }
 
+} // namespace FBX
+} // namespace Assimp
 
-// ------------------------------------------------------------------------------------------------
-Light::~Light()
-{
-}
-
-
-// ------------------------------------------------------------------------------------------------
-Null::Null(uint64_t id, const Element& element, const Document& doc, const std::string& name)
-: NodeAttribute(id,element,doc,name)
-{
-
-}
-
-
-// ------------------------------------------------------------------------------------------------
-Null::~Null()
-{
-
-}
-
-
-// ------------------------------------------------------------------------------------------------
-LimbNode::LimbNode(uint64_t id, const Element& element, const Document& doc, const std::string& name)
-: NodeAttribute(id,element,doc,name)
-{
-
-}
-
-
-// ------------------------------------------------------------------------------------------------
-LimbNode::~LimbNode()
-{
-
-}
-
-}
-}
-
-#endif
+#endif // ASSIMP_BUILD_NO_FBX_IMPORTER

+ 1 - 15
code/AssetLib/FBX/FBXParser.cpp

@@ -162,12 +162,6 @@ Element::Element(const Token& key_token, Parser& parser) : key_token(key_token)
     while(n->Type() != TokenType_KEY && n->Type() != TokenType_CLOSE_BRACKET);
 }
 
-// ------------------------------------------------------------------------------------------------
-Element::~Element()
-{
-     // no need to delete tokens, they are owned by the parser
-}
-
 // ------------------------------------------------------------------------------------------------
 Scope::Scope(Parser& parser,bool topLevel)
 {
@@ -226,12 +220,6 @@ Parser::Parser (const TokenList& tokens, bool is_binary)
     root.reset(new Scope(*this,true));
 }
 
-// ------------------------------------------------------------------------------------------------
-Parser::~Parser()
-{
-    // empty
-}
-
 // ------------------------------------------------------------------------------------------------
 TokenPtr Parser::AdvanceToNextToken()
 {
@@ -961,8 +949,7 @@ void ParseVectorDataArray(std::vector<float>& out, const Element& el)
 
 // ------------------------------------------------------------------------------------------------
 // read an array of uints
-void ParseVectorDataArray(std::vector<unsigned int>& out, const Element& el)
-{
+void ParseVectorDataArray(std::vector<unsigned int>& out, const Element& el) {
     out.resize( 0 );
     const TokenList& tok = el.Tokens();
     if(tok.empty()) {
@@ -1186,7 +1173,6 @@ aiMatrix4x4 ReadMatrix(const Element& element)
     return result;
 }
 
-
 // ------------------------------------------------------------------------------------------------
 // wrapper around ParseTokenAsString() with ParseError handling
 std::string ParseTokenAsString(const Token& t)

+ 2 - 2
code/AssetLib/FBX/FBXParser.h

@@ -87,7 +87,7 @@ class Element
 {
 public:
     Element(const Token& key_token, Parser& parser);
-    ~Element();
+    ~Element() = default;
 
     const Scope* Compound() const {
         return compound.get();
@@ -160,7 +160,7 @@ public:
     /** Parse given a token list. Does not take ownership of the tokens -
      *  the objects must persist during the entire parser lifetime */
     Parser (const TokenList& tokens,bool is_binary);
-    ~Parser();
+    ~Parser() = default;
 
     const Scope& GetRootScope() const {
         return *root.get();

+ 1 - 1
code/AssetLib/Irr/IRRLoader.cpp

@@ -874,7 +874,7 @@ void IRRImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSy
 
 	// Batch loader used to load external models
 	BatchLoader batch(pIOHandler);
-	//  batch.SetBasePath(pFile);
+	//batch.SetBasePath(pFile);
 
 	cameras.reserve(5);
 	lights.reserve(5);

+ 1 - 2
code/AssetLib/LWO/LWOLoader.cpp

@@ -287,7 +287,7 @@ void LWOImporter::InternReadFile(const std::string &pFile,
             if (UINT_MAX == iDefaultSurface) {
                 pSorted.erase(pSorted.end() - 1);
             }
-            for (unsigned int p = 0, j = 0; j < mSurfaces->size(); ++j) {
+            for (unsigned int j = 0; j < mSurfaces->size(); ++j) {
                 SortedRep &sorted = pSorted[j];
                 if (sorted.empty())
                     continue;
@@ -425,7 +425,6 @@ void LWOImporter::InternReadFile(const std::string &pFile,
                 } else {
                     ASSIMP_LOG_VERBOSE_DEBUG("LWO2: No need to compute normals, they're already there");
                 }
-                ++p;
             }
         }
 

+ 3 - 0
code/AssetLib/MDL/MDLMaterialLoader.cpp

@@ -449,6 +449,9 @@ void MDLImporter::ParseSkinLump_3DGS_MDL7(
         unsigned int iWidth,
         unsigned int iHeight) {
     std::unique_ptr<aiTexture> pcNew;
+    if (szCurrent == nullptr) {
+        return;
+    }
 
     // get the type of the skin
     unsigned int iMasked = (unsigned int)(iType & 0xF);

+ 21 - 29
code/AssetLib/OpenGEX/OpenGEXImporter.cpp

@@ -151,45 +151,46 @@ namespace Grammar {
     }
 
     static TokenType matchTokenType(const char *tokenType) {
-        if (MetricType == tokenType) {
+        const size_t len = std::strlen(tokenType);
+        if (0 == strncmp(MetricType, tokenType, len)) {
             return MetricToken;
-        } else if (NameType == tokenType) {
+        } else if (0 == strncmp(NameType, tokenType, len)) {
             return NameToken;
-        } else if (ObjectRefType == tokenType) {
+        } else if (0 == strncmp(ObjectRefType, tokenType, len)) {
             return ObjectRefToken;
-        } else if (MaterialRefType == tokenType) {
+        } else if (0 == strncmp(MaterialRefType, tokenType, len)) {
             return MaterialRefToken;
-        } else if (MetricKeyType == tokenType) {
+        } else if (0 == strncmp(MetricKeyType, tokenType, len)) {
             return MetricKeyToken;
-        } else if (GeometryNodeType == tokenType) {
+        } else if (0 == strncmp(GeometryNodeType, tokenType, len)) {
             return GeometryNodeToken;
-        } else if (CameraNodeType == tokenType) {
+        } else if (0 == strncmp(CameraNodeType, tokenType, len)) {
             return CameraNodeToken;
-        } else if (LightNodeType == tokenType) {
+        } else if (0 == strncmp(LightNodeType, tokenType, len)) {
             return LightNodeToken;
-        } else if (GeometryObjectType == tokenType) {
+        } else if (0 == strncmp(GeometryObjectType, tokenType, len)) {
             return GeometryObjectToken;
-        } else if (CameraObjectType == tokenType) {
+        } else if (0 == strncmp(CameraObjectType, tokenType, len)) {
             return CameraObjectToken;
-        } else if (LightObjectType == tokenType) {
+        } else if (0 == strncmp(LightObjectType, tokenType, len)) {
             return LightObjectToken;
-        } else if (TransformType == tokenType) {
+        } else if (0 == strncmp(TransformType, tokenType, len)) {
             return TransformToken;
-        } else if (MeshType == tokenType) {
+        } else if (0 == strncmp(MeshType, tokenType, len)) {
             return MeshToken;
-        } else if (VertexArrayType == tokenType) {
+        } else if (0 == strncmp(VertexArrayType, tokenType, len)) {
             return VertexArrayToken;
-        } else if (IndexArrayType == tokenType) {
+        } else if (0 == strncmp(IndexArrayType, tokenType, len)) {
             return IndexArrayToken;
-        } else if (MaterialType == tokenType) {
+        } else if (0 == strncmp(MaterialType, tokenType, len)) {
             return MaterialToken;
-        } else if (ColorType == tokenType) {
+        } else if (0 == strncmp(ColorType, tokenType, len)) {
             return ColorToken;
-        } else if (ParamType == tokenType) {
+        } else if (0 == strncmp(ParamType, tokenType, len)) {
             return ParamToken;
-        } else if (TextureType == tokenType) {
+        } else if (0 == strncmp(TextureType, tokenType, len)) {
             return TextureToken;
-        } else if (AttenType == tokenType) {
+        } else if (0 == strncmp(AttenType, tokenType, len)) {
             return AttenToken;
         }
 
@@ -256,11 +257,6 @@ OpenGEXImporter::RefInfo::RefInfo(aiNode *node, Type type, std::vector<std::stri
     // empty
 }
 
-//------------------------------------------------------------------------------------------------
-OpenGEXImporter::RefInfo::~RefInfo() {
-    // empty
-}
-
 //------------------------------------------------------------------------------------------------
 OpenGEXImporter::OpenGEXImporter() :
         m_root(nullptr),
@@ -285,10 +281,6 @@ OpenGEXImporter::OpenGEXImporter() :
     // empty
 }
 
-//------------------------------------------------------------------------------------------------
-OpenGEXImporter::~OpenGEXImporter() {
-}
-
 //------------------------------------------------------------------------------------------------
 bool OpenGEXImporter::CanRead(const std::string &file, IOSystem *pIOHandler, bool /*checkSig*/) const {
     static const char *tokens[] = { "Metric", "GeometryNode", "VertexArray (attrib", "IndexArray" };

+ 3 - 8
code/AssetLib/OpenGEX/OpenGEXImporter.h

@@ -79,12 +79,7 @@ struct MetricInfo {
     float m_floatValue;
     int m_intValue;
 
-    MetricInfo()
-    : m_stringValue( )
-    , m_floatValue( 0.0f )
-    , m_intValue( -1 ) {
-        // empty
-    }
+    MetricInfo(): m_stringValue( ), m_floatValue( 0.0f ), m_intValue( -1 ) {}
 };
 
 /** @brief  This class is used to implement the OpenGEX importer
@@ -97,7 +92,7 @@ public:
     OpenGEXImporter();
 
     /// The class destructor.
-    ~OpenGEXImporter() override;
+    ~OpenGEXImporter() override = default;
 
     /// BaseImporter override.
     bool CanRead( const std::string &file, IOSystem *pIOHandler, bool checkSig ) const override;
@@ -170,7 +165,7 @@ private:
         std::vector<std::string> m_Names;
 
         RefInfo( aiNode *node, Type type, std::vector<std::string> &names );
-        ~RefInfo();
+        ~RefInfo() = default;
 
         RefInfo( const RefInfo & ) = delete;
         RefInfo &operator = ( const RefInfo & ) = delete;

+ 2 - 2
code/AssetLib/glTF2/glTF2Exporter.cpp

@@ -683,7 +683,7 @@ bool glTF2Exporter::GetMatSheen(const aiMaterial &mat, glTF2::MaterialSheen &she
     }
 
     // Default Sheen color factor {0,0,0} disables Sheen, so do not export
-    if (sheen.sheenColorFactor == defaultSheenFactor) {
+    if (sheen.sheenColorFactor[0] == defaultSheenFactor[0] && sheen.sheenColorFactor[1] == defaultSheenFactor[1] && sheen.sheenColorFactor[2] == defaultSheenFactor[2]) {
         return false;
     }
 
@@ -908,7 +908,7 @@ Ref<Node> FindSkeletonRootJoint(Ref<Skin> &skinRef) {
     do {
         startNodeRef = parentNodeRef;
         parentNodeRef = startNodeRef->parent;
-    } while (!parentNodeRef->jointName.empty());
+    } while (parentNodeRef && !parentNodeRef->jointName.empty());
 
     return parentNodeRef;
 }

+ 10 - 3
code/Common/DefaultIOStream.cpp

@@ -63,7 +63,7 @@ inline int select_fseek(FILE *file, int64_t offset, int origin) {
 
 
 
-#if defined _WIN64 && (!defined __GNUC__ || __MSVCRT_VERSION__ >= 0x0601)
+#if defined _WIN32 && (!defined __GNUC__ || !defined __CLANG__ && __MSVCRT_VERSION__ >= 0x0601)
 template <>
 inline size_t select_ftell<8>(FILE *file) {
     return (size_t)::_ftelli64(file);
@@ -74,7 +74,7 @@ inline int select_fseek<8>(FILE *file, int64_t offset, int origin) {
     return ::_fseeki64(file, offset, origin);
 }
 
-#endif // #if defined _WIN32 && (!defined __GNUC__ || __MSVCRT_VERSION__ >= 0x0601)
+#endif
 
 } // namespace
 
@@ -149,13 +149,20 @@ size_t DefaultIOStream::FileSize() const {
         //
         // See here for details:
         // https://www.securecoding.cert.org/confluence/display/seccode/FIO19-C.+Do+not+use+fseek()+and+ftell()+to+compute+the+size+of+a+regular+file
-#if defined _WIN64 && (!defined __GNUC__ || __MSVCRT_VERSION__ >= 0x0601)
+#if defined _WIN32 && (!defined __GNUC__ || !defined __CLANG__ && __MSVCRT_VERSION__ >= 0x0601)
         struct __stat64 fileStat;
         //using fileno + fstat avoids having to handle the filename
         int err = _fstat64(_fileno(mFile), &fileStat);
         if (0 != err)
             return 0;
         mCachedSize = (size_t)(fileStat.st_size);
+#elif defined _WIN32
+        struct _stat32 fileStat;
+        //using fileno + fstat avoids having to handle the filename
+        int err = _fstat32(_fileno(mFile), &fileStat);
+        if (0 != err)
+            return 0;
+        mCachedSize = (size_t)(fileStat.st_size);
 #elif defined __GNUC__ || defined __APPLE__ || defined __MACH__ || defined __FreeBSD__
         struct stat fileStat;
         int err = stat(mFilename.c_str(), &fileStat);

+ 29 - 26
code/Common/ScenePreprocessor.cpp

@@ -105,36 +105,39 @@ void ScenePreprocessor::ProcessMesh(aiMesh *mesh) {
     for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
         if (!mesh->mTextureCoords[i]) {
             mesh->mNumUVComponents[i] = 0;
-        } else {
-            if (!mesh->mNumUVComponents[i]) {
-                mesh->mNumUVComponents[i] = 2;
-            }
+            continue;
+        } 
 
-            aiVector3D *p = mesh->mTextureCoords[i], *end = p + mesh->mNumVertices;
+        if (!mesh->mNumUVComponents[i]) {
+            mesh->mNumUVComponents[i] = 2;
+        }
 
-            // Ensure unused components are zeroed. This will make 1D texture channels work
-            // as if they were 2D channels .. just in case an application doesn't handle
-            // this case
-            if (2 == mesh->mNumUVComponents[i]) {
-                for (; p != end; ++p) {
-                    p->z = 0.f;
-                }
-            } else if (1 == mesh->mNumUVComponents[i]) {
-                for (; p != end; ++p) {
-                    p->z = p->y = 0.f;
-                }
-            } else if (3 == mesh->mNumUVComponents[i]) {
-                // Really 3D coordinates? Check whether the third coordinate is != 0 for at least one element
-                for (; p != end; ++p) {
-                    if (p->z != 0) {
-                        break;
-                    }
-                }
-                if (p == end) {
-                    ASSIMP_LOG_WARN("ScenePreprocessor: UVs are declared to be 3D but they're obviously not. Reverting to 2D.");
-                    mesh->mNumUVComponents[i] = 2;
+        aiVector3D *p = mesh->mTextureCoords[i], *end = p + mesh->mNumVertices;
+
+        // Ensure unused components are zeroed. This will make 1D texture channels work
+        // as if they were 2D channels .. just in case an application doesn't handle
+        // this case
+        if (2 == mesh->mNumUVComponents[i]) {
+            size_t num = 0;
+            for (; p != end; ++p) {
+                p->z = 0.f;
+                num++;
+            }
+        } else if (1 == mesh->mNumUVComponents[i]) {
+            for (; p != end; ++p) {
+                p->z = p->y = 0.f;
+            }
+        } else if (3 == mesh->mNumUVComponents[i]) {
+            // Really 3D coordinates? Check whether the third coordinate is != 0 for at least one element
+            for (; p != end; ++p) {
+                if (p->z != 0) {
+                    break;
                 }
             }
+            if (p == end) {
+                ASSIMP_LOG_WARN("ScenePreprocessor: UVs are declared to be 3D but they're obviously not. Reverting to 2D.");
+                mesh->mNumUVComponents[i] = 2;
+            }
         }
     }
 

+ 8 - 6
code/Common/SkeletonMeshBuilder.cpp

@@ -4,7 +4,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,
@@ -97,13 +96,14 @@ void SkeletonMeshBuilder::CreateGeometry(const aiNode *pNode) {
             const aiMatrix4x4 &childTransform = pNode->mChildren[a]->mTransformation;
             aiVector3D childpos(childTransform.a4, childTransform.b4, childTransform.c4);
             ai_real distanceToChild = childpos.Length();
-            if (distanceToChild < 0.0001)
+            if (distanceToChild < ai_epsilon) {
                 continue;
+            }
             aiVector3D up = aiVector3D(childpos).Normalize();
-
             aiVector3D orth(1.0, 0.0, 0.0);
-            if (std::fabs(orth * up) > 0.99)
+            if (std::fabs(orth * up) > 0.99) {
                 orth.Set(0.0, 1.0, 0.0);
+            }
 
             aiVector3D front = (up ^ orth).Normalize();
             aiVector3D side = (front ^ up).Normalize();
@@ -183,8 +183,9 @@ void SkeletonMeshBuilder::CreateGeometry(const aiNode *pNode) {
         // add all the vertices to the bone's influences
         bone->mNumWeights = numVertices;
         bone->mWeights = new aiVertexWeight[numVertices];
-        for (unsigned int a = 0; a < numVertices; a++)
+        for (unsigned int a = 0; a < numVertices; ++a) {
             bone->mWeights[a] = aiVertexWeight(vertexStartIndex + a, 1.0);
+        }
 
         // HACK: (thom) transform all vertices to the bone's local space. Should be done before adding
         // them to the array, but I'm tired now and I'm annoyed.
@@ -194,8 +195,9 @@ void SkeletonMeshBuilder::CreateGeometry(const aiNode *pNode) {
     }
 
     // and finally recurse into the children list
-    for (unsigned int a = 0; a < pNode->mNumChildren; a++)
+    for (unsigned int a = 0; a < pNode->mNumChildren; ++a) {
         CreateGeometry(pNode->mChildren[a]);
+    }
 }
 
 // ------------------------------------------------------------------------------------------------

+ 5 - 1
code/Common/Version.cpp

@@ -135,6 +135,9 @@ ASSIMP_API aiScene::aiScene() :
         mNumCameras(0),
         mCameras(nullptr),
         mMetaData(nullptr),
+        mName(),
+        mNumSkeletons(0),
+        mSkeletons(nullptr),
         mPrivate(new Assimp::ScenePrivateData()) {
     // empty
 }
@@ -180,7 +183,8 @@ ASSIMP_API aiScene::~aiScene() {
     delete[] mCameras;
 
     aiMetadata::Dealloc(mMetaData);
-    mMetaData = nullptr;
 
+    delete[] mSkeletons;
+    
     delete static_cast<Assimp::ScenePrivateData *>(mPrivate);
 }

+ 1 - 1
code/PostProcessing/EmbedTexturesProcess.cpp

@@ -128,7 +128,7 @@ bool EmbedTexturesProcess::addTexture(aiScene *pScene, const std::string &path)
 
     aiTexel* imageContent = new aiTexel[ 1ul + static_cast<unsigned long>( imageSize ) / sizeof(aiTexel)];
     pFile->Seek(0, aiOrigin_SET);
-    pFile->Read(reinterpret_cast<char*>(imageContent), imageSize, 1);
+    pFile->Read(reinterpret_cast<char*>(imageContent), static_cast<size_t>(imageSize), 1);
     mIOHandler->Close(pFile);
 
     // Enlarging the textures table

+ 4 - 0
code/res/assimp.rc

@@ -1,5 +1,9 @@
 #include "revision.h"
+#ifdef __GNUC__
+#include "winresrc.h"
+#else
 #include "winres.h"
+#endif
 
 LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
 #pragma code_page(1252)

+ 4 - 4
contrib/unzip/crypt.c

@@ -43,10 +43,10 @@
 
 #include "crypt.h"
 
-#ifdef _WIN32
+#ifdef _MSC_VER
 #   pragma warning(push)
 #   pragma warning(disable : 4244)
-#endif // _WIN32
+#endif // _MSC_VER
 
 /***************************************************************************/
 
@@ -164,8 +164,8 @@ int crypthead(const char *passwd, uint8_t *buf, int buf_size, uint32_t *pkeys,
     return n;
 }
 
-#ifdef _WIN32
+#ifdef _MSC_VER
 #   pragma warning(pop)
-#endif // _WIN32
+#endif // _MSC_VER
 
 /***************************************************************************/

+ 4 - 2
contrib/unzip/ioapi.c

@@ -23,8 +23,10 @@
 
 #ifdef _WIN32
 #   define snprintf _snprintf
+#ifdef _MSC_VER
 #   pragma warning(push)
 #   pragma warning(disable : 4131 4100)
+#endif
 #   ifdef __clang__
 #       pragma clang diagnostic push
 #       pragma clang diagnostic ignored "-Wunused-parameter"
@@ -357,9 +359,9 @@ void fill_fopen64_filefunc(zlib_filefunc64_def *pzlib_filefunc_def)
     pzlib_filefunc_def->opaque = NULL;
 }
 
-#ifdef _WIN32
+#ifdef _MSC_VER
 #   pragma warning(pop)
 #   ifdef __clang__
 #       pragma clang diagnostic pop
 #   endif
-#endif // _WIN32
+#endif // _MSC_VER

+ 4 - 4
contrib/unzip/unzip.c

@@ -73,10 +73,10 @@
 #  define TRYFREE(p) {if (p) free(p);}
 #endif
 
-#ifdef _WIN32
+#ifdef _MSC_VER
 #   pragma warning(push)
 #   pragma warning(disable : 4131 4244 4189 4245)
-#endif // _WIN32
+#endif // _MSC_VER
 
 const char unz_copyright[] =
    " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll";
@@ -1995,6 +1995,6 @@ extern int ZEXPORT unzEndOfFile(unzFile file)
     return 0;
 }
 
-#ifdef _WIN32
+#ifdef _MSC_VER
 #   pragma warning(pop)
-#endif // _WIN32
+#endif // _MSC_VER

+ 273 - 191
include/assimp/XmlParser.h

@@ -99,295 +99,129 @@ template <class TNodeType>
 class TXmlParser {
 public:
     /// @brief The default class constructor.
-    TXmlParser() :
-            mDoc(nullptr),
-            mData() {
-        // empty
-    }
+    TXmlParser();
 
     ///	@brief  The class destructor.
-    ~TXmlParser() {
-        clear();
-    }
+    ~TXmlParser();
 
     ///	@brief  Will clear the parsed xml-file.
-    void clear() {
-        if (mData.empty()) {
-            mDoc = nullptr;
-            return;
-        }
-        mData.clear();
-        delete mDoc;
-        mDoc = nullptr;
-    }
+    void clear();
 
     ///	@brief  Will search for a child-node by its name
     /// @param  name     [in] The name of the child-node.
     /// @return The node instance or nullptr, if nothing was found.
-    TNodeType *findNode(const std::string &name) {
-        if (name.empty()) {
-            return nullptr;
-        }
-
-        if (nullptr == mDoc) {
-            return nullptr;
-        }
-
-        find_node_by_name_predicate predicate(name);
-        mCurrent = mDoc->find_node(predicate);
-        if (mCurrent.empty()) {
-            return nullptr;
-        }
-
-        return &mCurrent;
-    }
+    TNodeType *findNode(const std::string &name);
 
     /// @brief  Will return true, if the node is a child-node.
     /// @param  name    [in] The name of the child node to look for.
     /// @return true, if the node is a child-node or false if not.
-    bool hasNode(const std::string &name) {
-        return nullptr != findNode(name);
-    }
+    bool hasNode(const std::string &name);
 
     /// @brief  Will parse an xml-file from a given stream.
     /// @param  stream      The input stream.
     /// @return true, if the parsing was successful, false if not.
-    bool parse(IOStream *stream) {
-        if (nullptr == stream) {
-            ASSIMP_LOG_DEBUG("Stream is nullptr.");
-            return false;
-        }
-
-        const size_t len = stream->FileSize();
-        mData.resize(len + 1);
-        memset(&mData[0], '\0', len + 1);
-        stream->Read(&mData[0], 1, len);
+    bool parse(IOStream *stream);
 
-        mDoc = new pugi::xml_document();
-        pugi::xml_parse_result parse_result = mDoc->load_string(&mData[0], pugi::parse_full);
-        if (parse_result.status == pugi::status_ok) {
-            return true;
-        }
-
-        ASSIMP_LOG_DEBUG("Error while parse xml.", std::string(parse_result.description()), " @ ", parse_result.offset);
-
-        return false;
-    }
-
-    /// @brief  Will return truem if a root node is there.
+    /// @brief  Will return true if a root node is there.
     /// @return true in case of an existing root.
-    bool hasRoot() const {
-        return nullptr != mDoc;
-    }
+    bool hasRoot() const;
+
     /// @brief  Will return the document pointer, is nullptr if no xml-file was parsed.
     /// @return The pointer showing to the document.
-    pugi::xml_document *getDocument() const {
-        return mDoc;
-    }
+    pugi::xml_document *getDocument() const;
 
     /// @brief  Will return the root node, const version.
     /// @return The root node.
-    const TNodeType getRootNode() const {
-        static pugi::xml_node none;
-        if (nullptr == mDoc) {
-            return none;
-        }
-        return mDoc->root();
-    }
+    const TNodeType getRootNode() const;
 
     /// @brief  Will return the root node, non-const version.
     /// @return The root node.
-    TNodeType getRootNode() {
-        static pugi::xml_node none;
-        if (nullptr == mDoc) {
-            return none;
-        }
-        return mDoc->root();
-    }
+    TNodeType getRootNode();
 
     /// @brief Will check if a node with the given name is in.
     /// @param node     [in] The node to look in.
     /// @param name     [in] The name of the child-node.
     /// @return true, if node was found, false if not.
-    static inline bool hasNode(XmlNode &node, const char *name) {
-        pugi::xml_node child = node.find_child(find_node_by_name_predicate(name));
-        return !child.empty();
-    }
+    static inline bool hasNode(XmlNode &node, const char *name);
 
     /// @brief Will check if an attribute is part of the XmlNode.
     /// @param xmlNode  [in] The node to search in.
     /// @param name     [in} The attribute name to look for.
     /// @return true, if the was found, false if not.
-    static inline bool hasAttribute(XmlNode &xmlNode, const char *name) {
-        pugi::xml_attribute attr = xmlNode.attribute(name);
-        return !attr.empty();
-    }
+    static inline bool hasAttribute(XmlNode &xmlNode, const char *name);
 
     /// @brief Will try to get an unsigned int attribute value.
     /// @param xmlNode  [in] The node to search in.
     /// @param name     [in] The attribute name to look for.
     /// @param val      [out] The unsigned int value from the attribute.
     /// @return true, if the node contains an attribute with the given name and if the value is an unsigned int.
-    static inline bool getUIntAttribute(XmlNode &xmlNode, const char *name, unsigned int &val) {
-        pugi::xml_attribute attr = xmlNode.attribute(name);
-        if (attr.empty()) {
-            return false;
-        }
-
-        val = attr.as_uint();
-        return true;
-    }
+    static inline bool getUIntAttribute(XmlNode &xmlNode, const char *name, unsigned int &val);
 
     /// @brief Will try to get an int attribute value.
     /// @param xmlNode  [in] The node to search in.
     /// @param name     [in] The attribute name to look for.
     /// @param val      [out] The int value from the attribute.
     /// @return true, if the node contains an attribute with the given name and if the value is an int.
-    static inline bool getIntAttribute(XmlNode &xmlNode, const char *name, int &val) {
-        pugi::xml_attribute attr = xmlNode.attribute(name);
-        if (attr.empty()) {
-            return false;
-        }
-
-        val = attr.as_int();
-        return true;
-    }
+    static inline bool getIntAttribute(XmlNode &xmlNode, const char *name, int &val);
 
     /// @brief Will try to get a real attribute value.
     /// @param xmlNode  [in] The node to search in.
     /// @param name     [in] The attribute name to look for.
     /// @param val      [out] The real value from the attribute.
     /// @return true, if the node contains an attribute with the given name and if the value is a real.
-    static inline bool getRealAttribute(XmlNode &xmlNode, const char *name, ai_real &val) {
-        pugi::xml_attribute attr = xmlNode.attribute(name);
-        if (attr.empty()) {
-            return false;
-        }
-#ifdef ASSIMP_DOUBLE_PRECISION
-        val = attr.as_double();
-#else
-        val = attr.as_float();
-#endif
-        return true;
-    }
+    static inline bool getRealAttribute(XmlNode &xmlNode, const char *name, ai_real &val);
 
     /// @brief Will try to get a float attribute value.
     /// @param xmlNode  [in] The node to search in.
     /// @param name     [in] The attribute name to look for.
     /// @param val      [out] The float value from the attribute.
     /// @return true, if the node contains an attribute with the given name and if the value is a float.
-    static inline bool getFloatAttribute(XmlNode &xmlNode, const char *name, float &val) {
-        pugi::xml_attribute attr = xmlNode.attribute(name);
-        if (attr.empty()) {
-            return false;
-        }
-
-        val = attr.as_float();
-        return true;
-    }
+    static inline bool getFloatAttribute(XmlNode &xmlNode, const char *name, float &val);
 
     /// @brief Will try to get a double attribute value.
     /// @param xmlNode  [in] The node to search in.
     /// @param name     [in] The attribute name to look for.
     /// @param val      [out] The double value from the attribute.
     /// @return true, if the node contains an attribute with the given name and if the value is a double.
-    static inline bool getDoubleAttribute(XmlNode &xmlNode, const char *name, double &val) {
-        pugi::xml_attribute attr = xmlNode.attribute(name);
-        if (attr.empty()) {
-            return false;
-        }
-
-        val = attr.as_double();
-        return true;
-    }
+    static inline bool getDoubleAttribute(XmlNode &xmlNode, const char *name, double &val);
 
     /// @brief Will try to get a std::string attribute value.
     /// @param xmlNode  [in] The node to search in.
     /// @param name     [in] The attribute name to look for.
     /// @param val      [out] The std::string value from the attribute.
     /// @return true, if the node contains an attribute with the given name and if the value is a std::string.
-    static inline bool getStdStrAttribute(XmlNode &xmlNode, const char *name, std::string &val) {
-        pugi::xml_attribute attr = xmlNode.attribute(name);
-        if (attr.empty()) {
-            return false;
-        }
-
-        val = attr.as_string();
-        return true;
-    }
+    static inline bool getStdStrAttribute(XmlNode &xmlNode, const char *name, std::string &val);
 
     /// @brief Will try to get a bool attribute value.
     /// @param xmlNode  [in] The node to search in.
     /// @param name     [in] The attribute name to look for.
     /// @param val      [out] The bool value from the attribute.
     /// @return true, if the node contains an attribute with the given name and if the value is a bool.
-    static inline bool getBoolAttribute(XmlNode &xmlNode, const char *name, bool &val) {
-        pugi::xml_attribute attr = xmlNode.attribute(name);
-        if (attr.empty()) {
-            return false;
-        }
-
-        val = attr.as_bool();
-        return true;
-    }
+    static inline bool getBoolAttribute(XmlNode &xmlNode, const char *name, bool &val);
 
     /// @brief Will try to get the value of the node as a string.
     /// @param node     [in] The node to search in.
     /// @param text     [out] The value as a text.
     /// @return true, if the value can be read out.
-    static inline bool getValueAsString(XmlNode &node, std::string &text) {
-        text = std::string();
-        if (node.empty()) {
-            return false;
-        }
-
-        text = node.text().as_string();
-
-        return true;
-    }
+    static inline bool getValueAsString(XmlNode &node, std::string &text);
 
     /// @brief Will try to get the value of the node as a float.
     /// @param node     [in] The node to search in.
     /// @param text     [out] The value as a float.
     /// @return true, if the value can be read out.
-    static inline bool getValueAsFloat(XmlNode &node, ai_real &v) {
-        if (node.empty()) {
-            return false;
-        }
-
-        v = node.text().as_float();
-
-        return true;
-    }
+    static inline bool getValueAsFloat(XmlNode &node, ai_real &v);
 
     /// @brief Will try to get the value of the node as an integer.
     /// @param node     [in] The node to search in.
     /// @param text     [out] The value as a int.
     /// @return true, if the value can be read out.
-    static inline bool getValueAsInt(XmlNode &node, int &v) {
-        if (node.empty()) {
-            return false;
-        }
-
-        v = node.text().as_int();
-
-        return true;
-    }
+    static inline bool getValueAsInt(XmlNode &node, int &v);
 
     /// @brief Will try to get the value of the node as an bool.
     /// @param node     [in] The node to search in.
     /// @param text     [out] The value as a bool.
     /// @return true, if the value can be read out.
-    static inline bool getValueAsBool(XmlNode& node, bool& v)
-    {
-        if (node.empty()) {
-            return false;
-        }
-
-        v = node.text().as_bool();
-
-        return true;
-    }
+    static inline bool getValueAsBool(XmlNode &node, bool &v);
 
 private:
     pugi::xml_document *mDoc;
@@ -395,6 +229,254 @@ private:
     std::vector<char> mData;
 };
 
+template <class TNodeType>
+inline TXmlParser<TNodeType>::TXmlParser() :
+        mDoc(nullptr),
+        mData() {
+    // empty
+}
+
+template <class TNodeType>
+inline TXmlParser<TNodeType>::~TXmlParser() {
+    clear();
+}
+
+template <class TNodeType>
+inline void TXmlParser<TNodeType>::clear() {
+    if (mData.empty()) {
+        if (mDoc) {
+            delete mDoc;
+        }
+        mDoc = nullptr;
+        return;
+    }
+
+    mData.clear();
+    delete mDoc;
+    mDoc = nullptr;
+}
+
+template <class TNodeType>
+inline TNodeType *TXmlParser<TNodeType>::findNode(const std::string &name) {
+    if (name.empty()) {
+        return nullptr;
+    }
+
+    if (nullptr == mDoc) {
+        return nullptr;
+    }
+
+    find_node_by_name_predicate predicate(name);
+    mCurrent = mDoc->find_node(predicate);
+    if (mCurrent.empty()) {
+        return nullptr;
+    }
+
+    return &mCurrent;
+}
+
+template <class TNodeType>
+bool TXmlParser<TNodeType>::hasNode(const std::string &name) {
+    return nullptr != findNode(name);
+}
+
+template <class TNodeType>
+bool TXmlParser<TNodeType>::parse(IOStream *stream) {
+    if (hasRoot()) {
+        clear();
+    }
+
+    if (nullptr == stream) {
+        ASSIMP_LOG_DEBUG("Stream is nullptr.");
+        return false;
+    }
+
+    const size_t len = stream->FileSize();
+    mData.resize(len + 1);
+    memset(&mData[0], '\0', len + 1);
+    stream->Read(&mData[0], 1, len);
+
+    mDoc = new pugi::xml_document();
+    pugi::xml_parse_result parse_result = mDoc->load_string(&mData[0], pugi::parse_full);
+    if (parse_result.status == pugi::status_ok) {
+        return true;
+    }
+
+    ASSIMP_LOG_DEBUG("Error while parse xml.", std::string(parse_result.description()), " @ ", parse_result.offset);
+
+    return false;
+}
+
+template <class TNodeType>
+bool TXmlParser<TNodeType>::hasRoot() const {
+    return nullptr != mDoc;
+}
+
+template <class TNodeType>
+pugi::xml_document *TXmlParser<TNodeType>::getDocument() const {
+    return mDoc;
+}
+
+template <class TNodeType>
+const TNodeType TXmlParser<TNodeType>::getRootNode() const {
+    static pugi::xml_node none;
+    if (nullptr == mDoc) {
+        return none;
+    }
+    return mDoc->root();
+}
+
+template <class TNodeType>
+TNodeType TXmlParser<TNodeType>::getRootNode() {
+    static pugi::xml_node none;
+    if (nullptr == mDoc) {
+        return none;
+    }
+
+    return mDoc->root();
+}
+
+template <class TNodeType>
+inline bool TXmlParser<TNodeType>::hasNode(XmlNode &node, const char *name) {
+    pugi::xml_node child = node.find_child(find_node_by_name_predicate(name));
+    return !child.empty();
+}
+
+template <class TNodeType>
+inline bool TXmlParser<TNodeType>::hasAttribute(XmlNode &xmlNode, const char *name) {
+    pugi::xml_attribute attr = xmlNode.attribute(name);
+    return !attr.empty();
+}
+
+template <class TNodeType>
+inline bool TXmlParser<TNodeType>::getUIntAttribute(XmlNode &xmlNode, const char *name, unsigned int &val) {
+    pugi::xml_attribute attr = xmlNode.attribute(name);
+    if (attr.empty()) {
+        return false;
+    }
+
+    val = attr.as_uint();
+    return true;
+}
+
+template <class TNodeType>
+inline bool TXmlParser<TNodeType>::getIntAttribute(XmlNode &xmlNode, const char *name, int &val) {
+    pugi::xml_attribute attr = xmlNode.attribute(name);
+    if (attr.empty()) {
+        return false;
+    }
+
+    val = attr.as_int();
+    return true;
+}
+
+template <class TNodeType>
+inline bool TXmlParser<TNodeType>::getRealAttribute(XmlNode &xmlNode, const char *name, ai_real &val) {
+    pugi::xml_attribute attr = xmlNode.attribute(name);
+    if (attr.empty()) {
+        return false;
+    }
+#ifdef ASSIMP_DOUBLE_PRECISION
+    val = attr.as_double();
+#else
+    val = attr.as_float();
+#endif
+    return true;
+}
+
+template <class TNodeType>
+inline bool TXmlParser<TNodeType>::getFloatAttribute(XmlNode &xmlNode, const char *name, float &val) {
+    pugi::xml_attribute attr = xmlNode.attribute(name);
+    if (attr.empty()) {
+        return false;
+    }
+
+    val = attr.as_float();
+
+    return true;
+}
+
+template <class TNodeType>
+inline bool TXmlParser<TNodeType>::getDoubleAttribute(XmlNode &xmlNode, const char *name, double &val) {
+    pugi::xml_attribute attr = xmlNode.attribute(name);
+    if (attr.empty()) {
+        return false;
+    }
+
+    val = attr.as_double();
+
+    return true;
+}
+
+template <class TNodeType>
+inline bool TXmlParser<TNodeType>::getStdStrAttribute(XmlNode &xmlNode, const char *name, std::string &val) {
+    pugi::xml_attribute attr = xmlNode.attribute(name);
+    if (attr.empty()) {
+        return false;
+    }
+
+    val = attr.as_string();
+
+    return true;
+}
+
+template <class TNodeType>
+inline bool TXmlParser<TNodeType>::getBoolAttribute(XmlNode &xmlNode, const char *name, bool &val) {
+    pugi::xml_attribute attr = xmlNode.attribute(name);
+    if (attr.empty()) {
+        return false;
+    }
+
+    val = attr.as_bool();
+
+    return true;
+}
+
+template <class TNodeType>
+inline bool TXmlParser<TNodeType>::getValueAsString(XmlNode &node, std::string &text) {
+    text = std::string();
+    if (node.empty()) {
+        return false;
+    }
+
+    text = node.text().as_string();
+
+    return true;
+}
+
+template <class TNodeType>
+inline bool TXmlParser<TNodeType>::getValueAsFloat(XmlNode &node, ai_real &v) {
+    if (node.empty()) {
+        return false;
+    }
+
+    v = node.text().as_float();
+
+    return true;
+}
+
+template <class TNodeType>
+inline bool TXmlParser<TNodeType>::getValueAsInt(XmlNode &node, int &v) {
+    if (node.empty()) {
+        return false;
+    }
+
+    v = node.text().as_int();
+
+    return true;
+}
+
+template <class TNodeType>
+inline bool TXmlParser<TNodeType>::getValueAsBool(XmlNode &node, bool &v) {
+    if (node.empty()) {
+        return false;
+    }
+
+    v = node.text().as_bool();
+
+    return true;
+}
+
 using XmlParser = TXmlParser<pugi::xml_node>;
 
 ///	@brief  This class declares an iterator to loop through all children of the root node.

+ 9 - 0
include/assimp/config.h.in

@@ -691,6 +691,15 @@ enum aiComponent
 #define AI_CONFIG_FBX_CONVERT_TO_M \
     "AI_CONFIG_FBX_CONVERT_TO_M"
 
+// ---------------------------------------------------------------------------
+/** @brief  Will enable the skeleton structo to store bone data.
+ *
+ *  This will decouple the bone coupling to the mesh. This feature is
+ *  experimental.
+ */
+#define AI_CONFIG_FBX_USE_SKELETON_BONE_CONTAINER \
+    "AI_CONFIG_FBX_USE_SKELETON_BONE_CONTAINER"
+
 // ---------------------------------------------------------------------------
 /** @brief  Set the vertex animation keyframe to be imported
  *

+ 120 - 21
include/assimp/mesh.h

@@ -120,7 +120,7 @@ extern "C" {
  * primitive are actually present in a mesh. The #aiProcess_SortByPType flag
  * executes a special post-processing algorithm which splits meshes with
  * *different* primitive types mixed up (e.g. lines and triangles) in several
- * 'clean' submeshes. Furthermore there is a configuration option (
+ * 'clean' sub-meshes. Furthermore there is a configuration option (
  * #AI_CONFIG_PP_SBP_REMOVE) to force #aiProcess_SortByPType to remove
  * specific kinds of primitives from the imported scene, completely and forever.
  * In many cases you'll probably want to set this setting to
@@ -269,12 +269,12 @@ struct aiBone {
     unsigned int mNumWeights;
 
 #ifndef ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS
-    // The bone armature node - used for skeleton conversion
-    // you must enable aiProcess_PopulateArmatureData to populate this
+    /// The bone armature node - used for skeleton conversion
+    /// you must enable aiProcess_PopulateArmatureData to populate this
     C_STRUCT aiNode *mArmature;
 
-    // The bone node in the scene - used for skeleton conversion
-    // you must enable aiProcess_PopulateArmatureData to populate this
+    /// The bone node in the scene - used for skeleton conversion
+    /// you must enable aiProcess_PopulateArmatureData to populate this
     C_STRUCT aiNode *mNode;
 
 #endif
@@ -296,7 +296,7 @@ struct aiBone {
 
 #ifdef __cplusplus
 
-    //! Default constructor
+    ///	@brief  Default constructor
     aiBone() AI_NO_EXCEPT
             : mName(),
               mNumWeights(0),
@@ -309,7 +309,7 @@ struct aiBone {
         // empty
     }
 
-    //! Copy constructor
+    /// @brief  Copy constructor
     aiBone(const aiBone &other) :
             mName(other.mName),
             mNumWeights(other.mNumWeights),
@@ -319,14 +319,27 @@ struct aiBone {
 #endif
             mWeights(nullptr),
             mOffsetMatrix(other.mOffsetMatrix) {
-        if (other.mWeights && other.mNumWeights) {
-            mWeights = new aiVertexWeight[mNumWeights];
-            ::memcpy(mWeights, other.mWeights, mNumWeights * sizeof(aiVertexWeight));
+        copyVertexWeights(other);
+    }
+
+    void copyVertexWeights( const aiBone &other ) {
+        if (other.mWeights == nullptr || other.mNumWeights == 0) {
+            mWeights = nullptr;
+            mNumWeights = 0;
+            return;
+        }
+
+        mNumWeights = other.mNumWeights;
+        if (mWeights) {
+            delete[] mWeights;
         }
+
+        mWeights = new aiVertexWeight[mNumWeights];
+        ::memcpy(mWeights, other.mWeights, mNumWeights * sizeof(aiVertexWeight));
     }
 
     //! Assignment operator
-    aiBone &operator=(const aiBone &other) {
+    aiBone &operator = (const aiBone &other) {
         if (this == &other) {
             return *this;
         }
@@ -334,21 +347,13 @@ struct aiBone {
         mName = other.mName;
         mNumWeights = other.mNumWeights;
         mOffsetMatrix = other.mOffsetMatrix;
-
-        if (other.mWeights && other.mNumWeights) {
-            if (mWeights) {
-                delete[] mWeights;
-            }
-
-            mWeights = new aiVertexWeight[mNumWeights];
-            ::memcpy(mWeights, other.mWeights, mNumWeights * sizeof(aiVertexWeight));
-        }
+        copyVertexWeights(other);
 
         return *this;
     }
 
     bool operator==(const aiBone &rhs) const {
-        if (mName != rhs.mName || mNumWeights != rhs.mNumWeights) {
+        if (mName != rhs.mName || mNumWeights != rhs.mNumWeights ) {
             return false;
         }
 
@@ -937,6 +942,100 @@ struct aiMesh {
 #endif // __cplusplus
 };
 
+struct aiSkeletonBone {
+    /// The parent bone index, is -1 one if this bone represents the root bone.
+    int mParent;
+
+
+#ifndef ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS
+    /// The bone armature node - used for skeleton conversion
+    /// you must enable aiProcess_PopulateArmatureData to populate this
+    C_STRUCT aiNode *mArmature;
+
+    /// The bone node in the scene - used for skeleton conversion
+    /// you must enable aiProcess_PopulateArmatureData to populate this
+    C_STRUCT aiNode *mNode;
+
+#endif
+    /// @brief The number of weights
+    unsigned int mNumnWeights;
+
+    /// The mesh index, which will get influenced by the weight.
+    C_STRUCT aiMesh *mMeshId;
+
+    /// The influence weights of this bone, by vertex index.
+    C_STRUCT aiVertexWeight *mWeights;
+
+    /** Matrix that transforms from bone space to mesh space in bind pose.
+     *
+     * This matrix describes the position of the mesh
+     * in the local space of this bone when the skeleton was bound.
+     * Thus it can be used directly to determine a desired vertex position,
+     * given the world-space transform of the bone when animated,
+     * and the position of the vertex in mesh space.
+     *
+     * It is sometimes called an inverse-bind matrix,
+     * or inverse bind pose matrix.
+     */
+    C_STRUCT aiMatrix4x4 mOffsetMatrix;
+
+    /// Matrix that transforms the locale bone in bind pose.
+    C_STRUCT aiMatrix4x4 mLocalMatrix;
+
+#ifdef __cplusplus
+    aiSkeletonBone() :
+            mParent(-1),
+            mArmature(nullptr),
+            mNode(nullptr),
+            mNumnWeights(0),
+            mMeshId(nullptr),
+            mWeights(nullptr),
+            mOffsetMatrix(),
+            mLocalMatrix() {
+        // empty
+    }
+
+    ~aiSkeletonBone() {
+        delete[] mWeights;
+        mWeights = nullptr;
+    }
+#endif // __cplusplus
+};
+/**
+ *  @brief  
+ */
+struct aiSkeleton {
+    /**
+     *
+     */
+    C_STRUCT aiString mName;
+
+    /**
+     *
+     */
+    unsigned int mNumBones;
+
+    /**
+     *
+     */
+    C_STRUCT aiSkeletonBone **mBones;
+
+#ifdef __cplusplus
+    /**
+     *
+     */
+    aiSkeleton() AI_NO_EXCEPT : mName(), mNumBones(0), mBones(nullptr) {
+        // empty
+    }
+
+    /**
+     *
+     */
+    ~aiSkeleton() {
+        delete[] mBones;
+    }
+#endif // __cplusplus
+};
 #ifdef __cplusplus
 }
 #endif //! extern "C"

+ 15 - 2
include/assimp/scene.h

@@ -79,8 +79,7 @@ extern "C" {
  * the imported scene does consist of only a single root node without children.
  */
 // -------------------------------------------------------------------------------
-struct ASSIMP_API aiNode
-{
+struct ASSIMP_API aiNode {
     /** The name of the node.
      *
      * The name might be empty (length of zero) but all nodes which
@@ -343,6 +342,16 @@ struct aiScene
      */
     C_STRUCT aiString mName;
 
+    /**
+     *
+     */
+    unsigned int mNumSkeletons;
+
+    /**
+     *
+     */
+    C_STRUCT aiSkeleton **mSkeletons;
+
 #ifdef __cplusplus
 
     //! Default constructor - set everything to 0/nullptr
@@ -383,6 +392,10 @@ struct aiScene
         return mAnimations != nullptr && mNumAnimations > 0;
     }
 
+    bool hasSkeletons() const {
+        return mSkeletons != nullptr && mNumSkeletons > 0;
+    }
+
     //! Returns a short filename from a full path
     static const char* GetShortFilename(const char* filename) {
         const char* lastSlash = strrchr(filename, '/');

文件差異過大導致無法顯示
+ 132 - 0
test/models/Collada/box_nested_animation.dae


二進制
test/models/FBX/animation_with_skeleton.fbx


+ 38 - 25
test/unit/utColladaImportExport.cpp

@@ -69,31 +69,44 @@ public:
 
     virtual bool importerTest() final {
         Assimp::Importer importer;
-        const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure);
-        if (scene == nullptr)
-            return false;
-
-        // Expected number of items
-        EXPECT_EQ(scene->mNumMeshes, 1u);
-        EXPECT_EQ(scene->mNumMaterials, 1u);
-        EXPECT_EQ(scene->mNumAnimations, 0u);
-        EXPECT_EQ(scene->mNumTextures, 0u);
-        EXPECT_EQ(scene->mNumLights, 1u);
-        EXPECT_EQ(scene->mNumCameras, 1u);
-
-        // Expected common metadata
-        aiString value;
-        EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT, value)) << "No importer format metadata";
-        EXPECT_STREQ("Collada Importer", value.C_Str());
-
-        EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT_VERSION, value)) << "No format version metadata";
-        EXPECT_STREQ("1.4.1", value.C_Str());
-
-        EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_GENERATOR, value)) << "No generator metadata";
-        EXPECT_EQ(strncmp(value.C_Str(), "Maya 8.0", 8), 0) << "AI_METADATA_SOURCE_GENERATOR was: " << value.C_Str();
-
-        EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_COPYRIGHT, value)) << "No copyright metadata";
-        EXPECT_EQ(strncmp(value.C_Str(), "Copyright 2006", 14), 0) << "AI_METADATA_SOURCE_COPYRIGHT was: " << value.C_Str();
+        {
+          const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure);
+          if (scene == nullptr)
+              return false;
+
+          // Expected number of items
+          EXPECT_EQ(scene->mNumMeshes, 1u);
+          EXPECT_EQ(scene->mNumMaterials, 1u);
+          EXPECT_EQ(scene->mNumAnimations, 0u);
+          EXPECT_EQ(scene->mNumTextures, 0u);
+          EXPECT_EQ(scene->mNumLights, 1u);
+          EXPECT_EQ(scene->mNumCameras, 1u);
+
+          // Expected common metadata
+          aiString value;
+          EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT, value)) << "No importer format metadata";
+          EXPECT_STREQ("Collada Importer", value.C_Str());
+
+          EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_FORMAT_VERSION, value)) << "No format version metadata";
+          EXPECT_STREQ("1.4.1", value.C_Str());
+
+          EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_GENERATOR, value)) << "No generator metadata";
+          EXPECT_EQ(strncmp(value.C_Str(), "Maya 8.0", 8), 0) << "AI_METADATA_SOURCE_GENERATOR was: " << value.C_Str();
+
+          EXPECT_TRUE(scene->mMetaData->Get(AI_METADATA_SOURCE_COPYRIGHT, value)) << "No copyright metadata";
+          EXPECT_EQ(strncmp(value.C_Str(), "Copyright 2006", 14), 0) << "AI_METADATA_SOURCE_COPYRIGHT was: " << value.C_Str();
+        }
+
+        {
+          const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/box_nested_animation.dae", aiProcess_ValidateDataStructure);
+          if (scene == nullptr)
+              return false;
+
+          // Expect only one animation with the correct name
+          EXPECT_EQ(scene->mNumAnimations, 1u);
+          EXPECT_EQ(std::string(scene->mAnimations[0]->mName.C_Str()), std::string("Armature"));
+
+        }
 
         return true;
     }

+ 7 - 0
test/unit/utFBXImporterExporter.cpp

@@ -423,3 +423,10 @@ TEST_F(utFBXImporterExporter, importMaxPbrMaterialsSpecularGloss) {
     ASSERT_EQ(mat->Get("$raw.3dsMax|main|emit_color", aiTextureType_NONE, 0, emitColor), aiReturn_SUCCESS);
     EXPECT_EQ(emitColor, aiColor4D(1, 0, 1, 1));
 }
+
+TEST_F(utFBXImporterExporter, importSkeletonTest) {
+    Assimp::Importer importer;
+    const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/animation_with_skeleton.fbx", aiProcess_ValidateDataStructure);
+    ASSERT_NE(nullptr, scene);
+    ASSERT_TRUE(scene->mRootNode);
+}

+ 4 - 1
test/unit/utOpenGEXImportExport.cpp

@@ -40,6 +40,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
 
 #include "AbstractImportExportBase.h"
+
+#include <assimp/scene.h>
 #include "UnitTestPCH.h"
 
 #include <assimp/Importer.hpp>
@@ -51,11 +53,12 @@ public:
     bool importerTest() override {
         Assimp::Importer importer;
         const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/OpenGEX/Example.ogex", 0);
+        EXPECT_EQ(1u, scene->mNumMeshes);
         return nullptr != scene;
     }
 };
 
-TEST_F(utOpenGEXImportExport, importLWSFromFileTest) {
+TEST_F(utOpenGEXImportExport, importOpenGexFromFileTest) {
     EXPECT_TRUE(importerTest());
 }
 

部分文件因文件數量過多而無法顯示