From c38c019a90b23a8db89c3da43f7851ffb120be85 Mon Sep 17 00:00:00 2001 From: JeffR Date: Sat, 22 Aug 2026 12:54:44 -0500 Subject: [PATCH] Fixes issues with node rotations as well as groundFrames processing for assimp shape imports --- Engine/source/ts/assimp/assimpAppNode.cpp | 8 +-- Engine/source/ts/assimp/assimpAppNode.h | 16 +---- Engine/source/ts/assimp/assimpShapeLoader.cpp | 60 ++++++++++++------- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/Engine/source/ts/assimp/assimpAppNode.cpp b/Engine/source/ts/assimp/assimpAppNode.cpp index 177b3ee0e8..ed3dea11da 100644 --- a/Engine/source/ts/assimp/assimpAppNode.cpp +++ b/Engine/source/ts/assimp/assimpAppNode.cpp @@ -84,11 +84,9 @@ MatrixF AssimpAppNode::getTransform(F32 time) // no parent (ie. root level) => scale by global shape mLastTransform.identity(); mLastTransform.scale(ColladaUtils::getOptions().unit * ColladaUtils::getOptions().formatScaleFactor); - if (!isBounds()) - { - MatrixF axisFix = ColladaUtils::getOptions().axisCorrectionMat; - mLastTransform.mulL(axisFix); - } + + MatrixF axisFix = ColladaUtils::getOptions().axisCorrectionMat; + mLastTransform.mulL(axisFix); } // If this node is animated in the active sequence, fetch the animated transform diff --git a/Engine/source/ts/assimp/assimpAppNode.h b/Engine/source/ts/assimp/assimpAppNode.h index c6bacb79b9..62c7e0c498 100644 --- a/Engine/source/ts/assimp/assimpAppNode.h +++ b/Engine/source/ts/assimp/assimpAppNode.h @@ -123,21 +123,7 @@ class AssimpAppNode : public AppNode MatrixF getNodeTransform(F32 time) override; bool animatesTransform(const AppSequence* appSeq) override; - bool isParentRoot() override - { - if (!appParent) - return false; // the scene root itself has no parent — not a content root - - // True when this node's immediate parent is the scene root node. - // mParentName is stored at construction from the AppNode's normalised name - // (empty names become "null"), so apply the same normalisation to the raw - // aiScene root name before comparing. - const char* rootName = mScene->mRootNode->mName.C_Str(); - if (dStrlen(rootName) == 0) - rootName = "null"; - - return dStrcmp(mParentName, rootName) == 0; - } + bool isParentRoot() override { return (appParent == NULL); } static void assimpToTorqueMat(const aiMatrix4x4& inAssimpMat, MatrixF& outMat); static aiNode* findChildNodeByName(const char* nodeName, aiNode* rootNode); diff --git a/Engine/source/ts/assimp/assimpShapeLoader.cpp b/Engine/source/ts/assimp/assimpShapeLoader.cpp index d1c295a4e1..f7eb7ce358 100644 --- a/Engine/source/ts/assimp/assimpShapeLoader.cpp +++ b/Engine/source/ts/assimp/assimpShapeLoader.cpp @@ -244,6 +244,9 @@ void AssimpShapeLoader::enumerateScene() aiEnableVerboseLogging(true); #endif + //We already do transform stuff on our end, so we don't need assimp doing it as well and potentially causing a double-up + mImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_IGNORE_UP_DIRECTION, true); + // Read the file mScene = mImporter.ReadFile(shapePath.getFullPath().c_str(), flags); @@ -310,20 +313,19 @@ void AssimpShapeLoader::enumerateScene() // Setup LOD checks detectDetails(); - // Process mRootNode as an AppNode directly. - // - // Making it the single parentless node means the !appParent branch in - // AssimpAppNode::getTransform() fires exactly once — for this node only. - // Scale + axisCorrectionMat are therefore applied in exactly one place. - // Every child (bones, mesh nodes, bounds) inherits the correction naturally - // through the parent chain; no per-node special-casing is needed. - AssimpAppNode* sceneRootAppNode = new AssimpAppNode(mScene, mScene->mRootNode, nullptr); - if (!processNode(sceneRootAppNode)) + // Register each direct child of mRootNode as its own parentless AppNode + // (mirrors ColladaShapeLoader::enumerateScene()) instead of one wrapper + // node. + for (U32 iNode = 0; iNode < mScene->mRootNode->mNumChildren; iNode++) + { + aiNode* childNode = mScene->mRootNode->mChildren[iNode]; + AssimpAppNode* appNode = new AssimpAppNode(mScene, childNode, nullptr); + if (!processNode(appNode)) { - Con::errorf("[ASSIMP] Failed to process scene root node '%s'.", - mScene->mRootNode->mName.C_Str()); - delete sceneRootAppNode; - sceneRootAppNode = nullptr; + Con::errorf("[ASSIMP] Failed to process root-level node '%s'.", + childNode->mName.C_Str()); + delete appNode; + } } // Bounds check — every Torque shape needs a bounds node. @@ -399,7 +401,8 @@ void AssimpShapeLoader::configureImportUnits() { } F32 fps; - if(getMetaFloat("CustomFrameRate", fps)) + // FBX uses -1 to indicate "no custom frame rate", so only use it if it's positive + if (getMetaFloat("CustomFrameRate", fps) && fps > 0.0f) opts.animFPS = fps; } } @@ -452,7 +455,7 @@ void AssimpShapeLoader::getRootAxisTransform() return v; }; - Point3F forward = axisToVector(frontAxis, frontSign == 1 ? -frontSign : frontSign); + Point3F forward = axisToVector(frontAxis, -frontSign); Point3F up = axisToVector(upAxis, upSign); Point3F right = mCross(forward, up); @@ -478,7 +481,7 @@ void AssimpShapeLoader::getRootAxisTransform() void AssimpShapeLoader::processAnimations() { - if (mScene->mNumAnimations == 0) + if (mScene->mNumAnimations == 0 || mScene->mAnimations[0] == NULL) return; // Multiple animations = multiple actions; single animation = flat timeline. @@ -505,10 +508,14 @@ void AssimpShapeLoader::processAnimations() Vector ambientChannels; F32 maxKeyTime = 0.0f; + F32 maxSourceDuration = 0.0f; + // mScene outlives this call, so reference its channels directly - no per-channel copy needed. for (U32 i = 0; i < mScene->mNumAnimations; ++i) { aiAnimation* anim = mScene->mAnimations[i]; + maxSourceDuration = getMax(maxSourceDuration, (F32)anim->mDuration); + for (U32 j = 0; j < anim->mNumChannels; j++) { aiNodeAnim* nodeAnim = anim->mChannels[j]; @@ -522,12 +529,21 @@ void AssimpShapeLoader::processAnimations() } } - ambientSeq->mNumChannels = ambientChannels.size(); - ambientSeq->mChannels = ambientChannels.address(); - ambientSeq->mDuration = maxKeyTime; - ambientSeq->mTicksPerSecond = targetTPS; + // no point in creating a NULL anim sequence... + if (ambientChannels.size() > 0) + { + ambientSeq->mNumChannels = ambientChannels.size(); + ambientSeq->mChannels = ambientChannels.address(); + // if we somehow dont have keys, just use the max source duration + ambientSeq->mDuration = (maxKeyTime > 0.0f) ? maxKeyTime : maxSourceDuration; + ambientSeq->mTicksPerSecond = targetTPS; - appSequences.push_back(new AssimpAppSequence(ambientSeq)); + appSequences.push_back(new AssimpAppSequence(ambientSeq)); + } + else + { + delete ambientSeq; + } return; } @@ -1135,7 +1151,7 @@ bool AssimpShapeLoader::getMetabool(const char* key, bool& boolVal) { if (mScene->mMetaData->mValues[n].mType == AI_BOOL) { - boolVal = (bool)mScene->mMetaData->mValues[n].mData; + boolVal = *(bool*)mScene->mMetaData->mValues[n].mData; return true; } }