Browse Source

Import settings persistence
Adds new settings to ColladaUtils::ImportSettings and TSShapeConstructor::ImportSettings for persistence. Shape will now be re-imported with the original settings if the source art is newer or the cached.dts file has been deleted.
Fixes material transparency blend mode assignment.
Adds implementation for override scale, material prefix and always/never import options.
Reads and applies metadata fields for scale and up axis from formats that provide it.
Eliminates the assimp.log file and redirects log messages to console.log. Verbose logging is enabled in debug builds.

OTHGMars 6 years ago
parent
commit
2eaa917e00

+ 77 - 18
Engine/source/ts/assimp/assimpAppMaterial.cpp

@@ -27,6 +27,7 @@
 #include "ts/assimp/assimpAppMesh.h"
 #include "ts/assimp/assimpAppMesh.h"
 #include "materials/materialManager.h"
 #include "materials/materialManager.h"
 #include "ts/tsMaterialList.h"
 #include "ts/tsMaterialList.h"
+#include "core/stream/fileStream.h"
 
 
 // assimp include files. 
 // assimp include files. 
 #include <assimp/cimport.h>
 #include <assimp/cimport.h>
@@ -34,6 +35,8 @@
 #include <assimp/postprocess.h>
 #include <assimp/postprocess.h>
 #include <assimp/types.h>
 #include <assimp/types.h>
 
 
+U32 AssimpAppMaterial::sDefaultMatNumber = 0;
+
 String AppMaterial::cleanString(const String& str)
 String AppMaterial::cleanString(const String& str)
 {
 {
    String cleanStr(str);
    String cleanStr(str);
@@ -52,7 +55,8 @@ String AppMaterial::cleanString(const String& str)
 
 
 AssimpAppMaterial::AssimpAppMaterial(const char* matName)
 AssimpAppMaterial::AssimpAppMaterial(const char* matName)
 {
 {
-   name = matName;
+   name = ColladaUtils::getOptions().matNamePrefix;
+   name += matName;
 
 
    // Set some defaults
    // Set some defaults
    flags |= TSMaterialList::S_Wrap;
    flags |= TSMaterialList::S_Wrap;
@@ -67,9 +71,12 @@ AssimpAppMaterial::AssimpAppMaterial(aiMaterial* mtl) :
    name = matName.C_Str();
    name = matName.C_Str();
    if (name.isEmpty())
    if (name.isEmpty())
    {
    {
-      name = cleanString(TSShapeLoader::getShapePath().getFileName());;
+      name = cleanString(TSShapeLoader::getShapePath().getFileName());
       name += "_defMat";
       name += "_defMat";
+      name += String::ToString("%d", sDefaultMatNumber);
+      sDefaultMatNumber++;
    }
    }
+   name = ColladaUtils::getOptions().matNamePrefix + name;
    Con::printf("[ASSIMP] Loading Material: %s", name.c_str());
    Con::printf("[ASSIMP] Loading Material: %s", name.c_str());
 #ifdef TORQUE_DEBUG
 #ifdef TORQUE_DEBUG
    enumerateMaterialProperties(mtl);
    enumerateMaterialProperties(mtl);
@@ -125,7 +132,7 @@ void AssimpAppMaterial::initMaterial(const Torque::Path& path, Material* mat) co
          if (dStrcmp("MASK", opacityMode.C_Str()) == 0)
          if (dStrcmp("MASK", opacityMode.C_Str()) == 0)
          {
          {
             translucent = true;
             translucent = true;
-            blendOp = Material::LerpAlpha;
+            blendOp = Material::None;
 
 
             float cutoff;
             float cutoff;
             if (AI_SUCCESS == mAIMat->Get("$mat.gltf.alphaCutoff", 0, 0, cutoff))
             if (AI_SUCCESS == mAIMat->Get("$mat.gltf.alphaCutoff", 0, 0, cutoff))
@@ -134,10 +141,16 @@ void AssimpAppMaterial::initMaterial(const Torque::Path& path, Material* mat) co
                mat->mAlphaTest = true;
                mat->mAlphaTest = true;
             }
             }
          }
          }
-         else if (dStrcmp("OPAQUE", opacityMode.C_Str()) != 0)
+         else if (dStrcmp("BLEND", opacityMode.C_Str()) == 0)
          {
          {
             translucent = true;
             translucent = true;
             blendOp = Material::LerpAlpha;
             blendOp = Material::LerpAlpha;
+            mat->mAlphaTest = false;
+         }
+         else
+         {  // OPAQUE
+            translucent = false;
+            blendOp = Material::LerpAlpha; // Make default so it doesn't get written to materials.cs
          }
          }
       }
       }
    }
    }
@@ -157,14 +170,14 @@ void AssimpAppMaterial::initMaterial(const Torque::Path& path, Material* mat) co
    {
    {
       torquePath = texName.C_Str();
       torquePath = texName.C_Str();
       if (!torquePath.isEmpty())
       if (!torquePath.isEmpty())
-         mat->mDiffuseMapFilename[0] = cleanTextureName(torquePath, cleanFile);
+         mat->mDiffuseMapFilename[0] = cleanTextureName(torquePath, cleanFile, path, false);
    }
    }
 
 
    if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0), texName))
    if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TEXTURE(aiTextureType_NORMALS, 0), texName))
    {
    {
       torquePath = texName.C_Str();
       torquePath = texName.C_Str();
       if (!torquePath.isEmpty())
       if (!torquePath.isEmpty())
-         mat->mNormalMapFilename[0] = cleanTextureName(torquePath, cleanFile);
+         mat->mNormalMapFilename[0] = cleanTextureName(torquePath, cleanFile, path, false);
    }
    }
 
 
 #ifdef TORQUE_PBR_MATERIALS
 #ifdef TORQUE_PBR_MATERIALS
@@ -177,27 +190,25 @@ void AssimpAppMaterial::initMaterial(const Torque::Path& path, Material* mat) co
       if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TEXTURE(aiTextureType_UNKNOWN, 0), texName))
       if (AI_SUCCESS == mAIMat->Get(AI_MATKEY_TEXTURE(aiTextureType_UNKNOWN, 0), texName))
          rmName = texName.C_Str();
          rmName = texName.C_Str();
 
 
-      //if (aoName.isNotEmpty() && (aoName == rmName))
-      //   mat->mOrmMapFilename[0] = cleanTextureName(aoName, cleanFile); // It's an ORM map
-      //else if (aoName.isNotEmpty() || rmName.isNotEmpty())
+      mat->mIsSRGb[0] = true;
       if (aoName.isNotEmpty() || rmName.isNotEmpty())
       if (aoName.isNotEmpty() || rmName.isNotEmpty())
       {  // If we have either map, fill all three slots
       {  // If we have either map, fill all three slots
          if (rmName.isNotEmpty())
          if (rmName.isNotEmpty())
          {
          {
-            mat->mRoughMapFilename[0] = cleanTextureName(rmName, cleanFile); // Roughness
+            mat->mRoughMapFilename[0] = cleanTextureName(rmName, cleanFile, path, false); // Roughness
             mat->mSmoothnessChan[0] = 1.0f;
             mat->mSmoothnessChan[0] = 1.0f;
             mat->mInvertSmoothness = (floatVal == 1.0f);
             mat->mInvertSmoothness = (floatVal == 1.0f);
-            mat->mMetalMapFilename[0] = cleanTextureName(rmName, cleanFile); // Metallic
+            mat->mMetalMapFilename[0] = cleanTextureName(rmName, cleanFile, path, false); // Metallic
             mat->mMetalChan[0] = 2.0f;
             mat->mMetalChan[0] = 2.0f;
          }
          }
          if (aoName.isNotEmpty())
          if (aoName.isNotEmpty())
          {
          {
-            mat->mAOMapFilename[0] = cleanTextureName(aoName, cleanFile); // occlusion
+            mat->mAOMapFilename[0] = cleanTextureName(aoName, cleanFile, path, false); // occlusion
             mat->mAOChan[0] = 0.0f;
             mat->mAOChan[0] = 0.0f;
          }
          }
          else
          else
          {
          {
-            mat->mAOMapFilename[0] = cleanTextureName(rmName, cleanFile); // occlusion
+            mat->mAOMapFilename[0] = cleanTextureName(rmName, cleanFile, path, false); // occlusion
             mat->mAOChan[0] = 0.0f;
             mat->mAOChan[0] = 0.0f;
          }
          }
       }
       }
