Browse Source

Merge pull request #45 from OTHGMars/Assimp_Anim

Assimp animations
Areloch 6 years ago
parent
commit
af27fea500

+ 2 - 0
Engine/lib/assimp/code/glTF2Asset.inl

@@ -405,6 +405,8 @@ inline void Buffer::Read(Value& obj, Asset& r)
 inline bool Buffer::LoadFromStream(IOStream& stream, size_t length, size_t baseOffset)
 {
     byteLength = length ? length : stream.FileSize();
+    if ((byteLength + baseOffset) > stream.FileSize())
+       byteLength = stream.FileSize() - baseOffset;
 
     if (baseOffset) {
         stream.Seek(baseOffset, aiOrigin_SET);

+ 2 - 0
Engine/lib/assimp/code/glTFAsset.inl

@@ -345,6 +345,8 @@ inline void Buffer::Read(Value& obj, Asset& r)
 inline bool Buffer::LoadFromStream(IOStream& stream, size_t length, size_t baseOffset)
 {
     byteLength = length ? length : stream.FileSize();
+    if ((byteLength + baseOffset) > stream.FileSize())
+       byteLength = stream.FileSize() - baseOffset;
 
     if (baseOffset) {
         stream.Seek(baseOffset, aiOrigin_SET);

+ 16 - 19
Engine/source/ts/assimp/assimpAppMesh.cpp

@@ -31,6 +31,9 @@
 #include <assimp/postprocess.h>
 #include <assimp/types.h>
 
+bool AssimpAppMesh::fixedSizeEnabled = false;
+S32 AssimpAppMesh::fixedSize = 2;
+
 //------------------------------------------------------------------------------
 
 AssimpAppMesh::AssimpAppMesh(const struct aiMesh* mesh, AssimpAppNode* node)
@@ -59,8 +62,7 @@ const char* AssimpAppMesh::getName(bool allowFixed)
 
    // If all geometry is being fixed to the same size, append the size
    // to the name
-   //return allowFixed && fixedSizeEnabled ? avar("%s %d", nodeName, fixedSize) : nodeName;
-   return nodeName;
+   return allowFixed && fixedSizeEnabled ? avar("%s %d", nodeName, fixedSize) : nodeName;
 }
 
 MatrixF AssimpAppMesh::getMeshTransform(F32 time)
@@ -77,6 +79,8 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset)
    uvs.reserve(mMeshData->mNumVertices);
    normals.reserve(mMeshData->mNumVertices);
 
+   bool flipNormals = Con::getBoolVariable("$Assimp::FlipNormals", false);
+
    bool noUVFound = false;
    for (U32 i = 0; i<mMeshData->mNumVertices; i++)
    {
@@ -93,6 +97,8 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset)
 
       tmpVert = Point3F(pt.x, pt.y, pt.z);
       tmpNormal = Point3F(nrm.x, nrm.y, nrm.z);
+      if (flipNormals)
+         tmpNormal *= -1.0f;
 
       objOffset.mulP(tmpVert);
 
@@ -155,23 +161,11 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset)
       const struct aiFace* face = &mMeshData->mFaces[n];
       if ( face->mNumIndices == 3 )
       {
-         if (Con::getBoolVariable("$Assimp::FlipNormals", true))
+         U32 indexCount = face->mNumIndices;
+         for (U32 ind = 0; ind < indexCount; ind++)
          {
-            U32 indexCount = face->mNumIndices;
-            for (S32 ind = indexCount - 1; ind >= 0; ind--)
-            {
-               U32 index = face->mIndices[ind];
-               indices.push_back(index);
-            }
-         }
-         else
-         {
-            U32 indexCount = face->mNumIndices;
-            for (U32 ind = 0; ind < indexCount; ind++)
-            {
-               U32 index = face->mIndices[ind];
-               indices.push_back(index);
-            }
+            U32 index = face->mIndices[ind];
+            indices.push_back(index);
          }
       } 
       else 
@@ -201,7 +195,10 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset)
    {
       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);
+      if (!nodePtr)
+         bones[b] = new AssimpAppNode(appNode->mScene, appNode->mNode);
+      else
+         bones[b] = new AssimpAppNode(appNode->mScene, nodePtr);
 
       MatrixF boneTransform;
       AssimpAppNode::assimpToTorqueMat(mMeshData->mBones[b]->mOffsetMatrix, boneTransform);

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

@@ -42,6 +42,9 @@ protected:
    const struct aiMesh* mMeshData;
    bool mIsSkinMesh;
 
+   static bool fixedSizeEnabled;                     ///< Set to true to fix the detail size to a particular value for all geometry
+   static S32 fixedSize;                             ///< The fixed detail size value for all geometry
+
 public:
 
    AssimpAppMesh(const struct aiMesh* mesh, AssimpAppNode* node);
@@ -54,8 +57,8 @@ public:
 
    static void fixDetailSize(bool fixed, S32 size=2)
    {
-      //fixedSizeEnabled = fixed;
-      //fixedSize = size;
+      fixedSizeEnabled = fixed;
+      fixedSize = size;
    }
 
    /// Get the name of this mesh

+ 123 - 2
Engine/source/ts/assimp/assimpAppNode.cpp

@@ -31,6 +31,8 @@
 #include <assimp/postprocess.h>
 #include <assimp/types.h>
 
+aiAnimation* AssimpAppNode::sActiveSequence = NULL;
+
 AssimpAppNode::AssimpAppNode(const struct aiScene* scene, const struct aiNode* node, AssimpAppNode* parent)
 :  mInvertMeshes(false),
    mLastTransformTime(TSShapeLoader::DefaultTime - 1),
@@ -94,12 +96,131 @@ MatrixF AssimpAppNode::getTransform(F32 time)
       //mLastTransform.scale(ColladaUtils::getOptions().unit);
    }
 
-   mLastTransform.mul(mNodeTransform);
+   // If this node is animated in the active sequence, fetch the animated transform
+   if (sActiveSequence)
+   {
+      MatrixF mat(true);
+      getAnimatedTransform(mat, time, sActiveSequence);
+      mLastTransform.mul(mat);
+   }
+   else
+      mLastTransform.mul(mNodeTransform);
+   
    mLastTransformTime = time;
-
    return mLastTransform;
 }
 
