diff --git a/Unreal/PS_AI_Agent/Content/MetaHumans/Animations/BodyBP.uasset b/Unreal/PS_AI_Agent/Content/MetaHumans/Animations/BodyBP.uasset index c99bf00..1d832f2 100644 Binary files a/Unreal/PS_AI_Agent/Content/MetaHumans/Animations/BodyBP.uasset and b/Unreal/PS_AI_Agent/Content/MetaHumans/Animations/BodyBP.uasset differ diff --git a/Unreal/PS_AI_Agent/Content/MetaHumans/Taro/BP_Taro.uasset b/Unreal/PS_AI_Agent/Content/MetaHumans/Taro/BP_Taro.uasset index 7c61c47..3c0ae22 100644 Binary files a/Unreal/PS_AI_Agent/Content/MetaHumans/Taro/BP_Taro.uasset and b/Unreal/PS_AI_Agent/Content/MetaHumans/Taro/BP_Taro.uasset differ diff --git a/Unreal/PS_AI_Agent/Content/NewElevenLabsEmotionPoseMap.uasset b/Unreal/PS_AI_Agent/Content/NewElevenLabsEmotionPoseMap.uasset index c3d14ff..5504676 100644 Binary files a/Unreal/PS_AI_Agent/Content/NewElevenLabsEmotionPoseMap.uasset and b/Unreal/PS_AI_Agent/Content/NewElevenLabsEmotionPoseMap.uasset differ diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Agent_ElevenLabs/Source/PS_AI_Agent_ElevenLabs/Private/AnimNode_ElevenLabsPosture.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Agent_ElevenLabs/Source/PS_AI_Agent_ElevenLabs/Private/AnimNode_ElevenLabsPosture.cpp index e57eb09..d550a5a 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Agent_ElevenLabs/Source/PS_AI_Agent_ElevenLabs/Private/AnimNode_ElevenLabsPosture.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Agent_ElevenLabs/Source/PS_AI_Agent_ElevenLabs/Private/AnimNode_ElevenLabsPosture.cpp @@ -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 diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Agent_ElevenLabs/Source/PS_AI_Agent_ElevenLabs/Private/ElevenLabsPostureComponent.cpp b/Unreal/PS_AI_Agent/Plugins/PS_AI_Agent_ElevenLabs/Source/PS_AI_Agent_ElevenLabs/Private/ElevenLabsPostureComponent.cpp index c79be15..5e84326 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Agent_ElevenLabs/Source/PS_AI_Agent_ElevenLabs/Private/ElevenLabsPostureComponent.cpp +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Agent_ElevenLabs/Source/PS_AI_Agent_ElevenLabs/Private/ElevenLabsPostureComponent.cpp @@ -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 }); } // ───────────────────────────────────────────────────────────────────────────── diff --git a/Unreal/PS_AI_Agent/Plugins/PS_AI_Agent_ElevenLabs/Source/PS_AI_Agent_ElevenLabs/Public/ElevenLabsPostureComponent.h b/Unreal/PS_AI_Agent/Plugins/PS_AI_Agent_ElevenLabs/Source/PS_AI_Agent_ElevenLabs/Public/ElevenLabsPostureComponent.h index bf4ae30..757c73e 100644 --- a/Unreal/PS_AI_Agent/Plugins/PS_AI_Agent_ElevenLabs/Source/PS_AI_Agent_ElevenLabs/Public/ElevenLabsPostureComponent.h +++ b/Unreal/PS_AI_Agent/Plugins/PS_AI_Agent_ElevenLabs/Source/PS_AI_Agent_ElevenLabs/Public/ElevenLabsPostureComponent.h @@ -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 ────────────────────────────────────────────────────────────