void UBlendSpaceBase::TickAssetPlayer(FAnimTickRecord& Instance, struct FAnimNotifyQueue& NotifyQueue, FAnimAssetTickContext& Context) const
{
	const float DeltaTime = Context.GetDeltaTime();
	float MoveDelta = Instance.PlayRateMultiplier * DeltaTime;

	// this happens even if MoveDelta == 0.f. This still should happen if it is being interpolated
	// since we allow setting position of blendspace, we can't ignore MoveDelta == 0.f
	// also now we don't have to worry about not following if DeltaTime = 0.f
	{
		// first filter input using blend filter
		const FVector BlendSpacePosition(Instance.BlendSpace.BlendSpacePositionX, Instance.BlendSpace.BlendSpacePositionY, 0.f);
		const FVector BlendInput = FilterInput(Instance.BlendSpace.BlendFilter, BlendSpacePosition, DeltaTime);
		EBlendSpaceAxis AxisToScale = GetAxisToScale();
		if (AxisToScale != BSA_None)
		{
			float FilterMultiplier = 1.f;
			// first use multiplier using new blendinput
			// new filtered input is going to be used for sampling animation
			// so we'll need to change playrate if you'd like to not slide foot
			if ( !BlendSpacePosition.Equals(BlendInput) )  
			{
				// apply speed change if you want, 
				if (AxisToScale == BSA_X)
				{
					if (BlendInput.X != 0.f)
					{
						FilterMultiplier = BlendSpacePosition.X / BlendInput.X;
					}
				}
				else if (AxisToScale == BSA_Y)
				{
					if (BlendInput.Y != 0.f)
					{
						FilterMultiplier = BlendSpacePosition.Y / BlendInput.Y;
					}
				}
			}

			// now find if clamped input is different
			// if different, then apply scale to fit in
			FVector ClampedInput = ClampBlendInput(BlendInput);
			if ( !ClampedInput.Equals(BlendInput) ) 
			{
				// apply speed change if you want, 
				if (AxisToScale == BSA_X)
				{
					if (ClampedInput.X != 0.f)
					{
						FilterMultiplier *= BlendInput.X / ClampedInput.X;
					}
				}
				else if (AxisToScale == BSA_Y)
				{
					if (ClampedInput.Y != 0.f)
					{
						FilterMultiplier *= BlendInput.Y / ClampedInput.Y;
					}
				}
			}


			MoveDelta *= FilterMultiplier;
			UE_LOG(LogAnimation, Log, TEXT("BlendSpace(%s) - BlendInput(%s) : FilteredBlendInput(%s), FilterMultiplier(%0.2f)"), *GetName(), *BlendSpacePosition.ToString(), *BlendInput.ToString(), FilterMultiplier );
		}

		check(Instance.BlendSpace.BlendSampleDataCache);

		// For Target weight interpolation, we'll need to save old data, and interpolate to new data
		TArray<FBlendSampleData>& OldSampleDataList = FBlendSpaceScratchData::Get().OldSampleDataList;
		TArray<FBlendSampleData>& NewSampleDataList = FBlendSpaceScratchData::Get().NewSampleDataList;
		check(!OldSampleDataList.Num() && !NewSampleDataList.Num()); // this must be called non-recursively

		OldSampleDataList.Append(*Instance.BlendSpace.BlendSampleDataCache);

		// get sample data based on new input
		// consolidate all samples and sort them, so that we can handle from biggest weight to smallest
		Instance.BlendSpace.BlendSampleDataCache->Reset();
		// new sample data that will be used for evaluation
		TArray<FBlendSampleData> & SampleDataList = *Instance.BlendSpace.BlendSampleDataCache;

		// get sample data from blendspace
		if (GetSamplesFromBlendInput(BlendInput, NewSampleDataList))
		{
			float NewAnimLength=0.f;
			float PreInterpAnimLength = 0.f;

			// if target weight interpolation is set
			if (TargetWeightInterpolationSpeedPerSec > 0.f)
			{
				UE_LOG(LogAnimation, Verbose, TEXT("Target Weight Interpolation: Target Samples "));
				// recalculate AnimLength based on weight of target animations - this is used for scaling animation later (change speed)
				PreInterpAnimLength = GetAnimationLengthFromSampleData(NewSampleDataList);
				UE_LOG(LogAnimation, Verbose, TEXT("BlendSpace(%s) - BlendInput(%s) : PreAnimLength(%0.5f) "), *GetName(), *BlendInput.ToString(), PreInterpAnimLength);

				// target weight interpolation
				if (InterpolateWeightOfSampleData(DeltaTime, OldSampleDataList, NewSampleDataList, SampleDataList))
				{
					// now I need to normalize
					FBlendSampleData::NormalizeDataWeight(SampleDataList);
				}
				else
				{
					// if interpolation failed, just copy new sample data tto sample data
					SampleDataList = NewSampleDataList;
				}

				// recalculate AnimLength based on weight of animations
				UE_LOG(LogAnimation, Verbose, TEXT("Target Weight Interpolation: Interp Samples "));
			}
			else
			{
				// when there is no target weight interpolation, just copy new to target
				SampleDataList.Append(NewSampleDataList);
			}

			bool bCanDoMarkerSync = (SampleIndexWithMarkers != INDEX_NONE) && (Context.IsSingleAnimationContext() || (Instance.bCanUseMarkerSync && Context.CanUseMarkerPosition()));

			if (bCanDoMarkerSync)
			{
				//Copy previous frame marker data to current frame
				for (FBlendSampleData& PrevBlendSampleItem : OldSampleDataList)
				{
					for (FBlendSampleData& CurrentBlendSampleItem : SampleDataList)
					{
						// it only can have one animation in the sample, make sure to copy Time
						if (PrevBlendSampleItem.Animation && PrevBlendSampleItem.Animation == CurrentBlendSampleItem.Animation)
						{
							CurrentBlendSampleItem.Time = PrevBlendSampleItem.Time;
							CurrentBlendSampleItem.PreviousTime = PrevBlendSampleItem.PreviousTime;
							CurrentBlendSampleItem.MarkerTickRecord = PrevBlendSampleItem.MarkerTickRecord;
						}
					}
				}
			}

			NewAnimLength = GetAnimationLengthFromSampleData(SampleDataList);

			if (PreInterpAnimLength > 0.f && NewAnimLength > 0.f)
			{
				MoveDelta *= PreInterpAnimLength / NewAnimLength;
			}

			float& NormalizedCurrentTime = *(Instance.TimeAccumulator);
			const float NormalizedPreviousTime = NormalizedCurrentTime;

			// @note for sync group vs non sync group
			// in blendspace, it will still sync even if only one node in sync group
			// so you're never non-sync group unless you have situation where some markers are relevant to one sync group but not all the time
			// here we save NormalizedCurrentTime as Highest weighted samples' position in sync group
			// if you're not in sync group, NormalizedCurrentTime is based on normalized length by sample weights
			// if you move between sync to non sync within blendspace, you're going to see pop because we'll have to jump
			// for now, our rule is to keep normalized time as highest weighted sample position within its own length
			// also MoveDelta doesn't work if you're in sync group. It will move according to sync group position
			// @todo consider using MoveDelta when  this is leader, but that can be scary because it's not matching with DeltaTime any more. 
			// if you have interpolation delay, that value can be applied, but the output might be unpredictable. 
			// 
			// to fix this better in the future, we should use marker sync position from last tick
			// but that still doesn't fix if you just join sync group, you're going to see pop since your animation doesn't fix

			if (Context.IsLeader())
			{
				// advance current time - blend spaces hold normalized time as when dealing with changing anim length it would be possible to go backwards
				UE_LOG(LogAnimation, Verbose, TEXT("BlendSpace(%s) - BlendInput(%s) : AnimLength(%0.5f) "), *GetName(), *BlendInput.ToString(), NewAnimLength);
				
				const int32 HighestMarkerSyncWeightIndex = bCanDoMarkerSync ? GetHighestWeightMarkerSyncSample(SampleDataList, SampleData) : -1;

				if (HighestMarkerSyncWeightIndex == -1)
				{
					bCanDoMarkerSync = false;
				}

				if (bCanDoMarkerSync)
				{
					FBlendSampleData& SampleDataItem = SampleDataList[HighestMarkerSyncWeightIndex];
					const FBlendSample& Sample = SampleData[SampleDataItem.SampleDataIndex];

					bool bResetMarkerDataOnFollowers = false;
					if (!Instance.MarkerTickRecord->IsValid())
					{
						SampleDataItem.MarkerTickRecord.Reset();
						bResetMarkerDataOnFollowers = true;
					}
					else if (!SampleDataItem.MarkerTickRecord.IsValid() && Context.MarkerTickContext.GetMarkerSyncStartPosition().IsValid())
					{
						Sample.Animation->GetMarkerIndicesForPosition(Context.MarkerTickContext.GetMarkerSyncStartPosition(), true, SampleDataItem.MarkerTickRecord.PreviousMarker, SampleDataItem.MarkerTickRecord.NextMarker, SampleDataItem.Time);
					}

					const float NewDeltaTime = Context.GetDeltaTime() * Instance.PlayRateMultiplier;
					if (!FMath::IsNearlyZero(NewDeltaTime))
					{
						Context.SetLeaderDelta(NewDeltaTime);
						Sample.Animation->TickByMarkerAsLeader(SampleDataItem.MarkerTickRecord, Context.MarkerTickContext, SampleDataItem.Time, SampleDataItem.PreviousTime, NewDeltaTime, true);
						check(Context.MarkerTickContext.IsMarkerSyncStartValid());
						TickFollowerSamples(SampleDataList, HighestMarkerSyncWeightIndex, Context, bResetMarkerDataOnFollowers);
					}
					NormalizedCurrentTime = SampleDataItem.Time / Sample.Animation->SequenceLength;
					*Instance.MarkerTickRecord = SampleDataItem.MarkerTickRecord;
				}
				else
				{
					// Advance time using current/new anim length
					float CurrentTime = NormalizedCurrentTime * NewAnimLength;
					FAnimationRuntime::AdvanceTime(Instance.bLooping, MoveDelta, /*inout*/ CurrentTime, NewAnimLength);
					NormalizedCurrentTime = NewAnimLength ? (CurrentTime / NewAnimLength) : 0.0f;
					UE_LOG(LogAnimMarkerSync, Log, TEXT("Leader (%s) (normal advance)  - PreviousTime (%0.2f), CurrentTime (%0.2f), MoveDelta (%0.2f) "), *GetName(), NormalizedPreviousTime, NormalizedCurrentTime, MoveDelta);
				}

				Context.SetAnimationPositionRatio(NormalizedCurrentTime);
			}
			else
			{
				if(!Context.MarkerTickContext.IsMarkerSyncStartValid())
				{
					bCanDoMarkerSync = false;
				}

				if (bCanDoMarkerSync)
				{
					const int32 HighestWeightIndex = GetHighestWeightSample(SampleDataList);
					FBlendSampleData& SampleDataItem = SampleDataList[HighestWeightIndex];
					const FBlendSample& Sample = SampleData[SampleDataItem.SampleDataIndex];

					if (Context.GetDeltaTime() != 0.f)
					{
						if(!Instance.MarkerTickRecord->IsValid())
						{
							SampleDataItem.Time = NormalizedCurrentTime * Sample.Animation->SequenceLength;
						}

						TickFollowerSamples(SampleDataList, -1, Context, false);
					}
					*Instance.MarkerTickRecord = SampleDataItem.MarkerTickRecord;
					NormalizedCurrentTime =  SampleDataItem.Time / Sample.Animation->SequenceLength;
				}
				else
				{
					NormalizedCurrentTime =  Context.GetAnimationPositionRatio();
					UE_LOG(LogAnimMarkerSync, Log, TEXT("Leader (%s) (normal advance)  - PreviousTime (%0.2f), CurrentTime (%0.2f), MoveDelta (%0.2f) "), *GetName(), NormalizedPreviousTime, NormalizedCurrentTime, MoveDelta);
				}
			}

			// generate notifies and sets time
			{
				TArray<const FAnimNotifyEvent*> Notifies;

				const float ClampedNormalizedPreviousTime = FMath::Clamp<float>(NormalizedPreviousTime, 0.f, 1.f);
				const float ClampedNormalizedCurrentTime = FMath::Clamp<float>(NormalizedCurrentTime, 0.f, 1.f);
				const bool bGenerateNotifies = Context.ShouldGenerateNotifies() && (NormalizedCurrentTime != NormalizedPreviousTime) && NotifyTriggerMode != ENotifyTriggerMode::None;
				
				// Get the index of the highest weight, assuming that the first is the highest until we find otherwise
				const bool bTriggerNotifyHighestWeightedAnim = NotifyTriggerMode == ENotifyTriggerMode::HighestWeightedAnimation && SampleDataList.Num() > 0;
				const int32 HighestWeightIndex = (bGenerateNotifies && bTriggerNotifyHighestWeightedAnim) ? GetHighestWeightSample(SampleDataList) : -1;

				for (int32 I = 0; I < SampleDataList.Num(); ++I)
				{
					FBlendSampleData& SampleEntry = SampleDataList[I];
					const int32 SampleDataIndex = SampleEntry.SampleDataIndex;

					// Skip SamplesPoints that has no relevant weight
					if( SampleData.IsValidIndex(SampleDataIndex) && (SampleEntry.TotalWeight > ZERO_ANIMWEIGHT_THRESH) )
					{
						const FBlendSample& Sample = SampleData[SampleDataIndex];
						if( Sample.Animation )
						{
							float PrevSampleDataTime;
							float& CurrentSampleDataTime = SampleEntry.Time;

							if (!bCanDoMarkerSync || Sample.Animation->AuthoredSyncMarkers.Num() == 0) //Have already updated time if we are doing marker sync
							{
								const float SampleNormalizedPreviousTime = Sample.Animation->RateScale >= 0.f ? ClampedNormalizedPreviousTime : 1.f - ClampedNormalizedPreviousTime;
								const float SampleNormalizedCurrentTime = Sample.Animation->RateScale >= 0.f ? ClampedNormalizedCurrentTime : 1.f - ClampedNormalizedCurrentTime;
								PrevSampleDataTime = SampleNormalizedPreviousTime * Sample.Animation->SequenceLength;
								CurrentSampleDataTime = SampleNormalizedCurrentTime * Sample.Animation->SequenceLength;
							}
							else
							{
								PrevSampleDataTime = SampleEntry.PreviousTime;
							}

							// Figure out delta time 
							float DeltaTimePosition = CurrentSampleDataTime - PrevSampleDataTime;
							const float SampleMoveDelta = MoveDelta * Sample.Animation->RateScale;

							// if we went against play rate, then loop around.
							if ((SampleMoveDelta * DeltaTimePosition) < 0.f)
							{
								DeltaTimePosition += FMath::Sign<float>(SampleMoveDelta) * Sample.Animation->SequenceLength;
							}

							if( bGenerateNotifies && (!bTriggerNotifyHighestWeightedAnim || (I == HighestWeightIndex)))
							{
								// Harvest and record notifies
								Sample.Animation->GetAnimNotifies(PrevSampleDataTime, DeltaTimePosition, Instance.bLooping, Notifies);
							}

							if (Context.RootMotionMode == ERootMotionMode::RootMotionFromEverything && Sample.Animation->bEnableRootMotion)
							{
								Context.RootMotionMovementParams.AccumulateWithBlend(Sample.Animation->ExtractRootMotion(PrevSampleDataTime, DeltaTimePosition, Instance.bLooping), SampleEntry.GetWeight());
							}


							UE_LOG(LogAnimation, Verbose, TEXT("%d. Blending animation(%s) with %f weight at time %0.2f"), I+1, *Sample.Animation->GetName(), SampleEntry.GetWeight(), CurrentSampleDataTime);
						}
					}
				}

				if (bGenerateNotifies && Notifies.Num() > 0)
				{
					NotifyQueue.AddAnimNotifies(Notifies, Instance.EffectiveBlendWeight);
				}
			}
		}
		OldSampleDataList.Reset();
		NewSampleDataList.Reset();
	}
}
Example #2
0
void UBlendSpaceBase::TickAssetPlayerInstance(const FAnimTickRecord& Instance, class UAnimInstance* InstanceOwner, FAnimAssetTickContext& Context) const
{
	const float DeltaTime = Context.GetDeltaTime();
	float MoveDelta = Instance.PlayRateMultiplier * DeltaTime;

	// this happens even if MoveDelta == 0.f. This still should happen if it is being interpolated
	// since we allow setting position of blendspace, we can't ignore MoveDelta == 0.f
	// also now we don't have to worry about not following if DeltaTime = 0.f
	{
		// first filter input using blend filter
		const FVector BlendInput = FilterInput(Instance.BlendFilter, Instance.BlendSpacePosition, DeltaTime);
		EBlendSpaceAxis AxisToScale = GetAxisToScale();
		if (AxisToScale != BSA_None)
		{
			float FilterMultiplier = 1.f;
			// first use multiplier using new blendinput
			// new filtered input is going to be used for sampling animation
			// so we'll need to change playrate if you'd like to not slide foot
			if ( !Instance.BlendSpacePosition.Equals(BlendInput) )  
			{
				// apply speed change if you want, 
				if (AxisToScale == BSA_X)
				{
					if (BlendInput.X != 0.f)
					{
						FilterMultiplier = Instance.BlendSpacePosition.X / BlendInput.X;
					}
				}
				else if (AxisToScale == BSA_Y)
				{
					if (BlendInput.Y != 0.f)
					{
						FilterMultiplier = Instance.BlendSpacePosition.Y / BlendInput.Y;
					}
				}
			}

			// now find if clamped input is different
			// if different, then apply scale to fit in
			FVector ClampedInput = ClampBlendInput(BlendInput);
			if ( !ClampedInput.Equals(BlendInput) ) 
			{
				// apply speed change if you want, 
				if (AxisToScale == BSA_X)
				{
					if (ClampedInput.X != 0.f)
					{
						FilterMultiplier *= BlendInput.X / ClampedInput.X;
					}
				}
				else if (AxisToScale == BSA_Y)
				{
					if (ClampedInput.Y != 0.f)
					{
						FilterMultiplier *= BlendInput.Y / ClampedInput.Y;
					}
				}
			}


			MoveDelta *= FilterMultiplier;
			UE_LOG(LogAnimation, Log, TEXT("BlendSpace(%s) - BlendInput(%s) : FilteredBlendInput(%s), FilterMultiplier(%0.2f)"), *GetName(), *Instance.BlendSpacePosition.ToString(), *BlendInput.ToString(), FilterMultiplier );
		}

		check(Instance.BlendSampleDataCache);

		// For Target weight interpolation, we'll need to save old data, and interpolate to new data
		static TArray<FBlendSampleData> OldSampleDataList;
		static TArray<FBlendSampleData> NewSampleDataList;
		check(IsInGameThread() && !OldSampleDataList.Num() && !NewSampleDataList.Num()); // this must be called non-recursively on the game thread

		OldSampleDataList.Append(*Instance.BlendSampleDataCache);

		// get sample data based on new input
		// consolidate all samples and sort them, so that we can handle from biggest weight to smallest
		Instance.BlendSampleDataCache->Reset();
		// new sample data that will be used for evaluation
		TArray<FBlendSampleData> & SampleDataList = *Instance.BlendSampleDataCache;

		// get sample data from blendspace
		if (GetSamplesFromBlendInput(BlendInput, NewSampleDataList))
		{
			float NewAnimLength=0;

			// if target weight interpolation is set
			if (TargetWeightInterpolationSpeedPerSec > 0.f)
			{
				UE_LOG(LogAnimation, Verbose, TEXT("Target Weight Interpolation: Target Samples "));
				// recalculate AnimLength based on weight of target animations - this is used for scaling animation later (change speed)
				float PreInterpAnimLength = GetAnimationLengthFromSampleData(NewSampleDataList);
				UE_LOG(LogAnimation, Verbose, TEXT("BlendSpace(%s) - BlendInput(%s) : PreAnimLength(%0.5f) "), *GetName(), *BlendInput.ToString(), PreInterpAnimLength);

				// target weight interpolation
				if (InterpolateWeightOfSampleData(DeltaTime, OldSampleDataList, NewSampleDataList, SampleDataList))
				{
					// now I need to normalize
					NormalizeSampleDataWeight(SampleDataList);
				}
				else
				{
					// if interpolation failed, just copy new sample data tto sample data
					SampleDataList = NewSampleDataList;
				}

				// recalculate AnimLength based on weight of animations
				UE_LOG(LogAnimation, Verbose, TEXT("Target Weight Interpolation: Interp Samples "));
				NewAnimLength = GetAnimationLengthFromSampleData(SampleDataList);
				// now scale the animation
				if (NewAnimLength > 0.f)
				{
					MoveDelta *= PreInterpAnimLength/NewAnimLength;
				}
			}
			else
			{
				// when there is no target weight interpolation, just copy new to target
				SampleDataList.Append(NewSampleDataList);
				NewAnimLength = GetAnimationLengthFromSampleData(SampleDataList);
			}

			float& NormalizedCurrentTime = *(Instance.TimeAccumulator);
			const float NormalizedPreviousTime = NormalizedCurrentTime;

			if (Context.IsLeader())
			{
				// advance current time - blend spaces hold normalized time as when dealing with changing anim length it would be possible to go backwards
				UE_LOG(LogAnimation, Verbose, TEXT("BlendSpace(%s) - BlendInput(%s) : AnimLength(%0.5f) "), *GetName(), *BlendInput.ToString(), NewAnimLength);

				// Advance time using current/new anim length
				float CurrentTime = NormalizedCurrentTime * NewAnimLength;
				FAnimationRuntime::AdvanceTime(Instance.bLooping, MoveDelta, /*inout*/ CurrentTime, NewAnimLength);
				NormalizedCurrentTime = NewAnimLength ? (CurrentTime / NewAnimLength) : 0.0f;

				Context.SetSyncPoint(NormalizedCurrentTime);
			}
			else
			{
				NormalizedCurrentTime = Context.GetSyncPoint();
			}

			// generate notifies and sets time
			{
				TArray<const FAnimNotifyEvent*> Notifies;

				// now calculate time for each samples
				const float ClampedNormalizedPreviousTime = FMath::Clamp<float>(NormalizedPreviousTime, 0.f, 1.f);
				const float ClampedNormalizedCurrentTime = FMath::Clamp<float>(NormalizedCurrentTime, 0.f, 1.f);
				const bool bGenerateNotifies = Context.ShouldGenerateNotifies() && (NormalizedCurrentTime != NormalizedPreviousTime) && NotifyTriggerMode != ENotifyTriggerMode::None;
				int32 HighestWeightIndex = 0;

				// Get the index of the highest weight, assuming that the first is the highest until we find otherwise
				bool bTriggerNotifyHighestWeightedAnim = NotifyTriggerMode == ENotifyTriggerMode::HighestWeightedAnimation && SampleDataList.Num() > 0;
				if(bGenerateNotifies && bTriggerNotifyHighestWeightedAnim)
				{
					float HighestWeight = SampleDataList[HighestWeightIndex].GetWeight();
					for(int32 I = 1 ; I < SampleDataList.Num(); I++)
					{
						if(SampleDataList[I].GetWeight() > HighestWeight)
						{
							HighestWeightIndex = I;
							HighestWeight = SampleDataList[I].GetWeight();
						}
					}
				}
				
				for (int32 I = 0; I < SampleDataList.Num(); ++I)
				{
					FBlendSampleData& SampleEntry = SampleDataList[I];
					const int32 SampleDataIndex = SampleEntry.SampleDataIndex;

					// Skip SamplesPoints that has no relevant weight
					if( SampleData.IsValidIndex(SampleDataIndex) && (SampleEntry.TotalWeight > ZERO_ANIMWEIGHT_THRESH) )
					{
						const FBlendSample& Sample = SampleData[SampleDataIndex];
						if( Sample.Animation )
						{
							const float SampleNormalizedPreviousTime = Sample.Animation->RateScale >= 0.f ? ClampedNormalizedPreviousTime : 1.f - ClampedNormalizedPreviousTime;
							const float SampleNormalizedCurrentTime = Sample.Animation->RateScale >= 0.f ? ClampedNormalizedCurrentTime : 1.f - ClampedNormalizedCurrentTime;

							const float PrevSampleDataTime = SampleNormalizedPreviousTime * Sample.Animation->SequenceLength;
							float& CurrentSampleDataTime = SampleEntry.Time;
							CurrentSampleDataTime = SampleNormalizedCurrentTime * Sample.Animation->SequenceLength;

							// Figure out delta time 
							float DeltaTimePosition = CurrentSampleDataTime - PrevSampleDataTime;
							const float SampleMoveDelta = MoveDelta * Sample.Animation->RateScale;

							// if we went against play rate, then loop around.
							if ((SampleMoveDelta * DeltaTimePosition) < 0.f)
							{
								DeltaTimePosition += FMath::Sign<float>(SampleMoveDelta) * Sample.Animation->SequenceLength;
							}

							if( bGenerateNotifies && (!bTriggerNotifyHighestWeightedAnim || (I == HighestWeightIndex)))
							{
								// Harvest and record notifies
								Sample.Animation->GetAnimNotifies(PrevSampleDataTime, DeltaTimePosition, Instance.bLooping, Notifies);
							}

							if (Context.RootMotionMode == ERootMotionMode::RootMotionFromEverything && Sample.Animation->bEnableRootMotion)
							{
								Context.RootMotionMovementParams.AccumulateWithBlend(Sample.Animation->ExtractRootMotion(PrevSampleDataTime, DeltaTimePosition, Instance.bLooping), SampleEntry.GetWeight());
							}


							UE_LOG(LogAnimation, Verbose, TEXT("%d. Blending animation(%s) with %f weight at time %0.2f"), I+1, *Sample.Animation->GetName(), SampleEntry.GetWeight(), CurrentSampleDataTime);
						}
					}
				}

				if (bGenerateNotifies && Notifies.Num() > 0)
				{
					InstanceOwner->AddAnimNotifies(Notifies, Instance.EffectiveBlendWeight);
				}
			}
		}
		OldSampleDataList.Reset();
		NewSampleDataList.Reset();
	}
}