+void AssimpAppNode::getAnimatedTransform(MatrixF& mat, F32 t, aiAnimation* animSeq)
+{
+   // Find the channel for this node
+   for (U32 i = 0; i < animSeq->mNumChannels; ++i)
+   {
+      if (strcmp(mName, animSeq->mChannels[i]->mNodeName.C_Str()) == 0)
+      {
+         aiNodeAnim *nodeAnim = animSeq->mChannels[i];
+         Point3F trans(Point3F::Zero);
+         Point3F scale(Point3F::One);
+         QuatF rot;
+         rot.identity();
+
+         // Transform
+         if (nodeAnim->mNumPositionKeys == 1)
+            trans.set(nodeAnim->mPositionKeys[0].mValue.x, nodeAnim->mPositionKeys[0].mValue.y, nodeAnim->mPositionKeys[0].mValue.z);
+         else
+         {
+            Point3F curPos, lastPos;
+            F32 lastT = 0.0;
+            for (U32 key = 0; key < nodeAnim->mNumPositionKeys; ++key)
+            {
+               F32 curT = (F32)nodeAnim->mPositionKeys[key].mTime;
+               curPos.set(nodeAnim->mPositionKeys[key].mValue.x, nodeAnim->mPositionKeys[key].mValue.y, nodeAnim->mPositionKeys[key].mValue.z);
+               if (curT > t)
+               {
+                  F32 factor = (t - lastT) / (curT - lastT);
+                  trans.interpolate(lastPos, curPos, factor);
+                  break;
+               }
+               else if ((curT == t) || (key == nodeAnim->mNumPositionKeys - 1))
+               {
+                  trans = curPos;
+                  break;
+               }
+
+               lastT = curT;
+               lastPos = curPos;
+            }
+         }
+
+         // Rotation
+         if (nodeAnim->mNumRotationKeys == 1)
+            rot.set(nodeAnim->mRotationKeys[0].mValue.x, nodeAnim->mRotationKeys[0].mValue.y,
+               nodeAnim->mRotationKeys[0].mValue.z, nodeAnim->mRotationKeys[0].mValue.w);
+         else
+         {
+            QuatF curRot, lastRot;
+            F32 lastT = 0.0;
+            for (U32 key = 0; key < nodeAnim->mNumRotationKeys; ++key)
+            {
+               F32 curT = (F32)nodeAnim->mRotationKeys[key].mTime;
+               curRot.set(nodeAnim->mRotationKeys[key].mValue.x, nodeAnim->mRotationKeys[key].mValue.y,
+                  nodeAnim->mRotationKeys[key].mValue.z, nodeAnim->mRotationKeys[key].mValue.w);
+               if (curT > t)
+               {
+                  F32 factor = (t - lastT) / (curT - lastT);
+                  rot.interpolate(lastRot, curRot, factor);
+                  break;
+               }
+               else if ((curT == t) || (key == nodeAnim->mNumRotationKeys - 1))
+               {
+                  rot = curRot;
+                  break;
+               }
+
+               lastT = curT;
+               lastRot = curRot;
+            }
+         }
+
+         // Scale
+         if (nodeAnim->mNumScalingKeys == 1)
+            scale.set(nodeAnim->mScalingKeys[0].mValue.x, nodeAnim->mScalingKeys[0].mValue.y, nodeAnim->mScalingKeys[0].mValue.z);
+         else
+         {
+            Point3F curScale, lastScale;
+            F32 lastT = 0.0;
+            for (U32 key = 0; key < nodeAnim->mNumScalingKeys; ++key)
+            {
+               F32 curT = (F32)nodeAnim->mScalingKeys[key].mTime;
+               curScale.set(nodeAnim->mScalingKeys[key].mValue.x, nodeAnim->mScalingKeys[key].mValue.y, nodeAnim->mScalingKeys[key].mValue.z);
+               if (curT > t)
+               {
+                  F32 factor = (t - lastT) / (curT - lastT);
+                  scale.interpolate(lastScale, curScale, factor);
+                  break;
+               }
+               else if ((curT == t) || (key == nodeAnim->mNumScalingKeys - 1))
+               {
+                  scale = curScale;
+                  break;
+               }
+
+               lastT = curT;
+               lastScale = curScale;
+            }
+         }
+
+         rot.setMatrix(&mat);
+         mat.inverse();
+         mat.setPosition(trans);
+         mat.scale(scale);
+         return;
+      }
+   }
+
+   // Node not found in the animation channels
+   mat = mNodeTransform;
+}
+
 bool AssimpAppNode::animatesTransform(const AppSequence* appSeq)
 {
    return false;

+ 4 - 0
Engine/source/ts/assimp/assimpAppNode.h

@@ -36,6 +36,7 @@
 #ifndef AI_TYPES_H_INC
 #include <assimp/types.h>
 #endif
+#include <assimp/scene.h>
 
 class AssimpAppNode : public AppNode
 {
@@ -43,6 +44,7 @@ class AssimpAppNode : public AppNode
    friend class AssimpAppMesh;
 
    MatrixF getTransform(F32 time);
+   void getAnimatedTransform(MatrixF& mat, F32 t, aiAnimation* animSeq);
    void buildMeshList();
    void buildChildList();
 
@@ -67,6 +69,8 @@ public:
       //
    }
 
+   static aiAnimation* sActiveSequence;
+
    //-----------------------------------------------------------------------
    const char *getName() { return mName; }
    const char *getParentName() { return mParentName; }

+ 53 - 11
Engine/source/ts/assimp/assimpAppSequence.cpp

@@ -10,29 +10,72 @@
 #include "console/persistenceManager.h"
 #include "ts/assimp/assimpAppMaterial.h"
 #include "ts/assimp/assimpAppSequence.h"
+#include "ts/assimp/assimpAppNode.h"
 
 AssimpAppSequence::AssimpAppSequence(aiAnimation *a) :
+   seqStart(0.0f),
    mAnim(a)
 {
-   fps = mAnim->mTicksPerSecond;
+   // From: http://sir-kimmi.de/assimp/lib_html/data.html#anims
+   // An aiAnimation has a duration. The duration as well as all time stamps are given in ticks.
+   // To get the correct timing, all time stamp thus have to be divided by aiAnimation::mTicksPerSecond.
+   // Beware, though, that certain combinations of file format and exporter don't always store this
+   // information in the exported file. In this case, mTicksPerSecond is set to 0 to indicate the lack of knowledge.
+   fps = (mAnim->mTicksPerSecond > 0) ? mAnim->mTicksPerSecond : 30.0f;
+
+   F32 maxEndTime = 0;
+   F32 minFrameTime = 1000.0f;
+   // Detect the frame rate (minimum time between keyframes) and max sequence time
+   for (U32 i = 0; i < mAnim->mNumChannels; ++i)
+   {
+      aiNodeAnim *nodeAnim = mAnim->mChannels[i];
+      if (nodeAnim->mNumPositionKeys)
+         maxEndTime = getMax(maxEndTime, (F32) nodeAnim->mPositionKeys[nodeAnim->mNumPositionKeys-1].mTime);
+      if (nodeAnim->mNumRotationKeys)
+         maxEndTime = getMax(maxEndTime, (F32) nodeAnim->mRotationKeys[nodeAnim->mNumRotationKeys-1].mTime);
+      if (nodeAnim->mNumScalingKeys)
+         maxEndTime = getMax(maxEndTime, (F32) nodeAnim->mScalingKeys[nodeAnim->mNumScalingKeys-1].mTime);
+
+      for (U32 key = 1; key < nodeAnim->mNumPositionKeys; ++key)
+      {
+         F32 deltaT = nodeAnim->mPositionKeys[key].mTime - nodeAnim->mPositionKeys[key-1].mTime;
+         minFrameTime = getMin(minFrameTime, deltaT);
+      }
+      for (U32 key = 1; key < nodeAnim->mNumRotationKeys; ++key)
+      {
+         F32 deltaT = nodeAnim->mRotationKeys[key].mTime - nodeAnim->mRotationKeys[key-1].mTime;
+         minFrameTime = getMin(minFrameTime, deltaT);
+      }
+      for (U32 key = 1; key < nodeAnim->mNumScalingKeys; ++key)
+      {
+         F32 deltaT = nodeAnim->mScalingKeys[key].mTime - nodeAnim->mScalingKeys[key-1].mTime;
+         minFrameTime = getMin(minFrameTime, deltaT);
+      }
+   }
+
+   fps = (minFrameTime > 0.0f) ? 1.0f / minFrameTime : fps;
+   fps = mClamp(fps, TSShapeLoader::MinFrameRate, TSShapeLoader::MaxFrameRate);
+   seqEnd = maxEndTime;
 }
 
 AssimpAppSequence::~AssimpAppSequence()
 {
 }
 
-F32 AssimpAppSequence::getStart() const 
-{ 
-   return 0.0f; 
-}
-F32 AssimpAppSequence::getEnd() const 
-{ 
-   return (F32)mAnim->mDuration / fps; 
+void AssimpAppSequence::setActive(bool active)
+{
+   if (active)
+      AssimpAppNode::sActiveSequence = mAnim;
+   else
+   {
+      if (AssimpAppNode::sActiveSequence == mAnim)
+         AssimpAppNode::sActiveSequence = NULL;
+   }
 }
 
 U32 AssimpAppSequence::getFlags() const 
 { 
-   return TSShape::Blend; 
+   return TSShape::Blend;
 }
 F32 AssimpAppSequence::getPriority() const 
 { 
@@ -41,5 +84,4 @@ F32 AssimpAppSequence::getPriority() const
 F32 AssimpAppSequence::getBlendRefTime() const 
 { 
    return -1.0f; 
-}
-
+}

+ 7 - 4
Engine/source/ts/assimp/assimpAppSequence.h

@@ -22,6 +22,9 @@
 
 class AssimpAppSequence : public AppSequence
 {
+   F32      seqStart;
+   F32      seqEnd;
+
 public:
 
    AssimpAppSequence(aiAnimation *a);
@@ -29,18 +32,18 @@ public:
 
    aiAnimation *mAnim;
 
-   virtual void setActive(bool active) { }
+   virtual void setActive(bool active);
 
    virtual S32 getNumTriggers() const { return 0; }
    virtual void getTrigger(S32 index, TSShape::Trigger& trigger) const { trigger.state = 0; }
 
    virtual const char* getName() const { return mAnim->mName.C_Str(); }
 
-   virtual F32 getStart() const;
-   virtual F32 getEnd() const;
+   F32 getStart() const { return seqStart; }
+   F32 getEnd() const { return seqEnd; }
+   void setEnd(F32 end) { seqEnd = end; }
 
    virtual U32 getFlags() const;
    virtual F32 getPriority() const;
    virtual F32 getBlendRefTime() const;
-
 };

+ 117 - 10
Engine/source/ts/assimp/assimpShapeLoader.cpp

@@ -33,6 +33,7 @@
 
 #include "ts/assimp/assimpShapeLoader.h"
 #include "ts/assimp/assimpAppNode.h"
+#include "ts/assimp/assimpAppMesh.h"
 #include "ts/assimp/assimpAppMaterial.h"
 #include "ts/assimp/assimpAppSequence.h"
 
@@ -147,6 +148,12 @@ void AssimpShapeLoader::enumerateScene()
        Con::getBoolVariable("$Assimp::OptimizeMeshes", false) ? aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph : 0 |
        0;
 
+   if(Con::getBoolVariable("$Assimp::FlipUVs", true))
+      ppsteps |= aiProcess_FlipUVs;
+
+   if(Con::getBoolVariable("$Assimp::FlipWindingOrder", false))
+      ppsteps |= aiProcess_FlipWindingOrder;
+
    if(Con::getBoolVariable("$Assimp::Triangulate", true))
       ppsteps |= aiProcess_Triangulate;
 
@@ -160,17 +167,25 @@ void AssimpShapeLoader::enumerateScene()
 
    aiPropertyStore* props = aiCreatePropertyStore();
 
-   aiSetImportPropertyInteger(props,   AI_CONFIG_IMPORT_TER_MAKE_UVS,         1);
-   aiSetImportPropertyInteger(props,   AI_CONFIG_PP_SBP_REMOVE,               (aiProcessPreset_TargetRealtime_Quality 
-                                                                                    | aiProcess_FlipWindingOrder | aiProcess_FlipUVs 
-                                                                                    | aiProcess_CalcTangentSpace
-                                                                                    | aiProcess_FixInfacingNormals) 
-                                                                                       & ~aiProcess_RemoveRedundantMaterials);
-   aiSetImportPropertyInteger(props,   AI_CONFIG_GLOB_MEASURE_TIME,           1);
-   aiSetImportPropertyFloat(props,     AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE,  80.f);
+   //aiSetImportPropertyInteger(props,   AI_CONFIG_IMPORT_TER_MAKE_UVS,         1);
+   //aiSetImportPropertyInteger(props,   AI_CONFIG_PP_SBP_REMOVE,               (aiProcessPreset_TargetRealtime_Quality 
+   //                                                                                 | aiProcess_FlipWindingOrder | aiProcess_FlipUVs 
+   //                                                                                 | aiProcess_CalcTangentSpace
+   //                                                                                 | aiProcess_FixInfacingNormals) 
+   //                                                                                    & ~aiProcess_RemoveRedundantMaterials);
+   //aiSetImportPropertyInteger(props,   AI_CONFIG_GLOB_MEASURE_TIME,           1);
+   //aiSetImportPropertyFloat(props,     AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE,  80.f);
    //aiSetImportPropertyInteger(props,AI_CONFIG_PP_PTV_KEEP_HIERARCHY,1);
 
-   //Assimp::Importer importer;
+   struct aiLogStream shapeLog;
+   shapeLog = aiGetPredefinedLogStream(aiDefaultLogStream_FILE, "assimp.log");
+   aiAttachLogStream(&shapeLog);
+#ifdef TORQUE_DEBUG
+   aiEnableVerboseLogging(true);
+#endif
+
+   //c = aiGetPredefinedLogStream(aiDefaultLogStream_STDOUT, NULL);
+   //aiAttachLogStream(&c);
 
    // Attempt to import with Assimp.
    //mScene = importer.ReadFile(shapePath.getFullPath().c_str(), (aiProcessPreset_TargetRealtime_Quality | aiProcess_FlipWindingOrder | aiProcess_FlipUVs | aiProcess_CalcTangentSpace)
@@ -189,6 +204,9 @@ void AssimpShapeLoader::enumerateScene()
       for ( U32 i = 0; i < mScene->mNumMaterials; i++ )
          AppMesh::appMaterials.push_back(new AssimpAppMaterial(mScene->mMaterials[i]));
 
+      // Setup LOD checks
+      detectDetails();
+
       // Define the root node, and process down the chain.
       AssimpAppNode* node = new AssimpAppNode(mScene, mScene->mRootNode, 0);
       
@@ -203,6 +221,8 @@ void AssimpShapeLoader::enumerateScene()
       TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import failed");
       Con::printf("[ASSIMP] Import Error: %s", aiGetErrorString());
    }
+
+   aiDetachLogStream(&shapeLog);
 }
 
 void AssimpShapeLoader::processAnimations()
