Fix : 스크립트 디테일 제어
This commit is contained in:
parent
b14c3f6f40
commit
783ab14f71
BIN
Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.prefab
(Stored with Git LFS)
vendored
BIN
Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar 5bone/BaseAvatar - OptiTrack 5 bone.prefab
(Stored with Git LFS)
vendored
Binary file not shown.
BIN
Assets/Resources/StreamingleDashboard/dashboard_script.txt
(Stored with Git LFS)
BIN
Assets/Resources/StreamingleDashboard/dashboard_script.txt
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Project/260219_구슬요방송/260219_구슬요방송.unity
(Stored with Git LFS)
BIN
Assets/ResourcesData/Project/260219_구슬요방송/260219_구슬요방송.unity
(Stored with Git LFS)
Binary file not shown.
@ -92,9 +92,6 @@ namespace KindRetargeting
|
|||||||
[Header("바닥 높이 조정")]
|
[Header("바닥 높이 조정")]
|
||||||
[SerializeField, Range(-1f, 1f)] public float floorHeight = 0f; // 바닥 높이 조정값
|
[SerializeField, Range(-1f, 1f)] public float floorHeight = 0f; // 바닥 높이 조정값
|
||||||
|
|
||||||
[Header("발목 높이 설정")]
|
|
||||||
[SerializeField] public float minimumAnkleHeight = 0.2f; // 수동 설정용 최소 발목 높이
|
|
||||||
|
|
||||||
[Header("머리 회전 오프셋")]
|
[Header("머리 회전 오프셋")]
|
||||||
[SerializeField, Range(-180f, 180f)] public float headRotationOffsetX = 0f; // 머리 좌우 기울기 (Roll)
|
[SerializeField, Range(-180f, 180f)] public float headRotationOffsetX = 0f; // 머리 좌우 기울기 (Roll)
|
||||||
[SerializeField, Range(-180f, 180f)] public float headRotationOffsetY = 0f; // 머리 좌우 회전 (Yaw)
|
[SerializeField, Range(-180f, 180f)] public float headRotationOffsetY = 0f; // 머리 좌우 회전 (Yaw)
|
||||||
@ -112,9 +109,6 @@ namespace KindRetargeting
|
|||||||
[Header("어깨 보정")]
|
[Header("어깨 보정")]
|
||||||
[SerializeField] public ShoulderCorrectionFunction shoulderCorrection = new ShoulderCorrectionFunction();
|
[SerializeField] public ShoulderCorrectionFunction shoulderCorrection = new ShoulderCorrectionFunction();
|
||||||
|
|
||||||
[Header("발 접지")]
|
|
||||||
[SerializeField] public FootGroundingController footGrounding = new FootGroundingController();
|
|
||||||
|
|
||||||
[Header("프랍 부착")]
|
[Header("프랍 부착")]
|
||||||
[SerializeField] public PropLocationController propLocation = new PropLocationController();
|
[SerializeField] public PropLocationController propLocation = new PropLocationController();
|
||||||
|
|
||||||
@ -273,9 +267,6 @@ namespace KindRetargeting
|
|||||||
if (targetAnimator != null)
|
if (targetAnimator != null)
|
||||||
shoulderCorrection.Initialize(targetAnimator);
|
shoulderCorrection.Initialize(targetAnimator);
|
||||||
|
|
||||||
// 발 접지 모듈 초기화
|
|
||||||
footGrounding.Initialize(ikSolver, targetAnimator);
|
|
||||||
|
|
||||||
// 프랍 부착 모듈 초기화
|
// 프랍 부착 모듈 초기화
|
||||||
if (targetAnimator != null)
|
if (targetAnimator != null)
|
||||||
propLocation.Initialize(targetAnimator);
|
propLocation.Initialize(targetAnimator);
|
||||||
@ -749,7 +740,6 @@ namespace KindRetargeting
|
|||||||
fingerShaped.OnUpdate();
|
fingerShaped.OnUpdate();
|
||||||
shoulderCorrection.OnUpdate();
|
shoulderCorrection.OnUpdate();
|
||||||
limbWeight.OnUpdate();
|
limbWeight.OnUpdate();
|
||||||
footGrounding.OnUpdate();
|
|
||||||
ikSolver.OnUpdate();
|
ikSolver.OnUpdate();
|
||||||
|
|
||||||
if (!Mathf.Approximately(previousScale, avatarScale))
|
if (!Mathf.Approximately(previousScale, avatarScale))
|
||||||
@ -764,7 +754,6 @@ namespace KindRetargeting
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void LateUpdate()
|
void LateUpdate()
|
||||||
{
|
{
|
||||||
footGrounding.OnLateUpdate();
|
|
||||||
ApplyHeadRotationOffset();
|
ApplyHeadRotationOffset();
|
||||||
ApplyHeadScale();
|
ApplyHeadScale();
|
||||||
}
|
}
|
||||||
@ -1186,7 +1175,7 @@ namespace KindRetargeting
|
|||||||
GameObject rightArmGoal = CreateIKTargetObject("RightArmGoal", targetAnimator.GetBoneTransform(HumanBodyBones.RightLowerArm), ikTargetsParent);
|
GameObject rightArmGoal = CreateIKTargetObject("RightArmGoal", targetAnimator.GetBoneTransform(HumanBodyBones.RightLowerArm), ikTargetsParent);
|
||||||
ikSolver.rightArm.bendGoal = rightArmGoal.transform;
|
ikSolver.rightArm.bendGoal = rightArmGoal.transform;
|
||||||
|
|
||||||
// 다리 타겟 (Foot + BendGoal) — 2-bone IK는 Foot까지만 풂, Toes는 FootGrounding에서 처리
|
// 다리 타겟 (Foot + BendGoal)
|
||||||
GameObject leftFoot = CreateIKTargetObject("LeftFoot", targetAnimator.GetBoneTransform(HumanBodyBones.LeftFoot), ikTargetsParent);
|
GameObject leftFoot = CreateIKTargetObject("LeftFoot", targetAnimator.GetBoneTransform(HumanBodyBones.LeftFoot), ikTargetsParent);
|
||||||
ikSolver.leftLeg.target = leftFoot.transform;
|
ikSolver.leftLeg.target = leftFoot.transform;
|
||||||
GameObject rightFoot = CreateIKTargetObject("RightFoot", targetAnimator.GetBoneTransform(HumanBodyBones.RightFoot), ikTargetsParent);
|
GameObject rightFoot = CreateIKTargetObject("RightFoot", targetAnimator.GetBoneTransform(HumanBodyBones.RightFoot), ikTargetsParent);
|
||||||
@ -1234,7 +1223,7 @@ namespace KindRetargeting
|
|||||||
{
|
{
|
||||||
if (targetAnimator == null) return;
|
if (targetAnimator == null) return;
|
||||||
|
|
||||||
// 손과 발 타겟 업데이트 (다리는 Foot 타겟, FootGrounding이 Toes 처리)
|
// 손과 발 타겟 업데이트
|
||||||
UpdateEndTarget(ikSolver.leftArm.target, HumanBodyBones.LeftHand);
|
UpdateEndTarget(ikSolver.leftArm.target, HumanBodyBones.LeftHand);
|
||||||
UpdateEndTarget(ikSolver.rightArm.target, HumanBodyBones.RightHand);
|
UpdateEndTarget(ikSolver.rightArm.target, HumanBodyBones.RightHand);
|
||||||
UpdateEndTarget(ikSolver.leftLeg.target, HumanBodyBones.LeftFoot);
|
UpdateEndTarget(ikSolver.leftLeg.target, HumanBodyBones.LeftFoot);
|
||||||
|
|||||||
@ -57,7 +57,6 @@ namespace KindRetargeting
|
|||||||
// ── 바닥 높이 ──
|
// ── 바닥 높이 ──
|
||||||
var floorFoldout = new Foldout { text = "바닥 높이 조정", value = false };
|
var floorFoldout = new Foldout { text = "바닥 높이 조정", value = false };
|
||||||
floorFoldout.Add(new PropertyField(serializedObject.FindProperty("floorHeight"), "바닥 높이 (-1 ~ 1)"));
|
floorFoldout.Add(new PropertyField(serializedObject.FindProperty("floorHeight"), "바닥 높이 (-1 ~ 1)"));
|
||||||
floorFoldout.Add(new PropertyField(serializedObject.FindProperty("minimumAnkleHeight"), "최소 발목 높이"));
|
|
||||||
root.Add(floorFoldout);
|
root.Add(floorFoldout);
|
||||||
|
|
||||||
// ── 머리 회전 오프셋 ──
|
// ── 머리 회전 오프셋 ──
|
||||||
@ -69,14 +68,6 @@ namespace KindRetargeting
|
|||||||
// ── 사지 가중치 (LimbWeight) ──
|
// ── 사지 가중치 (LimbWeight) ──
|
||||||
root.Add(BuildLimbWeightSection());
|
root.Add(BuildLimbWeightSection());
|
||||||
|
|
||||||
// ── 접지 설정 (FootGrounding) ──
|
|
||||||
root.Add(BuildGroundingSection());
|
|
||||||
|
|
||||||
// ── 손가락 복제 설정 ──
|
|
||||||
var fingerCopyFoldout = new Foldout { text = "손가락 복제 설정", value = false };
|
|
||||||
fingerCopyFoldout.Add(new PropertyField(serializedObject.FindProperty("fingerCopyMode"), "복제 방식"));
|
|
||||||
root.Add(fingerCopyFoldout);
|
|
||||||
|
|
||||||
// ── 손가락 셰이핑 (FingerShaped) ──
|
// ── 손가락 셰이핑 (FingerShaped) ──
|
||||||
root.Add(BuildFingerShapedSection());
|
root.Add(BuildFingerShapedSection());
|
||||||
|
|
||||||
@ -258,34 +249,6 @@ namespace KindRetargeting
|
|||||||
return foldout;
|
return foldout;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 접지 설정 ==========
|
|
||||||
|
|
||||||
private VisualElement BuildGroundingSection()
|
|
||||||
{
|
|
||||||
var foldout = new Foldout { text = "접지 설정 (FootGrounding)", value = false };
|
|
||||||
|
|
||||||
foldout.Add(new HelpBox(
|
|
||||||
"HIK 스타일 2-Pass 접지 시스템:\n" +
|
|
||||||
"• Pre-IK: 발이 바닥을 뚫지 않도록 IK 타겟 보정\n" +
|
|
||||||
"• Post-IK: Foot 회전으로 Toes 접지 미세 보정",
|
|
||||||
HelpBoxMessageType.Info));
|
|
||||||
|
|
||||||
foldout.Add(new PropertyField(serializedObject.FindProperty("footGrounding.groundHeight"), "바닥 높이"));
|
|
||||||
|
|
||||||
var weightSlider = new Slider("접지 강도", 0f, 1f) { showInputField = true };
|
|
||||||
weightSlider.BindProperty(serializedObject.FindProperty("footGrounding.groundingWeight"));
|
|
||||||
foldout.Add(weightSlider);
|
|
||||||
|
|
||||||
foldout.Add(new PropertyField(serializedObject.FindProperty("footGrounding.activationHeight"), "활성화 높이") { tooltip = "발목이 이 높이 이상이면 보정 비활성화" });
|
|
||||||
foldout.Add(new PropertyField(serializedObject.FindProperty("footGrounding.plantThreshold"), "접지 판정 범위"));
|
|
||||||
|
|
||||||
var smoothField = new Slider("스무딩 속도", 1f, 30f) { showInputField = true };
|
|
||||||
smoothField.BindProperty(serializedObject.FindProperty("footGrounding.smoothSpeed"));
|
|
||||||
foldout.Add(smoothField);
|
|
||||||
|
|
||||||
return foldout;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== 손가락 셰이핑 ==========
|
// ========== 손가락 셰이핑 ==========
|
||||||
|
|
||||||
private VisualElement BuildFingerShapedSection()
|
private VisualElement BuildFingerShapedSection()
|
||||||
|
|||||||
@ -228,19 +228,14 @@ public class RetargetingControlWindow : EditorWindow
|
|||||||
panel.Add(BuildShoulderSection(script, so));
|
panel.Add(BuildShoulderSection(script, so));
|
||||||
|
|
||||||
// 접지 설정
|
// 접지 설정
|
||||||
panel.Add(BuildGroundingSection(script, so));
|
|
||||||
|
|
||||||
// 손가락 제어 설정
|
// 손가락 제어 설정
|
||||||
panel.Add(BuildFingerControlSection(script, so));
|
panel.Add(BuildFingerControlSection(script, so));
|
||||||
|
|
||||||
// 손가락 복제 설정
|
|
||||||
panel.Add(BuildFingerCopySection(script, so));
|
|
||||||
|
|
||||||
// 바닥 높이 설정
|
// 바닥 높이 설정
|
||||||
var floorFoldout = new Foldout { text = "바닥 높이 설정", value = false };
|
var floorFoldout = new Foldout { text = "바닥 높이 설정", value = false };
|
||||||
var floorContainer = new VisualElement();
|
var floorContainer = new VisualElement();
|
||||||
floorContainer.Add(new PropertyField(so.FindProperty("floorHeight"), "바닥 높이 (-1 ~ 1)"));
|
floorContainer.Add(new PropertyField(so.FindProperty("floorHeight"), "바닥 높이 (-1 ~ 1)"));
|
||||||
floorContainer.Add(new PropertyField(so.FindProperty("minimumAnkleHeight"), "최소 발목 높이"));
|
|
||||||
floorContainer.Bind(so);
|
floorContainer.Bind(so);
|
||||||
floorFoldout.Add(floorContainer);
|
floorFoldout.Add(floorContainer);
|
||||||
panel.Add(floorFoldout);
|
panel.Add(floorFoldout);
|
||||||
@ -416,6 +411,7 @@ public class RetargetingControlWindow : EditorWindow
|
|||||||
// 프리셋
|
// 프리셋
|
||||||
container.Add(BuildFingerPresets(script, script.fingerShaped));
|
container.Add(BuildFingerPresets(script, script.fingerShaped));
|
||||||
|
|
||||||
|
container.Bind(so);
|
||||||
foldout.Add(container);
|
foldout.Add(container);
|
||||||
return foldout;
|
return foldout;
|
||||||
}
|
}
|
||||||
@ -512,21 +508,6 @@ public class RetargetingControlWindow : EditorWindow
|
|||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Finger Copy ==========
|
|
||||||
|
|
||||||
private VisualElement BuildFingerCopySection(CustomRetargetingScript script, SerializedObject so)
|
|
||||||
{
|
|
||||||
var foldout = new Foldout { text = "손가락 복제 설정", value = false };
|
|
||||||
var container = new VisualElement();
|
|
||||||
|
|
||||||
var copyModeProp = so.FindProperty("fingerCopyMode");
|
|
||||||
container.Add(new PropertyField(copyModeProp, "복제 방식"));
|
|
||||||
|
|
||||||
container.Bind(so);
|
|
||||||
foldout.Add(container);
|
|
||||||
return foldout;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== Head Rotation ==========
|
// ========== Head Rotation ==========
|
||||||
|
|
||||||
private VisualElement BuildHeadRotationSection(CustomRetargetingScript script, SerializedObject so)
|
private VisualElement BuildHeadRotationSection(CustomRetargetingScript script, SerializedObject so)
|
||||||
@ -778,30 +759,6 @@ public class RetargetingControlWindow : EditorWindow
|
|||||||
return foldout;
|
return foldout;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Foot Grounding ==========
|
|
||||||
|
|
||||||
private VisualElement BuildGroundingSection(CustomRetargetingScript script, SerializedObject so)
|
|
||||||
{
|
|
||||||
var foldout = new Foldout { text = "접지 설정", value = false };
|
|
||||||
var container = new VisualElement();
|
|
||||||
|
|
||||||
container.Add(new PropertyField(so.FindProperty("footGrounding.groundHeight"), "바닥 높이"));
|
|
||||||
|
|
||||||
var weightSlider = new Slider("접지 강도", 0f, 1f) { showInputField = true };
|
|
||||||
weightSlider.BindProperty(so.FindProperty("footGrounding.groundingWeight"));
|
|
||||||
container.Add(weightSlider);
|
|
||||||
|
|
||||||
container.Add(new PropertyField(so.FindProperty("footGrounding.activationHeight"), "활성화 높이") { tooltip = "발목이 이 높이 이상이면 보정 비활성화" });
|
|
||||||
container.Add(new PropertyField(so.FindProperty("footGrounding.plantThreshold"), "접지 판정 범위"));
|
|
||||||
|
|
||||||
var smoothField = new Slider("스무딩 속도", 1f, 30f) { showInputField = true };
|
|
||||||
smoothField.BindProperty(so.FindProperty("footGrounding.smoothSpeed"));
|
|
||||||
container.Add(smoothField);
|
|
||||||
|
|
||||||
container.Bind(so);
|
|
||||||
foldout.Add(container);
|
|
||||||
return foldout;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== Helpers ==========
|
// ========== Helpers ==========
|
||||||
|
|
||||||
|
|||||||
@ -1,238 +0,0 @@
|
|||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace KindRetargeting
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// HIK 스타일 2-Pass 접지 시스템.
|
|
||||||
///
|
|
||||||
/// Pass 1 (OnUpdate, Order 5 → IK 전):
|
|
||||||
/// IK 타겟 위치를 수정하여 Toes가 바닥을 뚫지 않도록 보정.
|
|
||||||
///
|
|
||||||
/// Pass 2 (OnLateUpdate → IK 후):
|
|
||||||
/// IK 결과의 잔차를 Foot 회전으로 미세 보정.
|
|
||||||
/// </summary>
|
|
||||||
[System.Serializable]
|
|
||||||
public class FootGroundingController
|
|
||||||
{
|
|
||||||
[Header("접지 설정")]
|
|
||||||
[Tooltip("바닥 Y 좌표 (월드 공간)")]
|
|
||||||
public float groundHeight = 0f;
|
|
||||||
|
|
||||||
[Tooltip("접지 보정 강도")]
|
|
||||||
[Range(0f, 1f)]
|
|
||||||
public float groundingWeight = 1f;
|
|
||||||
|
|
||||||
[Tooltip("이 높이 이상이면 AIRBORNE (보정 안 함)")]
|
|
||||||
public float activationHeight = 0.5f;
|
|
||||||
|
|
||||||
[Tooltip("Toes가 이 범위 안이면 접지 중으로 판정")]
|
|
||||||
public float plantThreshold = 0.02f;
|
|
||||||
|
|
||||||
[Header("스무딩")]
|
|
||||||
[Tooltip("보정량 변화 속도 (높을수록 빠른 반응)")]
|
|
||||||
[Range(1f, 30f)]
|
|
||||||
public float smoothSpeed = 10f;
|
|
||||||
|
|
||||||
private TwoBoneIKSolver ikSolver;
|
|
||||||
private Animator animator;
|
|
||||||
|
|
||||||
private Transform leftFoot;
|
|
||||||
private Transform rightFoot;
|
|
||||||
private Transform leftToes;
|
|
||||||
private Transform rightToes;
|
|
||||||
|
|
||||||
private Vector3 leftLocalToesOffset;
|
|
||||||
private Vector3 rightLocalToesOffset;
|
|
||||||
|
|
||||||
private float leftFootHeight;
|
|
||||||
private float rightFootHeight;
|
|
||||||
|
|
||||||
private bool leftHasToes;
|
|
||||||
private bool rightHasToes;
|
|
||||||
|
|
||||||
private float leftPrevAdj;
|
|
||||||
private float rightPrevAdj;
|
|
||||||
|
|
||||||
private bool isInitialized;
|
|
||||||
|
|
||||||
public void Initialize(TwoBoneIKSolver ikSolver, Animator animator)
|
|
||||||
{
|
|
||||||
this.ikSolver = ikSolver;
|
|
||||||
this.animator = animator;
|
|
||||||
|
|
||||||
if (animator == null || !animator.isHuman || ikSolver == null) return;
|
|
||||||
|
|
||||||
leftFoot = animator.GetBoneTransform(HumanBodyBones.LeftFoot);
|
|
||||||
rightFoot = animator.GetBoneTransform(HumanBodyBones.RightFoot);
|
|
||||||
leftToes = animator.GetBoneTransform(HumanBodyBones.LeftToes);
|
|
||||||
rightToes = animator.GetBoneTransform(HumanBodyBones.RightToes);
|
|
||||||
|
|
||||||
if (leftFoot == null || rightFoot == null) return;
|
|
||||||
|
|
||||||
leftHasToes = leftToes != null;
|
|
||||||
rightHasToes = rightToes != null;
|
|
||||||
|
|
||||||
if (leftHasToes)
|
|
||||||
{
|
|
||||||
leftLocalToesOffset = leftFoot.InverseTransformPoint(leftToes.position);
|
|
||||||
leftFootHeight = Mathf.Abs(leftFoot.position.y - leftToes.position.y);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
leftFootHeight = 0.05f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rightHasToes)
|
|
||||||
{
|
|
||||||
rightLocalToesOffset = rightFoot.InverseTransformPoint(rightToes.position);
|
|
||||||
rightFootHeight = Mathf.Abs(rightFoot.position.y - rightToes.position.y);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rightFootHeight = 0.05f;
|
|
||||||
}
|
|
||||||
|
|
||||||
isInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Pass 1: Pre-IK — IK 타겟 위치를 수정합니다.
|
|
||||||
/// </summary>
|
|
||||||
public void OnUpdate()
|
|
||||||
{
|
|
||||||
if (!isInitialized || groundingWeight < 0.001f) return;
|
|
||||||
|
|
||||||
float leftAdj = AdjustFootTarget(
|
|
||||||
ikSolver.leftLeg, leftLocalToesOffset, leftFootHeight,
|
|
||||||
leftHasToes, ikSolver.leftLeg.positionWeight);
|
|
||||||
float rightAdj = AdjustFootTarget(
|
|
||||||
ikSolver.rightLeg, rightLocalToesOffset, rightFootHeight,
|
|
||||||
rightHasToes, ikSolver.rightLeg.positionWeight);
|
|
||||||
|
|
||||||
float dt = Time.deltaTime * smoothSpeed;
|
|
||||||
leftPrevAdj = Mathf.Lerp(leftPrevAdj, leftAdj, Mathf.Clamp01(dt));
|
|
||||||
rightPrevAdj = Mathf.Lerp(rightPrevAdj, rightAdj, Mathf.Clamp01(dt));
|
|
||||||
}
|
|
||||||
|
|
||||||
private float AdjustFootTarget(TwoBoneIKSolver.LimbIK limb, Vector3 localToesOffset,
|
|
||||||
float footHeight, bool hasToes, float ikWeight)
|
|
||||||
{
|
|
||||||
if (limb.target == null || ikWeight < 0.01f) return 0f;
|
|
||||||
|
|
||||||
Transform ankleTarget = limb.target;
|
|
||||||
Vector3 anklePos = ankleTarget.position;
|
|
||||||
float ankleY = anklePos.y;
|
|
||||||
|
|
||||||
if (ankleY - groundHeight > activationHeight) return 0f;
|
|
||||||
|
|
||||||
float weight = groundingWeight * ikWeight;
|
|
||||||
|
|
||||||
if (!hasToes)
|
|
||||||
{
|
|
||||||
float minAnkleY = groundHeight + footHeight;
|
|
||||||
if (ankleY < minAnkleY)
|
|
||||||
{
|
|
||||||
float adj = (minAnkleY - ankleY) * weight;
|
|
||||||
anklePos.y += adj;
|
|
||||||
ankleTarget.position = anklePos;
|
|
||||||
return adj;
|
|
||||||
}
|
|
||||||
return 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3 predictedToesWorld = anklePos + ankleTarget.rotation * localToesOffset;
|
|
||||||
float predictedToesY = predictedToesWorld.y;
|
|
||||||
|
|
||||||
float adjustment = 0f;
|
|
||||||
|
|
||||||
if (predictedToesY < groundHeight + plantThreshold)
|
|
||||||
{
|
|
||||||
float toesError = groundHeight - predictedToesY;
|
|
||||||
|
|
||||||
if (ankleY < groundHeight + footHeight + plantThreshold)
|
|
||||||
{
|
|
||||||
float minAnkleY = groundHeight + footHeight;
|
|
||||||
if (ankleY < minAnkleY)
|
|
||||||
{
|
|
||||||
adjustment = (minAnkleY - ankleY) * weight;
|
|
||||||
anklePos.y += adjustment;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toesError > 0f)
|
|
||||||
{
|
|
||||||
float extra = toesError * weight;
|
|
||||||
anklePos.y += extra;
|
|
||||||
adjustment += extra;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (toesError > 0f)
|
|
||||||
{
|
|
||||||
adjustment = toesError * weight;
|
|
||||||
anklePos.y += adjustment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ankleTarget.position = anklePos;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
float minAnkleY = groundHeight + footHeight;
|
|
||||||
if (ankleY < minAnkleY)
|
|
||||||
{
|
|
||||||
adjustment = (minAnkleY - ankleY) * weight;
|
|
||||||
anklePos.y += adjustment;
|
|
||||||
ankleTarget.position = anklePos;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return adjustment;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Pass 2: Post-IK — 잔차를 Foot 회전으로 미세 보정합니다.
|
|
||||||
/// </summary>
|
|
||||||
public void OnLateUpdate()
|
|
||||||
{
|
|
||||||
if (!isInitialized || groundingWeight < 0.001f) return;
|
|
||||||
|
|
||||||
if (leftHasToes)
|
|
||||||
AlignFootToGround(leftFoot, leftToes, ikSolver.leftLeg.positionWeight);
|
|
||||||
if (rightHasToes)
|
|
||||||
AlignFootToGround(rightFoot, rightToes, ikSolver.rightLeg.positionWeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AlignFootToGround(Transform foot, Transform toes, float ikWeight)
|
|
||||||
{
|
|
||||||
if (foot == null || toes == null) return;
|
|
||||||
if (ikWeight < 0.01f) return;
|
|
||||||
|
|
||||||
if (foot.position.y - groundHeight > activationHeight) return;
|
|
||||||
|
|
||||||
float weight = groundingWeight * ikWeight;
|
|
||||||
|
|
||||||
float actualToesY = toes.position.y;
|
|
||||||
float error = actualToesY - groundHeight;
|
|
||||||
|
|
||||||
if (Mathf.Abs(error) < 0.001f) return;
|
|
||||||
|
|
||||||
if (error > plantThreshold) return;
|
|
||||||
|
|
||||||
Vector3 footToToes = toes.position - foot.position;
|
|
||||||
float horizontalDist = new Vector2(footToToes.x, footToToes.z).magnitude;
|
|
||||||
|
|
||||||
if (horizontalDist < 0.001f) return;
|
|
||||||
|
|
||||||
float pitchAngle = Mathf.Atan2(error, horizontalDist) * Mathf.Rad2Deg;
|
|
||||||
|
|
||||||
Vector3 footForwardFlat = new Vector3(footToToes.x, 0f, footToToes.z).normalized;
|
|
||||||
Vector3 pitchAxis = Vector3.Cross(Vector3.up, footForwardFlat);
|
|
||||||
if (pitchAxis.sqrMagnitude < 0.001f) return;
|
|
||||||
pitchAxis.Normalize();
|
|
||||||
|
|
||||||
Quaternion correction = Quaternion.AngleAxis(-pitchAngle, pitchAxis);
|
|
||||||
foot.rotation = Quaternion.Slerp(foot.rotation, correction * foot.rotation, weight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 1752f1fe3cee0b24e95ae0d5a0468865
|
|
||||||
BIN
Assets/Scripts/KindRetargeting/README.md
(Stored with Git LFS)
BIN
Assets/Scripts/KindRetargeting/README.md
(Stored with Git LFS)
Binary file not shown.
@ -288,13 +288,6 @@ namespace KindRetargeting.Remote
|
|||||||
{ "shoulderReverseLeft", script.shoulderCorrection.reverseLeftRotation },
|
{ "shoulderReverseLeft", script.shoulderCorrection.reverseLeftRotation },
|
||||||
{ "shoulderReverseRight", script.shoulderCorrection.reverseRightRotation },
|
{ "shoulderReverseRight", script.shoulderCorrection.reverseRightRotation },
|
||||||
|
|
||||||
// FootGrounding 데이터
|
|
||||||
{ "groundingWeight", script.footGrounding.groundingWeight },
|
|
||||||
{ "groundingGroundHeight", script.footGrounding.groundHeight },
|
|
||||||
{ "groundingActivationHeight", script.footGrounding.activationHeight },
|
|
||||||
{ "groundingPlantThreshold", script.footGrounding.plantThreshold },
|
|
||||||
{ "groundingSmoothSpeed", script.footGrounding.smoothSpeed },
|
|
||||||
|
|
||||||
// FingerShapedController 데이터
|
// FingerShapedController 데이터
|
||||||
{ "handPoseEnabled", script.fingerShaped.enabled },
|
{ "handPoseEnabled", script.fingerShaped.enabled },
|
||||||
{ "leftHandEnabled", script.fingerShaped.leftHandEnabled },
|
{ "leftHandEnabled", script.fingerShaped.leftHandEnabled },
|
||||||
@ -312,8 +305,6 @@ namespace KindRetargeting.Remote
|
|||||||
{ "rightPinkyCurl", script.fingerShaped.rightPinkyCurl },
|
{ "rightPinkyCurl", script.fingerShaped.rightPinkyCurl },
|
||||||
{ "rightSpreadFingers", script.fingerShaped.rightSpreadFingers },
|
{ "rightSpreadFingers", script.fingerShaped.rightSpreadFingers },
|
||||||
|
|
||||||
// 최소 발목 높이
|
|
||||||
{ "minimumAnkleHeight", GetPrivateField<float>(script, "minimumAnkleHeight") },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var response = new
|
var response = new
|
||||||
@ -449,23 +440,6 @@ namespace KindRetargeting.Remote
|
|||||||
script.shoulderCorrection.reverseRightRotation = value > 0.5f;
|
script.shoulderCorrection.reverseRightRotation = value > 0.5f;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// FootGrounding 속성
|
|
||||||
case "groundingWeight":
|
|
||||||
script.footGrounding.groundingWeight = value;
|
|
||||||
break;
|
|
||||||
case "groundingGroundHeight":
|
|
||||||
script.footGrounding.groundHeight = value;
|
|
||||||
break;
|
|
||||||
case "groundingActivationHeight":
|
|
||||||
script.footGrounding.activationHeight = value;
|
|
||||||
break;
|
|
||||||
case "groundingPlantThreshold":
|
|
||||||
script.footGrounding.plantThreshold = value;
|
|
||||||
break;
|
|
||||||
case "groundingSmoothSpeed":
|
|
||||||
script.footGrounding.smoothSpeed = value;
|
|
||||||
break;
|
|
||||||
|
|
||||||
// FingerShapedController 속성
|
// FingerShapedController 속성
|
||||||
case "handPoseEnabled":
|
case "handPoseEnabled":
|
||||||
script.fingerShaped.enabled = value > 0.5f;
|
script.fingerShaped.enabled = value > 0.5f;
|
||||||
@ -519,11 +493,6 @@ namespace KindRetargeting.Remote
|
|||||||
script.fingerShaped.rightSpreadFingers = value;
|
script.fingerShaped.rightSpreadFingers = value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// 최소 발목 높이
|
|
||||||
case "minimumAnkleHeight":
|
|
||||||
SetPrivateField(script, "minimumAnkleHeight", value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Debug.LogWarning($"[RetargetingRemote] 알 수 없는 속성: {property}");
|
Debug.LogWarning($"[RetargetingRemote] 알 수 없는 속성: {property}");
|
||||||
break;
|
break;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user