user 64a2069b69 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>
2026-03-07 23:04:55 +09:00

157 lines
5.7 KiB
C#

using UnityEngine;
using RootMotion.FinalIK;
namespace KindRetargeting
{
/// <summary>
/// FinalIK IKSolverTrigonometric.Solve()를 사용하는 IK 래퍼.
/// 4개 사지(양팔, 양다리)에 대해 FinalIK의 검증된 코사인 법칙 솔버를 호출합니다.
/// </summary>
[System.Serializable]
public class TwoBoneIKSolver
{
[System.Serializable]
public class LimbIK
{
public bool enabled = true;
public Transform target;
public Transform bendGoal;
[Range(0f, 1f)] public float positionWeight = 0f;
[Range(0f, 1f)] public float rotationWeight = 0f;
[Range(0f, 1f)] public float bendGoalWeight = 0f;
[HideInInspector] public Transform upper;
[HideInInspector] public Transform lower;
[HideInInspector] public Transform end;
[HideInInspector] public float upperLength;
[HideInInspector] public float lowerLength;
[HideInInspector] public Vector3 localBendNormal;
}
[HideInInspector] public Animator animator;
[Header("팔")]
public LimbIK leftArm = new LimbIK();
public LimbIK rightArm = new LimbIK();
[Header("다리")]
public LimbIK leftLeg = new LimbIK();
public LimbIK rightLeg = new LimbIK();
private bool isInitialized;
public void Initialize(Animator targetAnimator)
{
animator = targetAnimator;
if (animator == null || !animator.isHuman) return;
CacheLimb(leftArm, HumanBodyBones.LeftUpperArm, HumanBodyBones.LeftLowerArm, HumanBodyBones.LeftHand);
CacheLimb(rightArm, HumanBodyBones.RightUpperArm, HumanBodyBones.RightLowerArm, HumanBodyBones.RightHand);
CacheLimb(leftLeg, HumanBodyBones.LeftUpperLeg, HumanBodyBones.LeftLowerLeg, HumanBodyBones.LeftFoot);
CacheLimb(rightLeg, HumanBodyBones.RightUpperLeg, HumanBodyBones.RightLowerLeg, HumanBodyBones.RightFoot);
isInitialized = true;
}
private void CacheLimb(LimbIK limb, HumanBodyBones upperBone, HumanBodyBones lowerBone, HumanBodyBones endBone)
{
limb.upper = animator.GetBoneTransform(upperBone);
limb.lower = animator.GetBoneTransform(lowerBone);
limb.end = animator.GetBoneTransform(endBone);
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);
Vector3 ab = limb.lower.position - limb.upper.position;
Vector3 bc = limb.end.position - limb.lower.position;
Vector3 bendNormal = Vector3.Cross(ab, bc);
if (bendNormal.sqrMagnitude < 0.0001f)
{
bendNormal = Vector3.Cross(ab.normalized, Vector3.up);
if (bendNormal.sqrMagnitude < 0.0001f)
bendNormal = Vector3.Cross(ab.normalized, Vector3.forward);
}
bendNormal.Normalize();
limb.localBendNormal = Quaternion.Inverse(limb.upper.rotation) * bendNormal;
}
public void OnUpdate()
{
if (!isInitialized) return;
SolveLimb(leftArm);
SolveLimb(rightArm);
SolveLimb(leftLeg);
SolveLimb(rightLeg);
}
private void SolveLimb(LimbIK limb)
{
if (!limb.enabled || limb.target == null || limb.positionWeight < 0.001f) return;
if (limb.upper == null || limb.lower == null || limb.end == null) return;
Vector3 bendNormal = GetBendNormal(limb);
if (limb.bendGoal != null && limb.bendGoalWeight > 0.001f)
{
Vector3 goalNormal = Vector3.Cross(
limb.bendGoal.position - limb.upper.position,
limb.target.position - limb.upper.position
);
if (goalNormal.sqrMagnitude > 0.0001f)
{
bendNormal = Vector3.Lerp(bendNormal, goalNormal.normalized, limb.bendGoalWeight);
}
}
IKSolverTrigonometric.Solve(
limb.upper,
limb.lower,
limb.end,
limb.target.position,
bendNormal,
limb.positionWeight
);
if (limb.rotationWeight > 0.001f)
{
limb.end.rotation = Quaternion.Slerp(
limb.end.rotation,
limb.target.rotation,
limb.rotationWeight
);
}
}
private Vector3 GetBendNormal(LimbIK limb)
{
return limb.upper.rotation * limb.localBendNormal;
}
public float CalculateAutoFloorHeight(float comfortRatio = 0.98f)
{
if (animator == null || leftLeg.upper == null || leftLeg.lower == null || leftLeg.end == null) return 0f;
float upperLen = Vector3.Distance(leftLeg.upper.position, leftLeg.lower.position);
float lowerLen = Vector3.Distance(leftLeg.lower.position, leftLeg.end.position);
float totalLegLength = upperLen + lowerLen;
float comfortHeight = totalLegLength * comfortRatio;
Transform hips = animator.GetBoneTransform(HumanBodyBones.Hips);
Transform foot = leftLeg.end;
if (hips == null || foot == null) return 0f;
float currentHipToFoot = hips.position.y - foot.position.y;
float heightDiff = currentHipToFoot - comfortHeight;
return -heightDiff;
}
}
}