@@ -216,6 +236,55 @@ void AssimpShapeLoader::processAnimations()
    }
 }
 
+void AssimpShapeLoader::computeBounds(Box3F& bounds)
+{
+   TSShapeLoader::computeBounds(bounds);
+
+   // Check if the model origin needs adjusting
+   bool adjustCenter = Con::getBoolVariable("$Assimp::adjustCenter", false); //ColladaUtils::getOptions().adjustCenter
+   bool adjustFloor = Con::getBoolVariable("$Assimp::adjustFloor", false); //ColladaUtils::getOptions().adjustFloor
+   if (bounds.isValidBox() && (adjustCenter || adjustFloor))
+   {
+      // Compute shape offset
+      Point3F shapeOffset = Point3F::Zero;
+      if (adjustCenter)
+      {
+         bounds.getCenter(&shapeOffset);
+         shapeOffset = -shapeOffset;
+      }
+      if (adjustFloor)
+         shapeOffset.z = -bounds.minExtents.z;
+
+      // Adjust bounds
+      bounds.minExtents += shapeOffset;
+      bounds.maxExtents += shapeOffset;
+
+      // Now adjust all positions for root level nodes (nodes with no parent)
+      for (S32 iNode = 0; iNode < shape->nodes.size(); iNode++)
+      {
+         if (!appNodes[iNode]->isParentRoot())
+            continue;
+
+         // Adjust default translation
+         shape->defaultTranslations[iNode] += shapeOffset;
+
+         // Adjust animated translations
+         for (S32 iSeq = 0; iSeq < shape->sequences.size(); iSeq++)
+         {
+            const TSShape::Sequence& seq = shape->sequences[iSeq];
+            if (seq.translationMatters.test(iNode))
+            {
+               for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++)
+               {
+                  S32 index = seq.baseTranslation + seq.translationMatters.count(iNode)*seq.numKeyframes + iFrame;
+                  shape->nodeTranslations[index] += shapeOffset;
+               }
+            }
+         }
+      }
+   }
+}
+
 void AssimpShapeLoader::updateMaterialsScript(const Torque::Path &path)
 {
    Torque::Path scriptPath(path);
@@ -286,6 +355,44 @@ bool AssimpShapeLoader::ignoreNode(const String& name)
    return false;
 }
 
