Browse Source

Merge pull request #42 from OTHGMars/Assimp_WIP

Node transform and vertex weight fixes.
Areloch 6 years ago
parent
commit
5a93bfd39b

+ 73 - 70
Engine/source/ts/assimp/assimpAppMesh.cpp

@@ -37,6 +37,15 @@ AssimpAppMesh::AssimpAppMesh(const struct aiMesh* mesh, AssimpAppNode* node)
    : mMeshData(mesh), appNode(node)
    : mMeshData(mesh), appNode(node)
 {
 {
    Con::printf("[ASSIMP] Mesh Created: %s", getName());
    Con::printf("[ASSIMP] Mesh Created: %s", getName());
+
+   // See if it's a skinned mesh
+   mIsSkinMesh = false;
+   for (U32 b = 0; b < mesh->mNumBones; b++)
+      if (mMeshData->mBones[b]->mNumWeights > 0)
+      {
+         mIsSkinMesh = true;
+         break;
+      }
 }
 }
 
 
 const char* AssimpAppMesh::getName(bool allowFixed)
 const char* AssimpAppMesh::getName(bool allowFixed)
@@ -59,12 +68,11 @@ MatrixF AssimpAppMesh::getMeshTransform(F32 time)
    return appNode->getNodeTransform(time);
    return appNode->getNodeTransform(time);
 }
 }
 
 
-void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objectOffset)
+void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset)
 {
 {
    // After this function, the following are expected to be populated:
    // After this function, the following are expected to be populated:
    //     points, normals, uvs, primitives, indices
    //     points, normals, uvs, primitives, indices
    // There is also colors and uv2s but those don't seem to be required.
    // There is also colors and uv2s but those don't seem to be required.
-
    points.reserve(mMeshData->mNumVertices);
    points.reserve(mMeshData->mNumVertices);
    uvs.reserve(mMeshData->mNumVertices);
    uvs.reserve(mMeshData->mNumVertices);
    normals.reserve(mMeshData->mNumVertices);
    normals.reserve(mMeshData->mNumVertices);
@@ -74,23 +82,19 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objectOffset)
    {
    {
       // Points and Normals
       // Points and Normals
       aiVector3D pt = mMeshData->mVertices[i];
       aiVector3D pt = mMeshData->mVertices[i];
-      aiVector3D nrm = mMeshData->mNormals[i];
+      aiVector3D nrm;
+      if (mMeshData->HasNormals())
+         nrm = mMeshData->mNormals[i];
+      else
+         nrm.Set(0, 0, 0);
 
 
       Point3F tmpVert;
       Point3F tmpVert;
       Point3F tmpNormal;
       Point3F tmpNormal;
 
 
-      if (Con::getBoolVariable("$Assimp::SwapYZ", false))
-      {
-         tmpVert = Point3F(pt.x, pt.z, pt.y);
-         tmpNormal = Point3F(nrm.x, nrm.z, nrm.y);
-      }
-      else
-      {
-         tmpVert = Point3F(pt.x, pt.y, pt.z);
-         tmpNormal = Point3F(nrm.x, nrm.y, nrm.z);
-      }
+      tmpVert = Point3F(pt.x, pt.y, pt.z);
+      tmpNormal = Point3F(nrm.x, nrm.y, nrm.z);
 
 
-      //objectOffset.mulP(tmpVert);
+      objOffset.mulP(tmpVert);
 
 
       points.push_back(tmpVert);
       points.push_back(tmpVert);
 
 
@@ -128,8 +132,7 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objectOffset)
    }
    }
 
 
    U32 numFaces = mMeshData->mNumFaces;
    U32 numFaces = mMeshData->mNumFaces;
-   U32 primCount = 0;
-   primitives.reserve(numFaces);
+   //primitives.reserve(numFaces);
 
 
    //Fetch the number of indices
    //Fetch the number of indices
    U32 indicesCount = 0;
    U32 indicesCount = 0;
