begin posture with animation

This commit is contained in:
j.foucher 2026-02-26 08:57:37 +01:00
parent cdb118a83e
commit d2e904144a
6 changed files with 44 additions and 44 deletions

View File

@ -224,40 +224,14 @@ void FAnimNode_ElevenLabsPosture::Evaluate_AnyThread(FPoseContext& Output)
if (bUseChain) if (bUseChain)
{ {
// ── Multi-bone neck chain: distribute rotation across bones ────── // ── Multi-bone neck chain: per-bone swing-twist ──────────────────
// //
// 1. Clean the total rotation ONCE via swing-twist on the tip bone // Each bone in the chain gets a fractional rotation (via Slerp weight).
// (removes parasitic ear-to-shoulder tilt). // The swing-twist decomposition is done PER-BONE using each bone's own
// 2. Slerp a fraction to each bone in the chain. // tilt axis. This prevents parasitic ear-to-shoulder tilt that occurred
// when a single CleanRotation (derived from the tip bone) was applied
// to bones with different local orientations.
// Find the tip bone (last valid bone) for swing-twist reference
FCompactPoseBoneIndex TipBoneIdx(INDEX_NONE);
for (int32 i = ChainBoneIndices.Num() - 1; i >= 0; --i)
{
if (ChainBoneIndices[i].GetInt() != INDEX_NONE
&& ChainBoneIndices[i].GetInt() < Output.Pose.GetNumBones())
{
TipBoneIdx = ChainBoneIndices[i];
break;
}
}
if (TipBoneIdx.GetInt() == INDEX_NONE)
{
return; // No valid bones in chain
}
// Swing-twist: remove tilt from the composed rotation
const FQuat TipBoneRot = Output.Pose[TipBoneIdx].GetRotation();
const FQuat Combined = CachedHeadRotation * TipBoneRot;
const FVector BoneTiltAxis = TipBoneRot.RotateVector(FVector::RightVector);
FQuat Swing, TiltTwist;
Combined.ToSwingTwist(BoneTiltAxis, Swing, TiltTwist);
// Extract clean offset = Swing * Inverse(TipBoneRot)
const FQuat CleanRotation = (Swing * TipBoneRot.Inverse()).GetNormalized();
// Distribute fractional rotation to each bone
for (int32 i = 0; i < ChainBoneIndices.Num(); ++i) for (int32 i = 0; i < ChainBoneIndices.Num(); ++i)
{ {
const FCompactPoseBoneIndex BoneIdx = ChainBoneIndices[i]; const FCompactPoseBoneIndex BoneIdx = ChainBoneIndices[i];
@ -267,10 +241,21 @@ void FAnimNode_ElevenLabsPosture::Evaluate_AnyThread(FPoseContext& Output)
continue; continue;
} }
const FQuat FractionalRot = FQuat::Slerp(FQuat::Identity, CleanRotation, ChainBoneWeights[i]); // Fractional rotation for this bone
const FQuat FractionalRot = FQuat::Slerp(
FQuat::Identity, CachedHeadRotation, ChainBoneWeights[i]);
// Compose with THIS bone's own rotation
FTransform& BoneTransform = Output.Pose[BoneIdx]; FTransform& BoneTransform = Output.Pose[BoneIdx];
BoneTransform.SetRotation( const FQuat BoneRot = BoneTransform.GetRotation();
(FractionalRot * BoneTransform.GetRotation()).GetNormalized()); const FQuat Combined = FractionalRot * BoneRot;
// Swing-twist on THIS bone's tilt axis (removes roll/tilt)
const FVector BoneTiltAxis = BoneRot.RotateVector(FVector::RightVector);
FQuat Swing, TiltTwist;
Combined.ToSwingTwist(BoneTiltAxis, Swing, TiltTwist);
BoneTransform.SetRotation(Swing.GetNormalized());
} }
} }
else else

View File

@ -34,6 +34,11 @@ UElevenLabsPostureComponent::UElevenLabsPostureComponent()
PrimaryComponentTick.bCanEverTick = true; PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.TickGroup = TG_PrePhysics; PrimaryComponentTick.TickGroup = TG_PrePhysics;
bAutoActivate = true; bAutoActivate = true;
// Default neck bone chain for MetaHuman (neck_01 → neck_02 → head)
NeckBoneChain.Add({ FName(TEXT("neck_01")), 0.25f });
NeckBoneChain.Add({ FName(TEXT("neck_02")), 0.35f });
NeckBoneChain.Add({ FName(TEXT("head")), 0.40f });
} }
// ───────────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────────