+void AssimpShapeLoader::detectDetails()
+{
+   // Set LOD option
+   bool singleDetail = true;
+   switch (Con::getIntVariable("$Assimp::lodType", 0))
+   {
+   case ColladaUtils::ImportOptions::DetectDTS:
+      // Check for a baseXX->startXX hierarchy at the top-level, if we find
+      // one, use trailing numbers for LOD, otherwise use a single size
+      for (S32 iNode = 0; singleDetail && (iNode < mScene->mRootNode->mNumChildren); iNode++) {
+         aiNode* node = mScene->mRootNode->mChildren[iNode];
+         if (node && dStrStartsWith(node->mName.C_Str(), "base")) {
+            for (S32 iChild = 0; iChild < node->mNumChildren; iChild++) {
+               aiNode* child = node->mChildren[iChild];
+               if (child && dStrStartsWith(child->mName.C_Str(), "start")) {
+                  singleDetail = false;
+                  break;
+               }
+            }
+         }
+      }
+      break;
+
+   case ColladaUtils::ImportOptions::SingleSize:
+      singleDetail = true;
+      break;
+
+   case ColladaUtils::ImportOptions::TrailingNumber:
+      singleDetail = false;
+      break;
+
+   default:
+      break;
+   }
+
+   AssimpAppMesh::fixDetailSize(singleDetail, Con::getIntVariable("$Assimp::singleDetailSize", 2));
+}
+
 //-----------------------------------------------------------------------------
 /// This function is invoked by the resource manager based on file extension.
 TSShape* assimpLoadShape(const Torque::Path &path)