@@ -140,41 +143,18 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objectOffset)
 
 
    indices.reserve(indicesCount);
    indices.reserve(indicesCount);
 
 
-   /*U32 idxCount = 0;
-
-   for (U32 j = 0; j<mModel->mMaterials.size(); j++)
-   {
-      MikuModel::Material &mat = mModel->mMaterials[j];
-      U32 nextIdxCount = idxCount + mat.numIndices;
-
-      primitives.increment();
-
-      TSDrawPrimitive& primitive = primitives.last();
-      primitive.start = indices.size();
-      primitive.matIndex = (TSDrawPrimitive::Triangles | TSDrawPrimitive::Indexed) | j;
-      primitive.numElements = mat.numIndices;
-
-      for (U32 i = idxCount; i<nextIdxCount; i++)
-      {
-         indices.push_back(mModel->mIndices[i]);
-      }
-
-      idxCount = nextIdxCount;
-   }*/
+   // Create TSMesh primitive
+   primitives.increment();
+   TSDrawPrimitive& primitive = primitives.last();
+   primitive.start = 0;
+   primitive.matIndex = (TSDrawPrimitive::Triangles | TSDrawPrimitive::Indexed) | (S32)mMeshData->mMaterialIndex;
+   primitive.numElements = indicesCount;
 
 
    for ( U32 n = 0; n < mMeshData->mNumFaces; ++n)
    for ( U32 n = 0; n < mMeshData->mNumFaces; ++n)
    {
    {
       const struct aiFace* face = &mMeshData->mFaces[n];
       const struct aiFace* face = &mMeshData->mFaces[n];
       if ( face->mNumIndices == 3 )
       if ( face->mNumIndices == 3 )
       {
       {
-         // Create TSMesh primitive
-         primitives.increment();
-         TSDrawPrimitive& primitive = primitives.last();
-         primitive.start = indices.size();
-         primitive.matIndex = (TSDrawPrimitive::Triangles | TSDrawPrimitive::Indexed) | (S32)mMeshData->mMaterialIndex;
-         //primitive.numElements = face->mNumIndices;//3;
-         primitive.numElements = 3;
-
          if (Con::getBoolVariable("$Assimp::FlipNormals", true))
          if (Con::getBoolVariable("$Assimp::FlipNormals", true))
          {
          {
             U32 indexCount = face->mNumIndices;
             U32 indexCount = face->mNumIndices;
@@ -193,11 +173,6 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objectOffset)
                indices.push_back(index);
                indices.push_back(index);
             }
             }
          }
          }
-
-         // Load the indices in.
-         //indices.push_back(face->mIndices[0]);
-         //indices.push_back(face->mIndices[1]);
-         //indices.push_back(face->mIndices[2]);
       } 
       } 
       else 
       else 
       {
       {
@@ -206,39 +181,67 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objectOffset)
    }
    }
 
 
    U32 boneCount = mMeshData->mNumBones;
    U32 boneCount = mMeshData->mNumBones;
-
    bones.setSize(boneCount);
    bones.setSize(boneCount);
 
 
