Merge posture fixes: cascade two-step, ARKit normalization, diagonal tilt
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
81bcd67428
@ -28,7 +28,7 @@ void FAnimNode_ElevenLabsPosture::Initialize_AnyThread(const FAnimationInitializ
|
||||
// Find the ElevenLabsPostureComponent on the owning actor.
|
||||
PostureComponent.Reset();
|
||||
CachedEyeCurves.Reset();
|
||||
CachedHeadRotation = FRotator::ZeroRotator;
|
||||
CachedHeadRotation = FQuat::Identity;
|
||||
HeadBoneIndex = FCompactPoseBoneIndex(INDEX_NONE);
|
||||
|
||||
if (const FAnimInstanceProxy* Proxy = Context.AnimInstanceProxy)
|
||||
@ -142,7 +142,7 @@ void FAnimNode_ElevenLabsPosture::Update_AnyThread(const FAnimationUpdateContext
|
||||
|
||||
// Cache posture data from the component (game thread safe copy).
|
||||
CachedEyeCurves.Reset();
|
||||
CachedHeadRotation = FRotator::ZeroRotator;
|
||||
CachedHeadRotation = FQuat::Identity;
|
||||
|
||||
if (PostureComponent.IsValid())
|
||||
{
|
||||
@ -218,11 +218,51 @@ void FAnimNode_ElevenLabsPosture::Evaluate_AnyThread(FPoseContext& Output)
|
||||
}
|
||||
#else
|
||||
// ── PRODUCTION: Apply real head rotation ─────────────────────────
|
||||
if (!CachedHeadRotation.IsNearlyZero(0.1f))
|
||||
if (!CachedHeadRotation.Equals(FQuat::Identity, 0.001f))
|
||||
{
|
||||
const FQuat HeadOffset = CachedHeadRotation.Quaternion();
|
||||
// Pre-multiply: apply offset in parent space (neck)
|
||||
HeadTransform.SetRotation((HeadOffset * HeadTransform.GetRotation()).GetNormalized());
|
||||
const FQuat BoneRot = HeadTransform.GetRotation();
|
||||
const FQuat Combined = CachedHeadRotation * BoneRot;
|
||||
|
||||
// Remove Y-axis tilt from final rotation
|
||||
FQuat Swing, TiltTwist;
|
||||
Combined.ToSwingTwist(FVector::RightVector, Swing, TiltTwist);
|
||||
|
||||
HeadTransform.SetRotation(Swing.GetNormalized());
|
||||
|
||||
// ── Diagnostic log (every ~60 frames) ──
|
||||
static int32 DiagCounter = 0;
|
||||
if (++DiagCounter % 60 == 0)
|
||||
{
|
||||
const FRotator OffsetRot = CachedHeadRotation.Rotator();
|
||||
const FRotator BoneRefRot = BoneRot.Rotator();
|
||||
const FRotator CombRot = Combined.Rotator();
|
||||
const FRotator SwingRot = Swing.Rotator();
|
||||
const FRotator TiltRot = TiltTwist.Rotator();
|
||||
|
||||
UE_LOG(LogElevenLabsPostureAnimNode, Warning,
|
||||
TEXT("=== HEAD ROTATION DIAG ==="));
|
||||
UE_LOG(LogElevenLabsPostureAnimNode, Warning,
|
||||
TEXT(" Offset Q: X=%.4f Y=%.4f Z=%.4f W=%.4f (P=%.1f Y=%.1f R=%.1f)"),
|
||||
CachedHeadRotation.X, CachedHeadRotation.Y,
|
||||
CachedHeadRotation.Z, CachedHeadRotation.W,
|
||||
OffsetRot.Pitch, OffsetRot.Yaw, OffsetRot.Roll);
|
||||
UE_LOG(LogElevenLabsPostureAnimNode, Warning,
|
||||
TEXT(" BoneRef Q: X=%.4f Y=%.4f Z=%.4f W=%.4f (P=%.1f Y=%.1f R=%.1f)"),
|
||||
BoneRot.X, BoneRot.Y, BoneRot.Z, BoneRot.W,
|
||||
BoneRefRot.Pitch, BoneRefRot.Yaw, BoneRefRot.Roll);
|
||||
UE_LOG(LogElevenLabsPostureAnimNode, Warning,
|
||||
TEXT(" Combined Q: X=%.4f Y=%.4f Z=%.4f W=%.4f (P=%.1f Y=%.1f R=%.1f)"),
|
||||
Combined.X, Combined.Y, Combined.Z, Combined.W,
|
||||
CombRot.Pitch, CombRot.Yaw, CombRot.Roll);
|
||||
UE_LOG(LogElevenLabsPostureAnimNode, Warning,
|
||||
TEXT(" Swing Q: X=%.4f Y=%.4f Z=%.4f W=%.4f (P=%.1f Y=%.1f R=%.1f)"),
|
||||
Swing.X, Swing.Y, Swing.Z, Swing.W,
|
||||
SwingRot.Pitch, SwingRot.Yaw, SwingRot.Roll);
|
||||
UE_LOG(LogElevenLabsPostureAnimNode, Warning,
|
||||
TEXT(" TiltTwist: X=%.4f Y=%.4f Z=%.4f W=%.4f (P=%.1f Y=%.1f R=%.1f)"),
|
||||
TiltTwist.X, TiltTwist.Y, TiltTwist.Z, TiltTwist.W,
|
||||
TiltRot.Pitch, TiltRot.Yaw, TiltRot.Roll);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -230,10 +270,11 @@ void FAnimNode_ElevenLabsPosture::Evaluate_AnyThread(FPoseContext& Output)
|
||||
|
||||
void FAnimNode_ElevenLabsPosture::GatherDebugData(FNodeDebugData& DebugData)
|
||||
{
|
||||
const FRotator DebugRot = CachedHeadRotation.Rotator();
|
||||
FString DebugLine = FString::Printf(
|
||||
TEXT("ElevenLabs Posture (eyes: %d curves, head: Y=%.1f P=%.1f)"),
|
||||
CachedEyeCurves.Num(),
|
||||
CachedHeadRotation.Yaw, CachedHeadRotation.Pitch);
|
||||
DebugRot.Yaw, DebugRot.Pitch);
|
||||
DebugData.AddDebugItem(DebugLine);
|
||||
BasePose.GatherDebugData(DebugData);
|
||||
}
|
||||
|
||||
@ -275,13 +275,16 @@ void UElevenLabsPostureComponent::TickComponent(
|
||||
{
|
||||
FScopeLock Lock(&PostureDataLock);
|
||||
|
||||
// MetaHuman head bone axis mapping (independent quaternions to avoid
|
||||
// diagonal coupling that FRotator causes when both axes are non-zero):
|
||||
// Z-axis rotation = nod up/down → our HeadPitch
|
||||
// MetaHuman head bone axis mapping:
|
||||
// Z-axis rotation = nod up/down → our HeadPitch
|
||||
// X-axis rotation = turn left/right → our HeadYaw
|
||||
//
|
||||
// Store raw composed quaternion. The parasitic Y-axis tilt from
|
||||
// composition is removed in the AnimNode AFTER composing with the
|
||||
// bone's reference rotation (which also contributes Y-coupling).
|
||||
const FQuat NodQuat(FVector::UpVector, FMath::DegreesToRadians(-CurrentHeadPitch));
|
||||
const FQuat TurnQuat(FVector::ForwardVector, FMath::DegreesToRadians(CurrentHeadYaw));
|
||||
CurrentHeadRotation = (TurnQuat * NodQuat).Rotator();
|
||||
CurrentHeadRotation = TurnQuat * NodQuat;
|
||||
|
||||
// Eye yaw is negated to match ARKit curve direction convention.
|
||||
UpdateEyeCurves(-CurrentEyeYaw, CurrentEyePitch);
|
||||
|
||||
@ -62,9 +62,9 @@ private:
|
||||
* Copied from the component during Update (game thread safe). */
|
||||
TMap<FName, float> CachedEyeCurves;
|
||||
|
||||
/** Head rotation offset (yaw + pitch) to apply to the head bone.
|
||||
* Copied from the component during Update. */
|
||||
FRotator CachedHeadRotation = FRotator::ZeroRotator;
|
||||
/** Head rotation offset as quaternion (no Euler round-trip, avoids
|
||||
* parasitic tilt on diagonals). Copied from the component during Update. */
|
||||
FQuat CachedHeadRotation = FQuat::Identity;
|
||||
|
||||
/** Resolved head bone index in the skeleton. */
|
||||
FCompactPoseBoneIndex HeadBoneIndex = FCompactPoseBoneIndex(INDEX_NONE);
|
||||
|
||||
@ -139,10 +139,10 @@ public:
|
||||
return CurrentEyeCurves;
|
||||
}
|
||||
|
||||
/** Get current head rotation offset (yaw + pitch, applied by AnimNode).
|
||||
/** Get current head rotation offset (applied by AnimNode as FQuat to avoid
|
||||
* Euler round-trip that reintroduces parasitic tilt on diagonals).
|
||||
* Thread-safe copy. */
|
||||
UFUNCTION(BlueprintCallable, Category = "ElevenLabs|Posture")
|
||||
FRotator GetCurrentHeadRotation() const
|
||||
FQuat GetCurrentHeadRotation() const
|
||||
{
|
||||
FScopeLock Lock(&PostureDataLock);
|
||||
return CurrentHeadRotation;
|
||||
@ -194,8 +194,8 @@ private:
|
||||
/** 8 ARKit eye look curves (eyeLookUpLeft, eyeLookDownRight, etc.). */
|
||||
TMap<FName, float> CurrentEyeCurves;
|
||||
|
||||
/** Head bone rotation offset (Yaw + Pitch). */
|
||||
FRotator CurrentHeadRotation = FRotator::ZeroRotator;
|
||||
/** Head bone rotation offset as quaternion (no Euler round-trip). */
|
||||
FQuat CurrentHeadRotation = FQuat::Identity;
|
||||
|
||||
/** Cached skeletal mesh component on the owning actor. */
|
||||
TWeakObjectPtr<USkeletalMeshComponent> CachedMesh;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user