From 595aa5f71fe7341b33a444138711a60119423c8b Mon Sep 17 00:00:00 2001 From: user Date: Sun, 8 Mar 2026 00:23:54 +0900 Subject: [PATCH] =?UTF-8?q?Fix=20:=20=EC=97=90=EB=94=94=ED=84=B0=20ui=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Remote/RetargetingRemoteController.cs | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs b/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs index cb4b1198d..ea74f944e 100644 --- a/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs +++ b/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Reflection; using UnityEngine; @@ -131,6 +132,7 @@ namespace KindRetargeting.Remote string property = json["property"]?.ToString(); float value = json["value"]?.Value() ?? 0f; UpdateValue(charId, property, value); + BroadcastValueChanged(charId, property, value); } break; @@ -164,6 +166,20 @@ namespace KindRetargeting.Remote } break; + case "autoHipsOffset": + { + string charId = json["characterId"]?.ToString(); + AutoHipsOffset(charId); + } + break; + + case "autoCalibrateAll": + { + string charId = json["characterId"]?.ToString(); + AutoCalibrateAll(charId); + } + break; + default: Debug.LogWarning($"[RetargetingRemote] 알 수 없는 액션: {action}"); break; @@ -574,6 +590,7 @@ namespace KindRetargeting.Remote handPose.rightSpreadFingers = spread; } + SendCharacterData(characterId); SendStatus(true, $"{presetName} 프리셋 적용됨"); } @@ -612,6 +629,93 @@ namespace KindRetargeting.Remote SendStatus(true, "정면 캘리브레이션 완료"); } + private void AutoHipsOffset(string characterId) + { + var script = FindCharacter(characterId); + if (script == null) return; + + float offset = CalculateHipsOffsetFromLegDifference(script); + SetPrivateField(script, "hipsOffsetY", offset); + script.SaveSettings(); + + SendCharacterData(characterId); + SendStatus(true, $"다리 길이 자동 보정 완료: hipsOffsetY={offset:F4}"); + } + + private void AutoCalibrateAll(string characterId) + { + var script = FindCharacter(characterId); + if (script == null) return; + + Animator source = script.sourceAnimator; + Animator targetAnim = script.targetAnimator; + if (source == null || targetAnim == null || !source.isHuman || !targetAnim.isHuman) + { + SendStatus(false, "소스/타겟 Animator가 없거나 Humanoid가 아닙니다."); + return; + } + + // Step 1: 크기 초기화 + 힙 오프셋 계산 + script.ResetScale(); + SetPrivateField(script, "avatarScale", 1f); + SetPrivateField(script, "hipsOffsetY", CalculateHipsOffsetFromLegDifference(script)); + + // Step 2: 1프레임 후 목 높이 비율로 크기 조정 + StartCoroutine(AutoCalibrateCoroutine(script, characterId)); + } + + private IEnumerator AutoCalibrateCoroutine(CustomRetargetingScript script, string characterId) + { + yield return null; // 1프레임 대기 + + Animator source = script.sourceAnimator; + Animator targetAnim = script.targetAnimator; + + Transform sourceNeck = source.GetBoneTransform(HumanBodyBones.Neck); + Transform targetNeck = targetAnim.GetBoneTransform(HumanBodyBones.Neck); + if (sourceNeck == null || targetNeck == null) + { + SendStatus(false, "목 본을 찾을 수 없습니다."); + yield break; + } + + float scaleRatio = Mathf.Clamp(sourceNeck.position.y / Mathf.Max(targetNeck.position.y, 0.01f), 0.1f, 3f); + script.SetAvatarScale(scaleRatio); + SetPrivateField(script, "avatarScale", scaleRatio); + + yield return null; // 1프레임 대기 + + // Step 3: 힙 오프셋 재계산 + 머리 정면 캘리브레이션 + SetPrivateField(script, "hipsOffsetY", CalculateHipsOffsetFromLegDifference(script)); + script.CalibrateHeadToForward(); + script.SaveSettings(); + + SendCharacterData(characterId); + SendStatus(true, $"전체 자동 보정 완료: avatarScale={scaleRatio:F3}"); + } + + private float CalculateHipsOffsetFromLegDifference(CustomRetargetingScript script) + { + Animator source = script.sourceAnimator; + Animator targetAnim = script.targetAnimator; + if (source == null || targetAnim == null) return 0f; + + float sourceLeg = GetLegLength(source); + float targetLeg = GetLegLength(targetAnim); + if (sourceLeg < 0.01f || targetLeg < 0.01f) return 0f; + + return targetLeg - sourceLeg; + } + + private float GetLegLength(Animator animator) + { + Transform upper = animator.GetBoneTransform(HumanBodyBones.LeftUpperLeg); + Transform lower = animator.GetBoneTransform(HumanBodyBones.LeftLowerLeg); + Transform foot = animator.GetBoneTransform(HumanBodyBones.LeftFoot); + if (upper == null || lower == null || foot == null) return 0f; + return Vector3.Distance(upper.position, lower.position) + Vector3.Distance(lower.position, foot.position); + } + private CustomRetargetingScript FindCharacter(string characterId) { foreach (var script in registeredCharacters) @@ -624,6 +728,19 @@ namespace KindRetargeting.Remote return null; } + private void BroadcastValueChanged(string characterId, string property, float value) + { + var response = new + { + type = "valueChanged", + characterId = characterId, + property = property, + value = value + }; + + wsServer?.Broadcast(JsonConvert.SerializeObject(response)); + } + private void SendStatus(bool success, string message) { var response = new