+   // Count the total number of weights for all of the bones.
+   U32 totalWeights = 0;
+   U32 nonZeroWeights = 0;
+   for (U32 b = 0; b < boneCount; b++)
+      totalWeights += mMeshData->mBones[b]->mNumWeights;
+
+   // Assimp gives weights sorted by bone index. We need them in vertex order.
+   Vector<F32> tmpWeight;
+   Vector<S32> tmpBoneIndex;
+   Vector<S32> tmpVertexIndex;
+   tmpWeight.setSize(totalWeights);
+   tmpBoneIndex.setSize(totalWeights);
+   tmpVertexIndex.setSize(totalWeights);
+
    for (U32 b = 0; b < boneCount; b++)
    for (U32 b = 0; b < boneCount; b++)
    {
    {
       String name = mMeshData->mBones[b]->mName.C_Str();
       String name = mMeshData->mBones[b]->mName.C_Str();
+      aiNode* nodePtr = AssimpAppNode::findChildNodeByName(mMeshData->mBones[b]->mName.C_Str(), appNode->mScene->mRootNode);
+      bones[b] = new AssimpAppNode(appNode->mScene, nodePtr);
 
 
       MatrixF boneTransform;
       MatrixF boneTransform;
-
-      for (U32 m = 0; m < 16; ++m)
-      {
-         boneTransform[m] = *mMeshData->mBones[b]->mOffsetMatrix[m];
-      }
-
-      //initialTransforms.push_back(boneTransform);
-      initialTransforms.push_back(MatrixF::Identity);
+      AssimpAppNode::assimpToTorqueMat(mMeshData->mBones[b]->mOffsetMatrix, boneTransform);
+      initialTransforms.push_back(boneTransform);
 
 
       //Weights
       //Weights
       U32 numWeights = mMeshData->mBones[b]->mNumWeights;
       U32 numWeights = mMeshData->mBones[b]->mNumWeights;
 
 
-      weight.setSize(numWeights);
-      vertexIndex.setSize(numWeights);
-
       for (U32 w = 0; w < numWeights; ++w)
       for (U32 w = 0; w < numWeights; ++w)
       {
       {
-         aiVertexWeight* aiWeight = mMeshData->mBones[b]->mWeights;
+         aiVertexWeight* aiWeight = &mMeshData->mBones[b]->mWeights[w];
+
+         if (aiWeight->mWeight > 0.0f)
+         {
+            tmpWeight[nonZeroWeights] = aiWeight->mWeight;
+            tmpVertexIndex[nonZeroWeights] = aiWeight->mVertexId;
+            tmpBoneIndex[nonZeroWeights] = b;
+            nonZeroWeights++;
+         }
+      }
+   }
 
 
-         weight[w] = aiWeight->mWeight;
-         vertexIndex[w] = aiWeight->mVertexId;
-         boneIndex[w] = b;
-         //vertWeight. = aiWeight->
+   weight.setSize(nonZeroWeights);
+   vertexIndex.setSize(nonZeroWeights);
+   boneIndex.setSize(nonZeroWeights);
+
+   // Copy the weights to our vectors in vertex order
+   U32 nextWeight = 0;
+   for (U32 i = 0; i < mMeshData->mNumVertices; i++)
+   {
+      for (U32 ind = 0; ind < nonZeroWeights; ind++)
+      {
+         if (tmpVertexIndex[ind] == i)
+         {
+            weight[nextWeight] = tmpWeight[ind];
+            vertexIndex[nextWeight] = tmpVertexIndex[ind];
+            boneIndex[nextWeight] = tmpBoneIndex[ind];
+            nextWeight++;
+         }
       }
       }
-      //= mNumWeights
    }
    }
 
 
    if ( noUVFound )
    if ( noUVFound )
@@ -246,8 +249,8 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objectOffset)
 }
 }
 
 
 void AssimpAppMesh::lookupSkinData()
 void AssimpAppMesh::lookupSkinData()
-{
-
+{  // This function is intentionally left blank. The skin data - bones, weights and indexes are
+   // processed in lockMesh() with the rest of the mesh data.
 }
 }
 
 
 F32 AssimpAppMesh::getVisValue(F32 t)
 F32 AssimpAppMesh::getVisValue(F32 t)

+ 3 - 2
Engine/source/ts/assimp/assimpAppMesh.h

@@ -40,6 +40,7 @@ class AssimpAppMesh : public AppMesh
 protected:
 protected:
    class AssimpAppNode* appNode;                     ///< Pointer to the node that owns this mesh
    class AssimpAppNode* appNode;                     ///< Pointer to the node that owns this mesh
    const struct aiMesh* mMeshData;
    const struct aiMesh* mMeshData;
+   bool mIsSkinMesh;
 
 
 public:
 public:
 
 
