Update assimpShapeLoader.cpp

This commit is contained in:
marauder2k7 2026-06-24 14:11:13 +01:00
parent a8640de8ee
commit 07d0eeed77

View file

@ -454,11 +454,8 @@ void AssimpShapeLoader::processAnimations()
if (mScene->mNumAnimations == 0) if (mScene->mNumAnimations == 0)
return; return;
// First pass: check whether this is an export with multiple actions. // Multiple animations = multiple actions; single animation = flat timeline.
// tested on Blender -> FBX only, gltf doesnt work. bool hasMultipleActions = (mScene->mNumAnimations > 1);
bool hasMultipleActions = false;
if(mScene->mNumAnimations > 1)
hasMultipleActions = true;
if (!hasMultipleActions) if (!hasMultipleActions)
{ {
@ -475,8 +472,7 @@ void AssimpShapeLoader::processAnimations()
F64 targetTPS = (srcTPS > 0.0) ? srcTPS : ColladaUtils::getOptions().animFPS; F64 targetTPS = (srcTPS > 0.0) ? srcTPS : ColladaUtils::getOptions().animFPS;
// Original single-timeline path (no '|' in any animation name). // Single-timeline path: concatenate all channels into one ambient sequence.
// Concatenate all channels into one ambient sequence, exactly as before.
aiAnimation* ambientSeq = new aiAnimation(); aiAnimation* ambientSeq = new aiAnimation();
ambientSeq->mName = "ambient"; ambientSeq->mName = "ambient";
@ -527,19 +523,16 @@ void AssimpShapeLoader::processAnimations()
(F32)srcTPS); (F32)srcTPS);
} }
// Seperated action path.
// Data structures (kept as parallel vectors to avoid STL map dependency): // Data structures (kept as parallel vectors to avoid STL map dependency):
// actionNames[i] - unique action name (after '|') // actionNames[i] - unique action name (after '|')
// actionDurations[i] - duration (ticks) of that action // actionDurations[i] - duration (ticks) of that action
// actionChannels[i] - correctly-matched channels for that action // actionChannels[i] - channels for that action (deduped by mNodeName)
// actionNodesSeen[i] - node names already added (deduplication guard) Vector<String> actionNames;
Vector<String> actionNames; Vector<F64> actionDurations;
Vector<F64> actionDurations;
Vector< Vector<aiNodeAnim*> > actionChannels; Vector< Vector<aiNodeAnim*> > actionChannels;
Vector< Vector<String> > actionNodesSeen;
F64 ticksPerSecond = mScene->mAnimations[0]->mTicksPerSecond; F64 ticksPerSecond = mScene->mAnimations[0]->mTicksPerSecond;
if (ticksPerSecond <= 0.0) ticksPerSecond = 30.0; if (ticksPerSecond <= 0.0) ticksPerSecond = ColladaUtils::getOptions().animFPS;
// GLTF stores times in seconds (mTicksPerSecond==0, mDuration < 100). // GLTF stores times in seconds (mTicksPerSecond==0, mDuration < 100).
// FBX stores frame-count ticks (mTicksPerSecond > 0, mDuration in hundreds). // FBX stores frame-count ticks (mTicksPerSecond > 0, mDuration in hundreds).
@ -551,7 +544,6 @@ void AssimpShapeLoader::processAnimations()
if (timesInSeconds) if (timesInSeconds)
{ {
// Rescale all key timestamps in every animation channel in-place
for (U32 i = 0; i < mScene->mNumAnimations; ++i) for (U32 i = 0; i < mScene->mNumAnimations; ++i)
{ {
aiAnimation* anim = mScene->mAnimations[i]; aiAnimation* anim = mScene->mAnimations[i];
@ -572,8 +564,9 @@ void AssimpShapeLoader::processAnimations()
const char* fullName = anim->mName.C_Str(); const char* fullName = anim->mName.C_Str();
const char* pipe = dStrchr(fullName, '|'); const char* pipe = dStrchr(fullName, '|');
// Extract "NodePrefix" and "ActionName" from "NodePrefix|ActionName" // Strip "NodeName|" — only the action name (part after pipe) is needed.
String nodePrefix = pipe ? String(fullName, (U32)(pipe - fullName)) : String(""); // nodePrefix and the nodePrefix==chanNode filter are removed; node
// identity comes from chan->mNodeName on each channel directly.
String actionName = pipe ? String(pipe + 1) : String(fullName); String actionName = pipe ? String(pipe + 1) : String(fullName);
// Find or create the action slot // Find or create the action slot
@ -588,106 +581,58 @@ void AssimpShapeLoader::processAnimations()
actionNames.push_back(actionName); actionNames.push_back(actionName);
actionDurations.push_back(0.0); actionDurations.push_back(0.0);
actionChannels.push_back(Vector<aiNodeAnim*>()); actionChannels.push_back(Vector<aiNodeAnim*>());
actionNodesSeen.push_back(Vector<String>());
} }
// Track maximum duration for this action // Track maximum duration for this action
if (anim->mDuration > actionDurations[slot]) if (anim->mDuration > actionDurations[slot])
actionDurations[slot] = anim->mDuration; actionDurations[slot] = anim->mDuration;
// Accept channels whose node name matches the prefix // Add channels, deduplicating by chan->mNodeName
for (U32 j = 0; j < anim->mNumChannels; ++j) for (U32 j = 0; j < anim->mNumChannels; ++j)
{ {
aiNodeAnim* chan = anim->mChannels[j]; aiNodeAnim* chan = anim->mChannels[j];
String chanNode = chan->mNodeName.C_Str(); const char* nodeName = chan->mNodeName.C_Str();
if (!nodePrefix.isEmpty() && nodePrefix != chanNode)
continue;
// Deduplicate: first channel for a given node in this action wins
bool alreadyAdded = false; bool alreadyAdded = false;
for (U32 n = 0; n < actionNodesSeen[slot].size(); ++n) for (U32 n = 0; n < actionChannels[slot].size(); ++n)
{ {
if (actionNodesSeen[slot][n] == chanNode) { alreadyAdded = true; break; } if (dStrcmp(actionChannels[slot][n]->mNodeName.C_Str(), nodeName) == 0)
}
if (!alreadyAdded)
{
actionChannels[slot].push_back(chan);
actionNodesSeen[slot].push_back(chanNode);
}
}
}
// -----------------------------------------------------------------------
// AMBIENT and NAMED SEQUENCES both use own-action-only channels.
// -----------------------------------------------------------------------
{
// AMBIENT: use ONLY each node's own canonical action channel.
Vector<aiNodeAnim*> ownChans;
F64 ambientDuration = 0.0;
for (U32 i = 0; i < actionNames.size(); ++i)
{
String ownerName;
aiNodeAnim* ownerChan = nullptr;
for (U32 j = 0; j < actionChannels[i].size(); ++j)
{
const String& nodeName = actionNodesSeen[i][j];
U32 nodeLen = nodeName.length();
// Exact owner match: action name = nodeName + "Action" [+ optional suffix].
const char* actionStr = actionNames[i].c_str();
if (dStrnicmp(actionStr, nodeName.c_str(), nodeLen) == 0
&& dStrnicmp(actionStr + nodeLen, "Action", 6) == 0
&& nodeLen > ownerName.length())
{ {
ownerName = nodeName; alreadyAdded = true; break;
ownerChan = actionChannels[i][j];
} }
} }
if (!alreadyAdded)
if (ownerChan) actionChannels[slot].push_back(chan);
{
ownChans.push_back(ownerChan);
ambientDuration = getMax(ambientDuration, actionDurations[i]);
Con::printf("[ASSIMP] Ambient channel: node=%-25s action=%s duration=%.1f ticks",
ownerName.c_str(), actionNames[i].c_str(), (F32)actionDurations[i]);
}
} }
aiAnimation* ambientAnim = new aiAnimation();
ambientAnim->mName = aiString("ambient");
ambientAnim->mTicksPerSecond = ticksPerSecond;
ambientAnim->mDuration = ambientDuration;
ambientAnim->mNumChannels = ownChans.size();
ambientAnim->mChannels = new aiNodeAnim * [ownChans.size()];
for (U32 i = 0; i < ownChans.size(); ++i)
ambientAnim->mChannels[i] = ownChans[i];
Con::printf("[ASSIMP] Ambient: %d own-action channels, duration=%.1f ticks (%.2f sec)",
ambientAnim->mNumChannels, (F32)ambientDuration, (F32)(ambientDuration / ticksPerSecond));
appSequences.push_back(new AssimpAppSequence(ambientAnim));
} }
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// NAMED SEQUENCES: one sequence per unique action, containing ONLY the // Build NAMED SEQUENCES and collect AMBIENT
// owning node's channel.
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
Vector<aiNodeAnim*> ownChans;
Vector<String> ownNodesSeen;
F64 ambientDuration = 0.0;
for (U32 i = 0; i < actionNames.size(); ++i) for (U32 i = 0; i < actionNames.size(); ++i)
{ {
if (actionChannels[i].empty()) // Skip actions with no channels or no duration
if (actionChannels[i].empty() || actionDurations[i] <= 0.0)
{
Con::printf("[ASSIMP] Skipping action '%s' (empty or zero duration)",
actionNames[i].c_str());
continue; continue;
}
// Identify the owning node by exact nodeName+"Action" match (same logic as ambient) // Find owner: chan->mNodeName that action name starts with + "Action"
String ownerName; String ownerName;
aiNodeAnim* ownerChan = nullptr; aiNodeAnim* ownerChan = nullptr;
for (U32 j = 0; j < actionChannels[i].size(); ++j) for (U32 j = 0; j < actionChannels[i].size(); ++j)
{ {
const String& nodeName = actionNodesSeen[i][j]; const char* nodeName = actionChannels[i][j]->mNodeName.C_Str();
U32 nodeLen = nodeName.length(); U32 nodeLen = dStrlen(nodeName);
const char* actionStr = actionNames[i].c_str(); const char* actionStr = actionNames[i].c_str();
if (dStrnicmp(actionStr, nodeName.c_str(), nodeLen) == 0 if (dStrnicmp(actionStr, nodeName, nodeLen) == 0
&& dStrnicmp(actionStr + nodeLen, "Action", 6) == 0 && dStrnicmp(actionStr + nodeLen, "Action", 6) == 0
&& nodeLen > ownerName.length()) && nodeLen > ownerName.length())
{ {
@ -704,28 +649,75 @@ void AssimpShapeLoader::processAnimations()
if (ownerChan) if (ownerChan)
{ {
// Per-object action: single owner channel only. // Per-object action: single owner channel only.
// Non-owner nodes inherit parent motion via Torque's hierarchy.
seq->mNumChannels = 1; seq->mNumChannels = 1;
seq->mChannels = new aiNodeAnim * [1]; seq->mChannels = new aiNodeAnim * [1];
seq->mChannels[0] = ownerChan; seq->mChannels[0] = ownerChan;
Con::printf("[ASSIMP] Sequence '%s': owner=%s duration=%.1f ticks", Con::printf("[ASSIMP] Sequence '%s': owner=%s duration=%.1f ticks",
actionNames[i].c_str(), ownerName.c_str(), (F32)actionDurations[i]); actionNames[i].c_str(), ownerName.c_str(), (F32)actionDurations[i]);
// Collect owner channel for ambient (name-matched = safe data)
bool already = false;
for (U32 k = 0; k < ownNodesSeen.size(); ++k)
if (ownNodesSeen[k] == ownerName) { already = true; break; }
if (!already)
{
ownChans.push_back(ownerChan);
ownNodesSeen.push_back(ownerName);
ambientDuration = getMax(ambientDuration, actionDurations[i]);
Con::printf("[ASSIMP] Ambient channel: node=%-25s action=%s duration=%.1f ticks",
ownerName.c_str(), actionNames[i].c_str(), (F32)actionDurations[i]);
}
} }
else else
{ {
// No per-object naming match: this is a deliberately multi-node // No name match: renamed or multi-node authored action.
// authored action (e.g. "GateOpen", "ArmSystemExtend"). Include
// all channels so the full choreography is preserved.
seq->mNumChannels = actionChannels[i].size(); seq->mNumChannels = actionChannels[i].size();
seq->mChannels = new aiNodeAnim * [seq->mNumChannels]; seq->mChannels = new aiNodeAnim * [seq->mNumChannels];
for (U32 k = 0; k < actionChannels[i].size(); ++k) for (U32 k = 0; k < actionChannels[i].size(); ++k)
seq->mChannels[k] = actionChannels[i][k]; seq->mChannels[k] = actionChannels[i][k];
Con::printf("[ASSIMP] Sequence '%s': multi-node (%d channels) duration=%.1f ticks", Con::printf("[ASSIMP] Sequence '%s': multi-node (%d channels) duration=%.1f ticks",
actionNames[i].c_str(), seq->mNumChannels, (F32)actionDurations[i]); actionNames[i].c_str(), seq->mNumChannels, (F32)actionDurations[i]);
// Ambient fallback: only from no-owner slots so bystander channels
// from name-matched action evaluations never corrupt ambient.
// Each channel here came from NodeName|RenamedAction — own data.
for (U32 k = 0; k < actionChannels[i].size(); ++k)
{
const char* nodeName = actionChannels[i][k]->mNodeName.C_Str();
bool already = false;
for (U32 n = 0; n < ownNodesSeen.size(); ++n)
if (dStrcmp(ownNodesSeen[n].c_str(), nodeName) == 0) { already = true; break; }
if (!already)
{
ownChans.push_back(actionChannels[i][k]);
ownNodesSeen.push_back(String(nodeName));
ambientDuration = getMax(ambientDuration, actionDurations[i]);
Con::printf("[ASSIMP] Ambient fallback: node=%s action=%s",
nodeName, actionNames[i].c_str());
}
}
} }
appSequences.push_back(new AssimpAppSequence(seq)); appSequences.push_back(new AssimpAppSequence(seq));
} }
// Build ambient from collected channels, inserted at index 0
{
aiAnimation* ambientAnim = new aiAnimation();
ambientAnim->mName = aiString("ambient");
ambientAnim->mTicksPerSecond = ticksPerSecond;
ambientAnim->mDuration = ambientDuration;
ambientAnim->mNumChannels = ownChans.size();
ambientAnim->mChannels = new aiNodeAnim * [ownChans.size()];
for (U32 i = 0; i < ownChans.size(); ++i)
ambientAnim->mChannels[i] = ownChans[i];
Con::printf("[ASSIMP] Ambient: %d channels, duration=%.1f ticks (%.2f sec)",
ambientAnim->mNumChannels, (F32)ambientDuration, (F32)(ambientDuration / ticksPerSecond));
appSequences.push_back( new AssimpAppSequence(ambientAnim));
}
} }
void AssimpShapeLoader::computeBounds(Box3F& bounds) void AssimpShapeLoader::computeBounds(Box3F& bounds)