@@ -374,7 +481,7 @@ DefineEngineFunction(GetShapeInfo, GuiTreeViewCtrl*, (String filePath), ,
    //Details!
    for (U32 i = 0; i < shapeScene->mNumMeshes; i++)
    {
-      treeObj->insertItem(meshItem, String::ToString("%s", shapeScene->mMeshes[i]->mName));
+      treeObj->insertItem(meshItem, String::ToString("%s", shapeScene->mMeshes[i]->mName.C_Str()));
    }
 
    for (U32 i = 0; i < shapeScene->mNumMaterials; i++)

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

@@ -36,6 +36,7 @@ protected:
    const struct aiScene* mScene;
 
    virtual bool ignoreNode(const String& name);
+   void detectDetails();
 
 public:
    AssimpShapeLoader();
@@ -46,6 +47,8 @@ public:
    void updateMaterialsScript(const Torque::Path &path);
    void processAnimations();
 
+   void computeBounds(Box3F& bounds);
+
    static bool canLoadCachedDTS(const Torque::Path& path);
 };
 

+ 688 - 0
Templates/BaseGame/game/tools/gui/assimpImport.ed.gui

@@ -0,0 +1,688 @@
+//--- OBJECT WRITE BEGIN ---
+%guiContent = new GuiControl(AssimpImportDlg,EditorGuiGroup) {
+   isContainer = "1";
+   Profile = "ToolsGuiDefaultProfile";
+   HorizSizing = "width";
+   VertSizing = "height";
+   position = "0 0";
+   Extent = "1024 768";
+   MinExtent = "8 2";
+   canSave = "1";
+   Visible = "1";
+   tooltipprofile = "ToolsGuiToolTipProfile";
+   hovertime = "1000";
+   canSaveDynamicFields = "0";
+
+   new GuiWindowCtrl() {
+      resizeWidth = "0";
+      resizeHeight = "0";
+      canMove = "1";
+      canClose = "1";
+      canMinimize = "0";
+      canMaximize = "0";
+      minSize = "50 50";
+      closeCommand = "Canvas.popDialog(AssimpImportDlg);";
+      EdgeSnap = "1";
+      text = "Open Asset Import Library";
+      Margin = "0 0 0 0";
+      Padding = "0 0 0 0";
+      AnchorTop = "1";
+      AnchorBottom = "0";
+      AnchorLeft = "1";
+      AnchorRight = "0";
+      isContainer = "1";
+      Profile = "ToolsGuiWindowProfile";
+      HorizSizing = "center";
+      VertSizing = "center";
+      position = "254 136";
+      Extent = "416 390";
+      MinExtent = "8 8";
+      canSave = "1";
+      Visible = "1";
+      Accelerator = "escape";
+      tooltipprofile = "ToolsGuiToolTipProfile";
+      hovertime = "1000";
+      internalName = "window";
+      canSaveDynamicFields = "0";
+      
+      new GuiTextCtrl() {
+         text = "Up Axis";
+         maxLength = "1024";
+         Margin = "0 0 0 0";
+         Padding = "0 0 0 0";
+         AnchorTop = "1";
+         AnchorBottom = "0";
+         AnchorLeft = "1";
+         AnchorRight = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiTextRightProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "10 31";
+         Extent = "40 16";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+      new GuiPopUpMenuCtrl() {
+         maxPopupHeight = "200";
+         sbUsesNAColor = "0";
+         reverseTextList = "0";
+         bitmapBounds = "16 16";
+         maxLength = "1024";
+         Margin = "0 0 0 0";
+         Padding = "0 0 0 0";
+         AnchorTop = "1";
+         AnchorBottom = "0";
+         AnchorLeft = "1";
+         AnchorRight = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiPopUpMenuProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "56 30";
+         Extent = "66 18";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         hovertime = "1000";
+         internalName = "upAxis";
+         canSaveDynamicFields = "0";
+      };
+      
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Convert To Left Handed";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "10 50";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::ConvertToLeftHanded";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Converts the model to left-handed";
+         hovertime = "1000";
+         internalName = "overrideScale";
+         canSaveDynamicFields = "0";
+      };
+      
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Triangulate";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "10 70";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         Active = "0";
+         variable = "$Assimp::Triangulate";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Triangulate polygons with more than 3 edges.";
+         hovertime = "1000";
+         internalName = "overrideScale";
+         canSaveDynamicFields = "0";
+      };
+
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Calculate Tangent Space";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "10 90";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::CalcTangentSpace";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Calculate tangents and bitangents, if possible.";
+         hovertime = "1000";
+         internalName = "overrideScale";
+         canSaveDynamicFields = "0";
+      };
+      
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Validate Data Structure";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "10 110";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::ValidateDataStructure";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Perform a full validation of the loader's output.";
+         hovertime = "1000";
+         internalName = "overrideScale";
+         canSaveDynamicFields = "0";
+      };
+      
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Improve Cache Locality";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "10 130";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::ImproveCacheLocality";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Improve the cache locality of the output vertices.";
+         hovertime = "1000";
+         internalName = "overrideScale";
+         canSaveDynamicFields = "0";
+      };
+         
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Remove Redundant Materials";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "10 150";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::RemoveRedundantMaterials";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Removes redundant materials.";
+         hovertime = "1000";
+         internalName = "overrideScale";
+         canSaveDynamicFields = "0";
+      };
+      
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Find Degenerates";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "10 170";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::FindDegenerates";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Remove degenerated polygons from the import.";
+         hovertime = "1000";
+         internalName = "overrideScale";
+         canSaveDynamicFields = "0";
+      };
+      
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Find Invalid Data";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "10 190";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::FindInvalidData";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Detect invalid model data, such as invalid normal vectors.";
+         hovertime = "1000";
+         internalName = "overrideScale";
+         canSaveDynamicFields = "0";
+      };
+      
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Generate UV Coordinates";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "10 210";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::GenUVCoords";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Convert spherical, cylindrical, box and planar mapping to proper UVs.";
+         hovertime = "1000";
+         internalName = "overrideScale";
+         canSaveDynamicFields = "0";
+      };
+      
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Transform UV Coordinates";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "10 230";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::TransformUVCoords";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Preprocess UV transformations (scaling, translation ...)";
+         hovertime = "1000";
+         internalName = "overrideScale";
+         canSaveDynamicFields = "0";
+      };
+      
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Flip UV Coordinates";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "10 250";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::FlipUVs";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "This step flips all UV coordinates along the y-axis and adjusts material settings and bitangents accordingly.\nAssimp uses TL(0,0):BR(1,1). T3D uses TL(0,1):BR(1,0). This will be needed for most textured models.";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+      
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Find Instances";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "10 270";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::FindInstances";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "search for instanced meshes and remove them by references to one master.";
+         hovertime = "1000";
+         internalName = "overrideScale";
+         canSaveDynamicFields = "0";
+      };
+      
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Limit Bone Weights";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "10 290";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::LimitBoneWeights";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Limit bone weights to 4 per vertex.";
+         hovertime = "1000";
+         internalName = "overrideScale";
+         canSaveDynamicFields = "0";
+      };
+
+      new GuiTextCtrl() {
+         text = "LOD";
+         maxLength = "1024";
+         Margin = "0 0 0 0";
+         Padding = "0 0 0 0";
+         AnchorTop = "1";
+         AnchorBottom = "0";
+         AnchorLeft = "1";
+         AnchorRight = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiTextRightProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "210 31";
+         Extent = "22 16";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+      new GuiPopUpMenuCtrl() {
+         maxPopupHeight = "200";
+         sbUsesNAColor = "0";
+         reverseTextList = "0";
+         bitmapBounds = "16 16";
+         maxLength = "1024";
+         Margin = "0 0 0 0";
+         Padding = "0 0 0 0";
+         AnchorTop = "1";
+         AnchorBottom = "0";
+         AnchorLeft = "1";
+         AnchorRight = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiPopUpMenuProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "238 30";
+         Extent = "92 18";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Method used to determine LOD for meshes in the model";
+         hovertime = "1000";
+         internalName = "lodType";
+         canSaveDynamicFields = "0";
+      };
+      new GuiTextEditCtrl() {
+         historySize = "0";
+         password = "0";
+         tabComplete = "0";
+         sinkAllKeyEvents = "0";
+         passwordMask = "*";
+         text = "2";
+         maxLength = "1024";
+         Margin = "0 0 0 0";
+         Padding = "0 0 0 0";
+         AnchorTop = "1";
+         AnchorBottom = "0";
+         AnchorLeft = "1";
+         AnchorRight = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiTextEditProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "338 30";
+         Extent = "49 18";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Detail size for all meshes in this model (when LOD type is SingleSize)";
+         hovertime = "1000";
+         internalName = "singleDetailSize";
+         canSaveDynamicFields = "0";
+      };
+
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Center Model";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "210 50";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::adjustCenter";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Translates model so the origin is at the center";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Floor Model";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "210 70";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::adjustFloor";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Translates model so the origin is at the bottom";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Join Identical Vertices";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "210 90";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::JoinIdenticalVertices";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Identifies and joins identical vertex data sets within all imported meshes.";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Flip Winding Order";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "210 110";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::FlipWindingOrder";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "This step adjusts the output face winding order to be clockwise. The default face winding order is counter clockwise.";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Invert Normals";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "210 130";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::FlipNormals";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Reverse the normal vector direction for all normals.";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+
+      new GuiButtonCtrl() {
+         text = "OK";
+         groupNum = "-1";
+         buttonType = "PushButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiButtonProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "120 348";
+         Extent = "86 22";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         Command = "AssimpImportDlg.onOK();";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Load the COLLADA model";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+      new GuiButtonCtrl() {
+         text = "Cancel";
+         groupNum = "-1";
+         buttonType = "PushButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiButtonProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "220 348";
+         Extent = "86 22";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         Command = "AssimpImportDlg.onCancel();";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Exit without loading the COLLADA model";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+   };
+};
+//--- OBJECT WRITE END ---
+
+function AssimpImportDlg::showDialog(%this, %shapePath, %cmd)
+{
+   %this.path = %shapePath;
+   %this.cmd = %cmd;
+
+   if ($Assimp::OverrideUpAxis $= "")
+   {  // First load, so set best-case defaults
+      $Assimp::OverrideUpAxis = 1; // y-axis is up in most test shapes
+      $Assimp::lodType = 0; // DetectDTS
+      $Assimp::singleDetailSize = "2";
+
+      // $Assimp::FlipUVs will be needed for virtually all textured models
+      $Assimp::FlipUVs = true;
+      $Assimp::FlipWindingOrder = true; // Makes winding order clock wise
+      $Assimp::FindDegenerates = true;
+      $Assimp::FindInvalidData = true;
+      $Assimp::JoinIdenticalVertices = true;
+   }
+
+   %this-->upAxis.clear();
+   %this-->upAxis.add("X_AXIS", 0);
+   %this-->upAxis.add("Y_AXIS", 1);
+   %this-->upAxis.add("Z_AXIS", 2);
+   %this-->upAxis.setSelected($Assimp::OverrideUpAxis);
+
+   %this-->lodType.clear();
+   %this-->lodType.add("DetectDTS", 0);
+   %this-->lodType.add("SingleSize", 1);
+   %this-->lodType.add("TrailingNumber", 2);
+   %this-->lodType.setSelected($Assimp::lodType);
+
+   %this-->singleDetailSize.text = $Assimp::singleDetailSize;
+
+   //Triangulate is a default(currently mandatory) behavior
+   $Assimp::Triangulate = true;
+
+   Canvas.pushDialog(%this);
+}
+
+function AssimpImportDlg::onCancel(%this)
+{
+   Canvas.popDialog(%this);
+   ColladaImportTreeView.clear();
+}
+
+function AssimpImportDlg::onOK(%this)
+{
+   Canvas.popDialog(%this);
+   ColladaImportTreeView.clear();
+
+   $Assimp::OverrideUpAxis = %this-->upAxis.getSelected();
+
+   $Assimp::lodType = %this-->lodType.getSelected();
+   $Assimp::singleDetailSize = %this-->singleDetailSize.getText();
+
+   // Load the shape (always from the DAE)
+   $assimp::forceLoad = true;
+   eval(%this.cmd);
+   $assimp::forceLoad = true;
+}

+ 18 - 0
Templates/BaseGame/game/tools/gui/colladaImport.ed.gui

@@ -1696,3 +1696,21 @@ function convertColladaModels(%pattern)
 
    $collada::forceLoadDAE = false;
 }