@@ -207,7 +218,7 @@ void AssimpAppMaterial::initMaterial(const Torque::Path& path, Material* mat) co
    {
    {
       torquePath = texName.C_Str();
       torquePath = texName.C_Str();
       if (!torquePath.isEmpty())
       if (!torquePath.isEmpty())
-         mat->mSpecularMapFilename[0] = cleanTextureName(torquePath, cleanFile);
+         mat->mSpecularMapFilename[0] = cleanTextureName(torquePath, cleanFile, path, false);
    }
    }
 
 
    LinearColorF specularColor(1.0f, 1.0f, 1.0f, 1.0f);
    LinearColorF specularColor(1.0f, 1.0f, 1.0f, 1.0f);
@@ -234,22 +245,70 @@ void AssimpAppMaterial::initMaterial(const Torque::Path& path, Material* mat) co
    mat->mDoubleSided = doubleSided;
    mat->mDoubleSided = doubleSided;
 }
 }
 
 
-String AssimpAppMaterial::cleanTextureName(String& texName, String& shapeName)
+String AssimpAppMaterial::cleanTextureName(String& texName, String& shapeName, const Torque::Path& path, bool nameOnly /*= false*/)
 {
 {
+   Torque::Path foundPath;
    String cleanStr;
    String cleanStr;
 
 
    if (texName[0] == '*')
    if (texName[0] == '*')
-   {
+   {  // It's an embedded texture reference. Make the cached name and return
       cleanStr = shapeName;
       cleanStr = shapeName;
       cleanStr += "_cachedTex";
       cleanStr += "_cachedTex";
       cleanStr += texName.substr(1);
       cleanStr += texName.substr(1);
+      return cleanStr;
+   }
+
+   // See if the file exists
+   bool fileFound = false;
+   String testPath = path.getPath();
+   testPath += '/';
+   testPath += texName;
+   testPath.replace('\\', '/');
+   fileFound = Torque::FS::IsFile(testPath);
+
+   cleanStr = texName;
+   cleanStr.replace('\\', '/');
+   if (fileFound)
+   {
+      if (cleanStr.equal(texName))
+         return cleanStr;
+      foundPath = testPath;
    }
    }
    else
    else
    {
    {
-      cleanStr = texName;
-      cleanStr.replace('\\', '/');
+      // See if the file is in a sub-directory of the shape
+      Vector<String> foundFiles;
+      Torque::Path inPath(cleanStr);
+      String mainDotCsDir = Platform::getMainDotCsDir();
+      mainDotCsDir += "/";
+      S32 results = Torque::FS::FindByPattern(Torque::Path(mainDotCsDir + path.getPath() + "/"), inPath.getFullFileName(), true, foundFiles);
+      if (results == 0 || foundFiles.size() == 0) // Not under shape directory, try the full tree
+         results = Torque::FS::FindByPattern(Torque::Path(mainDotCsDir), inPath.getFullFileName(), true, foundFiles);
+
+      if (results > 0 && foundFiles.size() > 0)
+      {
+         fileFound = true;
+         foundPath = foundFiles[0];
+      }
    }
    }
 
 
+   if (fileFound)
+   {
+      if (nameOnly)
+         cleanStr = foundPath.getFullFileName();
+      else
+      {  // Unless the file is in the same directory as the materials.cs (covered above)
+         // we need to set the full path from the root directory. If we use "subdirectory/file.ext",
+         // the material manager won't find the image file, but it will be found the next time the
+         // material is loaded from file. If we use "./subdirectory/file.ext", the image will be found
+         // now, but not the next time it's loaded from file...
+         S32 rootLength = dStrlen(Platform::getMainDotCsDir());
+         cleanStr = foundPath.getFullPathWithoutRoot().substr(rootLength-1);
+      }
+   }
+   else if (nameOnly)
+      cleanStr += " (Not Found)";
+ 
    return cleanStr;
    return cleanStr;
 }
 }
 
 

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

@@ -40,7 +40,6 @@ class AssimpAppMaterial : public AppMaterial
 #ifdef TORQUE_DEBUG
 #ifdef TORQUE_DEBUG
    void enumerateMaterialProperties(aiMaterial* mtl);
    void enumerateMaterialProperties(aiMaterial* mtl);
 #endif
 #endif
-   static String cleanTextureName(String& texName, String& shapeName);
 
 
 public:
 public:
 
 
@@ -51,6 +50,9 @@ public:
    String getName() const { return name; }
    String getName() const { return name; }
    Material* createMaterial(const Torque::Path& path) const;
    Material* createMaterial(const Torque::Path& path) const;
    void initMaterial(const Torque::Path& path, Material* mat) const;
    void initMaterial(const Torque::Path& path, Material* mat) const;
+
+   static String cleanTextureName(String& texName, String& shapeName, const Torque::Path& path, bool nameOnly = false);
+   static U32 sDefaultMatNumber;
 };
 };
 
 
 #endif // _ASSIMP_APPMATERIAL_H_
 #endif // _ASSIMP_APPMATERIAL_H_

+ 8 - 5
Engine/source/ts/assimp/assimpAppMesh.cpp

@@ -79,7 +79,7 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset)
    uvs.reserve(mMeshData->mNumVertices);
    uvs.reserve(mMeshData->mNumVertices);
    normals.reserve(mMeshData->mNumVertices);
    normals.reserve(mMeshData->mNumVertices);
 
 
-   bool flipNormals = Con::getBoolVariable("$Assimp::FlipNormals", false);
+   bool flipNormals = ColladaUtils::getOptions().invertNormals;
 
 
    bool noUVFound = false;
    bool noUVFound = false;
    for (U32 i = 0; i<mMeshData->mNumVertices; i++)
    for (U32 i = 0; i<mMeshData->mNumVertices; i++)
@@ -203,14 +203,17 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset)
       MatrixF boneTransform;
       MatrixF boneTransform;
       AssimpAppNode::assimpToTorqueMat(mMeshData->mBones[b]->mOffsetMatrix, boneTransform);
       AssimpAppNode::assimpToTorqueMat(mMeshData->mBones[b]->mOffsetMatrix, boneTransform);
       Point3F boneScale = boneTransform.getScale();
       Point3F boneScale = boneTransform.getScale();
-      if (boneScale != Point3F::One)
+      Point3F bonePos = boneTransform.getPosition();
+      if (boneScale != Point3F::One && ColladaUtils::getOptions().ignoreNodeScale)
       {
       {
          Point3F scaleMult = Point3F::One / boneScale;
          Point3F scaleMult = Point3F::One / boneScale;
-         Point3F scalePos = boneTransform.getPosition();
          boneTransform.scale(scaleMult);
          boneTransform.scale(scaleMult);
-         scalePos /= scaleMult;
-         boneTransform.setPosition(scalePos);
+         bonePos /= scaleMult;
       }
       }
