Fix : 리타겟팅 버그 업데이트 프랍 데이터베이스 추가

This commit is contained in:
user 2026-03-29 19:16:22 +09:00
parent 4b59344a3e
commit 4aa22756e5
3 changed files with 57 additions and 11 deletions

View File

@ -109,8 +109,9 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
// OptiTrack 본 이름 → Transform 빠른 캐시 (GetMappedTransform O(n) → O(1)) // OptiTrack 본 이름 → Transform 빠른 캐시 (GetMappedTransform O(n) → O(1))
private Dictionary<string, Transform> m_optiNameTransformCache = new Dictionary<string, Transform>(); private Dictionary<string, Transform> m_optiNameTransformCache = new Dictionary<string, Transform>();
// 필터 적용 전 raw 월드 위치 (IK 타겟용 — 접지력 보존) // 필터 적용 전 raw 월드 위치/회전 (IK 타겟용 — 접지력 보존)
private Dictionary<HumanBodyBones, Vector3> m_rawWorldPositions = new Dictionary<HumanBodyBones, Vector3>(); private Dictionary<HumanBodyBones, Vector3> m_rawWorldPositions = new Dictionary<HumanBodyBones, Vector3>();
private Dictionary<HumanBodyBones, Quaternion> m_rawWorldRotations = new Dictionary<HumanBodyBones, Quaternion>();
// raw 위치를 캡처할 IK 포인트 본 목록 // raw 위치를 캡처할 IK 포인트 본 목록
private static readonly HumanBodyBones[] k_IKPointBones = new HumanBodyBones[] private static readonly HumanBodyBones[] k_IKPointBones = new HumanBodyBones[]
@ -373,12 +374,15 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
mapping.cachedTransform.localPosition = rawPos; mapping.cachedTransform.localPosition = rawPos;
} }
// Raw 상태에서 IK 포인트 월드 위치 캡처 // Raw 상태에서 IK 포인트 월드 위치/회전 캡처
foreach (var ikBone in k_IKPointBones) foreach (var ikBone in k_IKPointBones)
{ {
Transform t = GetBoneTransform(ikBone); Transform t = GetBoneTransform(ikBone);
if (t != null) if (t != null)
{
m_rawWorldPositions[ikBone] = t.position; m_rawWorldPositions[ikBone] = t.position;
m_rawWorldRotations[ikBone] = t.rotation;
}
} }
// Pass 2: 필터 적용된 데이터로 덮어쓰기 // Pass 2: 필터 적용된 데이터로 덮어쓰기
@ -419,20 +423,23 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
mapping.cachedTransform.localPosition = finalPos; mapping.cachedTransform.localPosition = finalPos;
} }
// 필터 없으면 현재 Transform 위치 곧 raw // 필터 없으면 현재 Transform 위치/회전이 곧 raw
foreach (var ikBone in k_IKPointBones) foreach (var ikBone in k_IKPointBones)
{ {
Transform t = GetBoneTransform(ikBone); Transform t = GetBoneTransform(ikBone);
if (t != null) if (t != null)
{
m_rawWorldPositions[ikBone] = t.position; m_rawWorldPositions[ikBone] = t.position;
m_rawWorldRotations[ikBone] = t.rotation;
}
} }
} }
// ── 어깨 증폭 + 상완 역보정 (1.0이면 스킵) ── // ── 어깨 증폭 + 상완 역보정 (1.0이면 스킵) ──
if (Mathf.Abs(shoulderAmplify - 1f) > 0.001f) if (Mathf.Abs(shoulderAmplify - 1f) > 0.001f)
{ {
AmplifyShoulderWithCompensation("LShoulder", "LUArm"); AmplifyShoulderWithCompensation("LShoulder", "LUArm", true);
AmplifyShoulderWithCompensation("RShoulder", "RUArm"); AmplifyShoulderWithCompensation("RShoulder", "RUArm", false);
} }
} }
@ -440,17 +447,35 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
/// <summary> /// <summary>
/// 어깨 회전을 rest pose 대비 증폭하고, 상완에서 추가 회전분을 상쇄하여 손 위치를 보존합니다. /// 어깨 회전을 rest pose 대비 증폭하고, 상완에서 추가 회전분을 상쇄하여 손 위치를 보존합니다.
/// </summary> /// </summary>
private void AmplifyShoulderWithCompensation(string shoulderName, string upperArmName) private float m_leftShoulderBlend = 0f;
private float m_rightShoulderBlend = 0f;
/// <param name="isLeft">true = 왼쪽(Z &lt; 0일 때 증폭), false = 오른쪽(Z &gt; 0일 때 증폭)</param>
private void AmplifyShoulderWithCompensation(string shoulderName, string upperArmName, bool isLeft)
{ {
Transform shoulder = GetMappedTransform(shoulderName); Transform shoulder = GetMappedTransform(shoulderName);
Transform upperArm = GetMappedTransform(upperArmName); Transform upperArm = GetMappedTransform(upperArmName);
if (shoulder == null || upperArm == null) return; if (shoulder == null || upperArm == null) return;
// rest pose 대비 델타 추출 → 증폭 // rest pose 대비 델타 추출
Quaternion restRot = GetRestLocalRotation(shoulderName); Quaternion restRot = GetRestLocalRotation(shoulderName);
Quaternion currentRot = shoulder.localRotation; Quaternion currentRot = shoulder.localRotation;
Quaternion delta = Quaternion.Inverse(restRot) * currentRot; Quaternion delta = Quaternion.Inverse(restRot) * currentRot;
Quaternion amplifiedDelta = Quaternion.SlerpUnclamped(Quaternion.identity, delta, shoulderAmplify);
// 어깨가 올라가는 방향일 때만 증폭 (왼쪽: Z < 0, 오른쪽: Z > 0)
Vector3 deltaEuler = delta.eulerAngles;
float z = deltaEuler.z > 180f ? deltaEuler.z - 360f : deltaEuler.z;
float targetBlend = isLeft ? Mathf.Clamp01(-z / 5f) : Mathf.Clamp01(z / 5f);
// 부드러운 블렌딩 (급격한 on/off 방지)
ref float blend = ref (isLeft ? ref m_leftShoulderBlend : ref m_rightShoulderBlend);
blend = Mathf.Lerp(blend, targetBlend, 10f * Time.deltaTime);
if (blend < 0.001f) return;
// 증폭량: 1(원본) ~ shoulderAmplify 사이를 blend로 보간
float effectiveAmplify = Mathf.Lerp(1f, shoulderAmplify, blend);
Quaternion amplifiedDelta = Quaternion.SlerpUnclamped(Quaternion.identity, delta, effectiveAmplify);
Quaternion amplifiedRot = restRot * amplifiedDelta; Quaternion amplifiedRot = restRot * amplifiedDelta;
// 추가된 회전량 // 추가된 회전량
@ -748,6 +773,14 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
return m_rawWorldPositions.TryGetValue(bone, out position); return m_rawWorldPositions.TryGetValue(bone, out position);
} }
/// <summary>
/// 1€ 필터 적용 전의 raw 월드 회전을 반환합니다.
/// </summary>
public bool TryGetRawWorldRotation(HumanBodyBones bone, out Quaternion rotation)
{
return m_rawWorldRotations.TryGetValue(bone, out rotation);
}
/// <summary> /// <summary>
/// HumanBodyBones enum으로 OptiTrack 매핑된 Transform 반환 /// HumanBodyBones enum으로 OptiTrack 매핑된 Transform 반환
/// (Humanoid 호환 레이어 — sourceAnimator.GetBoneTransform() 대체) /// (Humanoid 호환 레이어 — sourceAnimator.GetBoneTransform() 대체)