+
+function showImportDialog(%shapePath, %cmd)
+{
+   %this.path = %shapePath;
+   %this.cmd = %cmd;
+
+   if ( fileExt(%shapePath) $= ".dts" || fileExt(%shapePath) $= ".dsq" 
+      || fileExt(%shapePath) $= ".dae" || fileExt(%shapePath) $= ".kmz" )
+   {
+      // Regular Load
+      ColladaImportDlg.showDialog(%shapePath, %cmd);
+   } else if ( isSupportedFormat(stripChars(fileExt(%shapePath), ".")) )
+   {
+      // Assimp Load
+      if ( isObject(AssimpImportDlg) )
+         AssimpImportDlg.showDialog(%shapePath, %cmd);
+   }
+}

+ 2 - 1
Templates/BaseGame/game/tools/gui/guiDialogs.ed.cs

@@ -35,4 +35,5 @@ exec("./GuiEaseEditDlg.ed.cs");
 exec("./guiObjectInspector.ed.cs");
 exec("./uvEditor.ed.gui");
 exec("./objectSelection.ed.cs");
-exec("./postFxManager.gui");
+exec("./postFxManager.gui");
+exec("./assimpImport.ed.gui");

+ 2 - 2
Templates/BaseGame/game/tools/worldEditor/scripts/editors/creator.ed.cs

