We are not clear yet how we are getting to this crash, so don’t know the root cause. We have also created an ensure message and when the ensure message would trigger. We are still trying to figure out what is triggering this, but at the same time want to know what the ramifications of returning the “SourceTranslation” has on the game, is this ‘ok’ or will this cause some cascade of other issues. The ensure is to give us a
Code Details below
We have been seeing a crash in 3 different methods listed below, and updated them with the same ensureMsgF().
#Original Code
/**
* Get the specified bone translation retargeted from the source skeleton onto the target skeleton, corrected
* for differences between source and target rest poses
* @param TargetSkeletonBoneIndex Skeleton bone index on the target skeleton
* @param SourceTranslation Bone translation from the corresponding bone on the source skeleton
* @return Translation mapped onto the target skeleton
*/
FVector RetargetBoneTranslationToTargetSkeleton(int32 TargetSkeletonBoneIndex, const FVector& SourceTranslation) const
{
// Compute the translation part of FTransform(Q1) * Source * FTransform(Q0)
const TTuple<FQuat, FQuat>& QQ = RetargetingTable[TargetSkeletonBoneIndex];
return QQ.Get<0>().RotateVector(SourceTranslation);
}
// as well as these other 2 methods.
FQuat RetargetBoneRotationToTargetSkeleton(int32 TargetSkeletonBoneIndex, const FQuat& SourceRotation) const
// and
FVector RetargetAdditiveTranslationToTargetSkeleton(int32 TargetSkeletonBoneIndex, const FVector& SourceTranslation) const
The following is the ensureMsg we are putting in it’s place to capture more info
FQuat RetargetBoneRotationToTargetSkeleton(int32 TargetSkeletonBoneIndex, const FQuat& SourceRotation) const
{
// UL BEGIN temp fix for ensure when retargeting table gets an out-of-bounds index
if (!ensureMsgf(
RetargetingTable.IsValidIndex(TargetSkeletonBoneIndex),
TEXT("RetargetBoneRotationToTargetSkeleton: invalid TargetSkeletonBoneIndex %d. RetargetingTable.Num()=%d. SourceSkeleton=%s TargetSkeleton=%s. %s"),
TargetSkeletonBoneIndex,
RetargetingTable.Num(),
*GetNameSafe(SourceSkeleton.Get()),
*GetNameSafe(TargetSkeleton.Get()),
*GetPlayerDistanceDebugString(TargetSkeleton.Get())))
{
return SourceRotation; // HERE you can see what we send back if it fails
}
// UL END temp fix for ensure when retargeting table gets an out-of-bounds index
// Compute the rotation part of FTransform(Q1) * Source * FTransform(Q0)
const TTuple<FQuat, FQuat>& QQ = RetargetingTable[TargetSkeletonBoneIndex];
return QQ.Get<0>() * SourceRotation * QQ.Get<1>();
}
Hi, do you have line numbers from AnimationDecompression.cpp from when you hit the crashes/ensures? There’s a load of calls to RetargetBoneTranslationToTargetSkeleton and variant functions from DecompressPose, some of which are guarded by IsValid checks, some of which aren’t. So it’d be helpful for tracking down the cause to know exactly which call(s) are causing the problem.
In terms of the changes that you added, I think that should be fine since you should just be setting the bind pose back into the output pose for those missing bones. But my preference would be to track down any missing guards in DecompressPose and add the checks there.
Here is an EXTRACTED copy of the code in the AnimationDecompression.cpp [mention removed] I’ve put a comment where it’s hitting down below. Look for your name.
if (SkeletonRemapping.RequiresReferencePoseRetarget())
{
if (DecompressionContext.IsAdditiveAnimation())
{
for (FCompactPoseBoneIndex BoneIndex(bFirstTrackIsRootBone ? 1 : 0); BoneIndex < NumCompactBones; ++BoneIndex)
{
const FSkeletonPoseBoneIndex TargetSkeletonBoneIndex = RequiredBones.GetSkeletonPoseIndexFromCompactPoseIndex(BoneIndex);
if(!TargetSkeletonBoneIndex.IsValid())
{
continue;
}
// Mesh space additives do not require fix-up
if (DecompressionContext.GetAdditiveType() == AAT_LocalSpaceBase)
{
OutPose[BoneIndex].SetRotation(SkeletonRemapping.RetargetAdditiveRotationToTargetSkeleton(TargetSkeletonBoneIndex.GetInt(), OutPose[BoneIndex].GetRotation()));
}
// Check what retarget mode to use for the translational retargeting for this specific bone.
const int32 SourceSkeletonBoneIndex = SkeletonRemapping.GetSourceSkeletonBoneIndex(TargetSkeletonBoneIndex.GetInt());
const EBoneTranslationRetargetingMode::Type RetargetMode = FAnimationRuntime::GetBoneTranslationRetargetingMode(
bUseSourceRetargetModes,
SourceSkeletonBoneIndex,
TargetSkeletonBoneIndex.GetInt(),
SourceSkeleton,
TargetSkeleton,
bDisableRetargeting);
if (RetargetMode != EBoneTranslationRetargetingMode::Skeleton)
{
OutPose[BoneIndex].SetTranslation(SkeletonRemapping.RetargetAdditiveTranslationToTargetSkeleton(TargetSkeletonBoneIndex.GetInt(), OutPose[BoneIndex].GetTranslation()));
}
}
}
else
{
for (FCompactPoseBoneIndex BoneIndex(bFirstTrackIsRootBone ? 1 : 0); BoneIndex < NumCompactBones; ++BoneIndex)
{
const FSkeletonPoseBoneIndex TargetSkeletonBoneIndex = RequiredBones.GetSkeletonPoseIndexFromCompactPoseIndex(BoneIndex);
if(!TargetSkeletonBoneIndex.IsValid())
{
continue;
}
OutPose[BoneIndex].SetRotation(SkeletonRemapping.RetargetBoneRotationToTargetSkeleton(TargetSkeletonBoneIndex.GetInt(), OutPose[BoneIndex].GetRotation()));
// Check what retarget mode to use for the translational retargeting for this specific bone.
const int32 SourceSkeletonBoneIndex = SkeletonRemapping.GetSourceSkeletonBoneIndex(TargetSkeletonBoneIndex.GetInt());
const EBoneTranslationRetargetingMode::Type RetargetMode = FAnimationRuntime::GetBoneTranslationRetargetingMode(
bUseSourceRetargetModes,
SourceSkeletonBoneIndex,
TargetSkeletonBoneIndex.GetInt(),
SourceSkeleton,
TargetSkeleton,
bDisableRetargeting);
if (RetargetMode != EBoneTranslationRetargetingMode::Skeleton)
{
// Euan Charmichael, this is the line it's hitting
OutPose[BoneIndex].SetTranslation(SkeletonRemapping.RetargetBoneTranslationToTargetSkeleton(TargetSkeletonBoneIndex.GetInt(), OutPose[BoneIndex].GetTranslation()));
}
}
}
}
Thanks, that’s interesting because it suggests more is going on than just a missing guard, since we already have one there. This could be a race condition if something else is modifying the skeleton at the same time. We have seen that in the past, and I implemented a related fix that went into 5.7, which would be worth you backporting. It’s at 48060137. I’d also recommend resaving all of your skeletons and meshes to make sure that there aren’t any hierarchy changes which haven’t been saved.
Having said all of that, I’m assuming from the call stack that this is happening in a packaged build and in theory, nothing should be modifying the skeletons in that scenario (except for maybe Mutable). It’d be useful if you could confirm that it is happening in a packaged build and where the crash happens - on load/serialization, or just randomly during gameplay.
Ok, that makes sense. I was looking at other similar changes, and I’d also recommend backporting 50892441, as it could be related.
In terms of the change you have, for the code path that you mentioned in DecompressPose, it’s just going to be the transform that’s already in OutPose for that bone index. It depends on the exact codepath higher in the callstack, but most of the time this is just going to be the ref pose transform for that bone, on the skeleton that we’re playing the animation on (not the skeleton the animation was authored on). Your change is effectively the same as the guard that I was talking about previously, since you’re just filling OutPose with the existing transform.
Usually, this is fine, assuming the hierarchies are similar enough that it makes sense to share the animations between them, and maybe there are cosmetic bones, etc, that are missing in the source vs the target (the reason for the guard). But it’s more concerning in your case, since the bone id is valid, so it should exist in both skeletons. But it then fails in the remapping code. So it might be that you end up with ref pose transforms on certain bones that should be animated.
This is a WinGDK packaged build and XSX package build
I have LOW data on this but it happens both while playing the game and first load of a level. I have been trying to track the BP’s in our logs that produce this error and in just a couple runs that BP seems to have just got loaded, then produces the error. I need a lot more logs to confirm this.
On our end our TechAnimator Lexy just submit an ABP change with this description.
"[anim][zombie] SOD3-55699 changed additive anim in base ABP to point to a metazombie asset
not sure this will do anything, but giving it a try"
We haven’t backported this yet, I marked it as it seemed like the next step. I’m not authorized to do the backport and need an engineer to agree and do that ( I’m a TechArtist )
[mention removed] back to something I still don’t have an answer to. If we DO get into this area with bad data and just return the incoming SOURCE what does that do? Does that create a possible future crash, memory leak, or is it more of a visual thing?
I’m thinking in a case where we aren’t allowed to backport, and have to live with this, can we just return one of the two incoming values?