@@ -100,14 +101,14 @@ public:
    /// Return true if this mesh is a skin
    /// Return true if this mesh is a skin
    bool isSkin()
    bool isSkin()
    {
    {
-      return false;
+      return mIsSkinMesh;
    }
    }
 
 
    /// Generate the vertex, normal and triangle data for the mesh.
    /// Generate the vertex, normal and triangle data for the mesh.
    ///
    ///
    /// @param time           Time at which to generate the mesh data
    /// @param time           Time at which to generate the mesh data
    /// @param objectOffset   Transform to apply to the generated data (bounds transform)
    /// @param objectOffset   Transform to apply to the generated data (bounds transform)
-   void lockMesh(F32 time, const MatrixF& objectOffset);
+   void lockMesh(F32 time, const MatrixF& objOffset);
 
 
    /// Get the transform of this mesh at a certain time
    /// Get the transform of this mesh at a certain time
    ///
    ///

+ 115 - 56
Engine/source/ts/assimp/assimpAppNode.cpp

@@ -32,6 +32,9 @@
 #include <assimp/types.h>
 #include <assimp/types.h>
 
 
 AssimpAppNode::AssimpAppNode(const struct aiScene* scene, const struct aiNode* node, AssimpAppNode* parent)
 AssimpAppNode::AssimpAppNode(const struct aiScene* scene, const struct aiNode* node, AssimpAppNode* parent)
+:  mInvertMeshes(false),
+   mLastTransformTime(TSShapeLoader::DefaultTime - 1),
+   mDefaultTransformValid(false)
 {
 {
    mScene = scene;
    mScene = scene;
    mNode = node;
    mNode = node;
@@ -45,7 +48,8 @@ AssimpAppNode::AssimpAppNode(const struct aiScene* scene, const struct aiNode* n
    }
    }
 
 
    mParentName = dStrdup(parent ? parent->getName() : "ROOT");
    mParentName = dStrdup(parent ? parent->getName() : "ROOT");
-   Con::printf("[ASSIMP] Node Created: %s", mName);
+   assimpToTorqueMat(node->mTransformation, mNodeTransform);
+   Con::printf("[ASSIMP] Node Created: %s, Parent: %s", mName, mParentName);
 }
 }
 
 
 // Get all child nodes
 // Get all child nodes