@@ -675,7 +675,7 @@ function EWCreatorWindow::addShapeIcon( %this, %datablock )
    %shapePath = ( %datablock.shapeFile !$= "" ) ? %datablock.shapeFile : %datablock.shapeName;
    
    %createCmd = "EWCreatorWindow.createObject( \\\"" @ %cmd @ "\\\" );";
-   %ctrl.altCommand = "ColladaImportDlg.showDialog( \"" @ %shapePath @ "\", \"" @ %createCmd @ "\" );";
+   %ctrl.altCommand = "showImportDialog( \"" @ %shapePath @ "\", \"" @ %createCmd @ "\" );";
 
    %ctrl.iconBitmap = EditorIconRegistry::findIconByClassName( %class );
    %ctrl.text = %name;
@@ -701,7 +701,7 @@ function EWCreatorWindow::addStaticIcon( %this, %fullPath )
           "Last Modified: " @ fileModifiedTime( %fullPath );
 
    %createCmd = "EWCreatorWindow.createStatic( \\\"" @ %fullPath @ "\\\" );";
-   %ctrl.altCommand = "ColladaImportDlg.showDialog( \"" @ %fullPath @ "\", \"" @ %createCmd @ "\" );";
+   %ctrl.altCommand = "showImportDialog( \"" @ %fullPath @ "\", \"" @ %createCmd @ "\" );";
 
    %ctrl.iconBitmap = ( ( %ext $= ".dts" ) ? EditorIconRegistry::findIconByClassName( "TSStatic" ) : "tools/gui/images/iconCollada" );
    %ctrl.text = %file;

+ 287 - 16
Templates/Full/game/tools/gui/assimpImport.ed.gui

@@ -45,26 +45,52 @@
       internalName = "window";
       canSaveDynamicFields = "0";
       
