diff --git a/Engine/source/ts/assimp/assimpAppMaterial.cpp b/Engine/source/ts/assimp/assimpAppMaterial.cpp index 713ca9b87..474ea7c3b 100644 --- a/Engine/source/ts/assimp/assimpAppMaterial.cpp +++ b/Engine/source/ts/assimp/assimpAppMaterial.cpp @@ -27,6 +27,7 @@ #include "ts/assimp/assimpAppMesh.h" #include "materials/materialManager.h" #include "ts/tsMaterialList.h" +#include "core/stream/fileStream.h" // assimp include files. #include @@ -34,6 +35,8 @@ #include #include +U32 AssimpAppMaterial::sDefaultMatNumber = 0; + String AppMaterial::cleanString(const String& str) { String cleanStr(str); @@ -52,7 +55,8 @@ String AppMaterial::cleanString(const String& str) AssimpAppMaterial::AssimpAppMaterial(const char* matName) { - name = matName; + name = ColladaUtils::getOptions().matNamePrefix; + name += matName; // Set some defaults flags |= TSMaterialList::S_Wrap; @@ -67,9 +71,12 @@ AssimpAppMaterial::AssimpAppMaterial(aiMaterial* mtl) : name = matName.C_Str(); if (name.isEmpty()) { - name = cleanString(TSShapeLoader::getShapePath().getFileName());; + name = cleanString(TSShapeLoader::getShapePath().getFileName()); name += "_defMat"; + name += String::ToString("%d", sDefaultMatNumber); + sDefaultMatNumber++; } + name = ColladaUtils::getOptions().matNamePrefix + name; Con::printf("[ASSIMP] Loading Material: %s", name.c_str()); #ifdef TORQUE_DEBUG enumerateMaterialProperties(mtl); @@ -125,7 +132,7 @@ void AssimpAppMaterial::initMaterial(const Torque::Path& path, Material* mat) co if (dStrcmp("MASK", opacityMode.C_Str()) == 0) { translucent = true; - blendOp = Material::LerpAlpha; + blendOp = Material::None; float 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; } } - else if (dStrcmp("OPAQUE", opacityMode.C_Str()) != 0) + else if (dStrcmp("BLEND", opacityMode.C_Str()) == 0) { translucent = true; 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(); 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)) { torquePath = texName.C_Str(); if (!torquePath.isEmpty()) - mat->mNormalMapFilename[0] = cleanTextureName(torquePath, cleanFile); + mat->mNormalMapFilename[0] = cleanTextureName(torquePath, cleanFile, path, false); } #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)) 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 we have either map, fill all three slots 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->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; } if (aoName.isNotEmpty()) { - mat->mAOMapFilename[0] = cleanTextureName(aoName, cleanFile); // occlusion + mat->mAOMapFilename[0] = cleanTextureName(aoName, cleanFile, path, false); // occlusion mat->mAOChan[0] = 0.0f; } else { - mat->mAOMapFilename[0] = cleanTextureName(rmName, cleanFile); // occlusion + mat->mAOMapFilename[0] = cleanTextureName(rmName, cleanFile, path, false); // occlusion mat->mAOChan[0] = 0.0f; } } @@ -207,7 +218,7 @@ void AssimpAppMaterial::initMaterial(const Torque::Path& path, Material* mat) co { torquePath = texName.C_Str(); 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); @@ -234,22 +245,70 @@ void AssimpAppMaterial::initMaterial(const Torque::Path& path, Material* mat) co 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; if (texName[0] == '*') - { + { // It's an embedded texture reference. Make the cached name and return cleanStr = shapeName; cleanStr += "_cachedTex"; 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 { - cleanStr = texName; - cleanStr.replace('\\', '/'); + // See if the file is in a sub-directory of the shape + Vector 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; } diff --git a/Engine/source/ts/assimp/assimpAppMaterial.h b/Engine/source/ts/assimp/assimpAppMaterial.h index 64aaedf28..3c1482fa9 100644 --- a/Engine/source/ts/assimp/assimpAppMaterial.h +++ b/Engine/source/ts/assimp/assimpAppMaterial.h @@ -40,7 +40,6 @@ class AssimpAppMaterial : public AppMaterial #ifdef TORQUE_DEBUG void enumerateMaterialProperties(aiMaterial* mtl); #endif - static String cleanTextureName(String& texName, String& shapeName); public: @@ -51,6 +50,9 @@ public: String getName() const { return name; } Material* createMaterial(const Torque::Path& path) 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_ diff --git a/Engine/source/ts/assimp/assimpAppMesh.cpp b/Engine/source/ts/assimp/assimpAppMesh.cpp index 3ed762e57..053d4b374 100644 --- a/Engine/source/ts/assimp/assimpAppMesh.cpp +++ b/Engine/source/ts/assimp/assimpAppMesh.cpp @@ -79,7 +79,7 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset) uvs.reserve(mMeshData->mNumVertices); normals.reserve(mMeshData->mNumVertices); - bool flipNormals = Con::getBoolVariable("$Assimp::FlipNormals", false); + bool flipNormals = ColladaUtils::getOptions().invertNormals; bool noUVFound = false; for (U32 i = 0; imNumVertices; i++) @@ -203,14 +203,17 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset) MatrixF boneTransform; AssimpAppNode::assimpToTorqueMat(mMeshData->mBones[b]->mOffsetMatrix, boneTransform); 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 scalePos = boneTransform.getPosition(); boneTransform.scale(scaleMult); - scalePos /= scaleMult; - boneTransform.setPosition(scalePos); + bonePos /= scaleMult; } + + bonePos *= ColladaUtils::getOptions().unit; + boneTransform.setPosition(bonePos); + initialTransforms.push_back(boneTransform); //Weights diff --git a/Engine/source/ts/assimp/assimpAppNode.cpp b/Engine/source/ts/assimp/assimpAppNode.cpp index ca87f9d0c..4f95cefcb 100644 --- a/Engine/source/ts/assimp/assimpAppNode.cpp +++ b/Engine/source/ts/assimp/assimpAppNode.cpp @@ -91,22 +91,30 @@ MatrixF AssimpAppNode::getTransform(F32 time) else { // no parent (ie. root level) => scale by global shape mLastTransform.identity(); + mLastTransform.scale(ColladaUtils::getOptions().unit); if (!isBounds()) convertMat(mLastTransform); - - //mLastTransform.scale(ColladaUtils::getOptions().unit); } // If this node is animated in the active sequence, fetch the animated transform + MatrixF mat(true); if (sActiveSequence) - { - MatrixF mat(true); getAnimatedTransform(mat, time, sActiveSequence); - mLastTransform.mul(mat); - } 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; return mLastTransform; } @@ -280,12 +288,9 @@ void AssimpAppNode::convertMat(MatrixF& outMat) { 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 rot(0, 0) = 0.0f; rot(1, 0) = 1.0f; rot(1, 1) = 0.0f; rot(2, 1) = 1.0f; @@ -295,7 +300,7 @@ void AssimpAppNode::convertMat(MatrixF& outMat) outMat.mulL(rot); break; - case 1: //UPAXISTYPE_Y_UP: + case UPAXISTYPE_Y_UP: // rotate 180 around Y-axis, then 90 around X-axis rot(0, 0) = -1.0f; rot(1, 1) = 0.0f; rot(2, 1) = 1.0f; @@ -305,7 +310,7 @@ void AssimpAppNode::convertMat(MatrixF& outMat) outMat.mulL(rot); break; - case 2: //UPAXISTYPE_Z_UP: + case UPAXISTYPE_Z_UP: default: // nothing to do break; diff --git a/Engine/source/ts/assimp/assimpAppSequence.cpp b/Engine/source/ts/assimp/assimpAppSequence.cpp index 65a1f4b9e..e0a78175e 100644 --- a/Engine/source/ts/assimp/assimpAppSequence.cpp +++ b/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) { // Timing specified in frames fps = mClamp(fpsRequest, 5 /*TSShapeLoader::MinFrameRate*/, TSShapeLoader::MaxFrameRate); @@ -70,10 +70,7 @@ AssimpAppSequence::AssimpAppSequence(aiAnimation *a) : else { // Timing specified in seconds or ms depending on format if (maxEndTime > 1000.0f || mAnim->mDuration > 1000.0f) - { timeFactor = 1000.0f; // If it's more than 1000 seconds, assume it's ms. - Con::setIntVariable("$Assimp::AnimTiming", 1000); - } timeFactor = mClamp(timeFactor, 1, 1000); minFrameTime /= (F32)timeFactor; diff --git a/Engine/source/ts/assimp/assimpShapeLoader.cpp b/Engine/source/ts/assimp/assimpShapeLoader.cpp index 570d4825f..25f8ee088 100644 --- a/Engine/source/ts/assimp/assimpShapeLoader.cpp +++ b/Engine/source/ts/assimp/assimpShapeLoader.cpp @@ -39,6 +39,7 @@ #include "core/util/tVector.h" #include "core/strings/findMatch.h" +#include "core/strings/stringUnit.h" #include "core/stream/fileStream.h" #include "core/fileObject.h" #include "ts/tsShape.h" @@ -133,29 +134,16 @@ void AssimpShapeLoader::enumerateScene() // Post-Processing 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)) ppsteps |= aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph; @@ -163,34 +151,21 @@ void AssimpShapeLoader::enumerateScene() if (Con::getBoolVariable("$Assimp::SplitLargeMeshes", false)) 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 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); #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) - // & ~aiProcess_RemoveRedundantMaterials); - mScene = (aiScene*)aiImportFileExWithProperties(shapePath.getFullPath().c_str(), ppsteps, NULL, props); aiReleasePropertyStore(props); @@ -200,11 +175,38 @@ void AssimpShapeLoader::enumerateScene() Con::printf("[ASSIMP] Mesh Count: %d", mScene->mNumMeshes); 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 for (U32 i = 0; i < mScene->mNumTextures; ++i) extractTexture(i, mScene->mTextures[i]); // Load all the materials. + AssimpAppMaterial::sDefaultMatNumber = 0; for ( U32 i = 0; i < mScene->mNumMaterials; i++ ) AppMesh::appMaterials.push_back(new AssimpAppMaterial(mScene->mMaterials[i])); @@ -245,8 +247,8 @@ 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 + bool adjustCenter = ColladaUtils::getOptions().adjustCenter; + bool adjustFloor = ColladaUtils::getOptions().adjustFloor; if (bounds.isValidBox() && (adjustCenter || adjustFloor)) { // 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) { Torque::Path scriptPath(path); @@ -306,7 +428,7 @@ void AssimpShapeLoader::updateMaterialsScript(const Torque::Path &path) if ( Sim::findObject( MATMGR->getMapEntry( mat->getName() ), mappedMat ) ) { // Only update existing materials if forced to - if (Con::getBoolVariable("$Assimp::ForceUpdateMats", false)) + if (ColladaUtils::getOptions().forceUpdateMaterials) { mat->initMaterial(scriptPath, mappedMat); persistMgr.setDirty(mappedMat); @@ -351,20 +473,37 @@ bool AssimpShapeLoader::canLoadCachedDTS(const Torque::Path& path) 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) { // Do not add AssimpFbx dummy nodes to the TSShape. See: Assimp::FBX::ImportSettings::preservePivots // https://github.com/assimp/assimp/blob/master/code/FBXImportSettings.h#L116-L135 if (name.find("_$AssimpFbx$_") != String::NPos) return true; - return false; + + 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() { // Set LOD option bool singleDetail = true; - switch (Con::getIntVariable("$Assimp::lodType", 0)) + switch (ColladaUtils::getOptions().lodType) { case ColladaUtils::ImportOptions::DetectDTS: // Check for a baseXX->startXX hierarchy at the top-level, if we find @@ -395,7 +534,7 @@ void AssimpShapeLoader::detectDetails() break; } - AssimpAppMesh::fixDetailSize(singleDetail, Con::getIntVariable("$Assimp::singleDetailSize", 2)); + AssimpAppMesh::fixDetailSize(singleDetail, ColladaUtils::getOptions().singleDetailSize); } 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(metaData->mKeys[n], valString); + keyStr += valString.C_Str(); + break; + case AI_AIVECTOR3D: + metaData->Get(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. TSShape* assimpLoadShape(const Torque::Path &path) @@ -489,6 +770,14 @@ TSShape* assimpLoadShape(const Torque::Path &path) 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; TSShape* tss = loader.generateShape(path); if (tss) @@ -510,57 +799,30 @@ TSShape* assimpLoadShape(const Torque::Path &path) 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++) + GuiTreeViewCtrl* tree; + if (!Sim::findObject(ctrl, tree)) { - treeObj->insertItem(meshItem, String::ToString("%s", shapeScene->mMeshes[i]->mName.C_Str())); + Con::errorf("enumColladaScene::Could not find GuiTreeViewCtrl '%s'", ctrl); + return false; } - for (U32 i = 0; i < shapeScene->mNumMaterials; i++) - { - aiMaterial* aiMat = shapeScene->mMaterials[i]; + // 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; - 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())); - } - - 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)); - }*/ - - return treeObj; -} \ No newline at end of file + AssimpShapeLoader loader; + return loader.fillGuiTreeView(shapePath, tree); +} diff --git a/Engine/source/ts/assimp/assimpShapeLoader.h b/Engine/source/ts/assimp/assimpShapeLoader.h index 848baaaf9..d89eb97d5 100644 --- a/Engine/source/ts/assimp/assimpShapeLoader.h +++ b/Engine/source/ts/assimp/assimpShapeLoader.h @@ -28,6 +28,9 @@ #endif #include +class GuiTreeViewCtrl; +struct aiNode; +struct aiMetadata; //----------------------------------------------------------------------------- class AssimpShapeLoader : public TSShapeLoader { @@ -37,9 +40,18 @@ protected: const struct aiScene* mScene; virtual bool ignoreNode(const String& name); + virtual bool ignoreMesh(const String& name); void detectDetails(); 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: AssimpShapeLoader(); ~AssimpShapeLoader(); @@ -51,7 +63,10 @@ public: void computeBounds(Box3F& bounds); + bool fillGuiTreeView(const char* shapePath, GuiTreeViewCtrl* tree); + static bool canLoadCachedDTS(const Torque::Path& path); + static void assimpLogCallback(const char* message, char* user); }; #endif // _ASSIMP_SHAPELOADER_H_ diff --git a/Engine/source/ts/collada/colladaUtils.h b/Engine/source/ts/collada/colladaUtils.h index 144a62c7b..245f80161 100644 --- a/Engine/source/ts/collada/colladaUtils.h +++ b/Engine/source/ts/collada/colladaUtils.h @@ -81,6 +81,13 @@ namespace ColladaUtils NumLodTypes }; + enum eAnimTimingType + { + FrameCount = 0, + Seconds = 1, + Milliseconds = 1000 + }; + domUpAxisType upAxis; // Override for the collada element F32 unit; // Override for the collada element eLodType lodType; // LOD type option @@ -96,6 +103,22 @@ namespace ColladaUtils bool forceUpdateMaterials; // Force update of materials.cs 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() { reset(); @@ -117,6 +140,20 @@ namespace ColladaUtils adjustFloor = false; forceUpdateMaterials = 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; } }; diff --git a/Engine/source/ts/tsShapeConstruct.cpp b/Engine/source/ts/tsShapeConstruct.cpp index c77802895..a35bc3e81 100644 --- a/Engine/source/ts/tsShapeConstruct.cpp +++ b/Engine/source/ts/tsShapeConstruct.cpp @@ -71,6 +71,14 @@ ImplementEnumType( TSShapeConstructorLodType, { ColladaUtils::ImportOptions::TrailingNumber, "TrailingNumber" }, 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.forceUpdateMaterials = 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; } @@ -284,6 +307,35 @@ void TSShapeConstructor::initPersistFields() "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" "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" ); addGroup( "Sequences" ); diff --git a/Engine/source/ts/tsShapeConstruct.h b/Engine/source/ts/tsShapeConstruct.h index 7826e0507..dba25d0ac 100644 --- a/Engine/source/ts/tsShapeConstruct.h +++ b/Engine/source/ts/tsShapeConstruct.h @@ -328,9 +328,11 @@ public: typedef domUpAxisType TSShapeConstructorUpAxis; typedef ColladaUtils::ImportOptions::eLodType TSShapeConstructorLodType; +typedef ColladaUtils::ImportOptions::eAnimTimingType TSShapeConstructorAnimType; DefineEnumType( TSShapeConstructorUpAxis ); DefineEnumType(TSShapeConstructorLodType); +DefineEnumType(TSShapeConstructorAnimType); class TSShapeConstructorMethodActionCallback {