@@ -73,75 +77,130 @@ void AssimpAppNode::buildMeshList()
 
 
 MatrixF AssimpAppNode::getTransform(F32 time)
 MatrixF AssimpAppNode::getTransform(F32 time)
 {
 {
-   // Translate from assimp matrix to torque matrix.
-   // They're both row major, I wish I could just cast
-   // but that doesn't seem to be an option.
+   // Check if we can use the last computed transform
+   if (time == mLastTransformTime)
+      return mLastTransform;
 
 
-   // Note: this should be cached, it doesn't change
-   //       at this level. This is base transform.
+   if (appParent) {
+      // Get parent node's transform
+      mLastTransform = appParent->getTransform(time);
+   }
+   else {
+      // no parent (ie. root level) => scale by global shape <unit>
+      mLastTransform.identity();
+      if (!isBounds())
+         convertMat(mLastTransform);
+
+      //mLastTransform.scale(ColladaUtils::getOptions().unit);
+   }
 
 
-   // Y and Z and optionally swapped.
+   mLastTransform.mul(mNodeTransform);
+   mLastTransformTime = time;
 
 
-   MatrixF mat(false);
-   mat.setRow(0, Point4F((F32)mNode->mTransformation.a1,
-       (F32)mNode->mTransformation.a3,
-       (F32)mNode->mTransformation.a2,
-       (F32)mNode->mTransformation.a4)
-   );
+   return mLastTransform;
+}
+
+bool AssimpAppNode::animatesTransform(const AppSequence* appSeq)
+{
+   return false;
+}
 
 
-   // Check for Y Z Swap
-   if ( Con::getBoolVariable("$Assimp::SwapYZ", false) )
+/// Get the world transform of the node at the specified time
+MatrixF AssimpAppNode::getNodeTransform(F32 time)
+{
+   // Avoid re-computing the default transform if possible
+   if (mDefaultTransformValid && time == TSShapeLoader::DefaultTime)
    {
    {
-      mat.setRow(1, Point4F((F32)mNode->mTransformation.c1,
-          (F32)mNode->mTransformation.c3,
-          (F32)mNode->mTransformation.c2,
-          (F32)mNode->mTransformation.c4)
-      );
-      mat.setRow(2, Point4F((F32)mNode->mTransformation.b1,
-          (F32)mNode->mTransformation.b3,
-          (F32)mNode->mTransformation.b2,
-          (F32)mNode->mTransformation.b4)
-      );
-   } 
-   else 
+      return mDefaultNodeTransform;
+   }
+   else
    {
    {
-      mat.setRow(1, Point4F((F32)mNode->mTransformation.b1,
-          (F32)mNode->mTransformation.b3,
-          (F32)mNode->mTransformation.b2,
-          (F32)mNode->mTransformation.b4)
-      );
-      mat.setRow(2, Point4F((F32)mNode->mTransformation.c1,
-          (F32)mNode->mTransformation.c3,
-          (F32)mNode->mTransformation.c2,
-          (F32)mNode->mTransformation.c4)
-      );
+      MatrixF nodeTransform = getTransform(time);
+
+      // Check for inverted node coordinate spaces => can happen when modelers
+      // use the 'mirror' tool in their 3d app. Shows up as negative <scale>
+      // transforms in the collada model.
+      if (m_matF_determinant(nodeTransform) < 0.0f)
+      {
+         // Mark this node as inverted so we can mirror mesh geometry, then
+         // de-invert the transform matrix
+         mInvertMeshes = true;
+         nodeTransform.scale(Point3F(1, 1, -1));
+      }
+
+      // Cache the default transform
+      if (time == TSShapeLoader::DefaultTime)
+      {
+         mDefaultTransformValid = true;
+         mDefaultNodeTransform = nodeTransform;
+      }
+
+      return nodeTransform;
    }
    }
+}
 
 
-   mat.setRow(3, Point4F((F32)mNode->mTransformation.d1,
-       (F32)mNode->mTransformation.d3,
-       (F32)mNode->mTransformation.d2,
-       (F32)mNode->mTransformation.d4)
-   );
+void AssimpAppNode::assimpToTorqueMat(const aiMatrix4x4& inAssimpMat, MatrixF& outMat)
+{
+   outMat.setRow(0, Point4F((F32)inAssimpMat.a1, (F32)inAssimpMat.a2,
+      (F32)inAssimpMat.a3, (F32)inAssimpMat.a4));
 
 
-   // Node transformations are carried down the hiearchy
-   // so we need all of our parents transforms to make 
-   // this work.
-   /*if ( appParent != 0 )
-   {
-      MatrixF parentMat = appParent->getNodeTransform(time);
-      mat.mul(parentMat);
-   }*/
+   outMat.setRow(1, Point4F((F32)inAssimpMat.b1, (F32)inAssimpMat.b2,
+      (F32)inAssimpMat.b3, (F32)inAssimpMat.b4));
 
 
-   return mat;
+   outMat.setRow(2, Point4F((F32)inAssimpMat.c1, (F32)inAssimpMat.c2,
+      (F32)inAssimpMat.c3, (F32)inAssimpMat.c4));
+
+   outMat.setRow(3, Point4F((F32)inAssimpMat.d1, (F32)inAssimpMat.d2,
+      (F32)inAssimpMat.d3, (F32)inAssimpMat.d4));
 }
 }
 
 