-      new GuiCheckBoxCtrl() {
-         useInactiveState = "0";
-         text = " Swap Y and Z coordinates.";
-         groupNum = "-1";
-         buttonType = "ToggleButton";
-         useMouseEvents = "0";
+      new GuiTextCtrl() {
+         text = "Up Axis";
+         maxLength = "1024";
+         Margin = "0 0 0 0";
+         Padding = "0 0 0 0";
+         AnchorTop = "1";
+         AnchorBottom = "0";
+         AnchorLeft = "1";
+         AnchorRight = "0";
          isContainer = "0";
-         Profile = "ToolsGuiCheckBoxProfile";
+         Profile = "ToolsGuiTextRightProfile";
          HorizSizing = "right";
          VertSizing = "bottom";
-         position = "10 30";
-         Extent = "200 13";
+         position = "10 31";
+         Extent = "40 16";
          MinExtent = "8 2";
          canSave = "1";
          Visible = "1";
-         variable = "$Assimp::SwapYZ";
          tooltipprofile = "ToolsGuiToolTipProfile";
-         ToolTip = "Corrects transformations so Z is up.";
          hovertime = "1000";
-         internalName = "overrideScale";
+         canSaveDynamicFields = "0";
+      };
+      new GuiPopUpMenuCtrl() {
+         maxPopupHeight = "200";
+         sbUsesNAColor = "0";
+         reverseTextList = "0";
+         bitmapBounds = "16 16";
+         maxLength = "1024";
+         Margin = "0 0 0 0";
+         Padding = "0 0 0 0";
+         AnchorTop = "1";
+         AnchorBottom = "0";
+         AnchorLeft = "1";
+         AnchorRight = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiPopUpMenuProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "56 30";
+         Extent = "66 18";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         hovertime = "1000";
+         internalName = "upAxis";
          canSaveDynamicFields = "0";
       };
       
@@ -106,6 +132,7 @@
          MinExtent = "8 2";
          canSave = "1";
          Visible = "1";
+         Active = "0";
          variable = "$Assimp::Triangulate";
          tooltipprofile = "ToolsGuiToolTipProfile";
          ToolTip = "Triangulate polygons with more than 3 edges.";
@@ -300,7 +327,7 @@
       
       new GuiCheckBoxCtrl() {
          useInactiveState = "0";
-         text = " Find Instances";
+         text = " Flip UV Coordinates";
          groupNum = "-1";
          buttonType = "ToggleButton";
          useMouseEvents = "0";
@@ -313,6 +340,28 @@
          MinExtent = "8 2";
          canSave = "1";
          Visible = "1";
+         variable = "$Assimp::FlipUVs";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "This step flips all UV coordinates along the y-axis and adjusts material settings and bitangents accordingly.\nAssimp uses TL(0,0):BR(1,1). T3D uses TL(0,1):BR(1,0). This will be needed for most textured models.";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+      
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Find Instances";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "10 270";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
          variable = "$Assimp::FindInstances";
          tooltipprofile = "ToolsGuiToolTipProfile";
          ToolTip = "search for instanced meshes and remove them by references to one master.";
@@ -331,7 +380,7 @@
          Profile = "ToolsGuiCheckBoxProfile";
          HorizSizing = "right";
          VertSizing = "bottom";
-         position = "10 270";
+         position = "10 290";
          Extent = "200 13";
          MinExtent = "8 2";
          canSave = "1";
@@ -343,7 +392,196 @@
          internalName = "overrideScale";
          canSaveDynamicFields = "0";
       };
-         
+
+      new GuiTextCtrl() {
+         text = "LOD";
+         maxLength = "1024";
+         Margin = "0 0 0 0";
+         Padding = "0 0 0 0";
+         AnchorTop = "1";
+         AnchorBottom = "0";
+         AnchorLeft = "1";
+         AnchorRight = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiTextRightProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "210 31";
+         Extent = "22 16";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+      new GuiPopUpMenuCtrl() {
+         maxPopupHeight = "200";
+         sbUsesNAColor = "0";
+         reverseTextList = "0";
+         bitmapBounds = "16 16";
+         maxLength = "1024";
+         Margin = "0 0 0 0";
+         Padding = "0 0 0 0";
+         AnchorTop = "1";
+         AnchorBottom = "0";
+         AnchorLeft = "1";
+         AnchorRight = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiPopUpMenuProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "238 30";
+         Extent = "92 18";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Method used to determine LOD for meshes in the model";
+         hovertime = "1000";
+         internalName = "lodType";
+         canSaveDynamicFields = "0";
+      };
+      new GuiTextEditCtrl() {
+         historySize = "0";
+         password = "0";
+         tabComplete = "0";
+         sinkAllKeyEvents = "0";
+         passwordMask = "*";
+         text = "2";
+         maxLength = "1024";
+         Margin = "0 0 0 0";
+         Padding = "0 0 0 0";
+         AnchorTop = "1";
+         AnchorBottom = "0";
+         AnchorLeft = "1";
+         AnchorRight = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiTextEditProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "338 30";
+         Extent = "49 18";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Detail size for all meshes in this model (when LOD type is SingleSize)";
+         hovertime = "1000";
+         internalName = "singleDetailSize";
+         canSaveDynamicFields = "0";
+      };
+
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Center Model";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "210 50";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::adjustCenter";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Translates model so the origin is at the center";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Floor Model";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "210 70";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::adjustFloor";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Translates model so the origin is at the bottom";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Join Identical Vertices";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "210 90";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::JoinIdenticalVertices";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Identifies and joins identical vertex data sets within all imported meshes.";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Flip Winding Order";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "210 110";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::FlipWindingOrder";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "This step adjusts the output face winding order to be clockwise. The default face winding order is counter clockwise.";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+
+      new GuiCheckBoxCtrl() {
+         useInactiveState = "0";
+         text = " Invert Normals";
+         groupNum = "-1";
+         buttonType = "ToggleButton";
+         useMouseEvents = "0";
+         isContainer = "0";
+         Profile = "ToolsGuiCheckBoxProfile";
+         HorizSizing = "right";
+         VertSizing = "bottom";
+         position = "210 130";
+         Extent = "200 13";
+         MinExtent = "8 2";
+         canSave = "1";
+         Visible = "1";
+         variable = "$Assimp::FlipNormals";
+         tooltipprofile = "ToolsGuiToolTipProfile";
+         ToolTip = "Reverse the normal vector direction for all normals.";
+         hovertime = "1000";
+         canSaveDynamicFields = "0";
+      };
+
       new GuiButtonCtrl() {
          text = "OK";
          groupNum = "-1";
@@ -392,7 +630,35 @@ function AssimpImportDlg::showDialog(%this, %shapePath, %cmd)
 {
    %this.path = %shapePath;
    %this.cmd = %cmd;
-   
+
+   if ($Assimp::OverrideUpAxis $= "")
+   {  // First load, so set best-case defaults
+      $Assimp::OverrideUpAxis = 1; // y-axis is up in most test shapes
+      $Assimp::lodType = 0; // DetectDTS
+      $Assimp::singleDetailSize = "2";
+
+      // $Assimp::FlipUVs will be needed for virtually all textured models
+      $Assimp::FlipUVs = true;
+      $Assimp::FlipWindingOrder = true; // Makes winding order clock wise
+      $Assimp::FindDegenerates = true;
+      $Assimp::FindInvalidData = true;
+      $Assimp::JoinIdenticalVertices = true;
+   }
+
+   %this-->upAxis.clear();
+   %this-->upAxis.add("X_AXIS", 0);
+   %this-->upAxis.add("Y_AXIS", 1);
+   %this-->upAxis.add("Z_AXIS", 2);
+   %this-->upAxis.setSelected($Assimp::OverrideUpAxis);
+
+   %this-->lodType.clear();
+   %this-->lodType.add("DetectDTS", 0);
+   %this-->lodType.add("SingleSize", 1);
+   %this-->lodType.add("TrailingNumber", 2);
+   %this-->lodType.setSelected($Assimp::lodType);
+
+   %this-->singleDetailSize.text = $Assimp::singleDetailSize;
+
    //Triangulate is a default(currently mandatory) behavior
    $Assimp::Triangulate = true;
 
@@ -410,6 +676,11 @@ function AssimpImportDlg::onOK(%this)
    Canvas.popDialog(%this);
    ColladaImportTreeView.clear();
 
+   $Assimp::OverrideUpAxis = %this-->upAxis.getSelected();
+
+   $Assimp::lodType = %this-->lodType.getSelected();
+   $Assimp::singleDetailSize = %this-->singleDetailSize.getText();
+
    // Load the shape (always from the DAE)
    $assimp::forceLoad = true;
    eval(%this.cmd);

+ 1 - 1
Templates/Full/game/tools/shapeEditor/scripts/shapeEditor.ed.cs

@@ -267,7 +267,7 @@ function ShapeEdSelectWindow::onSelect( %this, %path )
    else
    {
       %cmd = "ShapeEditor.selectShape( \"" @ %path @ "\", false );";
-      ColladaImportDlg.showDialog( %path, %cmd );
+      showImportDialog( %path, %cmd );
    }
 }