Files
UnrealEngine/Engine/Source/Runtime/AnimGraphRuntime/Private/BoneControllers/AnimNode_LookAt.cpp
2025-05-18 13:04:45 +08:00

299 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BoneControllers/AnimNode_LookAt.h"
#include "Components/SkeletalMeshComponent.h"
#include "Materials/Material.h"
#include "PrimitiveDrawingUtils.h"
#include "Engine/SkeletalMeshSocket.h"
#include "Animation/AnimInstanceProxy.h"
#include "Animation/AnimStats.h"
#include "AnimationCoreLibrary.h"
#include "Engine/Engine.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Animation/AnimTrace.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(AnimNode_LookAt)
static const FVector DefaultLookAtAxis(0.f, 1.f, 0.f);
static const FVector DefaultLookUpAxis(1.f, 0.f, 0.f);
/////////////////////////////////////////////////////
// FAnimNode_LookAt
FAnimNode_LookAt::FAnimNode_LookAt()
: LookAtLocation(FVector(100.f, 0.f, 0.f))
, LookAt_Axis(DefaultLookAtAxis)
, bUseLookUpAxis(false)
, InterpolationType(EInterpolationBlend::Linear)
, LookUp_Axis(DefaultLookUpAxis)
, LookAtClamp(0.f)
, InterpolationTime(0.f)
, InterpolationTriggerThreashold(0.f)
#if WITH_EDITORONLY_DATA
, LookAtAxis_DEPRECATED(EAxisOption::Y)
, CustomLookAtAxis_DEPRECATED(FVector(0.f, 1.f, 0.f))
, LookUpAxis_DEPRECATED(EAxisOption::X)
, CustomLookUpAxis_DEPRECATED(FVector(1.f, 0.f, 0.f))
#endif
, CurrentLookAtLocation(ForceInitToZero)
, CurrentTargetLocation(ForceInitToZero)
, PreviousTargetLocation(ForceInitToZero)
, AccumulatedInterpoolationTime(0.f)
, CachedCurrentTargetLocation(ForceInitToZero)
{
}
void FAnimNode_LookAt::GatherDebugData(FNodeDebugData& DebugData)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(GatherDebugData)
FString DebugLine = DebugData.GetNodeName(this);
DebugLine += "(";
AddDebugNodeData(DebugLine);
if (LookAtTarget.HasValidSetup())
{
DebugLine += FString::Printf(TEXT(" Bone: %s, Look At Target: %s, Look At Location: %s, Target Location : %s)"), *BoneToModify.BoneName.ToString(), *LookAtTarget.GetTargetSetup().ToString(), *LookAtLocation.ToString(), *CachedCurrentTargetLocation.ToString());
}
else
{
DebugLine += FString::Printf(TEXT(" Bone: %s, Look At Location : %s, Target Location : %s)"), *BoneToModify.BoneName.ToString(), *LookAtLocation.ToString(), *CachedCurrentTargetLocation.ToString());
}
DebugData.AddDebugItem(DebugLine);
ComponentPose.GatherDebugData(DebugData);
}
float FAnimNode_LookAt::AlphaToBlendType(float InAlpha, EInterpolationBlend::Type BlendType)
{
switch (BlendType)
{
case EInterpolationBlend::Sinusoidal: return FMath::Clamp<float>((FMath::Sin(InAlpha * PI - HALF_PI) + 1.f) / 2.f, 0.f, 1.f);
case EInterpolationBlend::Cubic: return FMath::Clamp<float>(FMath::CubicInterp<float>(0.f, 0.f, 1.f, 0.f, InAlpha), 0.f, 1.f);
case EInterpolationBlend::EaseInOutExponent2: return FMath::Clamp<float>(FMath::InterpEaseInOut<float>(0.f, 1.f, InAlpha, 2), 0.f, 1.f);
case EInterpolationBlend::EaseInOutExponent3: return FMath::Clamp<float>(FMath::InterpEaseInOut<float>(0.f, 1.f, InAlpha, 3), 0.f, 1.f);
case EInterpolationBlend::EaseInOutExponent4: return FMath::Clamp<float>(FMath::InterpEaseInOut<float>(0.f, 1.f, InAlpha, 4), 0.f, 1.f);
case EInterpolationBlend::EaseInOutExponent5: return FMath::Clamp<float>(FMath::InterpEaseInOut<float>(0.f, 1.f, InAlpha, 5), 0.f, 1.f);
}
return InAlpha;
}
void FAnimNode_LookAt::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(EvaluateSkeletalControl_AnyThread)
ANIM_MT_SCOPE_CYCLE_COUNTER_VERBOSE(LookAt, !IsInGameThread());
check(OutBoneTransforms.Num() == 0);
const FBoneContainer& BoneContainer = Output.Pose.GetPose().GetBoneContainer();
const FCompactPoseBoneIndex ModifyBoneIndex = BoneToModify.GetCompactPoseIndex(BoneContainer);
FTransform ComponentBoneTransform = Output.Pose.GetComponentSpaceTransform(ModifyBoneIndex);
// get target location
FTransform TargetTransform = LookAtTarget.GetTargetTransform(LookAtLocation, Output.Pose, Output.AnimInstanceProxy->GetComponentTransform());
FVector TargetLocationInComponentSpace = TargetTransform.GetLocation();
FVector OldCurrentTargetLocation = CurrentTargetLocation;
FVector NewCurrentTargetLocation = TargetLocationInComponentSpace;
if ((NewCurrentTargetLocation - OldCurrentTargetLocation).SizeSquared() > InterpolationTriggerThreashold*InterpolationTriggerThreashold)
{
if (AccumulatedInterpoolationTime >= InterpolationTime)
{
// reset current Alpha, we're starting to move
AccumulatedInterpoolationTime = 0.f;
}
PreviousTargetLocation = OldCurrentTargetLocation;
CurrentTargetLocation = NewCurrentTargetLocation;
}
else if (InterpolationTriggerThreashold == 0.f)
{
CurrentTargetLocation = NewCurrentTargetLocation;
}
if (InterpolationTime > 0.f)
{
float CurrentAlpha = AccumulatedInterpoolationTime/InterpolationTime;
if (CurrentAlpha < 1.f)
{
float BlendAlpha = AlphaToBlendType(CurrentAlpha, InterpolationType);
CurrentLookAtLocation = FMath::Lerp(PreviousTargetLocation, CurrentTargetLocation, BlendAlpha);
}
}
else
{
CurrentLookAtLocation = CurrentTargetLocation;
}
#if UE_ENABLE_DEBUG_DRAWING
CachedOriginalTransform = ComponentBoneTransform;
CachedTargetCoordinate = LookAtTarget.GetTargetTransform(FVector::ZeroVector, Output.Pose, Output.AnimInstanceProxy->GetComponentTransform());
CachedPreviousTargetLocation = PreviousTargetLocation;
CachedCurrentLookAtLocation = CurrentLookAtLocation;
#endif
CachedCurrentTargetLocation = CurrentTargetLocation;
// lookat vector
FVector LookAtVector = LookAt_Axis.GetTransformedAxis(ComponentBoneTransform);
// find look up vector in local space
FVector LookUpVector = LookUp_Axis.GetTransformedAxis(ComponentBoneTransform);
// Find new transform from look at info
FQuat DeltaRotation = AnimationCore::SolveAim(ComponentBoneTransform, CurrentLookAtLocation, LookAtVector, bUseLookUpAxis, LookUpVector, LookAtClamp);
ModifyPoseFromDeltaRotation(Output, OutBoneTransforms, ComponentBoneTransform, DeltaRotation);
// Sort OutBoneTransforms so indices are in increasing order.
OutBoneTransforms.Sort(FCompareBoneTransformIndex());
#if UE_ENABLE_DEBUG_DRAWING
CachedLookAtTransform = ComponentBoneTransform;
#endif
TRACE_ANIM_NODE_VALUE(Output, TEXT("Bone"), BoneToModify.BoneName);
TRACE_ANIM_NODE_VALUE(Output, TEXT("Look At Target"), LookAtTarget.HasValidSetup() ? LookAtTarget.GetTargetSetup() : NAME_None);
TRACE_ANIM_NODE_VALUE(Output, TEXT("Look At Location"), LookAtLocation);
TRACE_ANIM_NODE_VALUE(Output, TEXT("Target Location"), CachedCurrentTargetLocation);
}
void FAnimNode_LookAt::EvaluateComponentSpaceInternal(FComponentSpacePoseContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(EvaluateComponentSpaceInternal)
Super::EvaluateComponentSpaceInternal(Context);
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
/*if (bEnableDebug)
{
const FTransform LocalToWorld = Context.AnimInstanceProxy->GetComponentTransform();
FVector TargetWorldLoc = LocalToWorld.TransformPosition(CachedCurrentTargetLocation);
FVector SourceWorldLoc = LocalToWorld.TransformPosition(CachedComponentBoneLocation);
Context.AnimInstanceProxy->AnimDrawDebugLine(SourceWorldLoc, TargetWorldLoc, FColor::Green);
}*/
#endif // !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
}
bool FAnimNode_LookAt::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones)
{
// if both bones are valid
return (BoneToModify.IsValidToEvaluate(RequiredBones) &&
// or if name isn't set (use Look At Location) or Look at bone is valid
// do not call isValid since that means if look at bone isn't in LOD, we won't evaluate
// we still should evaluate as long as the BoneToModify is valid even LookAtBone isn't included in required bones
(!LookAtTarget.HasTargetSetup() || LookAtTarget.IsValidToEvaluate(RequiredBones)) );
}
#if WITH_EDITOR
// can't use World Draw functions because this is called from Render of viewport, AFTER ticking component,
// which means LineBatcher already has ticked, so it won't render anymore
// to use World Draw functions, we have to call this from tick of actor
void FAnimNode_LookAt::ConditionalDebugDraw(FPrimitiveDrawInterface* PDI, USkeletalMeshComponent* MeshComp) const
{
auto CalculateLookAtMatrixFromTransform = [this](const FTransform& BaseTransform) -> FMatrix
{
FVector TransformedLookAtAxis = BaseTransform.TransformVector(LookAt_Axis.Axis);
const FVector DefaultUpVector = BaseTransform.GetUnitAxis(EAxis::Z);
FVector UpVector = (bUseLookUpAxis) ? BaseTransform.TransformVector(LookUp_Axis.Axis) : DefaultUpVector;
// if parallel with up vector, find something else
if (FMath::Abs(FVector::DotProduct(UpVector, TransformedLookAtAxis)) > (1.f - ZERO_ANIMWEIGHT_THRESH))
{
UpVector = BaseTransform.GetUnitAxis(EAxis::X);
}
FVector RightVector = FVector::CrossProduct(TransformedLookAtAxis, UpVector);
FMatrix Matrix;
FVector Location = BaseTransform.GetLocation();
Matrix.SetAxes(&TransformedLookAtAxis, &RightVector, &UpVector, &Location);
return Matrix;
};
// did not apply any of LocaltoWorld
if(PDI && MeshComp)
{
FTransform LocalToWorld = MeshComp->GetComponentTransform();
FTransform ComponentTransform = CachedOriginalTransform * LocalToWorld;
FTransform LookAtTransform = CachedLookAtTransform * LocalToWorld;
FTransform TargetTrasnform = CachedTargetCoordinate * LocalToWorld;
FVector BoneLocation = LookAtTransform.GetLocation();
// we're using interpolation, so print previous location
const bool bUseInterpolation = InterpolationTime > 0.f;
if(bUseInterpolation)
{
// this only will be different if we're interpolating
DrawDashedLine(PDI, BoneLocation, LocalToWorld.TransformPosition(CachedPreviousTargetLocation), FColor(0, 255, 0), 5.f, SDPG_World);
}
// current look at location (can be clamped or interpolating)
DrawDashedLine(PDI, BoneLocation, LocalToWorld.TransformPosition(CachedCurrentLookAtLocation), FColor::Yellow, 5.f, SDPG_World);
DrawWireStar(PDI, CachedCurrentLookAtLocation, 5.0f, FColor::Yellow, SDPG_World);
// draw current target information
DrawDashedLine(PDI, BoneLocation, LocalToWorld.TransformPosition(CachedCurrentTargetLocation), FColor::Blue, 5.f, SDPG_World);
DrawWireStar(PDI, CachedCurrentTargetLocation, 5.0f, FColor::Blue, SDPG_World);
// draw the angular clamp
if (LookAtClamp > 0.f)
{
float Angle = FMath::DegreesToRadians(LookAtClamp);
float ConeSize = 30.f;
DrawCone(PDI, FScaleMatrix(ConeSize) * CalculateLookAtMatrixFromTransform(ComponentTransform), Angle, Angle, 20, false, FLinearColor::Green, GEngine->DebugEditorMaterial->GetRenderProxy(), SDPG_World);
}
// draw directional - lookat and look up
DrawDirectionalArrow(PDI, CalculateLookAtMatrixFromTransform(LookAtTransform), FLinearColor::Red, 20, 5, SDPG_World);
DrawCoordinateSystem(PDI, BoneLocation, LookAtTransform.GetRotation().Rotator(), 20.f, SDPG_Foreground);
DrawCoordinateSystem(PDI, TargetTrasnform.GetLocation(), TargetTrasnform.GetRotation().Rotator(), 20.f, SDPG_Foreground);
}
}
#endif // WITH_EDITOR
void FAnimNode_LookAt::InitializeBoneReferences(const FBoneContainer& RequiredBones)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(InitializeBoneReferences)
BoneToModify.Initialize(RequiredBones);
LookAtTarget.InitializeBoneReferences(RequiredBones);
}
void FAnimNode_LookAt::UpdateInternal(const FAnimationUpdateContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(UpdateInternal)
FAnimNode_SkeletalControlBase::UpdateInternal(Context);
AccumulatedInterpoolationTime = FMath::Clamp(AccumulatedInterpoolationTime+Context.GetDeltaTime(), 0.f, InterpolationTime);;
}
void FAnimNode_LookAt::Initialize_AnyThread(const FAnimationInitializeContext& Context)
{
DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Initialize_AnyThread)
FAnimNode_SkeletalControlBase::Initialize_AnyThread(Context);
LookAtTarget.Initialize(Context.AnimInstanceProxy);
// initialize
LookUp_Axis.Initialize();
if (LookUp_Axis.Axis.IsZero())
{
UE_LOG(LogAnimation, Warning, TEXT("Zero-length look-up axis specified in LookAt node. Reverting to default. Instance:%s"), *GetFullNameSafe(Context.GetAnimInstanceObject()));
LookUp_Axis.Axis = DefaultLookUpAxis;
}
LookAt_Axis.Initialize();
if (LookAt_Axis.Axis.IsZero())
{
UE_LOG(LogAnimation, Warning, TEXT("Zero-length look-at axis specified in LookAt node. Reverting to default. Instance:%s"), *GetFullNameSafe(Context.GetAnimInstanceObject()));
LookAt_Axis.Axis = DefaultLookAtAxis;
}
}
void FAnimNode_LookAt::ModifyPoseFromDeltaRotation(FComponentSpacePoseContext& Output, TArray<FBoneTransform>& OutBoneTransforms, FTransform& InOutBoneToModifyTransform, const FQuat& DeltaRotation)
{
InOutBoneToModifyTransform.SetRotation(DeltaRotation * InOutBoneToModifyTransform.GetRotation());
const FCompactPoseBoneIndex BoneToModifyIndex = BoneToModify.GetCompactPoseIndex(Output.Pose.GetPose().GetBoneContainer());
OutBoneTransforms.Add(FBoneTransform(BoneToModifyIndex, InOutBoneToModifyTransform));
}