Binary file not shown.

View File

@ -1257,12 +1257,25 @@ namespace KindRetargeting
{ {
// 1€ 필터 적용 전 raw 위치 사용 (접지력 보존) // 1€ 필터 적용 전 raw 위치 사용 (접지력 보존)
// raw가 없으면 필터된 Transform.position fallback // raw가 없으면 필터된 Transform.position fallback
// 1€ 필터 적용 전 raw 위치/회전 사용 (접지력 보존)
Vector3 targetPosition; Vector3 targetPosition;
if (optitrackSource != null && optitrackSource.TryGetRawWorldPosition(endBone, out Vector3 rawPos)) if (optitrackSource != null && optitrackSource.TryGetRawWorldPosition(endBone, out Vector3 rawPos))
targetPosition = rawPos; targetPosition = rawPos;
else else
targetPosition = sourceBone.position; targetPosition = sourceBone.position;
Quaternion targetRotation = targetBone.rotation;
// raw 회전 + 리타게팅 오프셋 적용 (필터 스무딩 없는 회전)
Quaternion targetRotation;
if (optitrackSource != null
&& optitrackSource.TryGetRawWorldRotation(endBone, out Quaternion rawRot)
&& rotationOffsets.TryGetValue(endBone, out Quaternion endOffset))
{
targetRotation = rawRot * endOffset;
}
else
{
targetRotation = targetBone.rotation;
}
// 발 본인 경우 오프셋 적용 // 발 본인 경우 오프셋 적용
if (endBone == HumanBodyBones.LeftFoot || endBone == HumanBodyBones.RightFoot) if (endBone == HumanBodyBones.LeftFoot || endBone == HumanBodyBones.RightFoot)