Fix : 리타겟팅 스크립트 허리 방향 보정 로컬 방향으로 업데이트
This commit is contained in:
parent
637859bada
commit
3f160527a9
@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 타겟 아바타의 로컬 축과 월드 축의 관계를 분석하여 정규화 계수를 계산합니다.
|
||||
/// 이를 통해 사용자는 항상 직관적인 방향(앞/뒤/위/아래)으로 제어할 수 있습니다.
|
||||
/// </summary>
|
||||
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)");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 로컬 축이 월드 축과 같은 방향인지 반대 방향인지 판단합니다.
|
||||
/// </summary>
|
||||
/// <param name="localAxis">로컬 축 방향 (월드 공간)</param>
|
||||
/// <param name="worldAxis">비교할 월드 축</param>
|
||||
/// <returns>같은 방향이면 1, 반대면 -1</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HumanPoseHandler를 초기화합니다.
|
||||
/// </summary>
|
||||
@ -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<RetargetingSettings>(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;
|
||||
}
|
||||
|
||||
// 힙을 제외한 본들의 회전 동기화
|
||||
|
||||
@ -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--;
|
||||
}
|
||||
|
||||
|
||||
@ -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--;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user