+
+      bonePos *= ColladaUtils::getOptions().unit;
+      boneTransform.setPosition(bonePos);
+
       initialTransforms.push_back(boneTransform);
       initialTransforms.push_back(boneTransform);
 
 
       //Weights
       //Weights

+ 20 - 15
Engine/source/ts/assimp/assimpAppNode.cpp

@@ -91,22 +91,30 @@ MatrixF AssimpAppNode::getTransform(F32 time)
    else {
    else {
       // no parent (ie. root level) => scale by global shape <unit>
       // no parent (ie. root level) => scale by global shape <unit>
       mLastTransform.identity();
       mLastTransform.identity();
+      mLastTransform.scale(ColladaUtils::getOptions().unit);
       if (!isBounds())
       if (!isBounds())
          convertMat(mLastTransform);
          convertMat(mLastTransform);
-
-      //mLastTransform.scale(ColladaUtils::getOptions().unit);
    }
    }
 
 
    // If this node is animated in the active sequence, fetch the animated transform
    // If this node is animated in the active sequence, fetch the animated transform
+   MatrixF mat(true);
    if (sActiveSequence)
    if (sActiveSequence)
-   {
-      MatrixF mat(true);
       getAnimatedTransform(mat, time, sActiveSequence);
       getAnimatedTransform(mat, time, sActiveSequence);
-      mLastTransform.mul(mat);
-   }
    else
    else
-      mLastTransform.mul(mNodeTransform);
-   
+      mat = mNodeTransform;
+
+   // Remove node scaling?
+   Point3F nodeScale = mat.getScale();
+   if (nodeScale != Point3F::One && appParent && ColladaUtils::getOptions().ignoreNodeScale)
+   {
+      nodeScale.x = nodeScale.x ? (1.0f / nodeScale.x) : 0;
+      nodeScale.y = nodeScale.y ? (1.0f / nodeScale.y) : 0;
+      nodeScale.z = nodeScale.z ? (1.0f / nodeScale.z) : 0;
+      mat.scale(nodeScale);
+   }
+
+   mLastTransform.mul(mat);
+
    mLastTransformTime = time;
    mLastTransformTime = time;
    return mLastTransform;
    return mLastTransform;
 }
 }
@@ -280,12 +288,9 @@ void AssimpAppNode::convertMat(MatrixF& outMat)
 {
 {
    MatrixF rot(true);
    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))
+   switch (ColladaUtils::getOptions().upAxis)
    {
    {
-   case 0: //UPAXISTYPE_X_UP:
+   case UPAXISTYPE_X_UP:
       // rotate 90 around Y-axis, then 90 around Z-axis
       // rotate 90 around Y-axis, then 90 around Z-axis
       rot(0, 0) = 0.0f;  rot(1, 0) = 1.0f;
       rot(0, 0) = 0.0f;  rot(1, 0) = 1.0f;
       rot(1, 1) = 0.0f;	rot(2, 1) = 1.0f;
       rot(1, 1) = 0.0f;	rot(2, 1) = 1.0f;
@@ -295,7 +300,7 @@ void AssimpAppNode::convertMat(MatrixF& outMat)
       outMat.mulL(rot);
       outMat.mulL(rot);
       break;
       break;
 
 
-   case 1: //UPAXISTYPE_Y_UP:
+   case UPAXISTYPE_Y_UP:
       // rotate 180 around Y-axis, then 90 around X-axis
       // rotate 180 around Y-axis, then 90 around X-axis
       rot(0, 0) = -1.0f;
       rot(0, 0) = -1.0f;
       rot(1, 1) = 0.0f;	rot(2, 1) = 1.0f;
       rot(1, 1) = 0.0f;	rot(2, 1) = 1.0f;
@@ -305,7 +310,7 @@ void AssimpAppNode::convertMat(MatrixF& outMat)
       outMat.mulL(rot);
       outMat.mulL(rot);
       break;
       break;
 
 
-   case 2: //UPAXISTYPE_Z_UP:
+   case UPAXISTYPE_Z_UP:
    default:
    default:
       // nothing to do
       // nothing to do
       break;
       break;

+ 2 - 5
Engine/source/ts/assimp/assimpAppSequence.cpp

@@ -58,8 +58,8 @@ AssimpAppSequence::AssimpAppSequence(aiAnimation *a) :
       }
       }
    }
    }
 
 
