Refactor : 리타겟팅 컨트롤 패널 + 웹 대시보드 + 리모트 컨트롤러 동기화

- RetargetingControlWindow: 어깨 보정, 접지 설정, IK 토글, 최소 발목 높이 섹션 추가
- RetargetingRemoteController: 어깨 반전 토글, 개별 손가락 curl, 최소 발목 높이 Send/Update 추가
- dashboard_script.txt: 어깨 보정/접지 설정 섹션 신규, IK 토글, 개별 손가락 슬라이더, 최소 발목 높이 추가
- 에디터 인스펙터/컨트롤 패널/웹 대시보드 3곳 설정 항목 완전 동기화

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
user 2026-03-07 23:48:58 +09:00
parent e17cfc003c
commit defed38ae5
3 changed files with 138 additions and 5 deletions

Binary file not shown.

View File

@ -224,6 +224,12 @@ public class RetargetingControlWindow : EditorWindow
footContainer.Bind(so);
panel.Add(footFoldout);
// 어깨 보정
panel.Add(BuildShoulderSection(script, so));
// 접지 설정
panel.Add(BuildGroundingSection(script, so));
// 손가락 제어 설정
panel.Add(BuildFingerControlSection(script, so));
@ -232,9 +238,11 @@ public class RetargetingControlWindow : EditorWindow
// 바닥 높이 설정
var floorFoldout = new Foldout { text = "바닥 높이 설정", value = false };
var floorField = new PropertyField(so.FindProperty("floorHeight"), "바닥 높이 (-1 ~ 1)");
floorFoldout.Add(floorField);
floorField.Bind(so);
var floorContainer = new VisualElement();
floorContainer.Add(new PropertyField(so.FindProperty("floorHeight"), "바닥 높이 (-1 ~ 1)"));
floorContainer.Add(new PropertyField(so.FindProperty("minimumAnkleHeight"), "최소 발목 높이"));
floorContainer.Bind(so);
floorFoldout.Add(floorContainer);
panel.Add(floorFoldout);
// 아바타 크기 설정
@ -304,6 +312,10 @@ public class RetargetingControlWindow : EditorWindow
var foldout = new Foldout { text = "가중치 설정", value = false };
var container = new VisualElement();
// IK 활성화 토글
container.Add(new PropertyField(so.FindProperty("limbWeight.enableLeftArmIK"), "왼팔 IK 활성화"));
container.Add(new PropertyField(so.FindProperty("limbWeight.enableRightArmIK"), "오른팔 IK 활성화"));
container.Add(BuildMinMaxRange("손과 프랍과의 범위 (가중치 1 → 0)",
so.FindProperty("limbWeight.minDistance"), so.FindProperty("limbWeight.maxDistance"), 0f, 1f, so));
container.Add(BuildMinMaxRange("의자와 허리 거리 범위 (가중치 1 → 0)",
@ -736,6 +748,61 @@ public class RetargetingControlWindow : EditorWindow
return box;
}
// ========== Shoulder Correction ==========
private VisualElement BuildShoulderSection(CustomRetargetingScript script, SerializedObject so)
{
var foldout = new Foldout { text = "어깨 보정", value = false };
var container = new VisualElement();
var strength = new Slider("블렌드 강도", 0f, 5f) { showInputField = true };
strength.BindProperty(so.FindProperty("shoulderCorrection.blendStrength"));
container.Add(strength);
var maxBlend = new Slider("최대 블렌드", 0f, 1f) { showInputField = true };
maxBlend.BindProperty(so.FindProperty("shoulderCorrection.maxShoulderBlend"));
container.Add(maxBlend);
container.Add(new PropertyField(so.FindProperty("shoulderCorrection.reverseLeftRotation"), "왼쪽 회전 반전"));
container.Add(new PropertyField(so.FindProperty("shoulderCorrection.reverseRightRotation"), "오른쪽 회전 반전"));
container.Add(BuildMinMaxRange("높이 차이 범위",
so.FindProperty("shoulderCorrection.minHeightDifference"),
so.FindProperty("shoulderCorrection.maxHeightDifference"),
-0.5f, 2f, so));
container.Add(new PropertyField(so.FindProperty("shoulderCorrection.shoulderCorrectionCurve"), "보정 커브"));
container.Bind(so);
foldout.Add(container);
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 ==========
private VisualElement BuildMinMaxRange(string label, SerializedProperty minProp, SerializedProperty maxProp, float limitMin, float limitMax, SerializedObject so)

View File

@ -269,6 +269,8 @@ namespace KindRetargeting.Remote
{ "shoulderMaxBlend", script.shoulderCorrection.maxShoulderBlend },
{ "shoulderMaxHeightDiff", script.shoulderCorrection.maxHeightDifference },
{ "shoulderMinHeightDiff", script.shoulderCorrection.minHeightDifference },
{ "shoulderReverseLeft", script.shoulderCorrection.reverseLeftRotation },
{ "shoulderReverseRight", script.shoulderCorrection.reverseRightRotation },
// FootGrounding 데이터
{ "groundingWeight", script.footGrounding.groundingWeight },
@ -281,6 +283,21 @@ namespace KindRetargeting.Remote
{ "handPoseEnabled", script.fingerShaped.enabled },
{ "leftHandEnabled", script.fingerShaped.leftHandEnabled },
{ "rightHandEnabled", script.fingerShaped.rightHandEnabled },
{ "leftThumbCurl", script.fingerShaped.leftThumbCurl },
{ "leftIndexCurl", script.fingerShaped.leftIndexCurl },
{ "leftMiddleCurl", script.fingerShaped.leftMiddleCurl },
{ "leftRingCurl", script.fingerShaped.leftRingCurl },
{ "leftPinkyCurl", script.fingerShaped.leftPinkyCurl },
{ "leftSpreadFingers", script.fingerShaped.leftSpreadFingers },
{ "rightThumbCurl", script.fingerShaped.rightThumbCurl },
{ "rightIndexCurl", script.fingerShaped.rightIndexCurl },
{ "rightMiddleCurl", script.fingerShaped.rightMiddleCurl },
{ "rightRingCurl", script.fingerShaped.rightRingCurl },
{ "rightPinkyCurl", script.fingerShaped.rightPinkyCurl },
{ "rightSpreadFingers", script.fingerShaped.rightSpreadFingers },
// 최소 발목 높이
{ "minimumAnkleHeight", GetPrivateField<float>(script, "minimumAnkleHeight") },
};
var response = new
@ -409,6 +426,12 @@ namespace KindRetargeting.Remote
case "shoulderMinHeightDiff":
script.shoulderCorrection.minHeightDifference = value;
break;
case "shoulderReverseLeft":
script.shoulderCorrection.reverseLeftRotation = value > 0.5f;
break;
case "shoulderReverseRight":
script.shoulderCorrection.reverseRightRotation = value > 0.5f;
break;
// FootGrounding 속성
case "groundingWeight":
@ -442,6 +465,49 @@ namespace KindRetargeting.Remote
script.fingerShaped.enabled = true;
break;
// 개별 손가락 curl 값
case "leftThumbCurl":
script.fingerShaped.leftThumbCurl = value;
break;
case "leftIndexCurl":
script.fingerShaped.leftIndexCurl = value;
break;
case "leftMiddleCurl":
script.fingerShaped.leftMiddleCurl = value;
break;
case "leftRingCurl":
script.fingerShaped.leftRingCurl = value;
break;
case "leftPinkyCurl":
script.fingerShaped.leftPinkyCurl = value;
break;
case "leftSpreadFingers":
script.fingerShaped.leftSpreadFingers = value;
break;
case "rightThumbCurl":
script.fingerShaped.rightThumbCurl = value;
break;
case "rightIndexCurl":
script.fingerShaped.rightIndexCurl = value;
break;
case "rightMiddleCurl":
script.fingerShaped.rightMiddleCurl = value;
break;
case "rightRingCurl":
script.fingerShaped.rightRingCurl = value;
break;
case "rightPinkyCurl":
script.fingerShaped.rightPinkyCurl = value;
break;
case "rightSpreadFingers":
script.fingerShaped.rightSpreadFingers = value;
break;
// 최소 발목 높이
case "minimumAnkleHeight":
SetPrivateField(script, "minimumAnkleHeight", value);
break;
default:
Debug.LogWarning($"[RetargetingRemote] 알 수 없는 속성: {property}");
break;