Revert animation-only body tracking — restore AddActorWorldRotation

The pelvis bone rotation approach didn't work in practice. Reverts to
the previous AddActorWorldRotation() body tracking (replicated actor
rotation). Thread-safety fix and deprecated IsValid() removal are kept.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
j.foucher 2026-03-03 11:07:24 +01:00
parent 677f08e936
commit 7f92dcab51
4 changed files with 6 additions and 81 deletions

View File

@ -144,8 +144,6 @@ void FAnimNode_PS_AI_ConvAgent_Posture::CacheBones_AnyThread(const FAnimationCac
RightEyeBoneIndex = FCompactPoseBoneIndex(INDEX_NONE);
LeftEyeRefPoseRotation = FQuat::Identity;
RightEyeRefPoseRotation = FQuat::Identity;
BodyBoneIndex = FCompactPoseBoneIndex(INDEX_NONE);
BodyBoneRefPoseRotation = FQuat::Identity;
ChainBoneIndices.Reset();
ChainRefPoseRotations.Reset();
@ -249,22 +247,6 @@ void FAnimNode_PS_AI_ConvAgent_Posture::CacheBones_AnyThread(const FAnimationCac
ResolveEyeBone(DefaultRightEyeBone, RightEyeBoneIndex, RightEyeRefPoseRotation);
}
// ── Resolve body bone for body yaw rotation ─────────────────────
// The body yaw offset is applied to "pelvis" (or first skeleton bone)
// to rotate the entire skeleton without modifying actor rotation.
{
static const FName DefaultBodyBone(TEXT("pelvis"));
const int32 MeshIdx = RefSkeleton.FindBoneIndex(DefaultBodyBone);
if (MeshIdx != INDEX_NONE)
{
BodyBoneIndex = RequiredBones.MakeCompactPoseIndex(
FMeshPoseBoneIndex(MeshIdx));
BodyBoneRefPoseRotation = (MeshIdx < RefPose.Num())
? RefPose[MeshIdx].GetRotation()
: FQuat::Identity;
}
}
// ── Resolve ancestor chain for body drift compensation ────────────
// Walk from the parent of the first neck/head bone up to root.
// This lets us measure how much the spine/torso animation has
@ -346,7 +328,6 @@ void FAnimNode_PS_AI_ConvAgent_Posture::Update_AnyThread(const FAnimationUpdateC
CachedHeadCompensation = PostureComponent->GetHeadAnimationCompensation();
CachedEyeCompensation = PostureComponent->GetEyeAnimationCompensation();
CachedBodyDriftCompensation = PostureComponent->GetBodyDriftCompensation();
CachedBodyYawOffset = PostureComponent->GetBodyYawOffset();
}
}
@ -610,18 +591,6 @@ void FAnimNode_PS_AI_ConvAgent_Posture::Evaluate_AnyThread(FPoseContext& Output)
return;
}
// ── Apply body yaw rotation to pelvis bone ─────────────────────────────
// This replaces the old AddActorWorldRotation() approach — the actor rotation
// is never modified; the body turn is purely animation-driven (local, no replication).
if (FMath::Abs(CachedBodyYawOffset) > 0.1f
&& BodyBoneIndex.GetInt() != INDEX_NONE
&& BodyBoneIndex.GetInt() < Output.Pose.GetNumBones())
{
const FQuat BodyYawQuat(FVector::UpVector, FMath::DegreesToRadians(CachedBodyYawOffset));
FTransform& BodyTransform = Output.Pose[BodyBoneIndex];
BodyTransform.SetRotation((BodyYawQuat * BodyTransform.GetRotation()).GetNormalized());
}
// IMPORTANT: Even when posture is near-zero (head looking straight at target),
// we still need to run compensation to REMOVE the animation's head contribution.
// Only skip if BOTH posture is identity AND compensation is inactive (pure additive).

View File