-   S32 timeFactor = Con::getIntVariable("$Assimp::AnimTiming", 1);
-   S32 fpsRequest = Con::getIntVariable("$Assimp::AnimFPS", 30);
+   S32 timeFactor = ColladaUtils::getOptions().animTiming;
+   S32 fpsRequest = ColladaUtils::getOptions().animFPS;
    if (timeFactor == 0)
    if (timeFactor == 0)
    {  // Timing specified in frames
    {  // Timing specified in frames
       fps = mClamp(fpsRequest, 5 /*TSShapeLoader::MinFrameRate*/, TSShapeLoader::MaxFrameRate);
       fps = mClamp(fpsRequest, 5 /*TSShapeLoader::MinFrameRate*/, TSShapeLoader::MaxFrameRate);
@@ -70,10 +70,7 @@ AssimpAppSequence::AssimpAppSequence(aiAnimation *a) :
    else
    else
    {  // Timing specified in seconds or ms depending on format
    {  // Timing specified in seconds or ms depending on format
       if (maxEndTime > 1000.0f || mAnim->mDuration > 1000.0f)
       if (maxEndTime > 1000.0f || mAnim->mDuration > 1000.0f)
-      {
          timeFactor = 1000.0f;   // If it's more than 1000 seconds, assume it's ms.
          timeFactor = 1000.0f;   // If it's more than 1000 seconds, assume it's ms.
-         Con::setIntVariable("$Assimp::AnimTiming", 1000);
-      }
 
 
       timeFactor = mClamp(timeFactor, 1, 1000);
       timeFactor = mClamp(timeFactor, 1, 1000);
       minFrameTime /= (F32)timeFactor;
       minFrameTime /= (F32)timeFactor;

+ 359 - 97
Engine/source/ts/assimp/assimpShapeLoader.cpp

@@ -39,6 +39,7 @@
 
 
 #include "core/util/tVector.h"
 #include "core/util/tVector.h"
 #include "core/strings/findMatch.h"
 #include "core/strings/findMatch.h"
+#include "core/strings/stringUnit.h"
 #include "core/stream/fileStream.h"
 #include "core/stream/fileStream.h"
 #include "core/fileObject.h"
 #include "core/fileObject.h"
 #include "ts/tsShape.h"
 #include "ts/tsShape.h"
@@ -133,29 +134,16 @@ void AssimpShapeLoader::enumerateScene()
 
 
    // Post-Processing
    // Post-Processing
    unsigned int ppsteps = 
    unsigned int ppsteps = 
-       Con::getBoolVariable("$Assimp::ConvertToLeftHanded", false) ? aiProcess_ConvertToLeftHanded : 0 |
-       Con::getBoolVariable("$Assimp::CalcTangentSpace", false) ? aiProcess_CalcTangentSpace : 0 |
-       Con::getBoolVariable("$Assimp::JoinIdenticalVertices", false) ? aiProcess_JoinIdenticalVertices : 0 |
-       Con::getBoolVariable("$Assimp::ValidateDataStructure", false) ? aiProcess_ValidateDataStructure : 0 |
-       Con::getBoolVariable("$Assimp::ImproveCacheLocality", false) ? aiProcess_ImproveCacheLocality : 0 |
-       Con::getBoolVariable("$Assimp::RemoveRedundantMaterials", false) ? aiProcess_RemoveRedundantMaterials : 0 |
-       Con::getBoolVariable("$Assimp::FindDegenerates", false) ? aiProcess_FindDegenerates : 0 |
-       Con::getBoolVariable("$Assimp::FindInvalidData", false) ? aiProcess_FindInvalidData : 0 |
-       Con::getBoolVariable("$Assimp::GenUVCoords", false) ? aiProcess_GenUVCoords : 0 |
-       Con::getBoolVariable("$Assimp::TransformUVCoords", false) ? aiProcess_TransformUVCoords : 0 |
-       Con::getBoolVariable("$Assimp::FindInstances", false) ? aiProcess_FindInstances : 0 |
-       Con::getBoolVariable("$Assimp::LimitBoneWeights", false) ? aiProcess_LimitBoneWeights : 0 |
-       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;
+      (ColladaUtils::getOptions().convertLeftHanded ? aiProcess_MakeLeftHanded : 0) |
+      (ColladaUtils::getOptions().reverseWindingOrder ? aiProcess_FlipWindingOrder : 0) |
+      (ColladaUtils::getOptions().calcTangentSpace ? aiProcess_CalcTangentSpace : 0) |
+      (ColladaUtils::getOptions().joinIdenticalVerts ? aiProcess_JoinIdenticalVertices : 0) |
+      (ColladaUtils::getOptions().removeRedundantMats ? aiProcess_RemoveRedundantMaterials : 0) |
+      (ColladaUtils::getOptions().genUVCoords ? aiProcess_GenUVCoords : 0) |
+      (ColladaUtils::getOptions().transformUVCoords ? aiProcess_TransformUVCoords : 0) |
+      (ColladaUtils::getOptions().flipUVCoords ? aiProcess_FlipUVs : 0) |
+      (ColladaUtils::getOptions().findInstances ? aiProcess_FindInstances : 0) |
+      (ColladaUtils::getOptions().limitBoneWeights ? aiProcess_LimitBoneWeights : 0);
 
 
    if (Con::getBoolVariable("$Assimp::OptimizeMeshes", false))
    if (Con::getBoolVariable("$Assimp::OptimizeMeshes", false))
       ppsteps |= aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph;
       ppsteps |= aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph;
@@ -163,34 +151,21 @@ void AssimpShapeLoader::enumerateScene()
    if (Con::getBoolVariable("$Assimp::SplitLargeMeshes", false))
    if (Con::getBoolVariable("$Assimp::SplitLargeMeshes", false))
       ppsteps |= aiProcess_SplitLargeMeshes;
       ppsteps |= aiProcess_SplitLargeMeshes;
 
 
+   // Mandatory options
+   //ppsteps |= aiProcess_ValidateDataStructure | aiProcess_Triangulate | aiProcess_ImproveCacheLocality;
+   ppsteps |= aiProcess_Triangulate;
    //aiProcess_SortByPType              | // make 'clean' meshes which consist of a single typ of primitives
    //aiProcess_SortByPType              | // make 'clean' meshes which consist of a single typ of primitives
 
 
    aiPropertyStore* props = aiCreatePropertyStore();
    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_PP_PTV_KEEP_HIERARCHY,1);
-
-   struct aiLogStream shapeLog;
-   shapeLog = aiGetPredefinedLogStream(aiDefaultLogStream_FILE, "assimp.log");
+   struct aiLogStream shapeLog = aiGetPredefinedLogStream(aiDefaultLogStream_STDOUT, NULL);
+   shapeLog.callback = assimpLogCallback;
+   shapeLog.user = 0;
    aiAttachLogStream(&shapeLog);
    aiAttachLogStream(&shapeLog);
 #ifdef TORQUE_DEBUG
 #ifdef TORQUE_DEBUG
    aiEnableVerboseLogging(true);
    aiEnableVerboseLogging(true);
 #endif
 #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)
-   //   & ~aiProcess_RemoveRedundantMaterials);
-
    mScene = (aiScene*)aiImportFileExWithProperties(shapePath.getFullPath().c_str(), ppsteps, NULL, props);
    mScene = (aiScene*)aiImportFileExWithProperties(shapePath.getFullPath().c_str(), ppsteps, NULL, props);
 
 
    aiReleasePropertyStore(props);
    aiReleasePropertyStore(props);
@@ -200,11 +175,38 @@ void AssimpShapeLoader::enumerateScene()
       Con::printf("[ASSIMP] Mesh Count: %d", mScene->mNumMeshes);
       Con::printf("[ASSIMP] Mesh Count: %d", mScene->mNumMeshes);
       Con::printf("[ASSIMP] Material Count: %d", mScene->mNumMaterials);
       Con::printf("[ASSIMP] Material Count: %d", mScene->mNumMaterials);
 
 
+      // Set import options (if they are not set to override)
+      if (ColladaUtils::getOptions().unit <= 0.0f)
+      {
+         F64 unit;
+         if (!getMetaDouble("UnitScaleFactor", unit))
+         {
+            F32 floatVal;
+            S32 intVal;
+            if (getMetaFloat("UnitScaleFactor", floatVal))
+               unit = (F64)floatVal;
+            else if (getMetaInt("UnitScaleFactor", intVal))
+               unit = (F64)intVal;
+            else
+               unit = 1.0;
+         }
+         ColladaUtils::getOptions().unit = (F32)unit;
+      }
+
+      if (ColladaUtils::getOptions().upAxis == UPAXISTYPE_COUNT)
+      {
+         S32 upAxis;
+         if (!getMetaInt("UpAxis", upAxis))
+            upAxis = UPAXISTYPE_Z_UP;
+         ColladaUtils::getOptions().upAxis = (domUpAxisType) upAxis;
+      }
+
       // Extract embedded textures
       // Extract embedded textures
       for (U32 i = 0; i < mScene->mNumTextures; ++i)
       for (U32 i = 0; i < mScene->mNumTextures; ++i)
          extractTexture(i, mScene->mTextures[i]);
          extractTexture(i, mScene->mTextures[i]);
 
 
       // Load all the materials.
       // Load all the materials.
+      AssimpAppMaterial::sDefaultMatNumber = 0;
       for ( U32 i = 0; i < mScene->mNumMaterials; i++ )
       for ( U32 i = 0; i < mScene->mNumMaterials; i++ )
          AppMesh::appMaterials.push_back(new AssimpAppMaterial(mScene->mMaterials[i]));
          AppMesh::appMaterials.push_back(new AssimpAppMaterial(mScene->mMaterials[i]));
 
 
@@ -245,8 +247,8 @@ void AssimpShapeLoader::computeBounds(Box3F& bounds)
    TSShapeLoader::computeBounds(bounds);
    TSShapeLoader::computeBounds(bounds);
 
 
    // Check if the model origin needs adjusting
    // 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
+   bool adjustCenter = ColladaUtils::getOptions().adjustCenter;
+   bool adjustFloor = ColladaUtils::getOptions().adjustFloor;
    if (bounds.isValidBox() && (adjustCenter || adjustFloor))
    if (bounds.isValidBox() && (adjustCenter || adjustFloor))
    {
    {
       // Compute shape offset
       // Compute shape offset
@@ -289,6 +291,126 @@ void AssimpShapeLoader::computeBounds(Box3F& bounds)
    }
    }
 }
 }
 
 
+bool AssimpShapeLoader::fillGuiTreeView(const char* sourceShapePath, GuiTreeViewCtrl* tree)
+{
+   Assimp::Importer importer;
+   Torque::Path path(sourceShapePath);
+   String cleanFile = AppMaterial::cleanString(path.getFileName());
+
+   // Attempt to import with Assimp.
+   const aiScene* shapeScene = importer.ReadFile(path.getFullPath().c_str(), (aiProcessPreset_TargetRealtime_Quality | aiProcess_CalcTangentSpace)
+      & ~aiProcess_RemoveRedundantMaterials & ~aiProcess_GenSmoothNormals);
+
+   if (!shapeScene)
+      return false;
+   mScene = shapeScene;
+
+   // Initialize tree
+   tree->removeItem(0);
+   S32 meshItem = tree->insertItem(0, "Meshes", String::ToString("%i", shapeScene->mNumMeshes));
+   S32 matItem = tree->insertItem(0, "Materials", String::ToString("%i", shapeScene->mNumMaterials));
+   S32 animItem = tree->insertItem(0, "Animations", String::ToString("%i", shapeScene->mNumAnimations));
+   //S32 lightsItem = tree->insertItem(0, "Lights", String::ToString("%i", shapeScene->mNumLights));
+   //S32 texturesItem = tree->insertItem(0, "Textures", String::ToString("%i", shapeScene->mNumTextures));
+
+   //Details!
+   U32 numPolys = 0;
+   U32 numVerts = 0;
+   for (U32 i = 0; i < shapeScene->mNumMeshes; i++)
+   {
+      tree->insertItem(meshItem, String::ToString("%s", shapeScene->mMeshes[i]->mName.C_Str()));
+      numPolys += shapeScene->mMeshes[i]->mNumFaces;
+      numVerts += shapeScene->mMeshes[i]->mNumVertices;
+   }
+
+   U32 defaultMatNumber = 0;
+   for (U32 i = 0; i < shapeScene->mNumMaterials; i++)
+   {
+      aiMaterial* aiMat = shapeScene->mMaterials[i];
+
+      aiString matName;
+      aiMat->Get(AI_MATKEY_NAME, matName);
+      String name = matName.C_Str();
+      if (name.isEmpty())
+      {
+         name = AppMaterial::cleanString(path.getFileName());
+         name += "_defMat";
+         name += String::ToString("%d", defaultMatNumber);
+         defaultMatNumber++;
+      }
+
+      aiString texPath;
+      aiMat->GetTexture(aiTextureType::aiTextureType_DIFFUSE, 0, &texPath);
+      String texName = texPath.C_Str();
+      if (texName.isEmpty())
+      {
+         aiColor3D read_color(1.f, 1.f, 1.f);
+         if (AI_SUCCESS == aiMat->Get(AI_MATKEY_COLOR_DIFFUSE, read_color))
+            texName = String::ToString("Color: (%0.3f, %0.3f, %0.3f)", (F32)read_color.r, (F32)read_color.g, (F32)read_color.b);
+         else
+            texName = "No Texture";
+      }
+      else
+         texName = AssimpAppMaterial::cleanTextureName(texName, cleanFile, sourceShapePath, true);
+
+      tree->insertItem(matItem, String::ToString("%s", name.c_str()), String::ToString("%s", texName.c_str()));
+   }
+
+   for (U32 i = 0; i < shapeScene->mNumAnimations; i++)
+   {
+      String sequenceName = shapeScene->mAnimations[i]->mName.C_Str();
+      if (sequenceName.isEmpty())
+         sequenceName = "ambient";
+      tree->insertItem(animItem, sequenceName.c_str());
+   }
+
+   U32 numNodes = 0;
+   if (shapeScene->mRootNode)
+   {
+      S32 nodesItem = tree->insertItem(0, "Nodes", "");
+      addNodeToTree(nodesItem, shapeScene->mRootNode, tree, numNodes);
+      tree->setItemValue(nodesItem, String::ToString("%i", numNodes));
+   }
+
+   U32 numMetaTags = shapeScene->mMetaData ? shapeScene->mMetaData->mNumProperties : 0;
+   if (numMetaTags)
+      addMetaDataToTree(shapeScene->mMetaData, tree);
+
+   F64 unit;
+   if (!getMetaDouble("UnitScaleFactor", unit))
+      unit = 1.0f;
+
+   S32 upAxis;
+   if (!getMetaInt("UpAxis", upAxis))
+      upAxis = UPAXISTYPE_Z_UP;
+
+   /*for (U32 i = 0; i < shapeScene->mNumLights; i++)
+   {
+      treeObj->insertItem(lightsItem, String::ToString("%s", shapeScene->mLights[i]->mType));
+   }*/
+
+   // Store shape information in the tree control
+   tree->setDataField(StringTable->insert("_nodeCount"), 0, avar("%d", numNodes));
+   tree->setDataField(StringTable->insert("_meshCount"), 0, avar("%d", shapeScene->mNumMeshes));
+   tree->setDataField(StringTable->insert("_polygonCount"), 0, avar("%d", numPolys));
+   tree->setDataField(StringTable->insert("_materialCount"), 0, avar("%d", shapeScene->mNumMaterials));
+   tree->setDataField(StringTable->insert("_lightCount"), 0, avar("%d", shapeScene->mNumLights));
+   tree->setDataField(StringTable->insert("_animCount"), 0, avar("%d", shapeScene->mNumAnimations));
+   tree->setDataField(StringTable->insert("_textureCount"), 0, avar("%d", shapeScene->mNumTextures));
+   tree->setDataField(StringTable->insert("_vertCount"), 0, avar("%d", numVerts));
+   tree->setDataField(StringTable->insert("_metaTagCount"), 0, avar("%d", numMetaTags));
+   tree->setDataField(StringTable->insert("_unit"), 0, avar("%g", (F32)unit));
+
+   if (upAxis == UPAXISTYPE_X_UP)
+      tree->setDataField(StringTable->insert("_upAxis"), 0, "X_AXIS");
+   else if (upAxis == UPAXISTYPE_Y_UP)
+      tree->setDataField(StringTable->insert("_upAxis"), 0, "Y_AXIS");
+   else
+      tree->setDataField(StringTable->insert("_upAxis"), 0, "Z_AXIS");
+
+   return true;
+}
+
 void AssimpShapeLoader::updateMaterialsScript(const Torque::Path &path)
 void AssimpShapeLoader::updateMaterialsScript(const Torque::Path &path)
 {
 {
    Torque::Path scriptPath(path);
    Torque::Path scriptPath(path);
@@ -306,7 +428,7 @@ void AssimpShapeLoader::updateMaterialsScript(const Torque::Path &path)
          if ( Sim::findObject( MATMGR->getMapEntry( mat->getName() ), mappedMat ) )
          if ( Sim::findObject( MATMGR->getMapEntry( mat->getName() ), mappedMat ) )
          {
          {
             // Only update existing materials if forced to
             // Only update existing materials if forced to
-            if (Con::getBoolVariable("$Assimp::ForceUpdateMats", false))
+            if (ColladaUtils::getOptions().forceUpdateMaterials)
             {
             {
                mat->initMaterial(scriptPath, mappedMat);
                mat->initMaterial(scriptPath, mappedMat);
                persistMgr.setDirty(mappedMat);
                persistMgr.setDirty(mappedMat);
@@ -351,20 +473,37 @@ bool AssimpShapeLoader::canLoadCachedDTS(const Torque::Path& path)
    return false;
    return false;
 }
 }
 
 
+void AssimpShapeLoader::assimpLogCallback(const char* message, char* user)
+{
+   Con::printf("[Assimp log message] %s", StringUnit::getUnit(message, 0, "\n"));
+}
+
 bool AssimpShapeLoader::ignoreNode(const String& name)
 bool AssimpShapeLoader::ignoreNode(const String& name)
 {
 {
    // Do not add AssimpFbx dummy nodes to the TSShape. See: Assimp::FBX::ImportSettings::preservePivots
    // 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
    // https://github.com/assimp/assimp/blob/master/code/FBXImportSettings.h#L116-L135
    if (name.find("_$AssimpFbx$_") != String::NPos)
    if (name.find("_$AssimpFbx$_") != String::NPos)
       return true;
       return true;
-   return false;
+
+   if (FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().alwaysImport, name, false))
+      return false;
+
+   return FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().neverImport, name, false);
+}
+
+bool AssimpShapeLoader::ignoreMesh(const String& name)
+{
+   if (FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().alwaysImportMesh, name, false))
+      return false;
+   else
+      return FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().neverImportMesh, name, false);
 }
 }
 
 
 void AssimpShapeLoader::detectDetails()
 void AssimpShapeLoader::detectDetails()
 {
 {
    // Set LOD option
    // Set LOD option
    bool singleDetail = true;
    bool singleDetail = true;
-   switch (Con::getIntVariable("$Assimp::lodType", 0))
+   switch (ColladaUtils::getOptions().lodType)
    {
    {
    case ColladaUtils::ImportOptions::DetectDTS:
    case ColladaUtils::ImportOptions::DetectDTS:
       // Check for a baseXX->startXX hierarchy at the top-level, if we find
       // Check for a baseXX->startXX hierarchy at the top-level, if we find
@@ -395,7 +534,7 @@ void AssimpShapeLoader::detectDetails()
       break;
       break;
    }
    }
 
 
-   AssimpAppMesh::fixDetailSize(singleDetail, Con::getIntVariable("$Assimp::singleDetailSize", 2));
+   AssimpAppMesh::fixDetailSize(singleDetail, ColladaUtils::getOptions().singleDetailSize);
 }
 }
 
 
 void AssimpShapeLoader::extractTexture(U32 index, aiTexture* pTex)
 void AssimpShapeLoader::extractTexture(U32 index, aiTexture* pTex)
@@ -448,6 +587,148 @@ void AssimpShapeLoader::extractTexture(U32 index, aiTexture* pTex)
    }
    }
 }
 }
 
 
