diff --git a/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs b/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs
index c8df0e0b..1993683b 100644
--- a/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs
+++ b/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs
@@ -28,12 +28,23 @@ namespace KindRetargeting
// IK 컴포넌트 참조 변경
private FullBodyInverseKinematics_RND ikComponent;
- [Header("힙 위치 보정")]
+ [Header("힙 위치 보정 (로컬 좌표계 기반)")]
[SerializeField, Range(-1, 1)]
- private float hipsWeight = 0f; // 힙의 위치를 위아래로 보정하는 가중치
+ private float hipsOffsetX = 0f; // 캐릭터 기준 좌우 (항상 Right 방향)
+ [SerializeField, Range(-1, 1)]
+ private float hipsOffsetY = 0f; // 캐릭터 기준 상하 (항상 Up 방향)
+ [SerializeField, Range(-1, 1)]
+ private float hipsOffsetZ = 0f; // 캐릭터 기준 앞뒤 (항상 Forward 방향)
[HideInInspector] public float HipsWeightOffset = 1f;
+ // 로컬-월드 축 방향 정규화 계수
+ private Vector3 axisNormalizer = Vector3.one;
+
+ [Header("축 정규화 정보 (읽기 전용)")]
+ [SerializeField, HideInInspector]
+ private Vector3 debugAxisNormalizer = Vector3.one;
+
// HumanPoseHandler를 이용하여 원본 및 대상 아바타의 포즈를 관리
private HumanPoseHandler sourcePoseHandler;
private HumanPoseHandler targetPoseHandler;
@@ -119,7 +130,9 @@ namespace KindRetargeting
[System.Serializable]
private class RetargetingSettings
{
- public float hipsWeight;
+ public float hipsOffsetX; // 변경: hipsWeight → hipsOffsetX/Y/Z
+ public float hipsOffsetY;
+ public float hipsOffsetZ;
public float kneeInOutWeight;
public float kneeFrontBackWeight;
public float floorHeight;
@@ -167,6 +180,9 @@ namespace KindRetargeting
// IK 타겟 생성 (무릎 시각화 오브젝트 포함)
CreateIKTargets();
+ // T-포즈 전에 축 정규화 계수 계산
+ CalculateAxisNormalizer();
+
// 원본 및 대상 아바타를 T-포즈로 복원
SetTPose(sourceAnimator);
SetTPose(targetAnimator);
@@ -203,6 +219,59 @@ namespace KindRetargeting
ApplyScale();
}
+ ///
+ /// 타겟 아바타의 로컬 축과 월드 축의 관계를 분석하여 정규화 계수를 계산합니다.
+ /// 이를 통해 사용자는 항상 직관적인 방향(앞/뒤/위/아래)으로 제어할 수 있습니다.
+ ///
+ private void CalculateAxisNormalizer()
+ {
+ if (targetAnimator == null) return;
+
+ Transform hips = targetAnimator.GetBoneTransform(HumanBodyBones.Hips);
+ if (hips == null) return;
+
+ // 힙의 초기 로컬 축 방향을 월드 공간으로 변환
+ Vector3 localRight = hips.TransformDirection(Vector3.right);
+ Vector3 localUp = hips.TransformDirection(Vector3.up);
+ Vector3 localForward = hips.TransformDirection(Vector3.forward);
+
+ // 각 로컬 축이 월드의 어느 축과 가장 가까운지 판단
+ axisNormalizer = new Vector3(
+ GetAxisSign(localRight, Vector3.right), // X축 정규화
+ GetAxisSign(localUp, Vector3.up), // Y축 정규화
+ GetAxisSign(localForward, Vector3.forward) // Z축 정규화
+ );
+
+ debugAxisNormalizer = axisNormalizer; // Inspector 표시용
+
+ Debug.Log($"[{gameObject.name}] 축 정규화 계수:\n" +
+ $" X(좌우): {axisNormalizer.x} (로컬 Right {(axisNormalizer.x > 0 ? "→" : "←")} 월드 Right)\n" +
+ $" Y(상하): {axisNormalizer.y} (로컬 Up {(axisNormalizer.y > 0 ? "↑" : "↓")} 월드 Up)\n" +
+ $" Z(앞뒤): {axisNormalizer.z} (로컬 Forward {(axisNormalizer.z > 0 ? "→" : "←")} 월드 Forward)");
+ }
+
+ ///
+ /// 로컬 축이 월드 축과 같은 방향인지 반대 방향인지 판단합니다.
+ ///
+ /// 로컬 축 방향 (월드 공간)
+ /// 비교할 월드 축
+ /// 같은 방향이면 1, 반대면 -1
+ private float GetAxisSign(Vector3 localAxis, Vector3 worldAxis)
+ {
+ // 내적으로 방향성 판단
+ float dot = Vector3.Dot(localAxis.normalized, worldAxis);
+
+ // 가장 지배적인 축 성분의 부호를 반환
+ if (Mathf.Abs(dot) > 0.5f) // 45도 이내면 해당 축으로 간주
+ {
+ return Mathf.Sign(dot);
+ }
+
+ // 축이 너무 비스듬하면 경고 (정상적인 휴머노이드 리그라면 발생 안함)
+ Debug.LogWarning($"[AxisNormalizer] 축이 비정상적으로 기울어져 있습니다: {localAxis} vs {worldAxis}");
+ return 1f;
+ }
+
///
/// HumanPoseHandler를 초기화합니다.
///
@@ -341,7 +410,9 @@ namespace KindRetargeting
var settings = new RetargetingSettings
{
- hipsWeight = hipsWeight,
+ hipsOffsetX = hipsOffsetX,
+ hipsOffsetY = hipsOffsetY,
+ hipsOffsetZ = hipsOffsetZ,
kneeInOutWeight = kneeInOutWeight,
kneeFrontBackWeight = kneeFrontBackWeight,
floorHeight = floorHeight,
@@ -397,7 +468,9 @@ namespace KindRetargeting
var settings = JsonUtility.FromJson(json);
// 설정 적용
- hipsWeight = settings.hipsWeight;
+ hipsOffsetX = settings.hipsOffsetX;
+ hipsOffsetY = settings.hipsOffsetY;
+ hipsOffsetZ = settings.hipsOffsetZ;
kneeInOutWeight = settings.kneeInOutWeight;
kneeFrontBackWeight = settings.kneeFrontBackWeight;
floorHeight = settings.floorHeight;
@@ -707,20 +780,35 @@ namespace KindRetargeting
if (sourceHips != null && targetHips != null)
{
- // 힙 위치 동기화 + 힙 위치 보정 적용 + 바닥 높이 적용용
- Vector3 adjustedPosition = sourceHips.position;
- adjustedPosition.y += hipsWeight * HipsWeightOffset; // 기존 힙 높이 조정
- adjustedPosition.y += floorHeight; // 바닥 높이 조정 추가
- targetHips.position = adjustedPosition;
-
- // 힙 회전 동기화 (회전 오프셋 적용)
+ // 1. 힙 회전 먼저 동기화 (회전 오프셋 적용)
+ Quaternion finalHipsRotation = sourceHips.rotation;
if (rotationOffsets.TryGetValue(HumanBodyBones.Hips, out Quaternion hipsOffset))
{
- targetHips.rotation = sourceHips.rotation * hipsOffset;
-
- ikComponent.solver.spine.pelvisTarget.position = targetHips.position;
- ikComponent.solver.spine.pelvisTarget.rotation = targetHips.rotation;
+ finalHipsRotation = sourceHips.rotation * hipsOffset;
+ targetHips.rotation = finalHipsRotation;
}
+
+ // 2. 로컬 오프셋 벡터 생성 (정규화 계수 적용)
+ Vector3 normalizedOffset = new Vector3(
+ hipsOffsetX * axisNormalizer.x * HipsWeightOffset, // 좌우
+ hipsOffsetY * axisNormalizer.y * HipsWeightOffset, // 상하
+ hipsOffsetZ * axisNormalizer.z * HipsWeightOffset // 앞뒤
+ );
+
+ // 3. 로컬 좌표계를 월드 좌표계로 변환
+ Vector3 worldOffset = targetHips.rotation * normalizedOffset;
+
+ // 4. 힙 위치 동기화 + 로컬 오프셋 적용
+ Vector3 adjustedPosition = sourceHips.position + worldOffset;
+
+ // 5. 바닥 높이는 월드 Y축에 직접 적용 (중력 방향이므로 월드 기준)
+ adjustedPosition.y += floorHeight;
+
+ targetHips.position = adjustedPosition;
+
+ // 6. IK 타겟에도 동기화
+ ikComponent.solver.spine.pelvisTarget.position = targetHips.position;
+ ikComponent.solver.spine.pelvisTarget.rotation = targetHips.rotation;
}
// 힙을 제외한 본들의 회전 동기화
diff --git a/Assets/Scripts/KindRetargeting/Editor/CustomRetargetingScriptEditor.cs b/Assets/Scripts/KindRetargeting/Editor/CustomRetargetingScriptEditor.cs
index 1b611ecb..38d2cb92 100644
--- a/Assets/Scripts/KindRetargeting/Editor/CustomRetargetingScriptEditor.cs
+++ b/Assets/Scripts/KindRetargeting/Editor/CustomRetargetingScriptEditor.cs
@@ -22,7 +22,10 @@ namespace KindRetargeting
// SerializedProperty 변수들
private SerializedProperty sourceAnimatorProp;
private SerializedProperty targetAnimatorProp;
- private SerializedProperty hipsWeightProp;
+ private SerializedProperty hipsOffsetXProp;
+ private SerializedProperty hipsOffsetYProp;
+ private SerializedProperty hipsOffsetZProp;
+ private SerializedProperty debugAxisNormalizerProp;
private SerializedProperty fingerCopyModeProp;
private SerializedProperty useMotionFilterProp;
private SerializedProperty filterBufferSizeProp;
@@ -41,7 +44,10 @@ namespace KindRetargeting
// SerializedProperty 초기화
sourceAnimatorProp = serializedObject.FindProperty("sourceAnimator");
targetAnimatorProp = serializedObject.FindProperty("targetAnimator");
- hipsWeightProp = serializedObject.FindProperty("hipsWeight");
+ hipsOffsetXProp = serializedObject.FindProperty("hipsOffsetX");
+ hipsOffsetYProp = serializedObject.FindProperty("hipsOffsetY");
+ hipsOffsetZProp = serializedObject.FindProperty("hipsOffsetZ");
+ debugAxisNormalizerProp = serializedObject.FindProperty("debugAxisNormalizer");
fingerCopyModeProp = serializedObject.FindProperty("fingerCopyMode");
useMotionFilterProp = serializedObject.FindProperty("useMotionFilter");
filterBufferSizeProp = serializedObject.FindProperty("filterBufferSize");
@@ -86,11 +92,45 @@ namespace KindRetargeting
GUILayout.Space(5);
// 힙 위치 보정 Foldout
- showHipsSettings = EditorGUILayout.Foldout(showHipsSettings, "힙 위치 보정");
+ showHipsSettings = EditorGUILayout.Foldout(showHipsSettings, "힙 위치 보정 (로컬 좌표계)");
if (showHipsSettings)
{
EditorGUI.indentLevel++;
- EditorGUILayout.PropertyField(hipsWeightProp, new GUIContent("힙 가중치"));
+
+ // 축 정규화 정보 표시
+ if (debugAxisNormalizerProp != null)
+ {
+ Vector3 normalizer = debugAxisNormalizerProp.vector3Value;
+
+ EditorGUILayout.BeginVertical(EditorStyles.helpBox);
+ EditorGUILayout.LabelField("축 정규화 정보", EditorStyles.boldLabel);
+
+ EditorGUI.BeginDisabledGroup(true);
+ EditorGUILayout.Vector3Field("정규화 계수", normalizer);
+ EditorGUI.EndDisabledGroup();
+
+ EditorGUILayout.HelpBox(
+ $"X축: {(normalizer.x > 0 ? "정방향 ↔" : "역방향 ↔")}\n" +
+ $"Y축: {(normalizer.y > 0 ? "정방향 ↕" : "역방향 ↕")}\n" +
+ $"Z축: {(normalizer.z > 0 ? "정방향 ⇄" : "역방향 ⇄")}\n\n" +
+ "이 계수는 Start()에서 자동 계산되며, 항상 직관적인 방향으로 제어할 수 있게 합니다.",
+ MessageType.Info);
+ EditorGUILayout.EndVertical();
+
+ GUILayout.Space(5);
+ }
+
+ EditorGUILayout.PropertyField(hipsOffsetXProp,
+ new GUIContent("좌우 오프셋 (←-/+→)", "캐릭터 기준 왼쪽(-) / 오른쪽(+)"));
+ EditorGUILayout.PropertyField(hipsOffsetYProp,
+ new GUIContent("상하 오프셋 (↓-/+↑)", "캐릭터 기준 아래(-) / 위(+)"));
+ EditorGUILayout.PropertyField(hipsOffsetZProp,
+ new GUIContent("앞뒤 오프셋 (←-/+→)", "캐릭터 기준 뒤(-) / 앞(+)"));
+
+ EditorGUILayout.HelpBox(
+ "로컬 좌표계 기반: 캐릭터의 회전 상태와 관계없이 항상 캐릭터 기준으로 이동합니다.",
+ MessageType.Info);
+
EditorGUI.indentLevel--;
}
diff --git a/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs b/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs
index 60f223ec..1bfbb98d 100644
--- a/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs
+++ b/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs
@@ -255,12 +255,38 @@ public class RetargetingControlWindow : EditorWindow
}
// 힙 설정
- hipsSettingsFoldouts[instanceID] = EditorGUILayout.Foldout(hipsSettingsFoldouts[instanceID], "힙 위치 보정", true);
+ hipsSettingsFoldouts[instanceID] = EditorGUILayout.Foldout(hipsSettingsFoldouts[instanceID], "힙 위치 보정 (로컬)", true);
if (hipsSettingsFoldouts[instanceID])
{
EditorGUI.indentLevel++;
- var hipsWeightProp = serializedObject.FindProperty("hipsWeight");
- EditorGUILayout.PropertyField(hipsWeightProp, new GUIContent("힙 가중치"));
+
+ var hipsOffsetXProp = serializedObject.FindProperty("hipsOffsetX");
+ var hipsOffsetYProp = serializedObject.FindProperty("hipsOffsetY");
+ var hipsOffsetZProp = serializedObject.FindProperty("hipsOffsetZ");
+
+ // 정규화 계수 표시
+ var normalizerProp = serializedObject.FindProperty("debugAxisNormalizer");
+ if (normalizerProp != null)
+ {
+ Vector3 norm = normalizerProp.vector3Value;
+ if (norm != Vector3.zero) // 초기화되었을 때만 표시
+ {
+ string xIcon = norm.x > 0 ? "↔" : "↔(반전)";
+ string yIcon = norm.y > 0 ? "↕" : "↕(반전)";
+ string zIcon = norm.z > 0 ? "⇄" : "⇄(반전)";
+
+ EditorGUILayout.LabelField($"축 상태: X{xIcon} Y{yIcon} Z{zIcon}",
+ EditorStyles.miniLabel);
+ }
+ }
+
+ EditorGUILayout.Slider(hipsOffsetXProp, -1f, 1f,
+ new GUIContent("← 좌우 →", "캐릭터 기준 왼쪽(-) / 오른쪽(+)"));
+ EditorGUILayout.Slider(hipsOffsetYProp, -1f, 1f,
+ new GUIContent("↓ 상하 ↑", "캐릭터 기준 아래(-) / 위(+)"));
+ EditorGUILayout.Slider(hipsOffsetZProp, -1f, 1f,
+ new GUIContent("← 앞뒤 →", "캐릭터 기준 뒤(-) / 앞(+)"));
+
EditorGUI.indentLevel--;
}