View File

@ -77,7 +77,7 @@ public:
* Useful for actors without a skeleton (e.g. (0,0,160) for eye-level). */ * Useful for actors without a skeleton (e.g. (0,0,160) for eye-level). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture", UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ToolTip = "Offset from target actor origin.\nE.g. (0,0,160) for eye-level.")) meta = (ToolTip = "Offset from target actor origin.\nE.g. (0,0,160) for eye-level."))
FVector TargetOffset = FVector(0.0f, 0.0f, 160.0f); FVector TargetOffset = FVector(0.0f, 0.0f, 0.0f);
// ── Angle limits (degrees) ─────────────────────────────────────────────── // ── Angle limits (degrees) ───────────────────────────────────────────────
// //
@ -91,45 +91,55 @@ public:
/** Maximum head yaw rotation in degrees. */ /** Maximum head yaw rotation in degrees. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture", UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0", ClampMax = "90")) meta = (ClampMin = "0", ClampMax = "90"))
float MaxHeadYaw = 35.0f; float MaxHeadYaw = 40.0f;
/** Maximum head pitch rotation in degrees. */ /** Maximum head pitch rotation in degrees. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture", UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0", ClampMax = "90")) meta = (ClampMin = "0", ClampMax = "90"))
float MaxHeadPitch = 25.0f; float MaxHeadPitch = 30.0f;
/** Maximum horizontal eye angle in degrees. */ /** Maximum horizontal eye angle in degrees. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture", UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0", ClampMax = "90")) meta = (ClampMin = "0", ClampMax = "90"))
float MaxEyeHorizontal = 30.0f; float MaxEyeHorizontal = 15.0f;
/** Maximum vertical eye angle in degrees. */ /** Maximum vertical eye angle in degrees. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture", UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0", ClampMax = "90")) meta = (ClampMin = "0", ClampMax = "90"))
float MaxEyeVertical = 20.0f; float MaxEyeVertical = 10.0f;
// ── Smoothing speeds ───────────────────────────────────────────────────── // ── Smoothing speeds ─────────────────────────────────────────────────────
/** Body rotation interpolation speed (lower = slower, more natural). */ /** Body rotation interpolation speed (lower = slower, more natural). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture", UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0.1", ClampMax = "20")) meta = (ClampMin = "0.1", ClampMax = "20"))
float BodyInterpSpeed = 2.0f; float BodyInterpSpeed = 4.0f;
/** Head rotation interpolation speed. */ /** Head rotation interpolation speed. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture", UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0.1", ClampMax = "20")) meta = (ClampMin = "0.1", ClampMax = "20"))
float HeadInterpSpeed = 5.0f; float HeadInterpSpeed = 4.0f;
/** Eye movement interpolation speed (higher = snappier). */ /** Eye movement interpolation speed (higher = snappier). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture", UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0.1", ClampMax = "20")) meta = (ClampMin = "0.1", ClampMax = "20"))
float EyeInterpSpeed = 8.0f; float EyeInterpSpeed = 5.0f;
/** Interpolation speed when returning to neutral (TargetActor is null). */ /** Interpolation speed when returning to neutral (TargetActor is null). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture", UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0.1", ClampMax = "20")) meta = (ClampMin = "0.1", ClampMax = "20"))
float ReturnToNeutralSpeed = 3.0f; float ReturnToNeutralSpeed = 3.0f;
// ── Animation compensation ──────────────────────────────────────────────
/** How much the look-at overrides the animation's head rotation.
* 1.0 = full override head always points at target regardless of animation.
* 0.0 = pure additive posture stacks on top of animation (old behavior).
* Default: 1.0 for conversational AI (always look at who you talk to). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0", ClampMax = "1"))
float AnimationCompensation = 1.0f;
// ── Forward offset ────────────────────────────────────────────────────── // ── Forward offset ──────────────────────────────────────────────────────
/** Yaw offset (degrees) between the actor's forward (+X) and the mesh's /** Yaw offset (degrees) between the actor's forward (+X) and the mesh's
@ -139,7 +149,7 @@ public:
* -90 = mesh faces -Y */ * -90 = mesh faces -Y */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture", UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "-180", ClampMax = "180")) meta = (ClampMin = "-180", ClampMax = "180"))
float MeshForwardYawOffset = 0.0f; float MeshForwardYawOffset = 90.0f;
// ── Head bone ──────────────────────────────────────────────────────────── // ── Head bone ────────────────────────────────────────────────────────────