+void AssimpShapeLoader::addNodeToTree(S32 parentItem, aiNode* node, GuiTreeViewCtrl* tree, U32& nodeCount)
+{
+   // Add this node
+   S32 nodeItem = parentItem;
+   String nodeName = node->mName.C_Str();
+   if (!ignoreNode(nodeName))
+   {
+      if (nodeName.isEmpty())
+         nodeName = "null";
+      nodeItem = tree->insertItem(parentItem, nodeName.c_str(), String::ToString("%i", node->mNumChildren));
+      nodeCount++;
+   }
+
+   // Add any child nodes
+   for (U32 n = 0; n < node->mNumChildren; ++n)
+      addNodeToTree(nodeItem, node->mChildren[n], tree, nodeCount);
+}
+
+void AssimpShapeLoader::addMetaDataToTree(const aiMetadata* metaData, GuiTreeViewCtrl* tree)
+{
+   S32 metaItem = tree->insertItem(0, "MetaData", String::ToString("%i", metaData->mNumProperties));
+
+   aiString valString;
+   aiVector3D valVec;
+
+   for (U32 n = 0; n < metaData->mNumProperties; ++n)
+   {
+      String keyStr = metaData->mKeys[n].C_Str();
+      keyStr += ": ";
+      switch (metaData->mValues[n].mType)
+      {
+      case AI_BOOL:
+         keyStr += ((bool)metaData->mValues[n].mData) ? "true" : "false";
+         break;
+      case AI_INT32:
+         keyStr += String::ToString(*((S32*)(metaData->mValues[n].mData)));
+         break;
+      case AI_UINT64:
+         keyStr += String::ToString("%I64u", *((U64*)metaData->mValues[n].mData));
+         break;
+      case AI_FLOAT:
+         keyStr += String::ToString(*((F32*)metaData->mValues[n].mData));
+         break;
+      case AI_DOUBLE:
+         keyStr += String::ToString(*((F64*)metaData->mValues[n].mData));
+         break;
+      case AI_AISTRING:
+         metaData->Get<aiString>(metaData->mKeys[n], valString);
+         keyStr += valString.C_Str();
+         break;
+      case AI_AIVECTOR3D:
+         metaData->Get<aiVector3D>(metaData->mKeys[n], valVec);
+         keyStr += String::ToString("%f, %f, %f", valVec.x, valVec.y, valVec.z);
+         break;
+      default:
+         break;
+      }
+
+      tree->insertItem(metaItem, keyStr.c_str(), String::ToString("%i", n));
+   }
+}
+
+bool AssimpShapeLoader::getMetabool(const char* key, bool& boolVal)
+{
+   if (!mScene || !mScene->mMetaData)
+      return false;
+
+   String keyStr = key;
+   for (U32 n = 0; n < mScene->mMetaData->mNumProperties; ++n)
+   {
+      if (keyStr.equal(mScene->mMetaData->mKeys[n].C_Str(), String::NoCase))
+      {
+         if (mScene->mMetaData->mValues[n].mType == AI_BOOL)
+         {
+            boolVal = (bool)mScene->mMetaData->mValues[n].mData;
+            return true;
+         }
+      }
+   }
+   return false;
+}
+
+bool AssimpShapeLoader::getMetaInt(const char* key, S32& intVal)
+{
+   if (!mScene || !mScene->mMetaData)
+      return false;
+
+   String keyStr = key;
+   for (U32 n = 0; n < mScene->mMetaData->mNumProperties; ++n)
+   {
+      if (keyStr.equal(mScene->mMetaData->mKeys[n].C_Str(), String::NoCase))
+      {
+         if (mScene->mMetaData->mValues[n].mType == AI_INT32)
+         {
+            intVal = *((S32*)(mScene->mMetaData->mValues[n].mData));
+            return true;
+         }
+      }
+   }
+   return false;
+}
+
+bool AssimpShapeLoader::getMetaFloat(const char* key, F32& floatVal)
+{
+   if (!mScene || !mScene->mMetaData)
+      return false;
+
+   String keyStr = key;
+   for (U32 n = 0; n < mScene->mMetaData->mNumProperties; ++n)
+   {
+      if (keyStr.equal(mScene->mMetaData->mKeys[n].C_Str(), String::NoCase))
+      {
+         if (mScene->mMetaData->mValues[n].mType == AI_FLOAT)
+         {
+            floatVal = *((F32*)mScene->mMetaData->mValues[n].mData);
+            return true;
+         }
+      }
+   }
+   return false;
+}
+
+bool AssimpShapeLoader::getMetaDouble(const char* key, F64& doubleVal)
+{
+   if (!mScene || !mScene->mMetaData)
+      return false;
+
+   String keyStr = key;
+   for (U32 n = 0; n < mScene->mMetaData->mNumProperties; ++n)
+   {
+      if (keyStr.equal(mScene->mMetaData->mKeys[n].C_Str(), String::NoCase))
+      {
+         if (mScene->mMetaData->mValues[n].mType == AI_DOUBLE)
+         {
+            doubleVal = *((F64*)mScene->mMetaData->mValues[n].mData);
+            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)
@@ -489,6 +770,14 @@ TSShape* assimpLoadShape(const Torque::Path &path)
       return NULL;
       return NULL;
    }
    }
 
 
+   // Allow TSShapeConstructor object to override properties
+   ColladaUtils::getOptions().reset();
+   TSShapeConstructor* tscon = TSShapeConstructor::findShapeConstructor(path.getFullPath());
+   if (tscon)
+   {
+      ColladaUtils::getOptions() = tscon->mOptions;
+   }
+
    AssimpShapeLoader loader;
    AssimpShapeLoader loader;
    TSShape* tss = loader.generateShape(path);
    TSShape* tss = loader.generateShape(path);
    if (tss)
    if (tss)
@@ -510,57 +799,30 @@ TSShape* assimpLoadShape(const Torque::Path &path)
    return tss;
    return tss;
 }
 }
 
 
