Fix : 리타겟팅 스크립트 허리 방향 보정 로컬 방향으로 업데이트
This commit is contained in:
parent
637859bada
commit
3f160527a9
@ -28,12 +28,23 @@ namespace KindRetargeting
|
|||||||
// IK 컴포넌트 참조 변경
|
// IK 컴포넌트 참조 변경
|
||||||
private FullBodyInverseKinematics_RND ikComponent;
|
private FullBodyInverseKinematics_RND ikComponent;
|
||||||
|
|
||||||
[Header("힙 위치 보정")]
|
[Header("힙 위치 보정 (로컬 좌표계 기반)")]
|
||||||
[SerializeField, Range(-1, 1)]
|
[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;
|
[HideInInspector] public float HipsWeightOffset = 1f;
|
||||||
|
|
||||||
|
// 로컬-월드 축 방향 정규화 계수
|
||||||
|
private Vector3 axisNormalizer = Vector3.one;
|
||||||
|
|
||||||
|
[Header("축 정규화 정보 (읽기 전용)")]
|
||||||
|
[SerializeField, HideInInspector]
|
||||||
|
private Vector3 debugAxisNormalizer = Vector3.one;
|
||||||
|
|
||||||
// HumanPoseHandler를 이용하여 원본 및 대상 아바타의 포즈를 관리
|
// HumanPoseHandler를 이용하여 원본 및 대상 아바타의 포즈를 관리
|
||||||
private HumanPoseHandler sourcePoseHandler;
|
private HumanPoseHandler sourcePoseHandler;
|
||||||
private HumanPoseHandler targetPoseHandler;
|
private HumanPoseHandler targetPoseHandler;
|
||||||
@ -119,7 +130,9 @@ namespace KindRetargeting
|
|||||||
[System.Serializable]
|
[System.Serializable]
|
||||||
private class RetargetingSettings
|
private class RetargetingSettings
|
||||||
{
|
{
|
||||||
public float hipsWeight;
|
public float hipsOffsetX; // 변경: hipsWeight → hipsOffsetX/Y/Z
|
||||||
|
public float hipsOffsetY;
|
||||||
|
public float hipsOffsetZ;
|
||||||
public float kneeInOutWeight;
|
public float kneeInOutWeight;
|
||||||
public float kneeFrontBackWeight;
|
public float kneeFrontBackWeight;
|
||||||
public float floorHeight;
|
public float floorHeight;
|
||||||
@ -167,6 +180,9 @@ namespace KindRetargeting
|
|||||||
// IK 타겟 생성 (무릎 시각화 오브젝트 포함)
|
// IK 타겟 생성 (무릎 시각화 오브젝트 포함)
|
||||||
CreateIKTargets();
|
CreateIKTargets();
|
||||||
|
|
||||||
|
// T-포즈 전에 축 정규화 계수 계산
|
||||||
|
CalculateAxisNormalizer();
|
||||||
|
|
||||||
// 원본 및 대상 아바타를 T-포즈로 복원
|
// 원본 및 대상 아바타를 T-포즈로 복원
|
||||||
SetTPose(sourceAnimator);
|
SetTPose(sourceAnimator);
|
||||||
SetTPose(targetAnimator);
|
SetTPose(targetAnimator);
|
||||||
@ -203,6 +219,59 @@ namespace KindRetargeting
|
|||||||
ApplyScale();
|
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>
|
/// <summary>
|
||||||
/// HumanPoseHandler를 초기화합니다.
|
/// HumanPoseHandler를 초기화합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -341,7 +410,9 @@ namespace KindRetargeting
|
|||||||
|
|
||||||
var settings = new RetargetingSettings
|
var settings = new RetargetingSettings
|
||||||
{
|
{
|
||||||
hipsWeight = hipsWeight,
|
hipsOffsetX = hipsOffsetX,
|
||||||
|
hipsOffsetY = hipsOffsetY,
|
||||||
|
hipsOffsetZ = hipsOffsetZ,
|
||||||
kneeInOutWeight = kneeInOutWeight,
|
kneeInOutWeight = kneeInOutWeight,
|
||||||
kneeFrontBackWeight = kneeFrontBackWeight,
|
kneeFrontBackWeight = kneeFrontBackWeight,
|
||||||
floorHeight = floorHeight,
|
floorHeight = floorHeight,
|
||||||
@ -397,7 +468,9 @@ namespace KindRetargeting
|
|||||||
var settings = JsonUtility.FromJson<RetargetingSettings>(json);
|
var settings = JsonUtility.FromJson<RetargetingSettings>(json);
|
||||||
|
|
||||||
// 설정 적용
|
// 설정 적용
|
||||||
hipsWeight = settings.hipsWeight;
|
hipsOffsetX = settings.hipsOffsetX;
|
||||||
|
hipsOffsetY = settings.hipsOffsetY;
|
||||||
|
hipsOffsetZ = settings.hipsOffsetZ;
|
||||||
kneeInOutWeight = settings.kneeInOutWeight;
|
kneeInOutWeight = settings.kneeInOutWeight;
|
||||||
kneeFrontBackWeight = settings.kneeFrontBackWeight;
|
kneeFrontBackWeight = settings.kneeFrontBackWeight;
|
||||||
floorHeight = settings.floorHeight;
|
floorHeight = settings.floorHeight;
|
||||||
@ -707,21 +780,36 @@ namespace KindRetargeting
|
|||||||
|
|
||||||
if (sourceHips != null && targetHips != null)
|
if (sourceHips != null && targetHips != null)
|
||||||
{
|
{
|
||||||
// 힙 위치 동기화 + 힙 위치 보정 적용 + 바닥 높이 적용용
|
// 1. 힙 회전 먼저 동기화 (회전 오프셋 적용)
|
||||||
Vector3 adjustedPosition = sourceHips.position;
|
Quaternion finalHipsRotation = sourceHips.rotation;
|
||||||
adjustedPosition.y += hipsWeight * HipsWeightOffset; // 기존 힙 높이 조정
|
|
||||||
adjustedPosition.y += floorHeight; // 바닥 높이 조정 추가
|
|
||||||
targetHips.position = adjustedPosition;
|
|
||||||
|
|
||||||
// 힙 회전 동기화 (회전 오프셋 적용)
|
|
||||||
if (rotationOffsets.TryGetValue(HumanBodyBones.Hips, out Quaternion hipsOffset))
|
if (rotationOffsets.TryGetValue(HumanBodyBones.Hips, out Quaternion hipsOffset))
|
||||||
{
|
{
|
||||||
targetHips.rotation = sourceHips.rotation * hipsOffset;
|
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.position = targetHips.position;
|
||||||
ikComponent.solver.spine.pelvisTarget.rotation = targetHips.rotation;
|
ikComponent.solver.spine.pelvisTarget.rotation = targetHips.rotation;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 힙을 제외한 본들의 회전 동기화
|
// 힙을 제외한 본들의 회전 동기화
|
||||||
SyncBoneRotations(skipBone: HumanBodyBones.Hips);
|
SyncBoneRotations(skipBone: HumanBodyBones.Hips);
|
||||||
|
|||||||
@ -22,7 +22,10 @@ namespace KindRetargeting
|
|||||||
// SerializedProperty 변수들
|
// SerializedProperty 변수들
|
||||||
private SerializedProperty sourceAnimatorProp;
|
private SerializedProperty sourceAnimatorProp;
|
||||||
private SerializedProperty targetAnimatorProp;
|
private SerializedProperty targetAnimatorProp;
|
||||||
private SerializedProperty hipsWeightProp;
|
private SerializedProperty hipsOffsetXProp;
|
||||||
|
private SerializedProperty hipsOffsetYProp;
|
||||||
|
private SerializedProperty hipsOffsetZProp;
|
||||||
|
private SerializedProperty debugAxisNormalizerProp;
|
||||||
private SerializedProperty fingerCopyModeProp;
|
private SerializedProperty fingerCopyModeProp;
|
||||||
private SerializedProperty useMotionFilterProp;
|
private SerializedProperty useMotionFilterProp;
|
||||||
private SerializedProperty filterBufferSizeProp;
|
private SerializedProperty filterBufferSizeProp;
|
||||||
@ -41,7 +44,10 @@ namespace KindRetargeting
|
|||||||
// SerializedProperty 초기화
|
// SerializedProperty 초기화
|
||||||
sourceAnimatorProp = serializedObject.FindProperty("sourceAnimator");
|
sourceAnimatorProp = serializedObject.FindProperty("sourceAnimator");
|
||||||
targetAnimatorProp = serializedObject.FindProperty("targetAnimator");
|
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");
|
fingerCopyModeProp = serializedObject.FindProperty("fingerCopyMode");
|
||||||
useMotionFilterProp = serializedObject.FindProperty("useMotionFilter");
|
useMotionFilterProp = serializedObject.FindProperty("useMotionFilter");
|
||||||
filterBufferSizeProp = serializedObject.FindProperty("filterBufferSize");
|
filterBufferSizeProp = serializedObject.FindProperty("filterBufferSize");
|
||||||
@ -86,11 +92,45 @@ namespace KindRetargeting
|
|||||||
GUILayout.Space(5);
|
GUILayout.Space(5);
|
||||||
|
|
||||||
// 힙 위치 보정 Foldout
|
// 힙 위치 보정 Foldout
|
||||||
showHipsSettings = EditorGUILayout.Foldout(showHipsSettings, "힙 위치 보정");
|
showHipsSettings = EditorGUILayout.Foldout(showHipsSettings, "힙 위치 보정 (로컬 좌표계)");
|
||||||
if (showHipsSettings)
|
if (showHipsSettings)
|
||||||
{
|
{
|
||||||
EditorGUI.indentLevel++;
|
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--;
|
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])
|
if (hipsSettingsFoldouts[instanceID])
|
||||||
{
|
{
|
||||||
EditorGUI.indentLevel++;
|
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--;
|
EditorGUI.indentLevel--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user