-bool AssimpAppNode::animatesTransform(const AppSequence* appSeq)
+void AssimpAppNode::convertMat(MatrixF& outMat)
 {
 {
-   return false;
+   MatrixF rot(true);
+
+   // This is copied directly from ColladaUtils::convertTransform()
+   // ColladaUtils::getOptions().upAxis has been temporarily replaced with $Assimp::OverrideUpAxis for testing
+   // We need a plan for how the full set of assimp import options and settings is going to be managed.
+   switch (Con::getIntVariable("$Assimp::OverrideUpAxis", 2))
+   {
+   case 0: //UPAXISTYPE_X_UP:
+      // rotate 90 around Y-axis, then 90 around Z-axis
+      rot(0, 0) = 0.0f;  rot(1, 0) = 1.0f;
+      rot(1, 1) = 0.0f;	rot(2, 1) = 1.0f;
+      rot(0, 2) = 1.0f;	rot(2, 2) = 0.0f;
+
+      // pre-multiply the transform by the rotation matrix
+      outMat.mulL(rot);
+      break;
+
+   case 1: //UPAXISTYPE_Y_UP:
+      // rotate 180 around Y-axis, then 90 around X-axis
+      rot(0, 0) = -1.0f;
+      rot(1, 1) = 0.0f;	rot(2, 1) = 1.0f;
+      rot(1, 2) = 1.0f;	rot(2, 2) = 0.0f;
+
+      // pre-multiply the transform by the rotation matrix
+      outMat.mulL(rot);
+      break;
+
+   case 2: //UPAXISTYPE_Z_UP:
+   default:
+      // nothing to do
+      break;
+   }
 }
 }
 
 
-/// Get the world transform of the node at the specified time
-MatrixF AssimpAppNode::getNodeTransform(F32 time)
+aiNode* AssimpAppNode::findChildNodeByName(const char* nodeName, aiNode* rootNode)
 {
 {
-   return getTransform(time);
+   aiNode* retNode = NULL;
+   if (strcmp(nodeName, rootNode->mName.C_Str()) == 0)
+      return rootNode;
+
+   for (U32 i = 0; i < rootNode->mNumChildren; ++i)
+   {
+      retNode = findChildNodeByName(nodeName, rootNode->mChildren[i]);
+      if (retNode)
+         return retNode;
+   }
+   return nullptr;
 }
 }

+ 18 - 3
Engine/source/ts/assimp/assimpAppNode.h

@@ -33,6 +33,10 @@
 #include "ts/collada/colladaExtensions.h"
 #include "ts/collada/colladaExtensions.h"
 #endif
 #endif
 
 
+#ifndef AI_TYPES_H_INC
+#include <assimp/types.h>
+#endif
+
 class AssimpAppNode : public AppNode
 class AssimpAppNode : public AppNode
 {
 {
    typedef AppNode Parent;
    typedef AppNode Parent;
@@ -44,9 +48,16 @@ class AssimpAppNode : public AppNode
 
 
 protected:
 protected:
 
 
-   const struct aiScene*      mScene;
-   const struct aiNode*       mNode;        ///< Pointer to the node in the Collada DOM
-   AssimpAppNode*             appParent;        ///< Parent node in Collada-space
+   const struct aiScene*   mScene;
+   const struct aiNode*    mNode;                  ///< Pointer to the assimp scene node
+   AssimpAppNode*          appParent;              ///< Parent node
+   MatrixF                 mNodeTransform;         ///< Scene node transform converted to TorqueSpace (filled for ALL nodes)
+
+   bool                    mInvertMeshes;          ///< True if this node's coordinate space is inverted (left handed)
+   F32                     mLastTransformTime;     ///< Time of the last transform lookup (getTransform)
+   MatrixF                 mLastTransform;         ///< Last transform lookup (getTransform) (Only Non-Dummy Nodes)
+   bool                    mDefaultTransformValid; ///< Flag indicating whether the defaultNodeTransform is valid
+   MatrixF                 mDefaultNodeTransform;  ///< Transform at DefaultTime (Only Non-Dummy Nodes)
 
 
 public:
 public:
 
 
@@ -93,6 +104,10 @@ public:
    MatrixF getNodeTransform(F32 time);
    MatrixF getNodeTransform(F32 time);
    bool animatesTransform(const AppSequence* appSeq);
    bool animatesTransform(const AppSequence* appSeq);
    bool isParentRoot() { return (appParent == NULL); }
    bool isParentRoot() { return (appParent == NULL); }
+
+   static void assimpToTorqueMat(const aiMatrix4x4& inAssimpMat, MatrixF& outMat);
+   static void convertMat(MatrixF& outMat);
+   static aiNode* findChildNodeByName(const char* nodeName, aiNode* rootNode);
 };
 };
 
 
 #endif // _ASSIMP_APPNODE_H_
 #endif // _ASSIMP_APPNODE_H_

