182 lines
6.2 KiB
C#

using System.Collections.Generic;
using UnityEngine;
namespace KindRetargeting
{
/// <summary>
/// 아바타 간의 회전 오프셋을 계산하고 포즈 데이터를 복사하는 기능을 제공합니다.
/// </summary>
public class OffsetTransfer : MonoBehaviour
{
[Header("아바타 설정")]
[SerializeField] public Animator sourceAnimator; // 원본 아바타의 Animator
[SerializeField] public Animator targetAnimator; // 대상 아바타의 Animator
[Header("오프셋 설정")]
[SerializeField] private bool autoCalculateOnStart = true; // 시작 시 자동 계산 여부
[SerializeField] private bool autoCopyPose = true; // 매 프레임마다 자동으로 포즈 복사 여부
// 본별 회전 오프셋을 저장하는 딕셔너리
private Dictionary<HumanBodyBones, Quaternion> rotationOffsets = new Dictionary<HumanBodyBones, Quaternion>();
#region
/// <summary>
/// 계산된 회전 오프셋 딕셔너리를 반환합니다.
/// </summary>
public Dictionary<HumanBodyBones, Quaternion> RotationOffsets => rotationOffsets;
#endregion
#region
private void Start()
{
if (autoCalculateOnStart)
{
CalculateRotationOffsets();
}
}
#endregion
#region
private void Update()
{
if (autoCopyPose)
{
CopyPoseToTarget();
}
}
private void LateUpdate()
{
if (autoCopyPose)
{
CopyPoseToTarget();
}
}
#endregion
#region
/// <summary>
/// 원본과 대상 아바타의 각 본 간 회전 오프셋을 계산하여 저장합니다.
/// </summary>
public void CalculateRotationOffsets()
{
if (sourceAnimator == null || targetAnimator == null)
{
Debug.LogError("소스 또는 타겟 Animator가 설정되지 않았습니다.");
return;
}
// Dictionary가 null이면 초기화
if (rotationOffsets == null)
{
rotationOffsets = new Dictionary<HumanBodyBones, Quaternion>();
}
// 모든 본에 대해 오프셋 계산 (기본 몸체 본 + 손가락 본 + UpperChest)
for (int i = 0; i <= 54; i++)
{
HumanBodyBones bone = (HumanBodyBones)i;
Transform sourceBone = sourceAnimator.GetBoneTransform(bone);
Transform targetBone = targetAnimator.GetBoneTransform(bone);
if (sourceBone != null && targetBone != null)
{
Quaternion offset = Quaternion.Inverse(sourceBone.rotation) * targetBone.rotation;
// 최적화: ContainsKey + 인덱서 이중 조회 대신 인덱서 직접 사용
rotationOffsets[bone] = offset;
}
}
Debug.Log($"회전 오프셋 계산 완료: {rotationOffsets.Count}개 본");
}
/// <summary>
/// 특정 본의 회전 오프셋을 반환합니다.
/// </summary>
/// <param name="bone">본 타입</param>
/// <returns>회전 오프셋 (없으면 Identity)</returns>
public Quaternion GetRotationOffset(HumanBodyBones bone)
{
// 최적화: TryGetValue로 단일 조회
return rotationOffsets.TryGetValue(bone, out Quaternion offset) ? offset : Quaternion.identity;
}
/// <summary>
/// 특정 본에 회전 오프셋을 적용합니다.
/// </summary>
/// <param name="bone">본 타입</param>
/// <param name="originalRotation">원본 회전값</param>
/// <returns>오프셋이 적용된 회전값</returns>
public Quaternion ApplyRotationOffset(HumanBodyBones bone, Quaternion originalRotation)
{
// 최적화: TryGetValue로 단일 조회
return rotationOffsets.TryGetValue(bone, out Quaternion offset)
? originalRotation * offset
: originalRotation;
}
#endregion
#region
/// <summary>
/// 원본 아바타의 포즈를 대상 아바타에 복사합니다.
/// </summary>
public void CopyPoseToTarget()
{
if (sourceAnimator == null || targetAnimator == null)
{
Debug.LogError("소스 또는 타겟 Animator가 설정되지 않았습니다.");
return;
}
// 모든 본에 대해 포즈 복사
for (int i = 0; i <= 54; i++)
{
HumanBodyBones bone = (HumanBodyBones)i;
Transform sourceBone = sourceAnimator.GetBoneTransform(bone);
Transform targetBone = targetAnimator.GetBoneTransform(bone);
if (sourceBone != null && targetBone != null)
{
// 회전은 오프셋 적용, 위치는 그대로 복사
Quaternion targetRotation = ApplyRotationOffset(bone, sourceBone.rotation);
targetBone.rotation = targetRotation;
targetBone.position = sourceBone.position;
}
}
}
/// <summary>
/// 특정 본의 포즈를 복사합니다.
/// </summary>
/// <param name="bone">복사할 본</param>
public void CopyBonePose(HumanBodyBones bone)
{
if (sourceAnimator == null || targetAnimator == null)
{
Debug.LogError("소스 또는 타겟 Animator가 설정되지 않았습니다.");
return;
}
Transform sourceBone = sourceAnimator.GetBoneTransform(bone);
Transform targetBone = targetAnimator.GetBoneTransform(bone);
if (sourceBone != null && targetBone != null)
{
Quaternion targetRotation = ApplyRotationOffset(bone, sourceBone.rotation);
targetBone.rotation = targetRotation;
}
}
#endregion
}
}