-DefineEngineFunction(GetShapeInfo, GuiTreeViewCtrl*, (String filePath), ,
-   "Returns a list of supported shape formats in filter form.\n"
-   "Example output: DSQ Files|*.dsq|COLLADA Files|*.dae|")
+DefineEngineFunction(GetShapeInfo, bool, (const char* shapePath, const char* ctrl), ,
+   "(string shapePath, GuiTreeViewCtrl ctrl) Collect scene information from "
+   "a shape file and store it in a GuiTreeView control. This function is "
+   "used by the assimp import gui to show a preview of the scene contents "
+   "prior to import, and is probably not much use for anything else.\n"
+   "@param shapePath shape filename\n"
+   "@param ctrl GuiTreeView control to add elements to\n"
+   "@return true if successful, false otherwise\n"
+   "@ingroup Editors\n"
+   "@internal")
 {
 {
-   Assimp::Importer importer;
-
-   GuiTreeViewCtrl* treeObj = new GuiTreeViewCtrl();
-   treeObj->registerObject();
-
-   Torque::Path path = Torque::Path(filePath);
-
-   // Attempt to import with Assimp.
-   const aiScene* shapeScene = importer.ReadFile(path.getFullPath().c_str(), (aiProcessPreset_TargetRealtime_Quality | aiProcess_CalcTangentSpace)
-      & ~aiProcess_RemoveRedundantMaterials & ~aiProcess_GenSmoothNormals);
-
-   //Populate info
-   S32 meshItem = treeObj->insertItem(0, "Shape", String::ToString("%i", shapeScene->mNumMeshes));
-   S32 matItem = treeObj->insertItem(0, "Materials", String::ToString("%i", shapeScene->mNumMaterials));
-   S32 animItem = treeObj->insertItem(0, "Animations", String::ToString("%i", shapeScene->mNumAnimations));
-   S32 lightsItem = treeObj->insertItem(0, "Lights", String::ToString("%i", shapeScene->mNumLights));
-   S32 texturesItem = treeObj->insertItem(0, "Textures", String::ToString("%i", shapeScene->mNumTextures));
-   //S32 meshItem = ->insertItem(0, "Cameras", String::ToString("%s", shapeScene->mNumCameras));
-
-   //Details!
-   for (U32 i = 0; i < shapeScene->mNumMeshes; i++)
-   {
-      treeObj->insertItem(meshItem, String::ToString("%s", shapeScene->mMeshes[i]->mName.C_Str()));
-   }
-
-   for (U32 i = 0; i < shapeScene->mNumMaterials; i++)
+   GuiTreeViewCtrl* tree;
+   if (!Sim::findObject(ctrl, tree))
    {
    {
-      aiMaterial* aiMat = shapeScene->mMaterials[i];
-
-      aiString matName;
-      aiMat->Get(AI_MATKEY_NAME, matName);
-
-      aiString texPath;
-      aiMat->GetTexture(aiTextureType::aiTextureType_DIFFUSE, 0, &texPath);
-
-      treeObj->insertItem(matItem, String::ToString("%s", matName.C_Str()), String::ToString("%s", texPath.C_Str()));
+      Con::errorf("enumColladaScene::Could not find GuiTreeViewCtrl '%s'", ctrl);
+      return false;
    }
    }
 
 
-   for (U32 i = 0; i < shapeScene->mNumAnimations; i++)
-   {
-      treeObj->insertItem(animItem, String::ToString("%s", shapeScene->mAnimations[i]->mName.C_Str()));
-   }
-
-   /*for (U32 i = 0; i < shapeScene->mNumLights; i++)
-   {
-      treeObj->insertItem(lightsItem, String::ToString("%s", shapeScene->mLights[i]->mType));
-   }*/
+   // Check if a cached DTS is available => no need to import the source file
+   // if we can load the DTS instead
+   Torque::Path path(shapePath);
+   if (AssimpShapeLoader::canLoadCachedDTS(path))
+      return false;
 
 
-   return treeObj;
-}
+   AssimpShapeLoader loader;
+   return loader.fillGuiTreeView(shapePath, tree);
+}

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