@ -99,7 +99,6 @@ void UPS_AI_ConvAgent_PostureComponent::BeginPlay()
// Apply the mesh forward offset so "neutral" aligns with where the face points.
OriginalActorYaw = Owner->GetActorRotation().Yaw + MeshForwardYawOffset;
TargetBodyWorldYaw = Owner->GetActorRotation().Yaw;
CurrentBodyWorldYaw = Owner->GetActorRotation().Yaw;
if (bDebug)
{
@ -180,7 +179,6 @@ void UPS_AI_ConvAgent_PostureComponent::ResetBodyTarget()
if (AActor* Owner = GetOwner())
{
TargetBodyWorldYaw = Owner->GetActorRotation().Yaw;
CurrentBodyWorldYaw = Owner->GetActorRotation().Yaw;
}
}
@ -335,14 +333,11 @@ void UPS_AI_ConvAgent_PostureComponent::TickComponent(
if (bEnableBodyTracking)
{
const float BodyDelta = FMath::FindDeltaAngleDegrees(
CurrentBodyWorldYaw, TargetBodyWorldYaw);
Owner->GetActorRotation().Yaw, TargetBodyWorldYaw);
if (FMath::Abs(BodyDelta) > 0.1f)
{
const float BodyStep = FMath::FInterpTo(0.0f, BodyDelta, SafeDeltaTime, BodyInterpSpeed);
// Track body facing internally instead of modifying the actor rotation.
// The AnimNode applies the delta as a bone rotation — purely local,
// no actor rotation change, no replication conflict.
CurrentBodyWorldYaw += BodyStep;
Owner->AddActorWorldRotation(FRotator(0.0f, BodyStep, 0.0f));
}
}
@ -351,7 +346,7 @@ void UPS_AI_ConvAgent_PostureComponent::TickComponent(
float DeltaYaw = 0.0f;
if (!HorizontalDir.IsNearlyZero(1.0f))
{
const float CurrentFacingYaw = CurrentBodyWorldYaw + MeshForwardYawOffset;
const float CurrentFacingYaw = Owner->GetActorRotation().Yaw + MeshForwardYawOffset;
DeltaYaw = FMath::FindDeltaAngleDegrees(CurrentFacingYaw, TargetWorldYaw);
}
@ -464,11 +459,6 @@ void UPS_AI_ConvAgent_PostureComponent::TickComponent(
// Eye yaw is negated to match ARKit curve direction convention.
UpdateEyeCurves(-CurrentEyeYaw, CurrentEyePitch);
// Body yaw offset = how much the body has turned relative to the actor's rotation.
// The AnimNode applies this as a bone rotation (pelvis) — purely local.
CachedBodyYawOffset = FMath::FindDeltaAngleDegrees(
Owner->GetActorRotation().Yaw, CurrentBodyWorldYaw) * CurrentActiveAlpha;
}
// ── Debug gaze lines ────────────────────────────────────────────────────
@ -541,22 +531,21 @@ void UPS_AI_ConvAgent_PostureComponent::TickComponent(
DebugFrameCounter++;
if (DebugFrameCounter % 120 == 0)
{
const float FacingYaw = CurrentBodyWorldYaw + MeshForwardYawOffset;
const float FacingYaw = Owner->GetActorRotation().Yaw + MeshForwardYawOffset;
const FVector TP = TargetActor->GetActorLocation() + TargetOffset;
const FVector Dir = TP - Owner->GetActorLocation();
const float TgtYaw = FVector(Dir.X, Dir.Y, 0.0f).Rotation().Yaw;
const float Delta = FMath::FindDeltaAngleDegrees(FacingYaw, TgtYaw);
UE_LOG(LogPS_AI_ConvAgent_Posture, Log,
TEXT("Posture [%s -> %s]: Delta=%.1f | Head=%.1f/%.1f | Eyes=%.1f/%.1f | Body: enabled=%s TargetYaw=%.1f BodyYaw=%.1f (offset=%.1f)"),
TEXT("Posture [%s -> %s]: Delta=%.1f | Head=%.1f/%.1f | Eyes=%.1f/%.1f | Body: enabled=%s TargetYaw=%.1f ActorYaw=%.1f"),
*Owner->GetName(), *TargetActor->GetName(),
Delta,
CurrentHeadYaw, CurrentHeadPitch,
CurrentEyeYaw, CurrentEyePitch,
bEnableBodyTracking ? TEXT("Y") : TEXT("N"),
TargetBodyWorldYaw,
CurrentBodyWorldYaw,
CachedBodyYawOffset);
Owner->GetActorRotation().Yaw);
}
}
}

View File

@ -139,20 +139,6 @@ private:
* Cached from the component during Update. */
float CachedBodyDriftCompensation = 0.0f;
// ── Body yaw (animation-only body tracking) ──────────────────────────
/** Bone to apply body yaw rotation to. Rotates the entire skeleton
* visually without modifying the actor rotation (no replication conflict).
* Default "pelvis" works for MetaHuman and most humanoid skeletons. */
FCompactPoseBoneIndex BodyBoneIndex = FCompactPoseBoneIndex(INDEX_NONE);
/** Reference pose rotation for the body bone. */
FQuat BodyBoneRefPoseRotation = FQuat::Identity;
/** Body yaw offset in degrees (relative to actor rotation).
* Cached from the component during Update. */
float CachedBodyYawOffset = 0.0f;
#if !UE_BUILD_SHIPPING
/** Frame counter for periodic diagnostic logging in Evaluate. */
int32 EvalDebugFrameCounter = 0;

View File

@ -273,15 +273,6 @@ public:
* Scaled by activation alpha for smooth passthrough when inactive. */
float GetBodyDriftCompensation() const { return BodyDriftCompensation * CurrentActiveAlpha; }
/** Get body yaw offset in degrees (relative to actor's rotation).
* Applied by the AnimNode as a bone rotation purely local, no replication.
* Thread-safe copy, blended by activation alpha. */
float GetBodyYawOffset() const
{
FScopeLock Lock(&PostureDataLock);
return CachedBodyYawOffset;
}
/** Reset the persistent body yaw target to the actor's current facing.
* Call this when re-attaching a posture target so body tracking starts
* fresh instead of chasing a stale yaw from the previous interaction. */
@ -330,12 +321,6 @@ private:
* Same sticky pattern as TargetHeadYaw but for the body layer. */
float TargetBodyWorldYaw = 0.0f;
/** Interpolated actual body facing in world space.
* Replaces the old AddActorWorldRotation() approach the actor rotation
* is never modified; instead this tracks the virtual body facing and the
* AnimNode applies the delta as a bone rotation (100% local, no replication). */
float CurrentBodyWorldYaw = 0.0f;
/** Original actor yaw at BeginPlay (for neutral return when TargetActor is null). */
float OriginalActorYaw = 0.0f;
@ -353,10 +338,6 @@ private:
/** Head bone rotation offset as quaternion (no Euler round-trip). */
FQuat CurrentHeadRotation = FQuat::Identity;
/** Body yaw offset in degrees (relative to actor rotation).
* Computed in TickComponent, read by AnimNode via GetBodyYawOffset(). */
float CachedBodyYawOffset = 0.0f;
/** Cached skeletal mesh component on the owning actor (Body). */
TWeakObjectPtr<USkeletalMeshComponent> CachedMesh;