265 lines
8.3 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using KindRetargeting;
[DefaultExecutionOrder(16001)]
public class SimplePoseTransfer : MonoBehaviour
{
[System.Serializable]
public class TargetEntry
{
public Animator animator;
[Tooltip("월드 공간 힙 오프셋")]
public Vector3 hipOffset;
[Tooltip("소스의 루트 스케일 변화량을 타겟에도 적용")]
public bool syncRootScale = true;
}
[Header("Pose Transfer Settings")]
public Animator sourceBone;
public List<TargetEntry> targets = new List<TargetEntry>();
[Header("Scale Transfer")]
[Tooltip("소스의 머리 스케일을 타겟에도 적용")]
public bool transferHeadScale = true;
// 캐싱된 Transform들
private Transform[] cachedSourceBones;
private Transform[,] cachedTargetBones;
// 각 target의 초기 회전 차이를 저장
private Quaternion[,] boneRotationDifferences;
// 머리 스케일 관련
private CustomRetargetingScript sourceRetargetingScript;
private Vector3[] originalTargetHeadScales;
// 루트 스케일 관련
private Vector3 originalSourceRootScale;
private Vector3[] originalTargetRootScales;
private void Start()
{
Init();
}
public void Init()
{
if (targets == null || targets.Count == 0)
{
Debug.LogError("Targets are null or empty");
return;
}
if (sourceBone == null)
{
Debug.LogError("Source bone is null");
return;
}
InitializeTargetBones();
CacheAllBoneTransforms();
CacheHeadScales();
Debug.Log($"SimplePoseTransfer initialized with {targets.Count} targets");
}
public void SetHipOffset(int targetIndex, Vector3 offset)
{
if (targets == null || targetIndex < 0 || targetIndex >= targets.Count)
{
return;
}
TargetEntry entry = targets[targetIndex];
entry.hipOffset = offset;
targets[targetIndex] = entry;
}
public Vector3 GetHipOffset(int targetIndex)
{
if (targets == null || targetIndex < 0 || targetIndex >= targets.Count)
{
return Vector3.zero;
}
return targets[targetIndex].hipOffset;
}
private void CacheHeadScales()
{
// 소스에서 CustomRetargetingScript 찾기
sourceRetargetingScript = sourceBone.GetComponent<CustomRetargetingScript>();
// 소스 루트 스케일 저장
originalSourceRootScale = sourceBone.transform.localScale;
// 타겟들의 원본 머리/루트 스케일 저장
originalTargetHeadScales = new Vector3[targets.Count];
originalTargetRootScales = new Vector3[targets.Count];
for (int i = 0; i < targets.Count; i++)
{
Animator animator = targets[i].animator;
if (animator != null)
{
originalTargetRootScales[i] = animator.transform.localScale;
Transform headBone = animator.GetBoneTransform(HumanBodyBones.Head);
if (headBone != null)
{
originalTargetHeadScales[i] = headBone.localScale;
}
else
{
originalTargetHeadScales[i] = Vector3.one;
}
}
else
{
originalTargetRootScales[i] = Vector3.one;
}
}
}
private void InitializeTargetBones()
{
boneRotationDifferences = new Quaternion[targets.Count, 55];
for (int i = 0; i < targets.Count; i++)
{
Animator animator = targets[i].animator;
if (animator == null)
{
Debug.LogError($"targets[{i}].animator is null");
continue;
}
// 55개의 휴머노이드 본에 대해 회전 차이 계산
for (int j = 0; j < 55; j++)
{
Transform sourceBoneTransform = sourceBone.GetBoneTransform((HumanBodyBones)j);
Transform targetBoneTransform = animator.GetBoneTransform((HumanBodyBones)j);
if (sourceBoneTransform != null && targetBoneTransform != null)
{
boneRotationDifferences[i, j] = Quaternion.Inverse(sourceBoneTransform.rotation) * targetBoneTransform.rotation;
}
}
}
}
private void CacheAllBoneTransforms()
{
// 소스 본 캐싱
cachedSourceBones = new Transform[55];
for (int i = 0; i < 55; i++)
{
cachedSourceBones[i] = sourceBone.GetBoneTransform((HumanBodyBones)i);
}
// 타겟 본 캐싱
cachedTargetBones = new Transform[targets.Count, 55];
for (int t = 0; t < targets.Count; t++)
{
Animator animator = targets[t].animator;
if (animator == null) continue;
for (int i = 0; i < 55; i++)
{
cachedTargetBones[t, i] = animator.GetBoneTransform((HumanBodyBones)i);
}
}
}
private void LateUpdate()
{
TransferPoses();
}
private void TransferPoses()
{
if (sourceBone == null)
{
return;
}
for (int i = 0; i < targets.Count; i++)
{
Animator animator = targets[i].animator;
if (animator != null && animator.gameObject.activeInHierarchy)
{
TransferPoseToTarget(i);
}
}
}
private void TransferPoseToTarget(int targetIndex)
{
TargetEntry entry = targets[targetIndex];
Animator targetAnimator = entry.animator;
// 루트 회전 동기화
targetAnimator.transform.rotation = sourceBone.transform.rotation;
// 루트 스케일 동기화 (타겟별 on/off)
if (entry.syncRootScale)
{
ApplyRootScale(targetIndex, targetAnimator.transform);
}
// 모든 본에 대해 포즈 전송
for (int i = 0; i < 55; i++)
{
Transform targetBoneTransform = cachedTargetBones[targetIndex, i];
Transform sourceBoneTransform = cachedSourceBones[i];
if (targetBoneTransform != null && sourceBoneTransform != null)
{
// 회전 적용
targetBoneTransform.rotation = sourceBoneTransform.rotation * boneRotationDifferences[targetIndex, i];
// 펠비스 위치 동기화 (HumanBodyBones.Hips = 0) + 월드 공간 힙 오프셋
if (i == 0)
{
targetBoneTransform.position = sourceBoneTransform.position + entry.hipOffset;
}
// 머리 스케일 적용 (HumanBodyBones.Head = 10)
if (transferHeadScale && i == 10)
{
ApplyHeadScale(targetIndex, targetBoneTransform);
}
}
}
}
private void ApplyRootScale(int targetIndex, Transform targetRoot)
{
// 소스의 초기 루트 스케일 대비 현재 스케일 비율을 타겟 원본 스케일에 곱해 적용
Vector3 sourceCurrent = sourceBone.transform.localScale;
Vector3 sourceOriginal = originalSourceRootScale;
Vector3 ratio = new Vector3(
sourceOriginal.x != 0f ? sourceCurrent.x / sourceOriginal.x : 1f,
sourceOriginal.y != 0f ? sourceCurrent.y / sourceOriginal.y : 1f,
sourceOriginal.z != 0f ? sourceCurrent.z / sourceOriginal.z : 1f
);
targetRoot.localScale = Vector3.Scale(originalTargetRootScales[targetIndex], ratio);
}
private void ApplyHeadScale(int targetIndex, Transform targetHeadBone)
{
if (sourceRetargetingScript != null)
{
float headScale = sourceRetargetingScript.GetHeadScale();
targetHeadBone.localScale = originalTargetHeadScales[targetIndex] * headScale;
}
else
{
// CustomRetargetingScript가 없으면 소스 머리 스케일 직접 복사
Transform sourceHead = cachedSourceBones[10]; // HumanBodyBones.Head
if (sourceHead != null)
{
targetHeadBone.localScale = sourceHead.localScale;
}
}
}
}