@@ -28,6 +28,9 @@
 #endif
 #endif
 #include <assimp/texture.h>
 #include <assimp/texture.h>
 
 
+class GuiTreeViewCtrl;
+struct aiNode;
+struct aiMetadata;
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 class AssimpShapeLoader : public TSShapeLoader
 class AssimpShapeLoader : public TSShapeLoader
 {
 {
@@ -37,9 +40,18 @@ protected:
    const struct aiScene* mScene;
    const struct aiScene* mScene;
 
 
    virtual bool ignoreNode(const String& name);
    virtual bool ignoreNode(const String& name);
+   virtual bool ignoreMesh(const String& name);
    void detectDetails();
    void detectDetails();
    void extractTexture(U32 index, aiTexture* pTex);
    void extractTexture(U32 index, aiTexture* pTex);
 
 
+private:
+   void addNodeToTree(S32 parentItem, aiNode* node, GuiTreeViewCtrl* tree, U32& nodeCount);
+   void addMetaDataToTree(const aiMetadata* metaData, GuiTreeViewCtrl* tree);
+   bool getMetabool(const char* key, bool& boolVal);
+   bool getMetaInt(const char* key, S32& intVal);
+   bool getMetaFloat(const char* key, F32& floatVal);
+   bool getMetaDouble(const char* key, F64& doubleVal);
+
 public:
 public:
    AssimpShapeLoader();
    AssimpShapeLoader();
    ~AssimpShapeLoader();
    ~AssimpShapeLoader();
@@ -51,7 +63,10 @@ public:
 
 
    void computeBounds(Box3F& bounds);
    void computeBounds(Box3F& bounds);
 
 
+   bool fillGuiTreeView(const char* shapePath, GuiTreeViewCtrl* tree);
+
    static bool canLoadCachedDTS(const Torque::Path& path);
    static bool canLoadCachedDTS(const Torque::Path& path);
+   static void assimpLogCallback(const char* message, char* user);
 };
 };
 
 
 #endif // _ASSIMP_SHAPELOADER_H_
 #endif // _ASSIMP_SHAPELOADER_H_

+ 37 - 0
Engine/source/ts/collada/colladaUtils.h

@@ -81,6 +81,13 @@ namespace ColladaUtils
          NumLodTypes
          NumLodTypes
       };
       };
 
 
+      enum eAnimTimingType
+      {
+         FrameCount = 0,
+         Seconds = 1,
+         Milliseconds = 1000
+      };
+
       domUpAxisType  upAxis;           // Override for the collada <up_axis> element
       domUpAxisType  upAxis;           // Override for the collada <up_axis> element
       F32            unit;             // Override for the collada <unit> element
       F32            unit;             // Override for the collada <unit> element
       eLodType       lodType;          // LOD type option
       eLodType       lodType;          // LOD type option
@@ -96,6 +103,22 @@ namespace ColladaUtils
       bool           forceUpdateMaterials;   // Force update of materials.cs
       bool           forceUpdateMaterials;   // Force update of materials.cs
       bool           useDiffuseNames;  // Use diffuse texture as the material name
       bool           useDiffuseNames;  // Use diffuse texture as the material name
 
 
