#if WITH_EDITOR _/**_ _ * Initialize render data (e.g. vertex buffers) from model info_ _ * @param InLODModel The model to build the render data from._ _ * @param InVertexAttributeInfos The vertex attributes to possibly include and their stored data type._ _ * @param InBuildFlags See ESkeletalMeshVertexFlags._ _ */_ _ _void ENGINE_API BuildFromLODModel( const FSkeletalMeshLODModel* InLODModel, TConstArrayView<FSkeletalMeshVertexAttributeInfo> InVertexAttributeInfos = {}, ESkeletalMeshVertexFlags InBuildFlags = ESkeletalMeshVertexFlags::None ); #endif _// WITH_EDITOR_ ```
if(bDoParallelUpdate) { // 执行多线程更新 DispatchParallelEvaluationTasks(TickFunction); } else { _// we cant update on a worker thread, so perform the work here_ _ _DoParallelEvaluationTasks_OnGameThread(); // 只在主线程更新 PostAnimEvaluation(AnimEvaluationContext); }
_// set up a task to run on the game thread to accept the results_ FGraphEventArray Prerequistes; Prerequistes.Add(ParallelAnimationEvaluationTask); FGraphEventRef TickCompletionEvent = TGraphTask<FParallelAnimationCompletionTask>::CreateTask(&Prerequistes).ConstructAndDispatchWhenReady(this);
UAnimSequenceBase* CurrentSequence = GetSequence(); if (CurrentSequence && !ensureMsgf(!CurrentSequence->IsA<UAnimMontage>(), TEXT("Sequence players do not support anim montages."))) { CurrentSequence = nullptr; }
if (bExpectedAdditive && !bIsAdditive) { FText Message = FText::Format(LOCTEXT("AdditiveMismatchWarning", "Trying to play a non-additive animation '{0}' into a pose that is expected to be additive in anim instance '{1}'"), FText::FromString(CurrentSequence->GetName()), FText::FromString(Output.AnimInstanceProxy->GetAnimInstanceName())); Output.LogMessage(EMessageSeverity::Warning, Message); }
_// update montage should run in game thread_ _// if we do multi threading, make sure this stays in game thread. _ _// This is because branch points need to execute arbitrary code inside this call._ Montage_Advance(DeltaSeconds);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
voidUAnimInstance::Montage_UpdateWeight(float DeltaSeconds) { if (MontageInstances.IsEmpty()) { return; }
SCOPE_CYCLE_COUNTER(STAT_Montage_UpdateWeight);
_// go through all montage instances, and update them_ _ // and make sure their weight is updated properly_ _ _for (int32 I=0; I<MontageInstances.Num(); ++I) { if ( MontageInstances[I] ) { MontageInstances[I]->UpdateWeight(DeltaSeconds); } } }
voidUAnimInstance::Montage_Advance(float DeltaSeconds) { _// We're about to tick montages, queue their events to they're triggered after batched anim notifies._ _ _bQueueMontageEvents = true;
if (MontageInstances.IsEmpty()) { return; }
SCOPE_CYCLE_COUNTER(STAT_Montage_Advance);
_// go through all montage instances, and update them_ _ // and make sure their weight is updated properly_ _ _for (int32 InstanceIndex = 0; InstanceIndex < MontageInstances.Num(); InstanceIndex++) { FAnimMontageInstance* const MontageInstance = MontageInstances[InstanceIndex]; _// should never be NULL_ _ _ensure(MontageInstance); if (MontageInstance && MontageInstance->IsValid()) { boolconst bUsingBlendedRootMotion = (RootMotionMode == ERootMotionMode::RootMotionFromEverything); boolconst bNoRootMotionExtraction = (RootMotionMode == ERootMotionMode::NoRootMotionExtraction);
_// Extract root motion if we are using blend root motion (RootMotionFromEverything) or if we are set to extract root _ _ // motion AND we are the active root motion instance. This is so we can make root motion deterministic for networking when_ _ // we are not using RootMotionFromEverything_ _ _bool const bExtractRootMotion = !MontageInstance->IsRootMotionDisabled() && (bUsingBlendedRootMotion || (!bNoRootMotionExtraction && (MontageInstance == GetRootMotionMontageInstance())));
_// If MontageInstances has been modified while executing MontageInstance->Advance(), MontageInstance is unsafe to_ _ // access further. This happens for example if MontageInstance->Advance() triggers an anim notify in which the user_ _ // destroys the owning actor which in turn calls UninitializeAnimation(), or when the anim notify causes any montage_ _ // to stop or start playing. We just check here if the current MontageInstance is still safe to access._ _ _if (!MontageInstances.IsValidIndex(InstanceIndex) || MontageInstances[InstanceIndex] != MontageInstance) { break; }
MontageInstance->MontageSync_PostUpdate();
#if DO_CHECK && WITH_EDITORONLY_DATA && 0 _// We need to re-check IsValid() here because Advance() could have terminated this Montage._ _ _if (MontageInstance.IsValid()) { _// print blending time and weight and montage name_ _ _UE_LOG(LogAnimMontage, Warning, TEXT("%d. Montage (%s), DesiredWeight(%0.2f), CurrentWeight(%0.2f), BlendingTime(%0.2f)"), I + 1, *MontageInstance->Montage->GetName(), MontageInstance->GetDesiredWeight(), MontageInstance->GetWeight(), MontageInstance->GetBlendTime()); } #endif } } }
if (IsValid()) { _// with custom curves, we can't just filter by weight_ _ // also if you have custom curve with longer 0, you'll likely to pause montage during that blending time_ _ // I think that is a bug. It still should move, the weight might come back later. _ _ _if (bPlaying) { constbool bExtractRootMotion = (OutRootMotionParams != nullptr) && Montage->HasRootMotion(); DeltaTimeRecord.Set(Position, 0.f);
bDidUseMarkerSyncThisTick = CanUseMarkerSync(); if (bDidUseMarkerSyncThisTick) { MarkersPassedThisTick.Reset(); } _/** _ _ Limit number of iterations for performance._ _ This can get out of control if PlayRate is set really high, or there is a hitch, and Montage is looping for example._ _ */_ _ _const int32 MaxIterations = 10; int32 NumIterations = 0;
_/** _ _ If we're hitting our max number of iterations for whatever reason,_ _ make sure we're not accumulating too much time, and go out of range._ _ */_ _ _if (MontageSubStepper.GetRemainingTime() < 10.f) { MontageSubStepper.AddEvaluationTime(DeltaTime); }
_// Gather active anim state notifies if DeltaTime == 0 (happens when TimeDilation is 0.f), so these are not prematurely ended_ _ _if (DeltaTime == 0.f) { HandleEvents(Position, Position, nullptr); }
while (bPlaying && MontageSubStepper.HasTimeRemaining() && (++NumIterations < MaxIterations)) { SCOPE_CYCLE_COUNTER(STAT_AnimMontageInstance_Advance_Iteration);
_// If current section is last one, check to trigger a blend out and if it hasn't stopped yet, see if we should stop_ _ // We check this even if we haven't moved, in case our position was different from last frame._ _ // (Code triggered a position jump)._ _ _if (!IsStopped() && bEnableAutoBlendOut) { const int32 CurrentSectionIndex = MontageSubStepper.GetCurrentSectionIndex(); check(NextSections.IsValidIndex(CurrentSectionIndex)); const int32 NextSectionIndex = bPlayingForward ? NextSections[CurrentSectionIndex] : PrevSections[CurrentSectionIndex]; if (NextSectionIndex == INDEX_NONE) { constfloat PlayTimeToEnd = MontageSubStepper.GetRemainingPlayTimeToSectionEnd(Position);
_// ... trigger blend out if within blend out time window._ _ _if (PlayTimeToEnd <= FMath::Max<float>(BlendOutTriggerTime, UE_KINDA_SMALL_NUMBER)) { constfloat BlendOutTime = bCustomBlendOutTriggerTime ? DefaultBlendOutTime : PlayTimeToEnd; Stop(FAlphaBlend(Montage->BlendOut, BlendOutTime), false); } } }
constbool bHaveMoved = (SubStepResult == EMontageSubStepResult::Moved); if (bHaveMoved) { if (bDidUseMarkerSyncThisTick) { Montage->MarkerData.CollectMarkersInRange(PreviousSubStepPosition, Position, MarkersPassedThisTick, SubStepDeltaMove); }
_// Extract Root Motion for this time slice, and accumulate it._ _ // IsRootMotionDisabled() can be changed by AnimNotifyState BranchingPoints while advancing, so it needs to be checked here._ _ _if (bExtractRootMotion && AnimInstance.IsValid() && !IsRootMotionDisabled()) { const FTransform RootMotion = Montage->ExtractRootMotionFromTrackRange(PreviousSubStepPosition, Position); if (bBlendRootMotion) { _// Defer blending in our root motion until after we get our slot weight updated_ _ _const float Weight = Blend.GetBlendedValue(); AnimInstance.Get()->QueueRootMotionBlend(RootMotion, Montage->SlotAnimTracks[0].SlotName, Weight); } else { OutRootMotionParams->Accumulate(RootMotion); }
_// Delegate has to be called last in this loop_ _ // so that if this changes position, the new position will be applied in the next loop_ _ // first need to have event handler to handle it_ _ // Save off position before triggering events, in case they cause a jump to another position_ _ _const float PositionBeforeFiringEvents = Position;
if(bHaveMoved) { _// Save position before firing events._ _ _if (!bInterrupted) { _// Must grab a reference on the stack in case "this" is deleted during iteration_ _ _TWeakObjectPtr<UAnimInstance> AnimInstanceLocal = AnimInstance;
_// Break out if we no longer have active montage instances. This may happen when we call UninitializeAnimation from a notify_ _ _if (AnimInstanceLocal.IsValid() && AnimInstanceLocal->MontageInstances.Num() == 0) { return; } } }
_// Note that we have to check this even if there is no time remaining, in order to correctly handle loops_ _ // CVar allows reverting to old behavior, in case a project relies on it_ _ _if (MontageCVars::bEndSectionRequiresTimeRemaining == false || MontageSubStepper.HasTimeRemaining()) { _// if we reached end of section, and we were not processing a branching point, and no events has messed with out current position.._ _ // .. Move to next section._ _ // (this also handles looping, the same as jumping to a different section)._ _ _if (MontageSubStepper.HasReachedEndOfSection() && !BranchingPointMarker && (PositionBeforeFiringEvents == Position)) { _// Get recent NextSectionIndex in case it's been changed by previous events._ _ _const int32 CurrentSectionIndex = MontageSubStepper.GetCurrentSectionIndex(); const int32 RecentNextSectionIndex = bPlayingForward ? NextSections[CurrentSectionIndex] : PrevSections[CurrentSectionIndex]; constfloat EndOffset = UE_KINDA_SMALL_NUMBER / 2.f; _//KINDA_SMALL_NUMBER/2 because we use KINDA_SMALL_NUMBER to offset notifies for triggering and SMALL_NUMBER is too small_
_// Jump to next section's appropriate starting point (start or end)._ _ _Position = bPlayingForward ? LatestNextSectionStartTime : (LatestNextSectionEndTime - EndOffset); SubStepResult = EMontageSubStepResult::Moved; } else { _// If there is no next section and we've reached the end of this one, exit_
_ // Stop playing and clamp position to prevent playing animation data past the end of the current section_ _ // We already called Stop above if needed, like if bEnableAutoBlendOut is true_ _ _bPlaying = false;
if (SubStepResult == EMontageSubStepResult::NotMoved) { _// If it hasn't moved, there is nothing much to do but weight update_ _ _break; } } _// if we had a ForcedNextPosition set, reset it._ _ _ForcedNextToPosition.Reset(); ForcedNextFromPosition.Reset(); } }
_// If this Montage has no weight, it should be terminated._ _ _if (IsStopped() && (Blend.IsComplete())) { _// nothing else to do_ _ _Terminate(); return; }
if (!bInterrupted && AnimInstance.IsValid()) { SCOPE_CYCLE_COUNTER(STAT_AnimMontageInstance_TickBranchPoints);
_// Must grab a reference on the stack in case "this" is deleted during iteration_ _ _TWeakObjectPtr<UAnimInstance> AnimInstanceLocal = AnimInstance;
_// Tick all active state branching points_ _ _for (int32 Index = 0; Index < ActiveStateBranchingPoints.Num(); Index++) { FAnimNotifyEvent& NotifyEvent = ActiveStateBranchingPoints[Index]; if (NotifyEvent.NotifyStateClass) { FBranchingPointNotifyPayload BranchingPointNotifyPayload(AnimInstance->GetSkelMeshComponent(), Montage, &NotifyEvent, InstanceID); NotifyEvent.NotifyStateClass->BranchingPointNotifyTick(BranchingPointNotifyPayload, DeltaTime);
_// Break out if we no longer have active montage instances. This may happen when we call UninitializeAnimation from a notify_ _ _if (!ValidateInstanceAfterNotifyState(AnimInstanceLocal, NotifyEvent.NotifyStateClass)) { return; } } } } }
_// Find lengths of upper and lower limb in the ref skeleton._ _ // Use actual sizes instead of ref skeleton, so we take into account translation and scaling from other bone controllers._ _ _double MaxLimbLength = LowerLimbLength + UpperLimbLength;
_// Check to handle case where DesiredPos is the same as RootPos._ _ _FVector DesiredDir; if (DesiredLength < DOUBLE_KINDA_SMALL_NUMBER) { DesiredLength = DOUBLE_KINDA_SMALL_NUMBER; DesiredDir = FVector(1, 0, 0); } else { DesiredDir = DesiredDelta.GetSafeNormal(); }
_// Get joint target (used for defining plane that joint should be in)._ _ _FVector JointTargetDelta = JointTarget - RootPos; constdouble JointTargetLengthSqr = JointTargetDelta.SizeSquared();
_// Same check as above, to cover case when JointTarget position is the same as RootPos._ _ _FVector JointPlaneNormal, JointBendDir; if (JointTargetLengthSqr < FMath::Square(DOUBLE_KINDA_SMALL_NUMBER)) { JointBendDir = FVector(0, 1, 0); JointPlaneNormal = FVector(0, 0, 1); } else { JointPlaneNormal = DesiredDir ^ JointTargetDelta;
_// If we are trying to point the limb in the same direction that we are supposed to displace the joint in, _ _ // we have to just pick 2 random vector perp to DesiredDir and each other._ _ _if (JointPlaneNormal.SizeSquared() < FMath::Square(DOUBLE_KINDA_SMALL_NUMBER)) { DesiredDir.FindBestAxisVectors(JointPlaneNormal, JointBendDir); } else { JointPlaneNormal.Normalize();
_// Find the final member of the reference frame by removing any component of JointTargetDelta along DesiredDir._ _ // This should never leave a zero vector, because we've checked DesiredDir and JointTargetDelta are not parallel._ _ _JointBendDir = JointTargetDelta - ((JointTargetDelta | DesiredDir) * DesiredDir); JointBendDir.Normalize(); } }
_// If we are trying to reach a goal beyond the length of the limb, clamp it to something solvable and extend limb fully._ _ _if (DesiredLength >= MaxLimbLength) { OutEndPos = RootPos + (MaxLimbLength * DesiredDir); OutJointPos = RootPos + (UpperLimbLength * DesiredDir); } else { _// So we have a triangle we know the side lengths of. We can work out the angle between DesiredDir and the direction of the upper limb_ _ // using the sin rule:_ _ _const double TwoAB = 2.0 * UpperLimbLength * DesiredLength;
_// If CosAngle is less than 0, the upper arm actually points the opposite way to DesiredDir, so we handle that._ _ _const bool bReverseUpperBone = (CosAngle < 0.0);
_// Angle between upper limb and DesiredDir_ _ // ACos clamps internally so we dont need to worry about out-of-range values here._ _ _const double Angle = FMath::Acos(CosAngle);
_// Now we calculate the distance of the joint from the root -> effector line._ _ // This forms a right-angle triangle, with the upper limb as the hypotenuse._ _ _const double JointLineDist = UpperLimbLength * FMath::Sin(Angle);
_// And the final side of that triangle - distance along DesiredDir of perpendicular._ _ // ProjJointDistSqr can't be neg, because JointLineDist must be <= UpperLimbLength because appSin(Angle) is <= 1._ _ _const double ProjJointDistSqr = (UpperLimbLength*UpperLimbLength) - (JointLineDist*JointLineDist); _// although this shouldn't be ever negative, sometimes Xbox release produces -0.f, causing ProjJointDist to be NaN_ _ // so now I branch it. _ _ _double ProjJointDist = (ProjJointDistSqr > 0.0) ? FMath::Sqrt(ProjJointDistSqr) : 0.0; if (bReverseUpperBone) { ProjJointDist *= -1.f; }
_// So now we can work out where to put the joint!_ _ _OutJointPos = RootPos + (ProjJointDist * DesiredDir) + (JointLineDist * JointBendDir); } }
// 大腿沿着重力方向,与地面相交的点 _// Project Thigh Bone Location on plane made of FootIKLocation and FloorPlaneNormal, along Gravity Dir._ _// This will be the StrideWarpingPlaneOrigin_ const FVector StrideWarpingPlaneOrigin = (FMath::Abs(ResolvedGravityDirection | ResolvedFloorNormal) > DELTA) ? FMath::LinePlaneIntersection(ThighBoneLocation, ThighBoneLocation + ResolvedGravityDirection, IKFootLocation, ResolvedFloorNormal) : IKFootLocation;
// 脚的位置和给出的点,前进的方向所确定的平面 _// Project FK Foot along StrideWarping Plane, this will be our Scale Origin_ const FVector ScaleOrigin = FVector::PointPlaneProject(IKFootLocation, StrideWarpingPlaneOrigin, ActualStrideDirection);
// 脚的位置投影到上面的平面,可以拿到该长度,为脚步的大小,直接乘以放大系数,得到了目标位置 _// Now the ScaleOrigin and IKFootLocation are forming a line parallel to the floor, and we can scale the IK foot._ const FVector WarpedLocation = ScaleOrigin + (IKFootLocation - ScaleOrigin) * ActualStrideScale; Foot.IKFootBoneTransform.SetLocation(WarpedLocation);