@@ -244,6 +244,9 @@ void AssimpShapeLoader::enumerateScene()
244244 aiEnableVerboseLogging (true );
245245#endif
246246
247+ // We already do transform stuff on our end, so we don't need assimp doing it as well and potentially causing a double-up
248+ mImporter .SetPropertyBool (AI_CONFIG_IMPORT_FBX_IGNORE_UP_DIRECTION , true );
249+
247250 // Read the file
248251 mScene = mImporter .ReadFile (shapePath.getFullPath ().c_str (), flags);
249252
@@ -310,20 +313,19 @@ void AssimpShapeLoader::enumerateScene()
310313 // Setup LOD checks
311314 detectDetails ();
312315
313- // Process mRootNode as an AppNode directly.
314- //
315- // Making it the single parentless node means the !appParent branch in
316- // AssimpAppNode::getTransform() fires exactly once — for this node only.
317- // Scale + axisCorrectionMat are therefore applied in exactly one place.
318- // Every child (bones, mesh nodes, bounds) inherits the correction naturally
319- // through the parent chain; no per-node special-casing is needed.
320- AssimpAppNode* sceneRootAppNode = new AssimpAppNode (mScene , mScene ->mRootNode , nullptr );
321- if (!processNode (sceneRootAppNode))
316+ // Register each direct child of mRootNode as its own parentless AppNode
317+ // (mirrors ColladaShapeLoader::enumerateScene()) instead of one wrapper
318+ // node.
319+ for (U32 iNode = 0 ; iNode < mScene ->mRootNode ->mNumChildren ; iNode++)
320+ {
321+ aiNode* childNode = mScene ->mRootNode ->mChildren [iNode];
322+ AssimpAppNode* appNode = new AssimpAppNode (mScene , childNode, nullptr );
323+ if (!processNode (appNode))
322324 {
323- Con::errorf (" [ASSIMP] Failed to process scene root node '%s'." ,
324- mScene -> mRootNode ->mName .C_Str ());
325- delete sceneRootAppNode ;
326- sceneRootAppNode = nullptr ;
325+ Con::errorf (" [ASSIMP] Failed to process root-level node '%s'." ,
326+ childNode ->mName .C_Str ());
327+ delete appNode ;
328+ }
327329 }
328330
329331 // Bounds check — every Torque shape needs a bounds node.
@@ -399,7 +401,8 @@ void AssimpShapeLoader::configureImportUnits() {
399401 }
400402
401403 F32 fps;
402- if (getMetaFloat (" CustomFrameRate" , fps))
404+ // FBX uses -1 to indicate "no custom frame rate", so only use it if it's positive
405+ if (getMetaFloat (" CustomFrameRate" , fps) && fps > 0 .0f )
403406 opts.animFPS = fps;
404407 }
405408}
@@ -452,7 +455,7 @@ void AssimpShapeLoader::getRootAxisTransform()
452455 return v;
453456 };
454457
455- Point3F forward = axisToVector (frontAxis, frontSign == 1 ? -frontSign : frontSign);
458+ Point3F forward = axisToVector (frontAxis, - frontSign);
456459 Point3F up = axisToVector (upAxis, upSign);
457460 Point3F right = mCross (forward, up);
458461
@@ -478,7 +481,7 @@ void AssimpShapeLoader::getRootAxisTransform()
478481
479482void AssimpShapeLoader::processAnimations ()
480483{
481- if (mScene ->mNumAnimations == 0 )
484+ if (mScene ->mNumAnimations == 0 || mScene -> mAnimations [ 0 ] == NULL )
482485 return ;
483486
484487 // Multiple animations = multiple actions; single animation = flat timeline.
@@ -505,10 +508,14 @@ void AssimpShapeLoader::processAnimations()
505508
506509 Vector<aiNodeAnim*> ambientChannels;
507510 F32 maxKeyTime = 0 .0f ;
511+ F32 maxSourceDuration = 0 .0f ;
508512
513+ // mScene outlives this call, so reference its channels directly - no per-channel copy needed.
509514 for (U32 i = 0 ; i < mScene ->mNumAnimations ; ++i)
510515 {
511516 aiAnimation* anim = mScene ->mAnimations [i];
517+ maxSourceDuration = getMax (maxSourceDuration, (F32 )anim->mDuration );
518+
512519 for (U32 j = 0 ; j < anim->mNumChannels ; j++)
513520 {
514521 aiNodeAnim* nodeAnim = anim->mChannels [j];
@@ -522,12 +529,21 @@ void AssimpShapeLoader::processAnimations()
522529 }
523530 }
524531
525- ambientSeq->mNumChannels = ambientChannels.size ();
526- ambientSeq->mChannels = ambientChannels.address ();
527- ambientSeq->mDuration = maxKeyTime;
528- ambientSeq->mTicksPerSecond = targetTPS;
532+ // no point in creating a NULL anim sequence...
533+ if (ambientChannels.size () > 0 )
534+ {
535+ ambientSeq->mNumChannels = ambientChannels.size ();
536+ ambientSeq->mChannels = ambientChannels.address ();
537+ // if we somehow dont have keys, just use the max source duration
538+ ambientSeq->mDuration = (maxKeyTime > 0 .0f ) ? maxKeyTime : maxSourceDuration;
539+ ambientSeq->mTicksPerSecond = targetTPS;
529540
530- appSequences.push_back (new AssimpAppSequence (ambientSeq));
541+ appSequences.push_back (new AssimpAppSequence (ambientSeq));
542+ }
543+ else
544+ {
545+ delete ambientSeq;
546+ }
531547 return ;
532548 }
533549
@@ -1135,7 +1151,7 @@ bool AssimpShapeLoader::getMetabool(const char* key, bool& boolVal)
11351151 {
11361152 if (mScene ->mMetaData ->mValues [n].mType == AI_BOOL )
11371153 {
1138- boolVal = (bool )mScene ->mMetaData ->mValues [n].mData ;
1154+ boolVal = * (bool * )mScene ->mMetaData ->mValues [n].mData ;
11391155 return true ;
11401156 }
11411157 }
0 commit comments