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