+      // Assimp specific preprocess import options
+      bool           convertLeftHanded;   // Convert to left handed coordinate system.
+      bool           calcTangentSpace;    // Calculate tangents and bitangents, if possible.
+      bool           genUVCoords;         // Convert spherical, cylindrical, box and planar mapping to proper UVs.
+      bool           transformUVCoords;   // Preprocess UV transformations (scaling, translation ...)
+      bool           flipUVCoords;        // 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).
+      bool           findInstances;       // Search for instanced meshes and remove them by references to one master.
+      bool           limitBoneWeights;    // Limit bone weights to 4 per vertex.
+      bool           joinIdenticalVerts;  // Identifies and joins identical vertex data sets within all imported meshes.
+      bool           reverseWindingOrder; // This step adjusts the output face winding order to be clockwise. The default face winding order is counter clockwise.
+      bool           invertNormals;       // Reverse the normal vector direction for all normals.
+      bool           removeRedundantMats; // Removes redundant materials.
+      eAnimTimingType animTiming;         // How to import timing data as frames, seconds or milliseconds
+      S32            animFPS;             // FPS value to use if timing is set in frames and the animations does not have an fps set
+
       ImportOptions()
       ImportOptions()
       {
       {
          reset();
          reset();
@@ -117,6 +140,20 @@ namespace ColladaUtils
          adjustFloor = false;
          adjustFloor = false;
          forceUpdateMaterials = false;
          forceUpdateMaterials = false;
          useDiffuseNames = false;
          useDiffuseNames = false;
+
+         convertLeftHanded = false;
+         calcTangentSpace = false;
+         genUVCoords = false;
+         transformUVCoords = false;
+         flipUVCoords = true;
+         findInstances = false;
+         limitBoneWeights = false;
+         joinIdenticalVerts = true;
+         reverseWindingOrder = true;
+         invertNormals = false;
+         removeRedundantMats = true;
+         animTiming = Seconds;
+         animFPS = 30;
       }
       }
    };
    };
 
 

+ 52 - 0
Engine/source/ts/tsShapeConstruct.cpp

@@ -71,6 +71,14 @@ ImplementEnumType( TSShapeConstructorLodType,
    { ColladaUtils::ImportOptions::TrailingNumber,  "TrailingNumber" },
    { ColladaUtils::ImportOptions::TrailingNumber,  "TrailingNumber" },
 EndImplementEnumType;
 EndImplementEnumType;
 
 
+ImplementEnumType(TSShapeConstructorAnimType,
+   "\n\n"
+   "@ingroup TSShapeConstructor" )
+   { ColladaUtils::ImportOptions::FrameCount,      "Frames" },
+   { ColladaUtils::ImportOptions::Seconds,         "Seconds" },
+   { ColladaUtils::ImportOptions::Milliseconds,    "Milliseconds" },
+EndImplementEnumType;
+
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 
 
@@ -149,6 +157,21 @@ TSShapeConstructor::TSShapeConstructor()
    mOptions.adjustFloor = false;
    mOptions.adjustFloor = false;
    mOptions.forceUpdateMaterials = false;
    mOptions.forceUpdateMaterials = false;
    mOptions.useDiffuseNames = false;
    mOptions.useDiffuseNames = false;
+
+   mOptions.convertLeftHanded = false;
+   mOptions.calcTangentSpace = false;
+   mOptions.genUVCoords = false;
+   mOptions.transformUVCoords = false;
+   mOptions.flipUVCoords = true;
+   mOptions.findInstances = false;
+   mOptions.limitBoneWeights = false;
+   mOptions.joinIdenticalVerts = true;
+   mOptions.reverseWindingOrder = true;
+   mOptions.invertNormals = false;
+   mOptions.removeRedundantMats = true;
+   mOptions.animTiming = ColladaUtils::ImportOptions::Seconds;
+   mOptions.animFPS = 30;
+
    mShape = NULL;
    mShape = NULL;
 }
 }
 
 
@@ -284,6 +307,35 @@ void TSShapeConstructor::initPersistFields()
       "Forces update of the materials.cs file in the same folder as the COLLADA "
       "Forces update of the materials.cs file in the same folder as the COLLADA "
       "(.dae) file, even if Materials already exist. No effect for DTS files.\n"
       "(.dae) file, even if Materials already exist. No effect for DTS files.\n"
       "Normally only Materials that are not already defined are written to materials.cs." );
       "Normally only Materials that are not already defined are written to materials.cs." );
+
+   // Fields added for assimp options
+   addField( "convertLeftHanded", TypeBool, Offset(mOptions.convertLeftHanded, TSShapeConstructor),
+      "Convert to left handed coordinate system." );
+   addField( "calcTangentSpace", TypeBool, Offset(mOptions.calcTangentSpace, TSShapeConstructor),
+      "Calculate tangents and bitangents, if possible." );
+   addField( "genUVCoords", TypeBool, Offset(mOptions.genUVCoords, TSShapeConstructor),
+      "Convert spherical, cylindrical, box and planar mapping to proper UVs." );
+   addField( "transformUVCoords", TypeBool, Offset(mOptions.transformUVCoords, TSShapeConstructor),
+      "Preprocess UV transformations (scaling, translation ...)." );
+   addField( "flipUVCoords", TypeBool, Offset(mOptions.flipUVCoords, TSShapeConstructor),
+      "This step flips all UV coordinates along the y-axis and adjusts material settings and bitangents accordingly.\n"
+      "Assimp uses TL(0,0):BR(1,1). T3D uses TL(0,1):BR(1,0). This will be needed for most textured models." );
+   addField( "findInstances", TypeBool, Offset(mOptions.findInstances, TSShapeConstructor),
+      "Search for instanced meshes and remove them by references to one master." );
+   addField( "limitBoneWeights", TypeBool, Offset(mOptions.limitBoneWeights, TSShapeConstructor),
+      "Limit bone weights to 4 per vertex." );
+   addField( "joinIdenticalVerts", TypeBool, Offset(mOptions.joinIdenticalVerts, TSShapeConstructor),
+      "Identifies and joins identical vertex data sets within all imported meshes." );
+   addField( "reverseWindingOrder", TypeBool, Offset(mOptions.reverseWindingOrder, TSShapeConstructor),
+      "This step adjusts the output face winding order to be clockwise. The default assimp face winding order is counter clockwise." );
+   addField( "invertNormals", TypeBool, Offset(mOptions.invertNormals, TSShapeConstructor),
+      "Reverse the normal vector direction for all normals." );
+   addField( "removeRedundantMats", TypeBool, Offset(mOptions.removeRedundantMats, TSShapeConstructor),
+      "Removes redundant materials." );
+   addField( "animTiming", TYPEID< ColladaUtils::ImportOptions::eAnimTimingType >(), Offset(mOptions.animTiming, TSShapeConstructor),
+      "How to import timing data as frames, seconds or milliseconds." );
+   addField("animFPS", TypeS32, Offset(mOptions.animFPS, TSShapeConstructor),
+      "FPS value to use if timing is set in frames and the animations does not have an fps set.");
    endGroup( "Collada" );
    endGroup( "Collada" );
 
 
    addGroup( "Sequences" );
    addGroup( "Sequences" );

+ 2 - 0
Engine/source/ts/tsShapeConstruct.h

@@ -328,9 +328,11 @@ public:
 
 
 typedef domUpAxisType TSShapeConstructorUpAxis;
 typedef domUpAxisType TSShapeConstructorUpAxis;
 typedef ColladaUtils::ImportOptions::eLodType TSShapeConstructorLodType;
 typedef ColladaUtils::ImportOptions::eLodType TSShapeConstructorLodType;
+typedef ColladaUtils::ImportOptions::eAnimTimingType TSShapeConstructorAnimType;
 
 
 DefineEnumType( TSShapeConstructorUpAxis );
 DefineEnumType( TSShapeConstructorUpAxis );
 DefineEnumType(TSShapeConstructorLodType);
 DefineEnumType(TSShapeConstructorLodType);
+DefineEnumType(TSShapeConstructorAnimType);
 
 
 class TSShapeConstructorMethodActionCallback
 class TSShapeConstructorMethodActionCallback
 {
 {