+ 12 - 2
Engine/source/ts/assimp/assimpShapeLoader.cpp

@@ -103,7 +103,8 @@ MODULE_BEGIN( AssimpShapeLoader )
       TSShapeLoader::addFormat("3D GameStudio (3DGS)", "mdl");
       TSShapeLoader::addFormat("3D GameStudio (3DGS)", "mdl");
       TSShapeLoader::addFormat("3D GameStudio (3DGS) Terrain", "hmp");
       TSShapeLoader::addFormat("3D GameStudio (3DGS) Terrain", "hmp");
       TSShapeLoader::addFormat("Izware Nendo", "ndo");
       TSShapeLoader::addFormat("Izware Nendo", "ndo");
-	  TSShapeLoader::addFormat("gltf", "gltf");
+      TSShapeLoader::addFormat("gltf", "gltf");
+      TSShapeLoader::addFormat("gltf binary", "glb");
    }
    }
 MODULE_END;
 MODULE_END;
 
 
@@ -146,7 +147,7 @@ void AssimpShapeLoader::enumerateScene()
        Con::getBoolVariable("$Assimp::OptimizeMeshes", false) ? aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph : 0 |
        Con::getBoolVariable("$Assimp::OptimizeMeshes", false) ? aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph : 0 |
        0;
        0;
 
 
-   if(Con::getBoolVariable("$Assimp::Triangulate", false))
+   if(Con::getBoolVariable("$Assimp::Triangulate", true))
       ppsteps |= aiProcess_Triangulate;
       ppsteps |= aiProcess_Triangulate;
 
 
    if (Con::getBoolVariable("$Assimp::OptimizeMeshes", false))
    if (Con::getBoolVariable("$Assimp::OptimizeMeshes", false))
@@ -276,6 +277,15 @@ bool AssimpShapeLoader::canLoadCachedDTS(const Torque::Path& path)
    return false;
    return false;
 }
 }
 
 
+bool AssimpShapeLoader::ignoreNode(const String& name)
+{
+   // Do not add AssimpFbx dummy nodes to the TSShape. See: Assimp::FBX::ImportSettings::preservePivots
+   // https://github.com/assimp/assimp/blob/master/code/FBXImportSettings.h#L116-L135
+   if (name.find("_$AssimpFbx$_") != String::NPos)
+      return true;
+   return false;
+}
+
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 /// This function is invoked by the resource manager based on file extension.
 /// This function is invoked by the resource manager based on file extension.
 TSShape* assimpLoadShape(const Torque::Path &path)
 TSShape* assimpLoadShape(const Torque::Path &path)

+ 3 - 1
Engine/source/ts/assimp/assimpShapeLoader.h

@@ -34,7 +34,9 @@ class AssimpShapeLoader : public TSShapeLoader
 
 
 protected:
 protected:
    const struct aiScene* mScene;
    const struct aiScene* mScene;
-  
+
+   virtual bool ignoreNode(const String& name);
+
 public:
 public:
    AssimpShapeLoader();
    AssimpShapeLoader();
    ~AssimpShapeLoader();
    ~AssimpShapeLoader();