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)
{
// ── 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
// (removes parasitic ear-to-shoulder tilt).
// 2. Slerp a fraction to each bone in the chain.
// Each bone in the chain gets a fractional rotation (via Slerp weight).
// The swing-twist decomposition is done PER-BONE using each bone's own
// 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)
{
const FCompactPoseBoneIndex BoneIdx = ChainBoneIndices[i];
@ -267,10 +241,21 @@ void FAnimNode_ElevenLabsPosture::Evaluate_AnyThread(FPoseContext& Output)
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];
BoneTransform.SetRotation(
(FractionalRot * BoneTransform.GetRotation()).GetNormalized());
const FQuat BoneRot = BoneTransform.GetRotation();
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

View File

@ -34,6 +34,11 @@ UElevenLabsPostureComponent::UElevenLabsPostureComponent()
PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.TickGroup = TG_PrePhysics;
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). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
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) ───────────────────────────────────────────────
//
@ -91,45 +91,55 @@ public:
/** Maximum head yaw rotation in degrees. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0", ClampMax = "90"))
float MaxHeadYaw = 35.0f;
float MaxHeadYaw = 40.0f;
/** Maximum head pitch rotation in degrees. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0", ClampMax = "90"))
float MaxHeadPitch = 25.0f;
float MaxHeadPitch = 30.0f;
/** Maximum horizontal eye angle in degrees. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0", ClampMax = "90"))
float MaxEyeHorizontal = 30.0f;
float MaxEyeHorizontal = 15.0f;
/** Maximum vertical eye angle in degrees. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0", ClampMax = "90"))
float MaxEyeVertical = 20.0f;
float MaxEyeVertical = 10.0f;
// ── Smoothing speeds ─────────────────────────────────────────────────────
/** Body rotation interpolation speed (lower = slower, more natural). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0.1", ClampMax = "20"))
float BodyInterpSpeed = 2.0f;
float BodyInterpSpeed = 4.0f;
/** Head rotation interpolation speed. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0.1", ClampMax = "20"))
float HeadInterpSpeed = 5.0f;
float HeadInterpSpeed = 4.0f;
/** Eye movement interpolation speed (higher = snappier). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0.1", ClampMax = "20"))
float EyeInterpSpeed = 8.0f;
float EyeInterpSpeed = 5.0f;
/** Interpolation speed when returning to neutral (TargetActor is null). */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "0.1", ClampMax = "20"))
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 ──────────────────────────────────────────────────────
/** Yaw offset (degrees) between the actor's forward (+X) and the mesh's
@ -139,7 +149,7 @@ public:
* -90 = mesh faces -Y */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ElevenLabs|Posture",
meta = (ClampMin = "-180", ClampMax = "180"))
float MeshForwardYawOffset = 0.0f;
float MeshForwardYawOffset = 90.0f;
// ── Head bone ────────────────────────────────────────────────────────────