bool UTankAimingComponent::IsBarrelMoving() const
{
	if (!ensure(Barrel)) { return false; }

	FVector BarrelForward = Barrel->GetForwardVector();
	return !BarrelForward.Equals(AimForwardDirection, DeltaTolerance);
}
void UCrowdFollowingComponent::FollowPathSegment(float DeltaTime)
{
	if (!bEnableCrowdSimulation)
	{
		Super::FollowPathSegment(DeltaTime);
		return;
	}

	if (bUpdateDirectMoveVelocity)
	{
		const FVector CurrentTargetPt = DestinationActor.IsValid() ? DestinationActor->GetActorLocation() : GetCurrentTargetLocation();
		const FVector AgentLoc = GetCrowdAgentLocation();
		const FVector NewDirection = (CurrentTargetPt - AgentLoc).GetSafeNormal();

		const bool bDirectionChanged = !NewDirection.Equals(CrowdAgentMoveDirection);
		if (bDirectionChanged)
		{
			CurrentDestination.Set(Path->GetBaseActor(), CurrentTargetPt);
			CrowdAgentMoveDirection = NewDirection;
			MoveSegmentDirection = NewDirection;

			UCrowdManager* Manager = UCrowdManager::GetCurrent(GetWorld());
			Manager->SetAgentMoveDirection(this, NewDirection);

			UE_VLOG(GetOwner(), LogCrowdFollowing, Log, TEXT("Updated direct move direction for crowd agent."));
		}
	}

	UpdateMoveFocus();
}
bool UCapsuleComponent::AreSymmetricRotations(const FQuat& A, const FQuat& B, const FVector& Scale3D) const
{
	if (Scale3D.X != Scale3D.Y)
	{
		return false;
	}

	const FVector AUp = A.GetAxisZ();
	const FVector BUp = B.GetAxisZ();
	return AUp.Equals(BUp);
}
Esempio n. 4
0
 /** Check if this vertex is in the same place as given point */
 FORCEINLINE bool IsSame( const FVector &v )
 {
     const float eps = 0.01f;
     return v.Equals( Position, eps );
 }
Esempio n. 5
0
bool UKismetMathLibrary::NotEqual_VectorVector(FVector A, FVector B, float ErrorTolerance)
{
	return !A.Equals(B, ErrorTolerance);
}	
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();
	}
}
Esempio n. 7
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();
	}
}
void AMyCharacter::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	// Get mouse position
	APlayerController* pc = Cast<APlayerController>(GetController());

	float mouseX = 0.f;
	float mouseY = 0.f;

	FVector mousePos = FVector::ZeroVector;

	if (pc->GetMousePosition(mouseX, mouseY))
	{
		mousePos = FVector(mouseX, mouseY, 0.0f);
		// UE_LOG(LogTemp, Warning, TEXT("Mouse X %f: MouseY %f"), mouseX, mouseY);
	}

	// Calculate look at direction
	const FVector2D ViewportSize = FVector2D(GEngine->GameViewport->Viewport->GetSizeXY());

	FVector middleOfScreen = FVector(ViewportSize.X / 2, ViewportSize.Y / 2, 0.0f);

	FVector directionToMouse = mousePos - middleOfScreen;

	FRotator UpperBodyRot = directionToMouse.Rotation();

	UpperBodyRot.Roll = -90.0f;
	UpperBodyRot.Pitch = 0.0f;
	UpperBodyRot.Yaw = UpperBodyRot.Yaw + 90.0f;

	UpperBodyFB->SetWorldRotation(UpperBodyRot);

	// Determine Heading of lower Body
	FVector upperBodyVec = UpperBodyRot.Vector();
	upperBodyVec.Normalize();

	FVector vel = GetVelocity();
	vel.Z = 0.0f; // Ignore the movement in the vertical direction like when he is falling.

	// Calculate Movement Vector
	if (!vel.Equals(FVector::ZeroVector))
	{
		MovementDirectionVec = vel;
	}

	MovementDirectionVec.Normalize();

	// Calculate Angle
	float dotProduct = FVector::DotProduct(upperBodyVec, MovementDirectionVec);
	float magnitudes = upperBodyVec.Size() * MovementDirectionVec.Size();

	float AimAtAngle = FMath::RadiansToDegrees(acosf(dotProduct / magnitudes));

	FVector sideVector = UpperBodyRot.Vector().RotateAngleAxis(90.0f, FVector(0, 0, 1));

	// Greater than 0 is left, Less than zero is right
	float sideDirection = FVector::DotProduct(sideVector, MovementDirectionVec);

	// He is looking left
	if (sideDirection < 0 && AimAtAngle > StrafingStartAngle && AimAtAngle < StrafingEndAngle)
	{
		CurMovementAction = EMovementActions::STRAFING_LEFT;
	}

	// He is looking right
	if (sideDirection > 0 && AimAtAngle > StrafingStartAngle && AimAtAngle < StrafingEndAngle)
	{
		CurMovementAction = EMovementActions::STRAFING_RIGHT;
	}

	// Is he moving forward in relation to where he is facing
	if (AimAtAngle < StrafingStartAngle)
	{
		CurMovementAction = EMovementActions::MOVING_FORWARDS;
	}

	// Is he moving backward in relation to where he is facing
	if (AimAtAngle > StrafingEndAngle)
	{
		CurMovementAction = EMovementActions::MOVING_BACKWARDS;
	}

	// Debug
	// Draw Vectors for debugging
	// UE_LOG(LogTemp, Warning, TEXT("Angle %f"), AimAtAngle);

	DrawDebugLine(GetWorld(), UpperBodyFB->GetComponentLocation(), UpperBodyFB->GetComponentLocation() + (upperBodyVec * 500), FColor::Red, false, -1, 0, 12.333);
	DrawDebugLine(GetWorld(), UpperBodyFB->GetComponentLocation(), UpperBodyFB->GetComponentLocation() + (sideVector * 500), FColor::Green, false, -1, 0, 12.333);
	DrawDebugLine(GetWorld(), GetActorLocation(), GetActorLocation() + (MovementDirectionVec * 500), FColor::Yellow, false, -1, 0, 12.333);

	// Movement
	// UE_LOG(LogTemp, Warning, TEXT("Moving Forward %d, Strafing Left %d, Moving Backward %d, Strafing Right %d"), bIsMovingForwards, bIsStrafingLeft, bIsMovingBackwards, bIsStrafingRight);
	// UE_LOG(LogTemp, Warning, TEXT("Dot Product: %f"), dotProduct);
	// UE_LOG(LogTemp, Warning, TEXT("Side Direction: %f"), sideDirection);
	UE_LOG(LogTemp, Warning, TEXT("MovementDirectionVec: X = %f, Y = %f, Z = %f"), MovementDirectionVec.X, MovementDirectionVec.Y, MovementDirectionVec.Z);

	switch (CurMovementAction)
	{
	case EMovementActions::STRAFING_LEFT:
		UE_LOG(LogTemp, Warning, TEXT("STRAFING_LEFT"));
		break;
	case EMovementActions::STRAFING_RIGHT:
		UE_LOG(LogTemp, Warning, TEXT("STRAFING_RIGHT"));
		break;
	case EMovementActions::MOVING_FORWARDS:
		UE_LOG(LogTemp, Warning, TEXT("MOVING_FORWARDS"));
		break;
	case EMovementActions::MOVING_BACKWARDS:
		UE_LOG(LogTemp, Warning, TEXT("MOVING_BACKWARDS"));
		break;
	}
}