diff --git a/Engine/lib/assimp/code/glTF2Asset.inl b/Engine/lib/assimp/code/glTF2Asset.inl index b51975c77..196d664cb 100644 --- a/Engine/lib/assimp/code/glTF2Asset.inl +++ b/Engine/lib/assimp/code/glTF2Asset.inl @@ -405,6 +405,8 @@ inline void Buffer::Read(Value& obj, Asset& r) inline bool Buffer::LoadFromStream(IOStream& stream, size_t length, size_t baseOffset) { byteLength = length ? length : stream.FileSize(); + if ((byteLength + baseOffset) > stream.FileSize()) + byteLength = stream.FileSize() - baseOffset; if (baseOffset) { stream.Seek(baseOffset, aiOrigin_SET); diff --git a/Engine/lib/assimp/code/glTFAsset.inl b/Engine/lib/assimp/code/glTFAsset.inl index 7b7acd705..5c65767d1 100644 --- a/Engine/lib/assimp/code/glTFAsset.inl +++ b/Engine/lib/assimp/code/glTFAsset.inl @@ -345,6 +345,8 @@ inline void Buffer::Read(Value& obj, Asset& r) inline bool Buffer::LoadFromStream(IOStream& stream, size_t length, size_t baseOffset) { byteLength = length ? length : stream.FileSize(); + if ((byteLength + baseOffset) > stream.FileSize()) + byteLength = stream.FileSize() - baseOffset; if (baseOffset) { stream.Seek(baseOffset, aiOrigin_SET); diff --git a/Engine/source/ts/assimp/assimpAppMesh.cpp b/Engine/source/ts/assimp/assimpAppMesh.cpp index 82306eeb2..cd9c79bcb 100644 --- a/Engine/source/ts/assimp/assimpAppMesh.cpp +++ b/Engine/source/ts/assimp/assimpAppMesh.cpp @@ -31,6 +31,9 @@ #include #include +bool AssimpAppMesh::fixedSizeEnabled = false; +S32 AssimpAppMesh::fixedSize = 2; + //------------------------------------------------------------------------------ AssimpAppMesh::AssimpAppMesh(const struct aiMesh* mesh, AssimpAppNode* node) @@ -59,8 +62,7 @@ const char* AssimpAppMesh::getName(bool allowFixed) // If all geometry is being fixed to the same size, append the size // to the name - //return allowFixed && fixedSizeEnabled ? avar("%s %d", nodeName, fixedSize) : nodeName; - return nodeName; + return allowFixed && fixedSizeEnabled ? avar("%s %d", nodeName, fixedSize) : nodeName; } MatrixF AssimpAppMesh::getMeshTransform(F32 time) @@ -77,6 +79,8 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset) uvs.reserve(mMeshData->mNumVertices); normals.reserve(mMeshData->mNumVertices); + bool flipNormals = Con::getBoolVariable("$Assimp::FlipNormals", false); + bool noUVFound = false; for (U32 i = 0; imNumVertices; i++) { @@ -93,6 +97,8 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset) tmpVert = Point3F(pt.x, pt.y, pt.z); tmpNormal = Point3F(nrm.x, nrm.y, nrm.z); + if (flipNormals) + tmpNormal *= -1.0f; objOffset.mulP(tmpVert); @@ -155,23 +161,11 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset) const struct aiFace* face = &mMeshData->mFaces[n]; if ( face->mNumIndices == 3 ) { - if (Con::getBoolVariable("$Assimp::FlipNormals", true)) + U32 indexCount = face->mNumIndices; + for (U32 ind = 0; ind < indexCount; ind++) { - U32 indexCount = face->mNumIndices; - for (S32 ind = indexCount - 1; ind >= 0; ind--) - { - U32 index = face->mIndices[ind]; - indices.push_back(index); - } - } - else - { - U32 indexCount = face->mNumIndices; - for (U32 ind = 0; ind < indexCount; ind++) - { - U32 index = face->mIndices[ind]; - indices.push_back(index); - } + U32 index = face->mIndices[ind]; + indices.push_back(index); } } else @@ -201,7 +195,10 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset) { String name = mMeshData->mBones[b]->mName.C_Str(); aiNode* nodePtr = AssimpAppNode::findChildNodeByName(mMeshData->mBones[b]->mName.C_Str(), appNode->mScene->mRootNode); - bones[b] = new AssimpAppNode(appNode->mScene, nodePtr); + if (!nodePtr) + bones[b] = new AssimpAppNode(appNode->mScene, appNode->mNode); + else + bones[b] = new AssimpAppNode(appNode->mScene, nodePtr); MatrixF boneTransform; AssimpAppNode::assimpToTorqueMat(mMeshData->mBones[b]->mOffsetMatrix, boneTransform); diff --git a/Engine/source/ts/assimp/assimpAppMesh.h b/Engine/source/ts/assimp/assimpAppMesh.h index 9f6a4f87c..871ef86fe 100644 --- a/Engine/source/ts/assimp/assimpAppMesh.h +++ b/Engine/source/ts/assimp/assimpAppMesh.h @@ -42,6 +42,9 @@ protected: const struct aiMesh* mMeshData; bool mIsSkinMesh; + static bool fixedSizeEnabled; ///< Set to true to fix the detail size to a particular value for all geometry + static S32 fixedSize; ///< The fixed detail size value for all geometry + public: AssimpAppMesh(const struct aiMesh* mesh, AssimpAppNode* node); @@ -54,8 +57,8 @@ public: static void fixDetailSize(bool fixed, S32 size=2) { - //fixedSizeEnabled = fixed; - //fixedSize = size; + fixedSizeEnabled = fixed; + fixedSize = size; } /// Get the name of this mesh diff --git a/Engine/source/ts/assimp/assimpAppNode.cpp b/Engine/source/ts/assimp/assimpAppNode.cpp index e09a8b374..686c5bfa2 100644 --- a/Engine/source/ts/assimp/assimpAppNode.cpp +++ b/Engine/source/ts/assimp/assimpAppNode.cpp @@ -31,6 +31,8 @@ #include #include +aiAnimation* AssimpAppNode::sActiveSequence = NULL; + AssimpAppNode::AssimpAppNode(const struct aiScene* scene, const struct aiNode* node, AssimpAppNode* parent) : mInvertMeshes(false), mLastTransformTime(TSShapeLoader::DefaultTime - 1), @@ -94,12 +96,131 @@ MatrixF AssimpAppNode::getTransform(F32 time) //mLastTransform.scale(ColladaUtils::getOptions().unit); } - mLastTransform.mul(mNodeTransform); + // If this node is animated in the active sequence, fetch the animated transform + if (sActiveSequence) + { + MatrixF mat(true); + getAnimatedTransform(mat, time, sActiveSequence); + mLastTransform.mul(mat); + } + else + mLastTransform.mul(mNodeTransform); + mLastTransformTime = time; - return mLastTransform; } +void AssimpAppNode::getAnimatedTransform(MatrixF& mat, F32 t, aiAnimation* animSeq) +{ + // Find the channel for this node + for (U32 i = 0; i < animSeq->mNumChannels; ++i) + { + if (strcmp(mName, animSeq->mChannels[i]->mNodeName.C_Str()) == 0) + { + aiNodeAnim *nodeAnim = animSeq->mChannels[i]; + Point3F trans(Point3F::Zero); + Point3F scale(Point3F::One); + QuatF rot; + rot.identity(); + + // Transform + if (nodeAnim->mNumPositionKeys == 1) + trans.set(nodeAnim->mPositionKeys[0].mValue.x, nodeAnim->mPositionKeys[0].mValue.y, nodeAnim->mPositionKeys[0].mValue.z); + else + { + Point3F curPos, lastPos; + F32 lastT = 0.0; + for (U32 key = 0; key < nodeAnim->mNumPositionKeys; ++key) + { + F32 curT = (F32)nodeAnim->mPositionKeys[key].mTime; + curPos.set(nodeAnim->mPositionKeys[key].mValue.x, nodeAnim->mPositionKeys[key].mValue.y, nodeAnim->mPositionKeys[key].mValue.z); + if (curT > t) + { + F32 factor = (t - lastT) / (curT - lastT); + trans.interpolate(lastPos, curPos, factor); + break; + } + else if ((curT == t) || (key == nodeAnim->mNumPositionKeys - 1)) + { + trans = curPos; + break; + } + + lastT = curT; + lastPos = curPos; + } + } + + // Rotation + if (nodeAnim->mNumRotationKeys == 1) + rot.set(nodeAnim->mRotationKeys[0].mValue.x, nodeAnim->mRotationKeys[0].mValue.y, + nodeAnim->mRotationKeys[0].mValue.z, nodeAnim->mRotationKeys[0].mValue.w); + else + { + QuatF curRot, lastRot; + F32 lastT = 0.0; + for (U32 key = 0; key < nodeAnim->mNumRotationKeys; ++key) + { + F32 curT = (F32)nodeAnim->mRotationKeys[key].mTime; + curRot.set(nodeAnim->mRotationKeys[key].mValue.x, nodeAnim->mRotationKeys[key].mValue.y, + nodeAnim->mRotationKeys[key].mValue.z, nodeAnim->mRotationKeys[key].mValue.w); + if (curT > t) + { + F32 factor = (t - lastT) / (curT - lastT); + rot.interpolate(lastRot, curRot, factor); + break; + } + else if ((curT == t) || (key == nodeAnim->mNumRotationKeys - 1)) + { + rot = curRot; + break; + } + + lastT = curT; + lastRot = curRot; + } + } + + // Scale + if (nodeAnim->mNumScalingKeys == 1) + scale.set(nodeAnim->mScalingKeys[0].mValue.x, nodeAnim->mScalingKeys[0].mValue.y, nodeAnim->mScalingKeys[0].mValue.z); + else + { + Point3F curScale, lastScale; + F32 lastT = 0.0; + for (U32 key = 0; key < nodeAnim->mNumScalingKeys; ++key) + { + F32 curT = (F32)nodeAnim->mScalingKeys[key].mTime; + curScale.set(nodeAnim->mScalingKeys[key].mValue.x, nodeAnim->mScalingKeys[key].mValue.y, nodeAnim->mScalingKeys[key].mValue.z); + if (curT > t) + { + F32 factor = (t - lastT) / (curT - lastT); + scale.interpolate(lastScale, curScale, factor); + break; + } + else if ((curT == t) || (key == nodeAnim->mNumScalingKeys - 1)) + { + scale = curScale; + break; + } + + lastT = curT; + lastScale = curScale; + } + } + + rot.setMatrix(&mat); + mat.inverse(); + mat.setPosition(trans); + mat.scale(scale); + return; + } + } + + // Node not found in the animation channels + mat = mNodeTransform; +} + bool AssimpAppNode::animatesTransform(const AppSequence* appSeq) { return false; diff --git a/Engine/source/ts/assimp/assimpAppNode.h b/Engine/source/ts/assimp/assimpAppNode.h index c54a65625..3fe04d43e 100644 --- a/Engine/source/ts/assimp/assimpAppNode.h +++ b/Engine/source/ts/assimp/assimpAppNode.h @@ -36,6 +36,7 @@ #ifndef AI_TYPES_H_INC #include #endif +#include class AssimpAppNode : public AppNode { @@ -43,6 +44,7 @@ class AssimpAppNode : public AppNode friend class AssimpAppMesh; MatrixF getTransform(F32 time); + void getAnimatedTransform(MatrixF& mat, F32 t, aiAnimation* animSeq); void buildMeshList(); void buildChildList(); @@ -67,6 +69,8 @@ public: // } + static aiAnimation* sActiveSequence; + //----------------------------------------------------------------------- const char *getName() { return mName; } const char *getParentName() { return mParentName; } diff --git a/Engine/source/ts/assimp/assimpAppSequence.cpp b/Engine/source/ts/assimp/assimpAppSequence.cpp index 1384e1fcd..a5a92460e 100644 --- a/Engine/source/ts/assimp/assimpAppSequence.cpp +++ b/Engine/source/ts/assimp/assimpAppSequence.cpp @@ -10,29 +10,72 @@ #include "console/persistenceManager.h" #include "ts/assimp/assimpAppMaterial.h" #include "ts/assimp/assimpAppSequence.h" +#include "ts/assimp/assimpAppNode.h" AssimpAppSequence::AssimpAppSequence(aiAnimation *a) : + seqStart(0.0f), mAnim(a) { - fps = mAnim->mTicksPerSecond; + // From: http://sir-kimmi.de/assimp/lib_html/data.html#anims + // An aiAnimation has a duration. The duration as well as all time stamps are given in ticks. + // To get the correct timing, all time stamp thus have to be divided by aiAnimation::mTicksPerSecond. + // Beware, though, that certain combinations of file format and exporter don't always store this + // information in the exported file. In this case, mTicksPerSecond is set to 0 to indicate the lack of knowledge. + fps = (mAnim->mTicksPerSecond > 0) ? mAnim->mTicksPerSecond : 30.0f; + + F32 maxEndTime = 0; + F32 minFrameTime = 1000.0f; + // Detect the frame rate (minimum time between keyframes) and max sequence time + for (U32 i = 0; i < mAnim->mNumChannels; ++i) + { + aiNodeAnim *nodeAnim = mAnim->mChannels[i]; + if (nodeAnim->mNumPositionKeys) + maxEndTime = getMax(maxEndTime, (F32) nodeAnim->mPositionKeys[nodeAnim->mNumPositionKeys-1].mTime); + if (nodeAnim->mNumRotationKeys) + maxEndTime = getMax(maxEndTime, (F32) nodeAnim->mRotationKeys[nodeAnim->mNumRotationKeys-1].mTime); + if (nodeAnim->mNumScalingKeys) + maxEndTime = getMax(maxEndTime, (F32) nodeAnim->mScalingKeys[nodeAnim->mNumScalingKeys-1].mTime); + + for (U32 key = 1; key < nodeAnim->mNumPositionKeys; ++key) + { + F32 deltaT = nodeAnim->mPositionKeys[key].mTime - nodeAnim->mPositionKeys[key-1].mTime; + minFrameTime = getMin(minFrameTime, deltaT); + } + for (U32 key = 1; key < nodeAnim->mNumRotationKeys; ++key) + { + F32 deltaT = nodeAnim->mRotationKeys[key].mTime - nodeAnim->mRotationKeys[key-1].mTime; + minFrameTime = getMin(minFrameTime, deltaT); + } + for (U32 key = 1; key < nodeAnim->mNumScalingKeys; ++key) + { + F32 deltaT = nodeAnim->mScalingKeys[key].mTime - nodeAnim->mScalingKeys[key-1].mTime; + minFrameTime = getMin(minFrameTime, deltaT); + } + } + + fps = (minFrameTime > 0.0f) ? 1.0f / minFrameTime : fps; + fps = mClamp(fps, TSShapeLoader::MinFrameRate, TSShapeLoader::MaxFrameRate); + seqEnd = maxEndTime; } AssimpAppSequence::~AssimpAppSequence() { } -F32 AssimpAppSequence::getStart() const -{ - return 0.0f; -} -F32 AssimpAppSequence::getEnd() const -{ - return (F32)mAnim->mDuration / fps; +void AssimpAppSequence::setActive(bool active) +{ + if (active) + AssimpAppNode::sActiveSequence = mAnim; + else + { + if (AssimpAppNode::sActiveSequence == mAnim) + AssimpAppNode::sActiveSequence = NULL; + } } U32 AssimpAppSequence::getFlags() const { - return TSShape::Blend; + return TSShape::Blend; } F32 AssimpAppSequence::getPriority() const { @@ -41,5 +84,4 @@ F32 AssimpAppSequence::getPriority() const F32 AssimpAppSequence::getBlendRefTime() const { return -1.0f; -} - +} \ No newline at end of file diff --git a/Engine/source/ts/assimp/assimpAppSequence.h b/Engine/source/ts/assimp/assimpAppSequence.h index 81f682ced..28072af7e 100644 --- a/Engine/source/ts/assimp/assimpAppSequence.h +++ b/Engine/source/ts/assimp/assimpAppSequence.h @@ -22,6 +22,9 @@ class AssimpAppSequence : public AppSequence { + F32 seqStart; + F32 seqEnd; + public: AssimpAppSequence(aiAnimation *a); @@ -29,18 +32,18 @@ public: aiAnimation *mAnim; - virtual void setActive(bool active) { } + virtual void setActive(bool active); virtual S32 getNumTriggers() const { return 0; } virtual void getTrigger(S32 index, TSShape::Trigger& trigger) const { trigger.state = 0; } virtual const char* getName() const { return mAnim->mName.C_Str(); } - virtual F32 getStart() const; - virtual F32 getEnd() const; + F32 getStart() const { return seqStart; } + F32 getEnd() const { return seqEnd; } + void setEnd(F32 end) { seqEnd = end; } virtual U32 getFlags() const; virtual F32 getPriority() const; virtual F32 getBlendRefTime() const; - }; \ No newline at end of file diff --git a/Engine/source/ts/assimp/assimpShapeLoader.cpp b/Engine/source/ts/assimp/assimpShapeLoader.cpp index fd94ffb6a..f0e543704 100644 --- a/Engine/source/ts/assimp/assimpShapeLoader.cpp +++ b/Engine/source/ts/assimp/assimpShapeLoader.cpp @@ -33,6 +33,7 @@ #include "ts/assimp/assimpShapeLoader.h" #include "ts/assimp/assimpAppNode.h" +#include "ts/assimp/assimpAppMesh.h" #include "ts/assimp/assimpAppMaterial.h" #include "ts/assimp/assimpAppSequence.h" @@ -147,6 +148,12 @@ void AssimpShapeLoader::enumerateScene() Con::getBoolVariable("$Assimp::OptimizeMeshes", false) ? aiProcess_OptimizeMeshes | aiProcess_OptimizeGraph : 0 | 0; + if(Con::getBoolVariable("$Assimp::FlipUVs", true)) + ppsteps |= aiProcess_FlipUVs; + + if(Con::getBoolVariable("$Assimp::FlipWindingOrder", false)) + ppsteps |= aiProcess_FlipWindingOrder; + if(Con::getBoolVariable("$Assimp::Triangulate", true)) ppsteps |= aiProcess_Triangulate; @@ -160,17 +167,25 @@ void AssimpShapeLoader::enumerateScene() aiPropertyStore* props = aiCreatePropertyStore(); - aiSetImportPropertyInteger(props, AI_CONFIG_IMPORT_TER_MAKE_UVS, 1); - aiSetImportPropertyInteger(props, AI_CONFIG_PP_SBP_REMOVE, (aiProcessPreset_TargetRealtime_Quality - | aiProcess_FlipWindingOrder | aiProcess_FlipUVs - | aiProcess_CalcTangentSpace - | aiProcess_FixInfacingNormals) - & ~aiProcess_RemoveRedundantMaterials); - aiSetImportPropertyInteger(props, AI_CONFIG_GLOB_MEASURE_TIME, 1); - aiSetImportPropertyFloat(props, AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE, 80.f); + //aiSetImportPropertyInteger(props, AI_CONFIG_IMPORT_TER_MAKE_UVS, 1); + //aiSetImportPropertyInteger(props, AI_CONFIG_PP_SBP_REMOVE, (aiProcessPreset_TargetRealtime_Quality + // | aiProcess_FlipWindingOrder | aiProcess_FlipUVs + // | aiProcess_CalcTangentSpace + // | aiProcess_FixInfacingNormals) + // & ~aiProcess_RemoveRedundantMaterials); + //aiSetImportPropertyInteger(props, AI_CONFIG_GLOB_MEASURE_TIME, 1); + //aiSetImportPropertyFloat(props, AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE, 80.f); //aiSetImportPropertyInteger(props,AI_CONFIG_PP_PTV_KEEP_HIERARCHY,1); - //Assimp::Importer importer; + struct aiLogStream shapeLog; + shapeLog = aiGetPredefinedLogStream(aiDefaultLogStream_FILE, "assimp.log"); + aiAttachLogStream(&shapeLog); +#ifdef TORQUE_DEBUG + aiEnableVerboseLogging(true); +#endif + + //c = aiGetPredefinedLogStream(aiDefaultLogStream_STDOUT, NULL); + //aiAttachLogStream(&c); // Attempt to import with Assimp. //mScene = importer.ReadFile(shapePath.getFullPath().c_str(), (aiProcessPreset_TargetRealtime_Quality | aiProcess_FlipWindingOrder | aiProcess_FlipUVs | aiProcess_CalcTangentSpace) @@ -189,6 +204,9 @@ void AssimpShapeLoader::enumerateScene() for ( U32 i = 0; i < mScene->mNumMaterials; i++ ) AppMesh::appMaterials.push_back(new AssimpAppMaterial(mScene->mMaterials[i])); + // Setup LOD checks + detectDetails(); + // Define the root node, and process down the chain. AssimpAppNode* node = new AssimpAppNode(mScene, mScene->mRootNode, 0); @@ -203,6 +221,8 @@ void AssimpShapeLoader::enumerateScene() TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import failed"); Con::printf("[ASSIMP] Import Error: %s", aiGetErrorString()); } + + aiDetachLogStream(&shapeLog); } void AssimpShapeLoader::processAnimations() @@ -216,6 +236,55 @@ void AssimpShapeLoader::processAnimations() } } +void AssimpShapeLoader::computeBounds(Box3F& bounds) +{ + TSShapeLoader::computeBounds(bounds); + + // Check if the model origin needs adjusting + bool adjustCenter = Con::getBoolVariable("$Assimp::adjustCenter", false); //ColladaUtils::getOptions().adjustCenter + bool adjustFloor = Con::getBoolVariable("$Assimp::adjustFloor", false); //ColladaUtils::getOptions().adjustFloor + if (bounds.isValidBox() && (adjustCenter || adjustFloor)) + { + // Compute shape offset + Point3F shapeOffset = Point3F::Zero; + if (adjustCenter) + { + bounds.getCenter(&shapeOffset); + shapeOffset = -shapeOffset; + } + if (adjustFloor) + shapeOffset.z = -bounds.minExtents.z; + + // Adjust bounds + bounds.minExtents += shapeOffset; + bounds.maxExtents += shapeOffset; + + // Now adjust all positions for root level nodes (nodes with no parent) + for (S32 iNode = 0; iNode < shape->nodes.size(); iNode++) + { + if (!appNodes[iNode]->isParentRoot()) + continue; + + // Adjust default translation + shape->defaultTranslations[iNode] += shapeOffset; + + // Adjust animated translations + for (S32 iSeq = 0; iSeq < shape->sequences.size(); iSeq++) + { + const TSShape::Sequence& seq = shape->sequences[iSeq]; + if (seq.translationMatters.test(iNode)) + { + for (S32 iFrame = 0; iFrame < seq.numKeyframes; iFrame++) + { + S32 index = seq.baseTranslation + seq.translationMatters.count(iNode)*seq.numKeyframes + iFrame; + shape->nodeTranslations[index] += shapeOffset; + } + } + } + } + } +} + void AssimpShapeLoader::updateMaterialsScript(const Torque::Path &path) { Torque::Path scriptPath(path); @@ -286,6 +355,44 @@ bool AssimpShapeLoader::ignoreNode(const String& name) return false; } +void AssimpShapeLoader::detectDetails() +{ + // Set LOD option + bool singleDetail = true; + switch (Con::getIntVariable("$Assimp::lodType", 0)) + { + case ColladaUtils::ImportOptions::DetectDTS: + // Check for a baseXX->startXX hierarchy at the top-level, if we find + // one, use trailing numbers for LOD, otherwise use a single size + for (S32 iNode = 0; singleDetail && (iNode < mScene->mRootNode->mNumChildren); iNode++) { + aiNode* node = mScene->mRootNode->mChildren[iNode]; + if (node && dStrStartsWith(node->mName.C_Str(), "base")) { + for (S32 iChild = 0; iChild < node->mNumChildren; iChild++) { + aiNode* child = node->mChildren[iChild]; + if (child && dStrStartsWith(child->mName.C_Str(), "start")) { + singleDetail = false; + break; + } + } + } + } + break; + + case ColladaUtils::ImportOptions::SingleSize: + singleDetail = true; + break; + + case ColladaUtils::ImportOptions::TrailingNumber: + singleDetail = false; + break; + + default: + break; + } + + AssimpAppMesh::fixDetailSize(singleDetail, Con::getIntVariable("$Assimp::singleDetailSize", 2)); +} + //----------------------------------------------------------------------------- /// This function is invoked by the resource manager based on file extension. TSShape* assimpLoadShape(const Torque::Path &path) @@ -374,7 +481,7 @@ DefineEngineFunction(GetShapeInfo, GuiTreeViewCtrl*, (String filePath), , //Details! for (U32 i = 0; i < shapeScene->mNumMeshes; i++) { - treeObj->insertItem(meshItem, String::ToString("%s", shapeScene->mMeshes[i]->mName)); + treeObj->insertItem(meshItem, String::ToString("%s", shapeScene->mMeshes[i]->mName.C_Str())); } for (U32 i = 0; i < shapeScene->mNumMaterials; i++) diff --git a/Engine/source/ts/assimp/assimpShapeLoader.h b/Engine/source/ts/assimp/assimpShapeLoader.h index 807cd3fab..713e403cc 100644 --- a/Engine/source/ts/assimp/assimpShapeLoader.h +++ b/Engine/source/ts/assimp/assimpShapeLoader.h @@ -36,6 +36,7 @@ protected: const struct aiScene* mScene; virtual bool ignoreNode(const String& name); + void detectDetails(); public: AssimpShapeLoader(); @@ -46,6 +47,8 @@ public: void updateMaterialsScript(const Torque::Path &path); void processAnimations(); + void computeBounds(Box3F& bounds); + static bool canLoadCachedDTS(const Torque::Path& path); }; diff --git a/Templates/BaseGame/game/tools/gui/assimpImport.ed.gui b/Templates/BaseGame/game/tools/gui/assimpImport.ed.gui new file mode 100644 index 000000000..e42d9ab9d --- /dev/null +++ b/Templates/BaseGame/game/tools/gui/assimpImport.ed.gui @@ -0,0 +1,688 @@ +//--- OBJECT WRITE BEGIN --- +%guiContent = new GuiControl(AssimpImportDlg,EditorGuiGroup) { + isContainer = "1"; + Profile = "ToolsGuiDefaultProfile"; + HorizSizing = "width"; + VertSizing = "height"; + position = "0 0"; + Extent = "1024 768"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + + new GuiWindowCtrl() { + resizeWidth = "0"; + resizeHeight = "0"; + canMove = "1"; + canClose = "1"; + canMinimize = "0"; + canMaximize = "0"; + minSize = "50 50"; + closeCommand = "Canvas.popDialog(AssimpImportDlg);"; + EdgeSnap = "1"; + text = "Open Asset Import Library"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "1"; + Profile = "ToolsGuiWindowProfile"; + HorizSizing = "center"; + VertSizing = "center"; + position = "254 136"; + Extent = "416 390"; + MinExtent = "8 8"; + canSave = "1"; + Visible = "1"; + Accelerator = "escape"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + internalName = "window"; + canSaveDynamicFields = "0"; + + new GuiTextCtrl() { + text = "Up Axis"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 31"; + Extent = "40 16"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + new GuiPopUpMenuCtrl() { + maxPopupHeight = "200"; + sbUsesNAColor = "0"; + reverseTextList = "0"; + bitmapBounds = "16 16"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiPopUpMenuProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "56 30"; + Extent = "66 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + internalName = "upAxis"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Convert To Left Handed"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 50"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::ConvertToLeftHanded"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Converts the model to left-handed"; + hovertime = "1000"; + internalName = "overrideScale"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Triangulate"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 70"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + Active = "0"; + variable = "$Assimp::Triangulate"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Triangulate polygons with more than 3 edges."; + hovertime = "1000"; + internalName = "overrideScale"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Calculate Tangent Space"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 90"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::CalcTangentSpace"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Calculate tangents and bitangents, if possible."; + hovertime = "1000"; + internalName = "overrideScale"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Validate Data Structure"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 110"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::ValidateDataStructure"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Perform a full validation of the loader's output."; + hovertime = "1000"; + internalName = "overrideScale"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Improve Cache Locality"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 130"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::ImproveCacheLocality"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Improve the cache locality of the output vertices."; + hovertime = "1000"; + internalName = "overrideScale"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Remove Redundant Materials"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 150"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::RemoveRedundantMaterials"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Removes redundant materials."; + hovertime = "1000"; + internalName = "overrideScale"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Find Degenerates"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 170"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::FindDegenerates"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Remove degenerated polygons from the import."; + hovertime = "1000"; + internalName = "overrideScale"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Find Invalid Data"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 190"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::FindInvalidData"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Detect invalid model data, such as invalid normal vectors."; + hovertime = "1000"; + internalName = "overrideScale"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Generate UV Coordinates"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 210"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::GenUVCoords"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Convert spherical, cylindrical, box and planar mapping to proper UVs."; + hovertime = "1000"; + internalName = "overrideScale"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Transform UV Coordinates"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 230"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::TransformUVCoords"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Preprocess UV transformations (scaling, translation ...)"; + hovertime = "1000"; + internalName = "overrideScale"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Flip UV Coordinates"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 250"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::FlipUVs"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "This step flips all UV coordinates along the y-axis and adjusts material settings and bitangents accordingly.\nAssimp uses TL(0,0):BR(1,1). T3D uses TL(0,1):BR(1,0). This will be needed for most textured models."; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Find Instances"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 270"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::FindInstances"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "search for instanced meshes and remove them by references to one master."; + hovertime = "1000"; + internalName = "overrideScale"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Limit Bone Weights"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 290"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::LimitBoneWeights"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Limit bone weights to 4 per vertex."; + hovertime = "1000"; + internalName = "overrideScale"; + canSaveDynamicFields = "0"; + }; + + new GuiTextCtrl() { + text = "LOD"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "210 31"; + Extent = "22 16"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + new GuiPopUpMenuCtrl() { + maxPopupHeight = "200"; + sbUsesNAColor = "0"; + reverseTextList = "0"; + bitmapBounds = "16 16"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiPopUpMenuProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "238 30"; + Extent = "92 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Method used to determine LOD for meshes in the model"; + hovertime = "1000"; + internalName = "lodType"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl() { + historySize = "0"; + password = "0"; + tabComplete = "0"; + sinkAllKeyEvents = "0"; + passwordMask = "*"; + text = "2"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiTextEditProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "338 30"; + Extent = "49 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Detail size for all meshes in this model (when LOD type is SingleSize)"; + hovertime = "1000"; + internalName = "singleDetailSize"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Center Model"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "210 50"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::adjustCenter"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Translates model so the origin is at the center"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Floor Model"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "210 70"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::adjustFloor"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Translates model so the origin is at the bottom"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Join Identical Vertices"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "210 90"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::JoinIdenticalVertices"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Identifies and joins identical vertex data sets within all imported meshes."; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Flip Winding Order"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "210 110"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::FlipWindingOrder"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "This step adjusts the output face winding order to be clockwise. The default face winding order is counter clockwise."; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Invert Normals"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "210 130"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::FlipNormals"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Reverse the normal vector direction for all normals."; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + + new GuiButtonCtrl() { + text = "OK"; + groupNum = "-1"; + buttonType = "PushButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiButtonProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "120 348"; + Extent = "86 22"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + Command = "AssimpImportDlg.onOK();"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Load the COLLADA model"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + new GuiButtonCtrl() { + text = "Cancel"; + groupNum = "-1"; + buttonType = "PushButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiButtonProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "220 348"; + Extent = "86 22"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + Command = "AssimpImportDlg.onCancel();"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Exit without loading the COLLADA model"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + }; +}; +//--- OBJECT WRITE END --- + +function AssimpImportDlg::showDialog(%this, %shapePath, %cmd) +{ + %this.path = %shapePath; + %this.cmd = %cmd; + + if ($Assimp::OverrideUpAxis $= "") + { // First load, so set best-case defaults + $Assimp::OverrideUpAxis = 1; // y-axis is up in most test shapes + $Assimp::lodType = 0; // DetectDTS + $Assimp::singleDetailSize = "2"; + + // $Assimp::FlipUVs will be needed for virtually all textured models + $Assimp::FlipUVs = true; + $Assimp::FlipWindingOrder = true; // Makes winding order clock wise + $Assimp::FindDegenerates = true; + $Assimp::FindInvalidData = true; + $Assimp::JoinIdenticalVertices = true; + } + + %this-->upAxis.clear(); + %this-->upAxis.add("X_AXIS", 0); + %this-->upAxis.add("Y_AXIS", 1); + %this-->upAxis.add("Z_AXIS", 2); + %this-->upAxis.setSelected($Assimp::OverrideUpAxis); + + %this-->lodType.clear(); + %this-->lodType.add("DetectDTS", 0); + %this-->lodType.add("SingleSize", 1); + %this-->lodType.add("TrailingNumber", 2); + %this-->lodType.setSelected($Assimp::lodType); + + %this-->singleDetailSize.text = $Assimp::singleDetailSize; + + //Triangulate is a default(currently mandatory) behavior + $Assimp::Triangulate = true; + + Canvas.pushDialog(%this); +} + +function AssimpImportDlg::onCancel(%this) +{ + Canvas.popDialog(%this); + ColladaImportTreeView.clear(); +} + +function AssimpImportDlg::onOK(%this) +{ + Canvas.popDialog(%this); + ColladaImportTreeView.clear(); + + $Assimp::OverrideUpAxis = %this-->upAxis.getSelected(); + + $Assimp::lodType = %this-->lodType.getSelected(); + $Assimp::singleDetailSize = %this-->singleDetailSize.getText(); + + // Load the shape (always from the DAE) + $assimp::forceLoad = true; + eval(%this.cmd); + $assimp::forceLoad = true; +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/gui/colladaImport.ed.gui b/Templates/BaseGame/game/tools/gui/colladaImport.ed.gui index 30838c76d..31394d7d4 100644 --- a/Templates/BaseGame/game/tools/gui/colladaImport.ed.gui +++ b/Templates/BaseGame/game/tools/gui/colladaImport.ed.gui @@ -1696,3 +1696,21 @@ function convertColladaModels(%pattern) $collada::forceLoadDAE = false; } + +function showImportDialog(%shapePath, %cmd) +{ + %this.path = %shapePath; + %this.cmd = %cmd; + + if ( fileExt(%shapePath) $= ".dts" || fileExt(%shapePath) $= ".dsq" + || fileExt(%shapePath) $= ".dae" || fileExt(%shapePath) $= ".kmz" ) + { + // Regular Load + ColladaImportDlg.showDialog(%shapePath, %cmd); + } else if ( isSupportedFormat(stripChars(fileExt(%shapePath), ".")) ) + { + // Assimp Load + if ( isObject(AssimpImportDlg) ) + AssimpImportDlg.showDialog(%shapePath, %cmd); + } +} \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/gui/guiDialogs.ed.cs b/Templates/BaseGame/game/tools/gui/guiDialogs.ed.cs index f2de82d96..ae7afadbf 100644 --- a/Templates/BaseGame/game/tools/gui/guiDialogs.ed.cs +++ b/Templates/BaseGame/game/tools/gui/guiDialogs.ed.cs @@ -35,4 +35,5 @@ exec("./GuiEaseEditDlg.ed.cs"); exec("./guiObjectInspector.ed.cs"); exec("./uvEditor.ed.gui"); exec("./objectSelection.ed.cs"); -exec("./postFxManager.gui"); \ No newline at end of file +exec("./postFxManager.gui"); +exec("./assimpImport.ed.gui"); \ No newline at end of file diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/editors/creator.ed.cs b/Templates/BaseGame/game/tools/worldEditor/scripts/editors/creator.ed.cs index 006031668..406577556 100644 --- a/Templates/BaseGame/game/tools/worldEditor/scripts/editors/creator.ed.cs +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/editors/creator.ed.cs @@ -675,7 +675,7 @@ function EWCreatorWindow::addShapeIcon( %this, %datablock ) %shapePath = ( %datablock.shapeFile !$= "" ) ? %datablock.shapeFile : %datablock.shapeName; %createCmd = "EWCreatorWindow.createObject( \\\"" @ %cmd @ "\\\" );"; - %ctrl.altCommand = "ColladaImportDlg.showDialog( \"" @ %shapePath @ "\", \"" @ %createCmd @ "\" );"; + %ctrl.altCommand = "showImportDialog( \"" @ %shapePath @ "\", \"" @ %createCmd @ "\" );"; %ctrl.iconBitmap = EditorIconRegistry::findIconByClassName( %class ); %ctrl.text = %name; @@ -701,7 +701,7 @@ function EWCreatorWindow::addStaticIcon( %this, %fullPath ) "Last Modified: " @ fileModifiedTime( %fullPath ); %createCmd = "EWCreatorWindow.createStatic( \\\"" @ %fullPath @ "\\\" );"; - %ctrl.altCommand = "ColladaImportDlg.showDialog( \"" @ %fullPath @ "\", \"" @ %createCmd @ "\" );"; + %ctrl.altCommand = "showImportDialog( \"" @ %fullPath @ "\", \"" @ %createCmd @ "\" );"; %ctrl.iconBitmap = ( ( %ext $= ".dts" ) ? EditorIconRegistry::findIconByClassName( "TSStatic" ) : "tools/gui/images/iconCollada" ); %ctrl.text = %file; diff --git a/Templates/Full/game/tools/gui/assimpImport.ed.gui b/Templates/Full/game/tools/gui/assimpImport.ed.gui index d5934fe3b..e42d9ab9d 100644 --- a/Templates/Full/game/tools/gui/assimpImport.ed.gui +++ b/Templates/Full/game/tools/gui/assimpImport.ed.gui @@ -45,26 +45,52 @@ internalName = "window"; canSaveDynamicFields = "0"; - new GuiCheckBoxCtrl() { - useInactiveState = "0"; - text = " Swap Y and Z coordinates."; - groupNum = "-1"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; + new GuiTextCtrl() { + text = "Up Axis"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; isContainer = "0"; - Profile = "ToolsGuiCheckBoxProfile"; + Profile = "ToolsGuiTextRightProfile"; HorizSizing = "right"; VertSizing = "bottom"; - position = "10 30"; - Extent = "200 13"; + position = "10 31"; + Extent = "40 16"; MinExtent = "8 2"; canSave = "1"; Visible = "1"; - variable = "$Assimp::SwapYZ"; tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Corrects transformations so Z is up."; hovertime = "1000"; - internalName = "overrideScale"; + canSaveDynamicFields = "0"; + }; + new GuiPopUpMenuCtrl() { + maxPopupHeight = "200"; + sbUsesNAColor = "0"; + reverseTextList = "0"; + bitmapBounds = "16 16"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiPopUpMenuProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "56 30"; + Extent = "66 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + internalName = "upAxis"; canSaveDynamicFields = "0"; }; @@ -106,6 +132,7 @@ MinExtent = "8 2"; canSave = "1"; Visible = "1"; + Active = "0"; variable = "$Assimp::Triangulate"; tooltipprofile = "ToolsGuiToolTipProfile"; ToolTip = "Triangulate polygons with more than 3 edges."; @@ -300,7 +327,7 @@ new GuiCheckBoxCtrl() { useInactiveState = "0"; - text = " Find Instances"; + text = " Flip UV Coordinates"; groupNum = "-1"; buttonType = "ToggleButton"; useMouseEvents = "0"; @@ -313,6 +340,28 @@ MinExtent = "8 2"; canSave = "1"; Visible = "1"; + variable = "$Assimp::FlipUVs"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "This step flips all UV coordinates along the y-axis and adjusts material settings and bitangents accordingly.\nAssimp uses TL(0,0):BR(1,1). T3D uses TL(0,1):BR(1,0). This will be needed for most textured models."; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Find Instances"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "10 270"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; variable = "$Assimp::FindInstances"; tooltipprofile = "ToolsGuiToolTipProfile"; ToolTip = "search for instanced meshes and remove them by references to one master."; @@ -331,7 +380,7 @@ Profile = "ToolsGuiCheckBoxProfile"; HorizSizing = "right"; VertSizing = "bottom"; - position = "10 270"; + position = "10 290"; Extent = "200 13"; MinExtent = "8 2"; canSave = "1"; @@ -343,7 +392,196 @@ internalName = "overrideScale"; canSaveDynamicFields = "0"; }; - + + new GuiTextCtrl() { + text = "LOD"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiTextRightProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "210 31"; + Extent = "22 16"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + new GuiPopUpMenuCtrl() { + maxPopupHeight = "200"; + sbUsesNAColor = "0"; + reverseTextList = "0"; + bitmapBounds = "16 16"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiPopUpMenuProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "238 30"; + Extent = "92 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Method used to determine LOD for meshes in the model"; + hovertime = "1000"; + internalName = "lodType"; + canSaveDynamicFields = "0"; + }; + new GuiTextEditCtrl() { + historySize = "0"; + password = "0"; + tabComplete = "0"; + sinkAllKeyEvents = "0"; + passwordMask = "*"; + text = "2"; + maxLength = "1024"; + Margin = "0 0 0 0"; + Padding = "0 0 0 0"; + AnchorTop = "1"; + AnchorBottom = "0"; + AnchorLeft = "1"; + AnchorRight = "0"; + isContainer = "0"; + Profile = "ToolsGuiTextEditProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "338 30"; + Extent = "49 18"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Detail size for all meshes in this model (when LOD type is SingleSize)"; + hovertime = "1000"; + internalName = "singleDetailSize"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Center Model"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "210 50"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::adjustCenter"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Translates model so the origin is at the center"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Floor Model"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "210 70"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::adjustFloor"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Translates model so the origin is at the bottom"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Join Identical Vertices"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "210 90"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::JoinIdenticalVertices"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Identifies and joins identical vertex data sets within all imported meshes."; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Flip Winding Order"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "210 110"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::FlipWindingOrder"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "This step adjusts the output face winding order to be clockwise. The default face winding order is counter clockwise."; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + + new GuiCheckBoxCtrl() { + useInactiveState = "0"; + text = " Invert Normals"; + groupNum = "-1"; + buttonType = "ToggleButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiCheckBoxProfile"; + HorizSizing = "right"; + VertSizing = "bottom"; + position = "210 130"; + Extent = "200 13"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + variable = "$Assimp::FlipNormals"; + tooltipprofile = "ToolsGuiToolTipProfile"; + ToolTip = "Reverse the normal vector direction for all normals."; + hovertime = "1000"; + canSaveDynamicFields = "0"; + }; + new GuiButtonCtrl() { text = "OK"; groupNum = "-1"; @@ -392,7 +630,35 @@ function AssimpImportDlg::showDialog(%this, %shapePath, %cmd) { %this.path = %shapePath; %this.cmd = %cmd; - + + if ($Assimp::OverrideUpAxis $= "") + { // First load, so set best-case defaults + $Assimp::OverrideUpAxis = 1; // y-axis is up in most test shapes + $Assimp::lodType = 0; // DetectDTS + $Assimp::singleDetailSize = "2"; + + // $Assimp::FlipUVs will be needed for virtually all textured models + $Assimp::FlipUVs = true; + $Assimp::FlipWindingOrder = true; // Makes winding order clock wise + $Assimp::FindDegenerates = true; + $Assimp::FindInvalidData = true; + $Assimp::JoinIdenticalVertices = true; + } + + %this-->upAxis.clear(); + %this-->upAxis.add("X_AXIS", 0); + %this-->upAxis.add("Y_AXIS", 1); + %this-->upAxis.add("Z_AXIS", 2); + %this-->upAxis.setSelected($Assimp::OverrideUpAxis); + + %this-->lodType.clear(); + %this-->lodType.add("DetectDTS", 0); + %this-->lodType.add("SingleSize", 1); + %this-->lodType.add("TrailingNumber", 2); + %this-->lodType.setSelected($Assimp::lodType); + + %this-->singleDetailSize.text = $Assimp::singleDetailSize; + //Triangulate is a default(currently mandatory) behavior $Assimp::Triangulate = true; @@ -410,6 +676,11 @@ function AssimpImportDlg::onOK(%this) Canvas.popDialog(%this); ColladaImportTreeView.clear(); + $Assimp::OverrideUpAxis = %this-->upAxis.getSelected(); + + $Assimp::lodType = %this-->lodType.getSelected(); + $Assimp::singleDetailSize = %this-->singleDetailSize.getText(); + // Load the shape (always from the DAE) $assimp::forceLoad = true; eval(%this.cmd); diff --git a/Templates/Full/game/tools/shapeEditor/scripts/shapeEditor.ed.cs b/Templates/Full/game/tools/shapeEditor/scripts/shapeEditor.ed.cs index a62605821..1bada1ad7 100644 --- a/Templates/Full/game/tools/shapeEditor/scripts/shapeEditor.ed.cs +++ b/Templates/Full/game/tools/shapeEditor/scripts/shapeEditor.ed.cs @@ -267,7 +267,7 @@ function ShapeEdSelectWindow::onSelect( %this, %path ) else { %cmd = "ShapeEditor.selectShape( \"" @ %path @ "\", false );"; - ColladaImportDlg.showDialog( %path, %cmd ); + showImportDialog( %path, %cmd ); } }