Refactor : KindRetargeting dead code 제거 + IK 안정성/성능 개선
- TwoBoneIK: cosine law → FABRIK 6회 반복 (역관절 안정 + 본 길이 제약) - 자동 힙 상하 보정 매 프레임 적용, 수동 hipsOffsetX/Y/Z 제거 - kneeFrontBackWeight/InOutWeight, GetAvatarScale 등 dead code 정리 - FingerShapedController GC 제거 (HumanPose/Transform 캐싱) - IK 본 길이 / FieldInfo / Chair prop / 가중치 배열(List→float[]) 캐싱 - localAxisForWorldRight/Forward, IKJoints 다리 필드 등 미사용 정리 - 매직 넘버 55 → BoneCount 상수 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
db9e968499
commit
b9361a9d17
@ -732,14 +732,10 @@ namespace KindRetargeting
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 머리 본에 회전 오프셋을 적용합니다.
|
||||
/// 머리 본에 회전 오프셋을 적용합니다. headBone 멤버 캐시 재사용 (ApplyHeadScale과 일관).
|
||||
/// </summary>
|
||||
private void ApplyHeadRotationOffset()
|
||||
{
|
||||
if (targetAnimator == null) return;
|
||||
|
||||
// 머리 본 가져오기
|
||||
Transform headBone = targetAnimator.GetBoneTransform(HumanBodyBones.Head);
|
||||
if (headBone == null) return;
|
||||
|
||||
// 오프셋이 모두 0이면 스킵
|
||||
@ -913,16 +909,30 @@ namespace KindRetargeting
|
||||
SyncBoneRotations(skipBone: HumanBodyBones.Hips);
|
||||
}
|
||||
|
||||
// 자동 힙 보정 캐시 (다리 본 길이는 스케일 변경 시에만 갱신됨)
|
||||
private float cachedAutoHipsOffsetY = 0f;
|
||||
private bool autoHipsOffsetCacheValid = false;
|
||||
|
||||
/// <summary>
|
||||
/// 매 프레임 자동으로 적용되는 힙 상하 보정값.
|
||||
/// = (타겟 다리길이 - 소스 다리길이) + (타겟 Hips↔UpperLeg 갭) × avatarScale
|
||||
///
|
||||
/// 첫 항: 다리 길이 차이로 발이 뜨거나 묻히는 현상 보정.
|
||||
/// 둘째 항: Hips 본이 UpperLeg보다 위에 있는 아바타 추가 보정.
|
||||
/// 매 프레임 호출되는 자동 힙 보정값. 본 길이는 변하지 않으므로 캐시된 값을 반환.
|
||||
/// avatarScale 변경 시 ApplyScale → RefreshAutoHipsOffsetCache 로 자동 갱신.
|
||||
/// </summary>
|
||||
private float ComputeAutoHipsOffsetY()
|
||||
{
|
||||
if (optitrackSource == null || targetAnimator == null) return 0f;
|
||||
if (!autoHipsOffsetCacheValid) RefreshAutoHipsOffsetCache();
|
||||
return cachedAutoHipsOffsetY;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 자동 힙 보정 캐시를 재계산합니다 (Initialize / avatarScale 변경 시 호출).
|
||||
/// = (타겟 다리길이 - 소스 다리길이) + (타겟 Hips↔UpperLeg 갭) × avatarScale
|
||||
/// </summary>
|
||||
private void RefreshAutoHipsOffsetCache()
|
||||
{
|
||||
cachedAutoHipsOffsetY = 0f;
|
||||
autoHipsOffsetCacheValid = true;
|
||||
|
||||
if (optitrackSource == null || targetAnimator == null) return;
|
||||
|
||||
Transform sUp = optitrackSource.GetBoneTransform(HumanBodyBones.LeftUpperLeg);
|
||||
Transform sLo = optitrackSource.GetBoneTransform(HumanBodyBones.LeftLowerLeg);
|
||||
@ -934,14 +944,14 @@ namespace KindRetargeting
|
||||
|
||||
if (sUp == null || sLo == null || sFt == null
|
||||
|| tUp == null || tLo == null || tFt == null || tHi == null)
|
||||
return 0f;
|
||||
return;
|
||||
|
||||
float sourceLeg = Vector3.Distance(sUp.position, sLo.position) + Vector3.Distance(sLo.position, sFt.position);
|
||||
float targetLeg = Vector3.Distance(tUp.position, tLo.position) + Vector3.Distance(tLo.position, tFt.position);
|
||||
if (sourceLeg < 0.01f || targetLeg < 0.01f) return 0f;
|
||||
if (sourceLeg < 0.01f || targetLeg < 0.01f) return;
|
||||
|
||||
float hipsToLegGap = tHi.position.y - tUp.position.y;
|
||||
return (targetLeg - sourceLeg) + hipsToLegGap * avatarScale;
|
||||
cachedAutoHipsOffsetY = (targetLeg - sourceLeg) + hipsToLegGap * avatarScale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1465,6 +1475,10 @@ namespace KindRetargeting
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 스케일 변경으로 본 길이가 바뀌었으니 IK 캐시 + 자동 힙 보정 캐시 갱신
|
||||
ikSolver?.RefreshLimbLengths();
|
||||
RefreshAutoHipsOffsetCache();
|
||||
}
|
||||
|
||||
// 리셋 기능 추가
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace KindRetargeting
|
||||
{
|
||||
@ -9,8 +8,10 @@ namespace KindRetargeting
|
||||
private Animator animator;
|
||||
private HumanPoseHandler humanPoseHandler;
|
||||
|
||||
// 손가락을 제외한 모든 본의 로컬 회전 저장용 (SetHumanPose 호출 시 몸 복원용)
|
||||
private Dictionary<HumanBodyBones, Quaternion> savedBoneLocalRotations = new Dictionary<HumanBodyBones, Quaternion>();
|
||||
// 매 프레임 재사용되는 캐시 (GC 압박 제거)
|
||||
private Transform[] cachedNonFingerBones;
|
||||
private Quaternion[] savedBoneRotations;
|
||||
private HumanPose cachedHumanPose;
|
||||
|
||||
// 손가락을 제외한 모든 휴먼본 목록
|
||||
private static readonly HumanBodyBones[] nonFingerBones = new HumanBodyBones[]
|
||||
@ -71,6 +72,18 @@ namespace KindRetargeting
|
||||
if (animator == null || !animator.isHuman) return;
|
||||
|
||||
humanPoseHandler = new HumanPoseHandler(animator.avatar, animator.transform);
|
||||
|
||||
// Transform 배열 + 회전 배열 사전 캐싱 (매 프레임 GetBoneTransform 50회 → 0회)
|
||||
cachedNonFingerBones = new Transform[nonFingerBones.Length];
|
||||
savedBoneRotations = new Quaternion[nonFingerBones.Length];
|
||||
for (int i = 0; i < nonFingerBones.Length; i++)
|
||||
{
|
||||
cachedNonFingerBones[i] = animator.GetBoneTransform(nonFingerBones[i]);
|
||||
}
|
||||
|
||||
// HumanPose 사전 할당 (muscles 배열 95개 float, GC 회피)
|
||||
humanPoseHandler.GetHumanPose(ref cachedHumanPose);
|
||||
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
@ -92,40 +105,29 @@ namespace KindRetargeting
|
||||
|
||||
private void UpdateMuscleValues()
|
||||
{
|
||||
// 1. 손가락을 제외한 모든 본의 로컬 회전 저장 (SetHumanPose 호출 전)
|
||||
savedBoneLocalRotations.Clear();
|
||||
for (int i = 0; i < nonFingerBones.Length; i++)
|
||||
// 1. 비손가락 본의 로컬 회전을 배열에 저장 (캐시된 Transform 재사용)
|
||||
for (int i = 0; i < cachedNonFingerBones.Length; i++)
|
||||
{
|
||||
Transform bone = animator.GetBoneTransform(nonFingerBones[i]);
|
||||
if (bone != null)
|
||||
{
|
||||
savedBoneLocalRotations[nonFingerBones[i]] = bone.localRotation;
|
||||
}
|
||||
Transform bone = cachedNonFingerBones[i];
|
||||
if (bone != null) savedBoneRotations[i] = bone.localRotation;
|
||||
}
|
||||
|
||||
// 2. HumanPose 가져오기 및 손가락 머슬 설정
|
||||
HumanPose humanPose = new HumanPose();
|
||||
humanPoseHandler.GetHumanPose(ref humanPose);
|
||||
// 2. HumanPose 가져오기 (재사용 필드, GC 없음) + 손가락 머슬 설정
|
||||
humanPoseHandler.GetHumanPose(ref cachedHumanPose);
|
||||
|
||||
// 왼손 제어
|
||||
SetHandMuscles(true, leftThumbCurl, leftIndexCurl, leftMiddleCurl, leftRingCurl,
|
||||
leftPinkyCurl, leftSpreadFingers, ref humanPose);
|
||||
|
||||
// 오른손 제어
|
||||
leftPinkyCurl, leftSpreadFingers, ref cachedHumanPose);
|
||||
SetHandMuscles(false, rightThumbCurl, rightIndexCurl, rightMiddleCurl, rightRingCurl,
|
||||
rightPinkyCurl, rightSpreadFingers, ref humanPose);
|
||||
rightPinkyCurl, rightSpreadFingers, ref cachedHumanPose);
|
||||
|
||||
// 3. 머슬 포즈 적용 (손가락 포함 전체 본에 영향)
|
||||
humanPoseHandler.SetHumanPose(ref humanPose);
|
||||
humanPoseHandler.SetHumanPose(ref cachedHumanPose);
|
||||
|
||||
// 4. 손가락을 제외한 모든 본의 로컬 회전 복원 (본 길이 변형 방지)
|
||||
foreach (var kvp in savedBoneLocalRotations)
|
||||
// 4. 비손가락 본 로컬 회전 복원 (본 길이 변형 방지)
|
||||
for (int i = 0; i < cachedNonFingerBones.Length; i++)
|
||||
{
|
||||
Transform bone = animator.GetBoneTransform(kvp.Key);
|
||||
if (bone != null)
|
||||
{
|
||||
bone.localRotation = kvp.Value;
|
||||
}
|
||||
Transform bone = cachedNonFingerBones[i];
|
||||
if (bone != null) bone.localRotation = savedBoneRotations[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -39,13 +39,14 @@ namespace KindRetargeting
|
||||
private CustomRetargetingScript crs;
|
||||
private Transform characterRoot;
|
||||
|
||||
List<float> leftArmEndWeights = new List<float>();
|
||||
List<float> rightArmEndWeights = new List<float>();
|
||||
List<float> leftLegEndWeights = new List<float>();
|
||||
List<float> rightLegEndWeights = new List<float>();
|
||||
// 가중치 배열: 인덱스 의미는 Initialize 주석 참조 (크기 고정 → float[] 사용으로 GC/인덱싱 비용 절감)
|
||||
readonly float[] leftArmEndWeights = new float[2]; // [0] 양손거리 [1] 프랍거리
|
||||
readonly float[] rightArmEndWeights = new float[2];
|
||||
readonly float[] leftLegEndWeights = new float[2]; // [0] 앉기 수평거리 [1] 발 높이
|
||||
readonly float[] rightLegEndWeights = new float[2];
|
||||
|
||||
List<float> leftLegBendWeights = new List<float>();
|
||||
List<float> rightLegBendWeights = new List<float>();
|
||||
readonly float[] leftLegBendWeights = new float[1] { 1f };
|
||||
readonly float[] rightLegBendWeights = new float[1] { 1f };
|
||||
|
||||
private float MasterleftArmEndWeights = 0f;
|
||||
private float MasterrightArmEndWeights = 0f;
|
||||
@ -55,9 +56,11 @@ namespace KindRetargeting
|
||||
private float MasterrightLegBendWeights = 0f;
|
||||
|
||||
public List<Transform> props = new List<Transform>();
|
||||
// SitChairDistances 매 프레임 GetComponent 비용 회피용 (Initialize 시점 한 번 캐싱)
|
||||
private List<Transform> chairProps = new List<Transform>();
|
||||
|
||||
// 힙스 가중치 리스트 추가
|
||||
List<float> hipsWeights = new List<float>();
|
||||
// 힙스 가중치: [0] 의자 거리 [1] 지면 높이
|
||||
readonly float[] hipsWeights = new float[2] { 1f, 1f };
|
||||
private float MasterHipsWeight = 1f;
|
||||
|
||||
// 의자 좌석 높이 오프셋 (월드 Y 기준)
|
||||
@ -76,44 +79,26 @@ namespace KindRetargeting
|
||||
|
||||
InitWeightLayers();
|
||||
|
||||
//프랍 오브젝트 찾기
|
||||
props = Object.FindObjectsByType<PropTypeController>(FindObjectsSortMode.None).Select(controller => controller.transform).ToList();
|
||||
//프랍 오브젝트 찾기 + 의자 타입 별도 캐싱
|
||||
var allPropControllers = Object.FindObjectsByType<PropTypeController>(FindObjectsSortMode.None);
|
||||
props = allPropControllers.Select(c => c.transform).ToList();
|
||||
chairProps = allPropControllers
|
||||
.Where(c => c.propType == EnumsList.PropType.Chair)
|
||||
.Select(c => c.transform)
|
||||
.ToList();
|
||||
|
||||
// 다른 캐릭터의 손을 props에 추가
|
||||
GetHand();
|
||||
|
||||
//HandDistances()에서 사용을 위한 리스트 추가
|
||||
//손 거리에 따른 웨이트 업데이트 인덱스 0번
|
||||
leftArmEndWeights.Add(0);
|
||||
rightArmEndWeights.Add(0);
|
||||
|
||||
// 프랍과의 거리에 따른 웨이트 업데이트 인덱스 1번
|
||||
leftArmEndWeights.Add(0);
|
||||
rightArmEndWeights.Add(0);
|
||||
|
||||
// 앉아있을 때 다리와의 거리에 따른 가중치 적용 인덱스 0번
|
||||
leftLegEndWeights.Add(0);
|
||||
rightLegEndWeights.Add(0);
|
||||
|
||||
// 다리 골 가중치 초기화
|
||||
leftLegBendWeights.Add(1f); // 기본 가중치
|
||||
rightLegBendWeights.Add(1f); // 기본 가중치
|
||||
// 가중치 배열은 필드 선언 시 고정 크기로 초기화됨 (다리 EndWeight[1]만 1f 기본값)
|
||||
leftLegEndWeights[1] = 1f;
|
||||
rightLegEndWeights[1] = 1f;
|
||||
|
||||
if (this.characterRoot == null)
|
||||
{
|
||||
this.characterRoot = crs.transform;
|
||||
}
|
||||
|
||||
// 힙스 가중치 초기화 인덱스 0번
|
||||
hipsWeights.Add(1f); // 의자 거리 기반 가중치
|
||||
|
||||
// 지면 높이 기반 가중치 초기화 인덱스 1번
|
||||
hipsWeights.Add(1f); // 지면 높이 기반 가중치
|
||||
|
||||
// 발 높이 기반 가중치 초기화 인덱스 1번
|
||||
leftLegEndWeights.Add(1f);
|
||||
rightLegEndWeights.Add(1f);
|
||||
|
||||
isInitialized = true;
|
||||
}
|
||||
|
||||
@ -237,7 +222,7 @@ namespace KindRetargeting
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessSitLegWeight(Transform hips, Transform footTarget, List<float> weightList, int weightIndex)
|
||||
private void ProcessSitLegWeight(Transform hips, Transform footTarget, float[] weightArr, int weightIndex)
|
||||
{
|
||||
if (footTarget == null) return;
|
||||
|
||||
@ -256,7 +241,7 @@ namespace KindRetargeting
|
||||
float weight = 1f - Mathf.Clamp01((horizontalDistance - MIN_LEG_DISTANCE_RATIO) /
|
||||
(MAX_LEG_DISTANCE_RATIO - MIN_LEG_DISTANCE_RATIO));
|
||||
|
||||
weightList[weightIndex] = weight;
|
||||
weightArr[weightIndex] = weight;
|
||||
}
|
||||
|
||||
void PropDistances()
|
||||
@ -298,40 +283,29 @@ namespace KindRetargeting
|
||||
{
|
||||
if (crs == null) return;
|
||||
|
||||
Transform hipsTransform = crs.optitrackSource.GetBoneTransform(HumanBodyBones.Hips);
|
||||
if (hipsTransform != null && props != null)
|
||||
Transform hipsTransform = crs?.optitrackSource?.GetBoneTransform(HumanBodyBones.Hips);
|
||||
if (hipsTransform == null || chairProps.Count == 0)
|
||||
{
|
||||
float minDistance = float.MaxValue;
|
||||
bool foundChair = false;
|
||||
|
||||
foreach (Transform prop in props)
|
||||
{
|
||||
PropTypeController ptc = prop.GetComponent<PropTypeController>();
|
||||
if (ptc != null && ptc.propType == EnumsList.PropType.Chair)
|
||||
{
|
||||
float distance = Vector3.Distance(hipsTransform.position, prop.childCount > 0 ? prop.GetChild(0).position : prop.position);
|
||||
if (distance < minDistance)
|
||||
{
|
||||
minDistance = distance;
|
||||
foundChair = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float t = Mathf.Clamp01((minDistance - hipsMinDistance) / (hipsMaxDistance - hipsMinDistance));
|
||||
hipsWeights[0] = t; // 직접 HipsWeightOffset 수정 대신 배열에 저장
|
||||
|
||||
// 의자 좌석 높이 오프셋 계산 (가까울수록 더 적용) - 캐릭터별 설정 사용
|
||||
if (foundChair)
|
||||
{
|
||||
// t가 0에 가까울수록 의자에 가까움 → 좌석 오프셋 더 적용
|
||||
targetChairSeatOffset = chairSeatHeightOffset * (1f - t);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetChairSeatOffset = 0f;
|
||||
}
|
||||
hipsWeights[0] = 1f;
|
||||
targetChairSeatOffset = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
// chairProps는 Initialize에서 미리 필터링됨 (매 프레임 GetComponent 호출 회피)
|
||||
float minDistance = float.MaxValue;
|
||||
for (int i = 0; i < chairProps.Count; i++)
|
||||
{
|
||||
Transform chair = chairProps[i];
|
||||
if (chair == null) continue;
|
||||
Vector3 chairPos = chair.childCount > 0 ? chair.GetChild(0).position : chair.position;
|
||||
float distance = Vector3.Distance(hipsTransform.position, chairPos);
|
||||
if (distance < minDistance) minDistance = distance;
|
||||
}
|
||||
|
||||
float t = Mathf.Clamp01((minDistance - hipsMinDistance) / (hipsMaxDistance - hipsMinDistance));
|
||||
hipsWeights[0] = t;
|
||||
// t가 0에 가까울수록 의자에 가까움 → 좌석 오프셋 더 적용
|
||||
targetChairSeatOffset = chairSeatHeightOffset * (1f - t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -400,18 +374,18 @@ namespace KindRetargeting
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 리스트에서 최대값을 찾습니다.
|
||||
/// 배열에서 최대값을 찾습니다.
|
||||
/// </summary>
|
||||
private float GetMaxValue(List<float> list)
|
||||
private float GetMaxValue(float[] arr)
|
||||
{
|
||||
if (list.Count == 0) return 0f;
|
||||
if (arr.Length == 0) return 0f;
|
||||
|
||||
float max = list[0];
|
||||
for (int i = 1; i < list.Count; i++)
|
||||
float max = arr[0];
|
||||
for (int i = 1; i < arr.Length; i++)
|
||||
{
|
||||
if (list[i] > max)
|
||||
if (arr[i] > max)
|
||||
{
|
||||
max = list[i];
|
||||
max = arr[i];
|
||||
}
|
||||
}
|
||||
return max;
|
||||
@ -419,18 +393,18 @@ namespace KindRetargeting
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 리스트에서 최소값을 찾습니다.
|
||||
/// 배열에서 최소값을 찾습니다.
|
||||
/// </summary>
|
||||
private float GetMinValue(List<float> list)
|
||||
private float GetMinValue(float[] arr)
|
||||
{
|
||||
if (list.Count == 0) return 0f;
|
||||
if (arr.Length == 0) return 0f;
|
||||
|
||||
float min = list[0];
|
||||
for (int i = 1; i < list.Count; i++)
|
||||
float min = arr[0];
|
||||
for (int i = 1; i < arr.Length; i++)
|
||||
{
|
||||
if (list[i] < min)
|
||||
if (arr[i] < min)
|
||||
{
|
||||
min = list[i];
|
||||
min = arr[i];
|
||||
}
|
||||
}
|
||||
return min;
|
||||
@ -473,7 +447,7 @@ namespace KindRetargeting
|
||||
{
|
||||
if (crs == null || ikSolver == null) return;
|
||||
|
||||
Transform hipsTransform = crs.optitrackSource.GetBoneTransform(HumanBodyBones.Hips);
|
||||
Transform hipsTransform = crs?.optitrackSource?.GetBoneTransform(HumanBodyBones.Hips);
|
||||
if (hipsTransform != null)
|
||||
{
|
||||
float groundHeight = characterRoot.position.y;
|
||||
@ -506,7 +480,7 @@ namespace KindRetargeting
|
||||
);
|
||||
}
|
||||
|
||||
private void ProcessFootHeightWeight(Transform footTarget, List<float> weightList, int weightIndex)
|
||||
private void ProcessFootHeightWeight(Transform footTarget, float[] weightArr, int weightIndex)
|
||||
{
|
||||
if (footTarget == null) return;
|
||||
|
||||
@ -518,7 +492,7 @@ namespace KindRetargeting
|
||||
(footHeightMaxThreshold - footHeightMinThreshold));
|
||||
|
||||
// 계산된 가중치 설정
|
||||
weightList[weightIndex] = weight;
|
||||
weightArr[weightIndex] = weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -624,11 +624,25 @@ namespace KindRetargeting.Remote
|
||||
|
||||
#region Reflection Helpers
|
||||
|
||||
// 슬라이더 드래그(60fps) 시 매번 GetField 호출되는 것을 방지.
|
||||
// (Type, fieldName) → FieldInfo 1회 lookup 후 캐싱.
|
||||
private static readonly Dictionary<(System.Type, string), FieldInfo> _fieldCache = new Dictionary<(System.Type, string), FieldInfo>();
|
||||
private const BindingFlags FIELD_FLAGS = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public;
|
||||
|
||||
private static FieldInfo ResolveField(System.Type type, string fieldName)
|
||||
{
|
||||
var key = (type, fieldName);
|
||||
if (_fieldCache.TryGetValue(key, out FieldInfo cached)) return cached;
|
||||
FieldInfo field = type.GetField(fieldName, FIELD_FLAGS);
|
||||
_fieldCache[key] = field;
|
||||
return field;
|
||||
}
|
||||
|
||||
private T GetPrivateField<T>(object obj, string fieldName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var field = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
|
||||
var field = ResolveField(obj.GetType(), fieldName);
|
||||
if (field != null)
|
||||
return (T)field.GetValue(obj);
|
||||
|
||||
@ -646,7 +660,7 @@ namespace KindRetargeting.Remote
|
||||
{
|
||||
try
|
||||
{
|
||||
var field = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
|
||||
var field = ResolveField(obj.GetType(), fieldName);
|
||||
if (field != null)
|
||||
{
|
||||
field.SetValue(obj, value);
|
||||
|
||||
@ -5,6 +5,9 @@ using KindRetargeting;
|
||||
[DefaultExecutionOrder(16001)]
|
||||
public class SimplePoseTransfer : MonoBehaviour
|
||||
{
|
||||
// 0~54: 휴머노이드 본 (몸체 + 손가락). HumanBodyBones.LastBone 직전까지.
|
||||
private const int BoneCount = 55;
|
||||
|
||||
[System.Serializable]
|
||||
public class TargetEntry
|
||||
{
|
||||
@ -121,7 +124,7 @@ public class SimplePoseTransfer : MonoBehaviour
|
||||
|
||||
private void InitializeTargetBones()
|
||||
{
|
||||
boneRotationDifferences = new Quaternion[targets.Count, 55];
|
||||
boneRotationDifferences = new Quaternion[targets.Count, BoneCount];
|
||||
|
||||
for (int i = 0; i < targets.Count; i++)
|
||||
{
|
||||
@ -133,7 +136,7 @@ public class SimplePoseTransfer : MonoBehaviour
|
||||
}
|
||||
|
||||
// 55개의 휴머노이드 본에 대해 회전 차이 계산
|
||||
for (int j = 0; j < 55; j++)
|
||||
for (int j = 0; j < BoneCount; j++)
|
||||
{
|
||||
Transform sourceBoneTransform = sourceBone.GetBoneTransform((HumanBodyBones)j);
|
||||
Transform targetBoneTransform = animator.GetBoneTransform((HumanBodyBones)j);
|
||||
@ -149,19 +152,19 @@ public class SimplePoseTransfer : MonoBehaviour
|
||||
private void CacheAllBoneTransforms()
|
||||
{
|
||||
// 소스 본 캐싱
|
||||
cachedSourceBones = new Transform[55];
|
||||
for (int i = 0; i < 55; i++)
|
||||
cachedSourceBones = new Transform[BoneCount];
|
||||
for (int i = 0; i < BoneCount; i++)
|
||||
{
|
||||
cachedSourceBones[i] = sourceBone.GetBoneTransform((HumanBodyBones)i);
|
||||
}
|
||||
|
||||
// 타겟 본 캐싱
|
||||
cachedTargetBones = new Transform[targets.Count, 55];
|
||||
cachedTargetBones = new Transform[targets.Count, BoneCount];
|
||||
for (int t = 0; t < targets.Count; t++)
|
||||
{
|
||||
Animator animator = targets[t].animator;
|
||||
if (animator == null) continue;
|
||||
for (int i = 0; i < 55; i++)
|
||||
for (int i = 0; i < BoneCount; i++)
|
||||
{
|
||||
cachedTargetBones[t, i] = animator.GetBoneTransform((HumanBodyBones)i);
|
||||
}
|
||||
@ -205,7 +208,7 @@ public class SimplePoseTransfer : MonoBehaviour
|
||||
}
|
||||
|
||||
// 모든 본에 대해 포즈 전송
|
||||
for (int i = 0; i < 55; i++)
|
||||
for (int i = 0; i < BoneCount; i++)
|
||||
{
|
||||
Transform targetBoneTransform = cachedTargetBones[targetIndex, i];
|
||||
Transform sourceBoneTransform = cachedSourceBones[i];
|
||||
|
||||
@ -88,6 +88,25 @@ namespace KindRetargeting
|
||||
limb.localBendNormal = Quaternion.Inverse(limb.upper.rotation) * bendNormal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// avatarScale 등으로 아바타 크기가 바뀌었을 때 호출하여 본 길이 캐시를 재계산.
|
||||
/// </summary>
|
||||
public void RefreshLimbLengths()
|
||||
{
|
||||
if (!isInitialized) return;
|
||||
RecacheLength(leftArm);
|
||||
RecacheLength(rightArm);
|
||||
RecacheLength(leftLeg);
|
||||
RecacheLength(rightLeg);
|
||||
}
|
||||
|
||||
private void RecacheLength(LimbIK limb)
|
||||
{
|
||||
if (limb.upper == null || limb.lower == null || limb.end == null) return;
|
||||
limb.upperLength = Vector3.Distance(limb.upper.position, limb.lower.position);
|
||||
limb.lowerLength = Vector3.Distance(limb.lower.position, limb.end.position);
|
||||
}
|
||||
|
||||
public void OnUpdate()
|
||||
{
|
||||
if (!isInitialized) return;
|
||||
@ -111,8 +130,9 @@ namespace KindRetargeting
|
||||
Quaternion fkLowerRot = limb.lower.rotation;
|
||||
Quaternion fkEndRot = limb.end.rotation;
|
||||
|
||||
float upperLen = Vector3.Distance(limb.upper.position, limb.lower.position);
|
||||
float lowerLen = Vector3.Distance(limb.lower.position, limb.end.position);
|
||||
// 본 길이는 Initialize/RefreshLimbLengths에서 캐싱됨 (avatarScale 변경 시 갱신)
|
||||
float upperLen = limb.upperLength;
|
||||
float lowerLen = limb.lowerLength;
|
||||
|
||||
Vector3 targetPos = limb.target.position;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user