We recently upgraded to 5.6 and found a difference in behaviour when using pose search branch in notifies. In our use case, we have some animations with multiple branch in notifies. Which can generate different asset indexes.
The issue happens in UPoseSearchDatabase::GetPoseIndex, where the first AssetIndex finds a proper valid PoseIdx. However, the next iteration goes to another index, which does not have any poses for the sampled animation asset time and returns an invalid PoseIdx.
This causes UPoseSearchLibrary::UpdateMotionMatchingState to set bCanAdvance to false, which then causes a stutter.
I recall from another thread that the branch in notify perhaps wasn’t being tested as much. I wonder if the exlusion notify would make sure they all go to the same index. In any case the branch in notifies are quite useful for our workflow.
Please let us know if this is a known issue or if you think something may be wrong on our end.
Hi Sergio, thanks for flagging this. This is a bug with how the pose index is being returned from the current animation asset. It would affect both sequences with multiple pose search branch-in notifies as well as those that have a single branch in with an exclude notify within their range. Both those approaches end up with multiple entries in FSearchIndexBase::Assets for the different frame ranges.
I’m going to commit a fix for this, which will return early if we find a valid pose index. It’ll look something like this:
int32 UPoseSearchDatabase::GetPoseIndex(const UObject* AnimationAsset, float AnimationAssetTime, bool bMirrored, const FVector& BlendParameters) const
{
using namespace UE::PoseSearch;
if (AnimationAsset)
{
float MinSquaredLength = UE_MAX_FLT;
const float SampleRate = Schema->SampleRate;
const TConstArrayView<int32> AssetIndexesForSourceAsset = GetAssetIndexesForSourceAsset(AnimationAsset);
const FSearchIndex& SearchIndex = GetSearchIndex();
for (int32 AssetIndex : AssetIndexesForSourceAsset)
{
const FSearchIndexAsset& SearchIndexAsset = SearchIndex.Assets[AssetIndex];
if (SearchIndexAsset.IsMirrored() == bMirrored)
{
const float BlendParametersSquaredLength = (BlendParameters - SearchIndexAsset.GetBlendParameters()).SquaredLength();
// using <= so we don't have to check for PoseIdx == INDEX_NONE, since any float will be smaller or equal than UE_MAX_FLT
if (BlendParametersSquaredLength <= MinSquaredLength)
{
MinSquaredLength = BlendParametersSquaredLength;
const FPoseSearchDatabaseAnimationAssetBase* DatabaseAnimationAssetBase = GetDatabaseAnimationAsset(SearchIndexAsset);
check(DatabaseAnimationAssetBase);
check(DatabaseAnimationAssetBase->GetAnimationAsset() == AnimationAsset);
const float RealAssetTime = AnimationAssetTime * SearchIndexAsset.GetToRealTimeFactor();
const int32 PoseIdx = SearchIndexAsset.GetPoseIndexFromTime(RealAssetTime, SampleRate);
if (PoseIdx != INDEX_NONE)
{
return PoseIdx;
}
}
}
}
}
return INDEX_NONE;
}
Let me know that fixes things for you.
Thanks,
Euan
Yeah, you’re right. I missed that we are setting MinSquaredLength each time through the loop. Your suggested change looks like the right way to go here.
Hi Euan,
would it be an issue to exit the loop early in case a better BlendParametersSquaredLength could be found on another iteration? I have a similar workaround that only sets PoseIdx if it’s valid and then returns it normally, which should let for this case to be handled.
int32 CandidateIdx = SearchIndexAsset.GetPoseIndexFromTime(RealAssetTime, SampleRate);
if(CandidateIdx != INDEX_NONE)
{
PoseIdx = CandidateIdx;
}