Пример #1
0
void UAnimPreviewInstance::MontagePreview_StepBackward()
{
	if (UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset))
	{
		bool bWasPlaying = IsPlayingMontage() && (bLooping || bPlaying); // we need to handle non-looped case separately, even if paused during playthrough
		MontagePreview_SetReverse(true);
		if (! bWasPlaying)
		{
			if (! bLooping)
			{
				float StoppedAt = CurrentTime;
				if (! bWasPlaying)
				{
					// play montage but at last known location
					MontagePreview_Restart();
					SetPosition(StoppedAt, false);
				}
				int32 LastPreviewSectionIdx = MontagePreview_FindLastSection(MontagePreviewStartSectionIdx);
				if (FMath::Abs(CurrentTime - (Montage->CompositeSections[LastPreviewSectionIdx].GetTime() + Montage->GetSectionLength(LastPreviewSectionIdx))) <= MontagePreview_CalculateStepLength())
				{
					// special case as we could stop at the end of our last section which is also beginning of following section - we don't want to get stuck there, but be inside of our starting section
					Montage_JumpToSection(Montage->GetSectionName(LastPreviewSectionIdx));
				}
				else if (FMath::Abs(CurrentTime - Montage->CompositeSections[MontagePreviewStartSectionIdx].GetTime()) <= MontagePreview_CalculateStepLength())
				{
					// we're at the end of playing backward, jump right to the end
					Montage_JumpToSectionsEnd(Montage->GetSectionName(MontagePreviewStartSectionIdx));
					if (! bWasPlaying)
					{
						MontagePreview_SetPlaying(false);
					}
					return; // can't go further than beginning of first section
				}
			}
			else
			{
				MontagePreview_Restart();
			}
		}
		MontagePreview_SetPlaying(true);

		// Advance a single frame, leaving it paused afterwards
		int32 NumFrames = Montage->GetNumberOfFrames();
		// Add DELTA to prefer next frame when we're close to the boundary
		float CurrentFraction = CurrentTime / Montage->SequenceLength + DELTA;
		float NextFrame = FMath::Clamp<float>(FMath::FloorToFloat(CurrentFraction * NumFrames) - 1.0f, 0, NumFrames);
		float NewTime = Montage->SequenceLength * (NextFrame / NumFrames);

		GetSkelMeshComponent()->GlobalAnimRateScale = 1.0f;
		GetSkelMeshComponent()->TickAnimation(FMath::Abs(NewTime - CurrentTime));

		MontagePreview_SetPlaying(false);
	}
}
void UAnimSingleNodeInstance::SetVertexAnimation(UVertexAnimation * NewVertexAnim, bool bIsLooping, float InPlayRate)
{
	if (NewVertexAnim != CurrentVertexAnim)
	{
		CurrentVertexAnim = NewVertexAnim;
	}

	if (USkeletalMeshComponent * MeshComponent = GetSkelMeshComponent())
	{
		if (MeshComponent->SkeletalMesh == NULL)
		{
			// if it does not have SkeletalMesh, we nullify it
			CurrentVertexAnim = NULL;
		}
		else if (CurrentVertexAnim != NULL)
		{
			// if we have an anim, make sure their mesh matches, otherwise, null it
			if (MeshComponent->SkeletalMesh != CurrentVertexAnim->BaseSkelMesh)
			{
				// clear asset since we do not have matching skeleton
				CurrentVertexAnim = NULL;
			}
		}
	}

	bLooping = bIsLooping;
	PlayRate = InPlayRate;

	// reinitialize
	InitializeAnimation();
}
Пример #3
0
void UAnimInstance::SequenceEvaluatePose(UAnimSequenceBase* Sequence, FA2Pose& Pose, const FAnimExtractContext & ExtractionContext)
{
	SCOPE_CYCLE_COUNTER(STAT_AnimNativeEvaluatePoses);
	checkSlow( RequiredBones.IsValid() );

	USkeletalMeshComponent* Component = GetSkelMeshComponent();

	if(const UAnimSequence* AnimSequence = Cast<const UAnimSequence>(Sequence))
	{
		FAnimationRuntime::GetPoseFromSequence(
			AnimSequence,
			RequiredBones,
			/*out*/ Pose.Bones, 
			ExtractionContext);
	}
	else if(const UAnimComposite* Composite = Cast<const UAnimComposite>(Sequence))
	{
		FAnimationRuntime::GetPoseFromAnimTrack(
			Composite->AnimationTrack, 
			RequiredBones, 
			/*out*/ Pose.Bones,
			ExtractionContext);
	}
	else
	{
#if 0
		UE_LOG(LogAnimation, Log, TEXT("FAnimationRuntime::GetPoseFromSequence - %s - No animation data!"), *GetFName());
#endif
		FAnimationRuntime::FillWithRefPose(Pose.Bones, RequiredBones);
	}
}
Пример #4
0
void UAnimPreviewInstance::SetKeyImplementation(const FCompactPose& PreControllerInLocalSpace, const FCompactPose& PostControllerInLocalSpace)
{
#if WITH_EDITOR
	// evaluate the curve data first
	UAnimSequence* CurrentSequence = Cast<UAnimSequence>(CurrentAsset);
	UDebugSkelMeshComponent* Component = Cast<UDebugSkelMeshComponent> (GetSkelMeshComponent());

	if(CurrentSequence && CurrentSkeleton && Component && Component->SkeletalMesh)
	{
		FScopedTransaction ScopedTransaction(LOCTEXT("SetKey", "Set Key"));
		CurrentSequence->Modify(true);
		Modify();

		TArray<FName> BonesToModify;
		// need to get component transform first. Depending on when this gets called, the transform is not up-to-date. 
		// first look at the bonecontrollers, and convert each bone controller to transform curve key
		// and add new curvebonecontrollers with additive data type
		// clear bone controller data
		for(auto& SingleBoneController : BoneControllers)
		{
			// find bone name, and just get transform of the bone in local space
			// and get the additive data
			// find if this already exists, then just add curve data only
			FName BoneName = SingleBoneController.BoneToModify.BoneName;
			// now convert data
			const FMeshPoseBoneIndex MeshBoneIndex(Component->GetBoneIndex(BoneName));
			const FCompactPoseBoneIndex BoneIndex = RequiredBones.MakeCompactPoseIndex(MeshBoneIndex);
			FTransform  LocalTransform = PostControllerInLocalSpace[BoneIndex];

			// now we have LocalTransform and get additive data
			FTransform AdditiveTransform = LocalTransform.GetRelativeTransform(PreControllerInLocalSpace[BoneIndex]);
			AddKeyToSequence(CurrentSequence, CurrentTime, BoneName, AdditiveTransform);

			BonesToModify.Add(BoneName);
		}

		// see if the bone is selected right now and if that is added - if bone is selected, we should add identity key to it. 
		if ( Component->BonesOfInterest.Num() > 0 )
		{
			// if they're selected, we should add to the modifyBone list even if they're not modified, so that they can key that point. 
			// first make sure those are added 
			// if not added, make sure to set the key for them
			for (const auto& BoneIndex : Component->BonesOfInterest)
			{
				FName BoneName = Component->GetBoneName(BoneIndex);
				// if it's not on BonesToModify, add identity here. 
				if (!BonesToModify.Contains(BoneName))
				{
					AddKeyToSequence(CurrentSequence, CurrentTime, BoneName, FTransform::Identity);
				}
			}
		}

		ResetModifiedBone(false);

		OnSetKeyCompleteDelegate.ExecuteIfBound();
	}
#endif
}
Пример #5
0
void UAnimSingleNodeInstance::SetAnimationAsset(class UAnimationAsset* NewAsset,bool bIsLooping,float InPlayRate)
{
	if (NewAsset != CurrentAsset)
	{
		CurrentAsset = NewAsset;
	}

	if (USkeletalMeshComponent * MeshComponent = GetSkelMeshComponent())
	{
		if (MeshComponent->SkeletalMesh == NULL)
		{
			// if it does not have SkeletalMesh, we nullify it
			CurrentAsset = NULL;
		}
		else if (CurrentAsset != NULL)
		{
			// if we have an asset, make sure their skeleton matches, otherwise, null it
			if (CurrentSkeleton != CurrentAsset->GetSkeleton())
			{
				// clear asset since we do not have matching skeleton
				CurrentAsset = NULL;
			}
		}
	}

	bLooping = bIsLooping;
	PlayRate = InPlayRate;
	CurrentTime = 0.f;
	BlendSpaceInput = FVector::ZeroVector;
#if WITH_EDITORONLY_DATA
	PreviewPoseCurrentTime = 0.0f;
#endif

	UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset);
	if ( Montage!=NULL )
	{
		ReinitializeSlotNodes();
		if ( Montage->SlotAnimTracks.Num() > 0 )
		{
			RegisterSlotNodeWithAnimInstance(Montage->SlotAnimTracks[0].SlotName);
		}
		RestartMontage( Montage );
		SetPlaying(bPlaying);
	}
	else
	{
		// otherwise stop all montages
		StopAllMontages(0.25f);

		UBlendSpaceBase * BlendSpace = Cast<UBlendSpaceBase>(CurrentAsset);
		if(BlendSpace)
		{
			BlendSpace->InitializeFilter(&BlendFilter);
		}
	}
}
Пример #6
0
APawn* UAnimInstance::TryGetPawnOwner()
{
	USkeletalMeshComponent* OwnerComponent = GetSkelMeshComponent();
	if (AActor* OwnerActor = OwnerComponent->GetOwner())
	{
		return Cast<APawn>(OwnerActor);
	}

	return NULL;
}
Пример #7
0
void UAnimSingleNodeInstance::NativeInitializeAnimation()
{
	CurrentAsset = NULL;
	CurrentVertexAnim = NULL;
#if WITH_EDITORONLY_DATA
	PreviewPoseCurrentTime = 0.0f;
#endif

	// it's already doing it when evaluate
	BlendSpaceInput = FVector::ZeroVector;
	CurrentTime = 0.f;
	USkeletalMeshComponent* SkelComp = GetSkelMeshComponent();
	SkelComp->AnimationData.Initialize(this);
}
void UAnimSingleNodeInstance::InternalBlendSpaceEvaluatePose(class UBlendSpaceBase* BlendSpace, TArray<FBlendSampleData>& BlendSampleDataCache, struct FA2Pose& Pose, bool bIsLooping)
{
	USkeletalMeshComponent* Component = GetSkelMeshComponent();

	if (BlendSpace->IsValidAdditive())
	{
		FA2Pose BasePose;
		FA2Pose AdditivePose;
		BasePose.Bones.AddUninitialized(Pose.Bones.Num());
		AdditivePose.Bones.AddUninitialized(Pose.Bones.Num());

#if WITH_EDITORONLY_DATA
		if (BlendSpace->PreviewBasePose)
		{
			BlendSpace->PreviewBasePose->GetBonePose(/*out*/ BasePose.Bones, RequiredBones, FAnimExtractContext(PreviewPoseCurrentTime, bIsLooping));
		}
		else
#endif // WITH_EDITORONLY_DATA
		{
			// otherwise, get ref pose
			FAnimationRuntime::FillWithRefPose(BasePose.Bones, RequiredBones);
		}

		FAnimationRuntime::GetPoseFromBlendSpace(
			BlendSpace,
			BlendSampleDataCache, 
			bIsLooping,
			RequiredBones,
			/*out*/ AdditivePose.Bones);

		if (BlendSpace->IsA(UAimOffsetBlendSpace::StaticClass()) ||
			BlendSpace->IsA(UAimOffsetBlendSpace1D::StaticClass()) )
		{
			BlendRotationOffset(BasePose, AdditivePose, 1.f, Pose);
		}
		else
		{
			ApplyAdditiveSequence(BasePose, AdditivePose, 1.f, Pose);
		}
	}
	else
	{
		FAnimationRuntime::GetPoseFromBlendSpace(
			BlendSpace,
			BlendSampleDataCache, 
			bIsLooping,
			RequiredBones,
			/*out*/ Pose.Bones);
	}
}
void UAnimSingleNodeInstance::SetAnimationAsset(class UAnimationAsset* NewAsset,bool bIsLooping,float InPlayRate)
{
	if (NewAsset != CurrentAsset)
	{
		CurrentAsset = NewAsset;
	}

	if (USkeletalMeshComponent * MeshComponent = GetSkelMeshComponent())
	{
		if (MeshComponent->SkeletalMesh == NULL)
		{
			// if it does not have SkeletalMesh, we nullify it
			CurrentAsset = NULL;
		}
		else if (CurrentAsset != NULL)
		{
			// if we have an asset, make sure their skeleton matches, otherwise, null it
			if (CurrentSkeleton != CurrentAsset->GetSkeleton())
			{
				// clear asset since we do not have matching skeleton
				CurrentAsset = NULL;
			}
		}
	}

	bLooping = bIsLooping;
	PlayRate = InPlayRate;
	CurrentTime = 0.f;
#if WITH_EDITORONLY_DATA
	PreviewPoseCurrentTime = 0.0f;
#endif

	UAnimMontage* Montage = Cast<UAnimMontage>(CurrentAsset);
	if ( Montage!=NULL )
	{
		ActiveSlotWeights.Empty();
		if ( Montage->SlotAnimTracks.Num() > 0 )
		{
			RegisterSlotNode(Montage->SlotAnimTracks[0].SlotName);
		}
		RestartMontage( Montage );
	}
	else
	{
		// otherwise stop all montages
		StopAllMontages(0.25f);
	}
}
Пример #10
0
void UAnimInstance::RecalcRequiredBones()
{
	USkeletalMeshComponent * SkelMeshComp = GetSkelMeshComponent();
	check( SkelMeshComp )

	if( SkelMeshComp->SkeletalMesh && SkelMeshComp->SkeletalMesh->Skeleton )
	{
		RequiredBones.InitializeTo(SkelMeshComp->RequiredBones, *SkelMeshComp->SkeletalMesh);
	}
	else if( CurrentSkeleton != NULL )
	{
		RequiredBones.InitializeTo(SkelMeshComp->RequiredBones, *CurrentSkeleton);
	}

	// When RequiredBones mapping has changed, AnimNodes need to update their bones caches. 
	bBoneCachesInvalidated = true;
}
Пример #11
0
void UAnimSingleNodeInstance::InternalBlendSpaceEvaluatePose(class UBlendSpaceBase* BlendSpace, TArray<FBlendSampleData>& BlendSampleDataCache, FPoseContext& OutContext)
{
	USkeletalMeshComponent* Component = GetSkelMeshComponent();

	if (BlendSpace->IsValidAdditive())
	{
		FCompactPose& OutPose = OutContext.Pose;
		FBlendedCurve& OutCurve = OutContext.Curve;
		FCompactPose AdditivePose;
		FBlendedCurve AdditiveCurve;
		AdditivePose.SetBoneContainer(&OutPose.GetBoneContainer());
		AdditiveCurve.InitFrom(OutCurve);

#if WITH_EDITORONLY_DATA
		if (BlendSpace->PreviewBasePose)
		{
			BlendSpace->PreviewBasePose->GetBonePose(/*out*/ OutPose, /*out*/OutCurve, FAnimExtractContext(PreviewPoseCurrentTime));
		}
		else
#endif // WITH_EDITORONLY_DATA
		{
			// otherwise, get ref pose
			OutPose.ResetToRefPose();
		}

		BlendSpace->GetAnimationPose(BlendSampleDataCache, AdditivePose, AdditiveCurve);

		enum EAdditiveAnimationType AdditiveType = BlendSpace->bRotationBlendInMeshSpace? AAT_RotationOffsetMeshSpace : AAT_LocalSpaceBase;

		FAnimationRuntime::AccumulateAdditivePose(OutPose, AdditivePose, OutCurve, AdditiveCurve, 1.f, AdditiveType);
	}
	else
	{
		BlendSpace->GetAnimationPose(BlendSampleDataCache, OutContext.Pose, OutContext.Curve);
	}
}
Пример #12
0
void UAnimInstance::InitializeAnimation()
{
	// make sure your skeleton is initialized
	// you can overwrite different skeleton
	USkeletalMeshComponent* OwnerComponent = GetSkelMeshComponent();
	if (OwnerComponent->SkeletalMesh != NULL)
	{
		CurrentSkeleton = OwnerComponent->SkeletalMesh->Skeleton;
	}
	else
	{
		CurrentSkeleton = NULL;
	}

	if (UAnimBlueprintGeneratedClass* AnimBlueprintClass = Cast<UAnimBlueprintGeneratedClass>(GetClass()))
	{
		// Grab a pointer to the root node
		if (AnimBlueprintClass->RootAnimNodeProperty != NULL)
		{
			RootNode = AnimBlueprintClass->RootAnimNodeProperty->ContainerPtrToValuePtr<FAnimNode_Base>(this);
		}
		else
		{
			RootNode = NULL;
		}

		// if no mesh, use Blueprint Skeleton
		if (CurrentSkeleton == NULL)
		{
			CurrentSkeleton = AnimBlueprintClass->TargetSkeleton;
		}

#if WITH_EDITORONLY_DATA
		if (UAnimBlueprint* Blueprint = Cast<UAnimBlueprint>(AnimBlueprintClass->ClassGeneratedBy))
		{
			if (Blueprint->Status == BS_Error)
			{
				RootNode = NULL;
			}
		}
#endif

#if WITH_EDITOR
		LifeTimer = 0.0;
		CurrentLifeTimerScrubPosition = 0.0;

		if (UAnimBlueprint* Blueprint = Cast<UAnimBlueprint>(AnimBlueprintClass->ClassGeneratedBy))
		{
			if (Blueprint->GetObjectBeingDebugged() == this)
			{
				// Reset the snapshot buffer
				AnimBlueprintClass->GetAnimBlueprintDebugData().ResetSnapshotBuffer();
			}
		}
#endif
	}

	// before initialize, need to recalculate required bone list
	RecalcRequiredBones();

	// Clear cached list, we're about to re-update it.

	ActiveSlotWeights.Empty();

	ClearMorphTargets();
	NativeInitializeAnimation();
	BlueprintInitializeAnimation();

	if (RootNode != NULL)
	{
		IncrementContextCounter();
		FAnimationInitializeContext InitContext(this);
		RootNode->Initialize(InitContext);
	}
}
Пример #13
0
UWorld* UAnimInstance::GetWorld() const
{
	return GetSkelMeshComponent()->GetWorld();
}
Пример #14
0
USkeletalMeshComponent* UAnimInstance::GetOwningComponent()
{
	return GetSkelMeshComponent();
}
Пример #15
0
AActor* UAnimInstance::GetOwningActor()
{
	USkeletalMeshComponent* OwnerComponent = GetSkelMeshComponent();
	return OwnerComponent->GetOwner();
}
Пример #16
0
void UAnimInstance::TriggerAnimNotifies(float DeltaSeconds)
{
	TArray<const FAnimNotifyEvent *> NewActiveAnimNotifyState;
	USkeletalMeshComponent * SkelMeshComp = GetSkelMeshComponent();

	// Remove NULL entries.
	ActiveAnimNotifyState.RemoveSwap(NULL);

	for (int32 Index=0; Index<AnimNotifies.Num(); Index++)
	{
		const FAnimNotifyEvent * AnimNotifyEvent = AnimNotifies[Index];

		// AnimNotifyState
		if( AnimNotifyEvent->NotifyStateClass )
		{
			if( !ActiveAnimNotifyState.RemoveSingleSwap(AnimNotifyEvent) )
			{
				AnimNotifyEvent->NotifyStateClass->NotifyBegin(SkelMeshComp, Cast<UAnimSequence>(AnimNotifyEvent->NotifyStateClass->GetOuter()));
			}
			NewActiveAnimNotifyState.Add(AnimNotifyEvent);
			continue;
		}

		// This checking-for-blueprint class is a hack to allow the old UAnimNotify_* notifies to work for a little longer until they are all removed.
		// Once done, we can remove passing the UAnimNotify to the custom event notifies (since they cant contain custom/user data anyways).
		// The "is blue print class" can just become a "is notify != null".
		bool bIsBlueprintNotify = false;
		if( AnimNotifyEvent->Notify != NULL)
		{
			if( !AnimNotifies[Index]->Notify->GetClass()->HasAllClassFlags(CLASS_Native) )
			{
				bIsBlueprintNotify = true;
			}
		}

		if( bIsBlueprintNotify )
		{
			// Blueprint notify: just call Notify. UAnimNotify will forward this to the blueprintable event which will do the work.
			AnimNotifyEvent->Notify->Notify(SkelMeshComp, Cast<UAnimSequence>(AnimNotifyEvent->Notify->GetOuter()));
		}
		else if( AnimNotifyEvent->NotifyName != NAME_None )
		{
			// Custom Event based notifies. These will call a AnimNotify_* function on the AnimInstance.
			FString FuncName = FString::Printf(TEXT("AnimNotify_%s"), *AnimNotifyEvent->NotifyName.ToString());
			FName FuncFName = FName(*FuncName);

			UFunction* Function = FindFunction(FuncFName);
			if( Function )
			{
				// if parameter is none, add event
				if ( Function->NumParms == 0 )
				{
					ProcessEvent( Function, NULL );								
				}
				else if ( Function->NumParms == 1 &&  
					Cast<UObjectProperty>(Function->PropertyLink) != NULL)
				{
					struct FAnimNotifierHandler_Parms
					{
						UAnimNotify* Notify;
					};

					FAnimNotifierHandler_Parms Parms;
					Parms.Notify = AnimNotifyEvent->Notify;
					ProcessEvent( Function, &Parms );								
				}
				else
				{
					// Actor has event, but with different parameters. Print warning
					UE_LOG(LogAnimNotify, Warning, TEXT("Anim notifier named %s, but the parameter number does not match or not of the correct type"), *FuncName);
				}
			}
		}
	}

	// Send end notification to AnimNotifyState not active anymore.
	for(int32 Index=0; Index<ActiveAnimNotifyState.Num(); Index++)
	{
		const FAnimNotifyEvent * AnimNotifyEvent = ActiveAnimNotifyState[Index];
		AnimNotifyEvent->NotifyStateClass->NotifyEnd(SkelMeshComp, Cast<UAnimSequence>(AnimNotifyEvent->NotifyStateClass->GetOuter()));
	}

	// Switch our arrays.
	ActiveAnimNotifyState = NewActiveAnimNotifyState;

	// Tick currently active AnimNotifyState
	for(int32 Index=0; Index<ActiveAnimNotifyState.Num(); Index++)
	{
		const FAnimNotifyEvent * AnimNotifyEvent = ActiveAnimNotifyState[Index];
		AnimNotifyEvent->NotifyStateClass->NotifyTick(SkelMeshComp, Cast<UAnimSequence>(AnimNotifyEvent->NotifyStateClass->GetOuter()), DeltaSeconds);
	}
}
Пример #17
0
void UAnimInstance::AnimNotify_Sound(UAnimNotify* AnimNotify)
{
	AnimNotify->Notify(GetSkelMeshComponent(), NULL);
}
Пример #18
0
bool UAnimPreviewInstance::NativeEvaluateAnimation(FPoseContext& Output)
{
#if WITH_EDITORONLY_DATA
	if(bForceRetargetBasePose)
	{
		USkeletalMeshComponent* MeshComponent = GetSkelMeshComponent();
		if(MeshComponent && MeshComponent->SkeletalMesh)
		{
			FAnimationRuntime::FillWithRetargetBaseRefPose(Output.Pose, GetSkelMeshComponent()->SkeletalMesh);
		}
		else
		{
			// ideally we'll return just ref pose, but not sure if this will work with LODs
			Output.Pose.ResetToRefPose();
		}
	}
	else
#endif // #if WITH_EDITORONLY_DATA
	{
		Super::NativeEvaluateAnimation(Output);
	}

	if (bEnableControllers)
	{
		UDebugSkelMeshComponent* Component = Cast<UDebugSkelMeshComponent>(GetSkelMeshComponent());

		if(Component && CurrentSkeleton)
		{
			// update curve controllers
			UpdateCurveController();

			// create bone controllers from 
			if(BoneControllers.Num() > 0 || CurveBoneControllers.Num() > 0)
			{
				FCompactPose PreController, PostController;
				// if set key is true, we should save pre controller local space transform 
				// so that we can calculate the delta correctly
				if(bSetKey)
				{
					PreController = Output.Pose;
				}

				FCSPose<FCompactPose> OutMeshPose;
				OutMeshPose.InitPose(&RequiredBones);

				// apply curve data first
				ApplyBoneControllers(Component, CurveBoneControllers, OutMeshPose);

				// and now apply bone controllers data
				// it is possible they can be overlapping, but then bone controllers will overwrite
				ApplyBoneControllers(Component, BoneControllers, OutMeshPose);

				// convert back to local @todo check this
				OutMeshPose.ConvertToLocalPoses(Output.Pose);

				if(bSetKey)
				{
					// now we have post controller, and calculate delta now
					PostController = Output.Pose;
					SetKeyImplementation(PreController, PostController);
				}
			}
			// if any other bone is selected, still go for set key even if nothing changed
			else if(Component->BonesOfInterest.Num() > 0)
			{
				if(bSetKey)
				{
					// in this case, pose is same
					SetKeyImplementation(Output.Pose, Output.Pose);
				}
			}
		}

		// we should unset here, just in case somebody clicks the key when it's not valid
		if(bSetKey)
		{
			bSetKey = false;
		}
	}

	return true;
}