Refactor : TwoBoneIKSolver를 Serializable 모듈로 전환
- TwoBoneIKSolver: MonoBehaviour → [Serializable] 클래스 - Start()/Update() → Initialize(Animator)/OnUpdate() - CRS에서 ikSolver 필드로 소유 및 호출 - FootGroundingController/LimbWeightController: GetComponent<TwoBoneIKSolver> → crs.ikSolver로 변경 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5c65185a61
commit
64a2069b69
@ -19,7 +19,7 @@ namespace KindRetargeting
|
|||||||
[HideInInspector] public Animator targetAnimator; // 대상 아바타의 Animator
|
[HideInInspector] public Animator targetAnimator; // 대상 아바타의 Animator
|
||||||
|
|
||||||
// IK 컴포넌트 참조
|
// IK 컴포넌트 참조
|
||||||
private TwoBoneIKSolver ikSolver;
|
[SerializeField] public TwoBoneIKSolver ikSolver = new TwoBoneIKSolver();
|
||||||
|
|
||||||
[Header("힙 위치 보정 (로컬 좌표계 기반)")]
|
[Header("힙 위치 보정 (로컬 좌표계 기반)")]
|
||||||
[SerializeField, Range(-1, 1)]
|
[SerializeField, Range(-1, 1)]
|
||||||
@ -269,8 +269,7 @@ namespace KindRetargeting
|
|||||||
// 설정 로드
|
// 설정 로드
|
||||||
LoadSettings();
|
LoadSettings();
|
||||||
|
|
||||||
// IK 컴포넌트 참조 가져오기
|
// IK 모듈은 InitializeIKJoints에서 초기화
|
||||||
ikSolver = GetComponent<TwoBoneIKSolver>();
|
|
||||||
|
|
||||||
// IK 타겟 생성 (무릎 시각화 오브젝트 포함)
|
// IK 타겟 생성 (무릎 시각화 오브젝트 포함)
|
||||||
CreateIKTargets();
|
CreateIKTargets();
|
||||||
@ -832,6 +831,9 @@ namespace KindRetargeting
|
|||||||
// 어깨 보정 (기존 ExecutionOrder 3)
|
// 어깨 보정 (기존 ExecutionOrder 3)
|
||||||
shoulderCorrection.OnUpdate();
|
shoulderCorrection.OnUpdate();
|
||||||
|
|
||||||
|
// IK 솔버 (기존 ExecutionOrder 6)
|
||||||
|
ikSolver.OnUpdate();
|
||||||
|
|
||||||
// 스케일 변경 확인 및 적용
|
// 스케일 변경 확인 및 적용
|
||||||
if (!Mathf.Approximately(previousScale, avatarScale))
|
if (!Mathf.Approximately(previousScale, avatarScale))
|
||||||
{
|
{
|
||||||
@ -1158,11 +1160,6 @@ namespace KindRetargeting
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void CreateIKTargets()
|
private void CreateIKTargets()
|
||||||
{
|
{
|
||||||
// IK 컴포넌트 가져오기 또는 새로 추가
|
|
||||||
ikSolver = GetComponent<TwoBoneIKSolver>();
|
|
||||||
if (ikSolver == null)
|
|
||||||
ikSolver = gameObject.AddComponent<TwoBoneIKSolver>();
|
|
||||||
|
|
||||||
ikSolver.animator = targetAnimator;
|
ikSolver.animator = targetAnimator;
|
||||||
|
|
||||||
// IK 타겟들을 담을 부모 오브젝트 생성
|
// IK 타겟들을 담을 부모 오브젝트 생성
|
||||||
@ -1194,7 +1191,7 @@ namespace KindRetargeting
|
|||||||
ikSolver.rightLeg.bendGoal = rightLegGoal.transform;
|
ikSolver.rightLeg.bendGoal = rightLegGoal.transform;
|
||||||
|
|
||||||
// TwoBoneIKSolver 본 캐싱 초기화
|
// TwoBoneIKSolver 본 캐싱 초기화
|
||||||
ikSolver.Initialize();
|
ikSolver.Initialize(targetAnimator);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -67,7 +67,8 @@ namespace KindRetargeting
|
|||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
ikSolver = GetComponent<TwoBoneIKSolver>();
|
var crs = GetComponent<CustomRetargetingScript>();
|
||||||
|
if (crs != null) ikSolver = crs.ikSolver;
|
||||||
animator = GetComponent<Animator>();
|
animator = GetComponent<Animator>();
|
||||||
|
|
||||||
if (animator == null || !animator.isHuman || ikSolver == null) return;
|
if (animator == null || !animator.isHuman || ikSolver == null) return;
|
||||||
|
|||||||
@ -96,9 +96,8 @@ namespace KindRetargeting
|
|||||||
|
|
||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
ikSolver = GetComponent<TwoBoneIKSolver>();
|
|
||||||
|
|
||||||
crs = GetComponent<CustomRetargetingScript>();
|
crs = GetComponent<CustomRetargetingScript>();
|
||||||
|
if (crs != null) ikSolver = crs.ikSolver;
|
||||||
|
|
||||||
InitWeightLayers();
|
InitWeightLayers();
|
||||||
|
|
||||||
|
|||||||
@ -7,8 +7,8 @@ namespace KindRetargeting
|
|||||||
/// FinalIK IKSolverTrigonometric.Solve()를 사용하는 IK 래퍼.
|
/// FinalIK IKSolverTrigonometric.Solve()를 사용하는 IK 래퍼.
|
||||||
/// 4개 사지(양팔, 양다리)에 대해 FinalIK의 검증된 코사인 법칙 솔버를 호출합니다.
|
/// 4개 사지(양팔, 양다리)에 대해 FinalIK의 검증된 코사인 법칙 솔버를 호출합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DefaultExecutionOrder(6)]
|
[System.Serializable]
|
||||||
public class TwoBoneIKSolver : MonoBehaviour
|
public class TwoBoneIKSolver
|
||||||
{
|
{
|
||||||
[System.Serializable]
|
[System.Serializable]
|
||||||
public class LimbIK
|
public class LimbIK
|
||||||
@ -27,7 +27,6 @@ namespace KindRetargeting
|
|||||||
[HideInInspector] public float upperLength;
|
[HideInInspector] public float upperLength;
|
||||||
[HideInInspector] public float lowerLength;
|
[HideInInspector] public float lowerLength;
|
||||||
|
|
||||||
// 초기 벤드 법선 (upper 본 로컬 공간 — FinalIK 방식)
|
|
||||||
[HideInInspector] public Vector3 localBendNormal;
|
[HideInInspector] public Vector3 localBendNormal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,15 +42,9 @@ namespace KindRetargeting
|
|||||||
|
|
||||||
private bool isInitialized;
|
private bool isInitialized;
|
||||||
|
|
||||||
private void Start()
|
public void Initialize(Animator targetAnimator)
|
||||||
{
|
{
|
||||||
Initialize();
|
animator = targetAnimator;
|
||||||
}
|
|
||||||
|
|
||||||
public void Initialize()
|
|
||||||
{
|
|
||||||
if (animator == null)
|
|
||||||
animator = GetComponent<Animator>();
|
|
||||||
if (animator == null || !animator.isHuman) return;
|
if (animator == null || !animator.isHuman) return;
|
||||||
|
|
||||||
CacheLimb(leftArm, HumanBodyBones.LeftUpperArm, HumanBodyBones.LeftLowerArm, HumanBodyBones.LeftHand);
|
CacheLimb(leftArm, HumanBodyBones.LeftUpperArm, HumanBodyBones.LeftLowerArm, HumanBodyBones.LeftHand);
|
||||||
@ -73,8 +66,6 @@ namespace KindRetargeting
|
|||||||
limb.upperLength = Vector3.Distance(limb.upper.position, limb.lower.position);
|
limb.upperLength = Vector3.Distance(limb.upper.position, limb.lower.position);
|
||||||
limb.lowerLength = Vector3.Distance(limb.lower.position, limb.end.position);
|
limb.lowerLength = Vector3.Distance(limb.lower.position, limb.end.position);
|
||||||
|
|
||||||
// 초기 벤드 법선을 upper 본의 로컬 공간에 캐싱 (FinalIK TrigonometricBone 방식)
|
|
||||||
// 런타임에 upper.rotation * localBendNormal로 안정적인 월드 법선 획득
|
|
||||||
Vector3 ab = limb.lower.position - limb.upper.position;
|
Vector3 ab = limb.lower.position - limb.upper.position;
|
||||||
Vector3 bc = limb.end.position - limb.lower.position;
|
Vector3 bc = limb.end.position - limb.lower.position;
|
||||||
Vector3 bendNormal = Vector3.Cross(ab, bc);
|
Vector3 bendNormal = Vector3.Cross(ab, bc);
|
||||||
@ -90,7 +81,7 @@ namespace KindRetargeting
|
|||||||
limb.localBendNormal = Quaternion.Inverse(limb.upper.rotation) * bendNormal;
|
limb.localBendNormal = Quaternion.Inverse(limb.upper.rotation) * bendNormal;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Update()
|
public void OnUpdate()
|
||||||
{
|
{
|
||||||
if (!isInitialized) return;
|
if (!isInitialized) return;
|
||||||
|
|
||||||
@ -105,10 +96,8 @@ namespace KindRetargeting
|
|||||||
if (!limb.enabled || limb.target == null || limb.positionWeight < 0.001f) return;
|
if (!limb.enabled || limb.target == null || limb.positionWeight < 0.001f) return;
|
||||||
if (limb.upper == null || limb.lower == null || limb.end == null) return;
|
if (limb.upper == null || limb.lower == null || limb.end == null) return;
|
||||||
|
|
||||||
// 벤드 법선 계산
|
|
||||||
Vector3 bendNormal = GetBendNormal(limb);
|
Vector3 bendNormal = GetBendNormal(limb);
|
||||||
|
|
||||||
// bendGoal 적용
|
|
||||||
if (limb.bendGoal != null && limb.bendGoalWeight > 0.001f)
|
if (limb.bendGoal != null && limb.bendGoalWeight > 0.001f)
|
||||||
{
|
{
|
||||||
Vector3 goalNormal = Vector3.Cross(
|
Vector3 goalNormal = Vector3.Cross(
|
||||||
@ -121,7 +110,6 @@ namespace KindRetargeting
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FinalIK 정적 솔버 호출
|
|
||||||
IKSolverTrigonometric.Solve(
|
IKSolverTrigonometric.Solve(
|
||||||
limb.upper,
|
limb.upper,
|
||||||
limb.lower,
|
limb.lower,
|
||||||
@ -131,7 +119,6 @@ namespace KindRetargeting
|
|||||||
limb.positionWeight
|
limb.positionWeight
|
||||||
);
|
);
|
||||||
|
|
||||||
// 끝단 회전
|
|
||||||
if (limb.rotationWeight > 0.001f)
|
if (limb.rotationWeight > 0.001f)
|
||||||
{
|
{
|
||||||
limb.end.rotation = Quaternion.Slerp(
|
limb.end.rotation = Quaternion.Slerp(
|
||||||
@ -142,19 +129,11 @@ namespace KindRetargeting
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 벤드 법선을 upper 본의 회전에서 유도합니다 (FinalIK 방식).
|
|
||||||
/// 위치 기반 Cross(ab, bc)는 직선 근처에서 불안정하지만,
|
|
||||||
/// 회전 기반은 본의 회전을 그대로 따르므로 안정적입니다.
|
|
||||||
/// </summary>
|
|
||||||
private Vector3 GetBendNormal(LimbIK limb)
|
private Vector3 GetBendNormal(LimbIK limb)
|
||||||
{
|
{
|
||||||
return limb.upper.rotation * limb.localBendNormal;
|
return limb.upper.rotation * limb.localBendNormal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 히프 높이 자동 보정값을 계산합니다.
|
|
||||||
/// </summary>
|
|
||||||
public float CalculateAutoFloorHeight(float comfortRatio = 0.98f)
|
public float CalculateAutoFloorHeight(float comfortRatio = 0.98f)
|
||||||
{
|
{
|
||||||
if (animator == null || leftLeg.upper == null || leftLeg.lower == null || leftLeg.end == null) return 0f;
|
if (animator == null || leftLeg.upper == null || leftLeg.lower == null || leftLeg.end == null) return 0f;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user