diff --git a/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs b/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs index 83bf24768..0744f9d78 100644 --- a/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs +++ b/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs @@ -104,6 +104,12 @@ namespace KindRetargeting [Header("프랍 부착")] [SerializeField] public PropLocationController propLocation = new PropLocationController(); + [Header("사지 가중치")] + [SerializeField] public LimbWeightController limbWeight = new LimbWeightController(); + + [Header("손가락 셰이핑")] + [SerializeField] public FingerShapedController fingerShaped = new FingerShapedController(); + [Header("아바타 크기 조정")] [SerializeField, Range(0.1f, 3f)] private float avatarScale = 1f; private float previousScale = 1f; @@ -337,6 +343,13 @@ namespace KindRetargeting // 프랍 부착 모듈 초기화 if (targetAnimator != null) propLocation.Initialize(targetAnimator); + + // 사지 가중치 모듈 초기화 + limbWeight.Initialize(ikSolver, this, transform); + + // 손가락 셰이핑 모듈 초기화 + if (targetAnimator != null) + fingerShaped.Initialize(targetAnimator); } /// @@ -534,8 +547,7 @@ namespace KindRetargeting } // LimbWeightController에서 의자 높이 오프셋 가져오기 - var limbController = GetComponent(); - float chairOffset = limbController != null ? limbController.chairSeatHeightOffset : 0.05f; + float chairOffset = limbWeight.chairSeatHeightOffset; var settings = new RetargetingSettings { @@ -613,11 +625,7 @@ namespace KindRetargeting headScale = settings.headScale; // LimbWeightController에 의자 높이 오프셋 적용 - var limbController = GetComponent(); - if (limbController != null) - { - limbController.chairSeatHeightOffset = settings.chairSeatHeightOffset; - } + limbWeight.chairSeatHeightOffset = settings.chairSeatHeightOffset; // 머리 회전 오프셋 로드 headRotationOffsetX = settings.headRotationOffsetX; @@ -834,9 +842,15 @@ namespace KindRetargeting break; } + // 손가락 셰이핑 (기존 ExecutionOrder 2) + fingerShaped.OnUpdate(); + // 어깨 보정 (기존 ExecutionOrder 3) shoulderCorrection.OnUpdate(); + // 사지 가중치 (기존 ExecutionOrder 4) + limbWeight.OnUpdate(); + // 발 접지 Pre-IK (기존 ExecutionOrder 5) footGrounding.OnUpdate(); @@ -1404,6 +1418,7 @@ namespace KindRetargeting { sourcePoseHandler?.Dispose(); targetPoseHandler?.Dispose(); + fingerShaped.Cleanup(); } /// diff --git a/Assets/Scripts/KindRetargeting/Editor/FingerShapedControllerEditor.cs b/Assets/Scripts/KindRetargeting/Editor/FingerShapedControllerEditor.cs deleted file mode 100644 index 7bfe40f6f..000000000 --- a/Assets/Scripts/KindRetargeting/Editor/FingerShapedControllerEditor.cs +++ /dev/null @@ -1,193 +0,0 @@ -using UnityEngine; -using UnityEditor; -using UnityEngine.UIElements; -using UnityEditor.UIElements; - -[CustomEditor(typeof(FingerShapedController))] -public class FingerShapedControllerEditor : Editor -{ - private const string CommonUssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss"; - private static readonly string[] fingerPropNames = { "ThumbCurl", "IndexCurl", "MiddleCurl", "RingCurl", "PinkyCurl" }; - private static readonly string[] fingerKoreanNames = { "엄지", "검지", "중지", "약지", "새끼" }; - - private FingerShapedController controller; - - public override VisualElement CreateInspectorGUI() - { - controller = target as FingerShapedController; - if (controller == null) return new VisualElement(); - var root = new VisualElement(); - - var commonUss = AssetDatabase.LoadAssetAtPath(CommonUssPath); - if (commonUss != null) root.styleSheets.Add(commonUss); - - // 활성화 토글 - var enabledProp = serializedObject.FindProperty("m_Enabled"); - var enableToggle = new PropertyField(enabledProp, "손가락 제어 활성화"); - root.Add(enableToggle); - - // 왼손 - var leftFoldout = new Foldout { text = "왼손", value = true }; - leftFoldout.Add(BuildHandControls("left")); - root.Add(leftFoldout); - - // 오른손 - var rightFoldout = new Foldout { text = "오른손", value = true }; - rightFoldout.Add(BuildHandControls("right")); - root.Add(rightFoldout); - - // 프리셋 버튼 - root.Add(BuildPresetButtons()); - - return root; - } - - private VisualElement BuildHandControls(string prefix) - { - var container = new VisualElement(); - container.style.backgroundColor = new Color(0, 0, 0, 0.08f); - container.style.borderTopLeftRadius = container.style.borderTopRightRadius = - container.style.borderBottomLeftRadius = container.style.borderBottomRightRadius = 4; - container.style.paddingTop = container.style.paddingBottom = - container.style.paddingLeft = container.style.paddingRight = 6; - - // 헤더: 활성화 토글 + 초기화 버튼 - var header = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center, marginBottom = 4 } }; - var handEnabledProp = serializedObject.FindProperty($"{prefix}HandEnabled"); - header.Add(new PropertyField(handEnabledProp, "제어 활성화") { style = { flexGrow = 1 } }); - var resetBtn = new Button(() => ResetHandValues(prefix)) { text = "초기화" }; - resetBtn.style.width = 60; - header.Add(resetBtn); - container.Add(header); - - // 손가락 슬라이더 (가로 배치) - var slidersRow = new VisualElement { style = { flexDirection = FlexDirection.Row, justifyContent = Justify.Center, marginTop = 4 } }; - - for (int i = 0; i < fingerPropNames.Length; i++) - { - var fingerProp = serializedObject.FindProperty($"{prefix}{fingerPropNames[i]}"); - slidersRow.Add(BuildVerticalSlider(fingerKoreanNames[i], fingerProp)); - } - container.Add(slidersRow); - - // 벌리기 슬라이더 - var spreadProp = serializedObject.FindProperty($"{prefix}SpreadFingers"); - var spreadSlider = new Slider("벌리기", -1f, 1f) { showInputField = true }; - spreadSlider.BindProperty(spreadProp); - spreadSlider.style.marginTop = 8; - container.Add(spreadSlider); - - // 비활성 시 숨기기 - container.TrackPropertyValue(handEnabledProp, prop => - { - slidersRow.style.display = prop.boolValue ? DisplayStyle.Flex : DisplayStyle.None; - spreadSlider.style.display = prop.boolValue ? DisplayStyle.Flex : DisplayStyle.None; - }); - slidersRow.style.display = handEnabledProp.boolValue ? DisplayStyle.Flex : DisplayStyle.None; - spreadSlider.style.display = handEnabledProp.boolValue ? DisplayStyle.Flex : DisplayStyle.None; - - return container; - } - - private VisualElement BuildVerticalSlider(string label, SerializedProperty prop) - { - var column = new VisualElement { style = { alignItems = Align.Center, width = 45, marginLeft = 2, marginRight = 2 } }; - - column.Add(new Label(label) { style = { fontSize = 10, color = new Color(0.6f, 0.6f, 0.6f) } }); - - var valueLabel = new Label(prop.floatValue.ToString("F1")) { style = { fontSize = 10, color = new Color(0.6f, 0.6f, 0.6f) } }; - column.Add(valueLabel); - - // UI Toolkit Slider in vertical mode (direction: Vertical 대신 세로용 Slider 사용) - // SliderDirection.Vertical 사용 - var slider = new Slider(-1f, 1f) { direction = SliderDirection.Vertical }; - slider.style.height = 100; - slider.style.width = 25; - slider.BindProperty(prop); - slider.RegisterValueChangedCallback(evt => valueLabel.text = evt.newValue.ToString("F1")); - - // 초기값 동기화 - column.TrackPropertyValue(prop, p => valueLabel.text = p.floatValue.ToString("F1")); - - column.Add(slider); - return column; - } - - private VisualElement BuildPresetButtons() - { - var container = new VisualElement { style = { marginTop = 10 } }; - container.Add(new Label("손 모양 프리셋") { style = { unityFontStyleAndWeight = FontStyle.Bold, marginBottom = 4 } }); - - string[,] presets = { - { "가위", "바위", "보" }, - { "브이", "검지", "초기화" } - }; - - for (int row = 0; row < 2; row++) - { - var btnRow = new VisualElement { style = { flexDirection = FlexDirection.Row, justifyContent = Justify.Center, marginBottom = 4 } }; - for (int col = 0; col < 3; col++) - { - string presetName = presets[row, col]; - var btn = new Button(() => ApplyPreset(presetName)) { text = presetName }; - btn.style.height = 30; - btn.style.width = 100; - btn.style.marginLeft = btn.style.marginRight = 4; - btnRow.Add(btn); - } - container.Add(btnRow); - } - - return container; - } - - private void ResetHandValues(string prefix) - { - serializedObject.Update(); - string[] props = { "ThumbCurl", "IndexCurl", "MiddleCurl", "RingCurl", "PinkyCurl", "SpreadFingers" }; - foreach (var prop in props) - serializedObject.FindProperty($"{prefix}{prop}").floatValue = 0f; - serializedObject.ApplyModifiedProperties(); - } - - private void ApplyPreset(string presetName) - { - if (!controller.enabled) - controller.enabled = true; - - switch (presetName) - { - case "가위": SetPreset(1f, 1f, -1f, -1f, -1f, 0.3f); break; - case "바위": SetPreset(-1f, -1f, -1f, -1f, -1f, 0f); break; - case "보": SetPreset(1f, 1f, 1f, 1f, 1f, 1f); break; - case "브이": SetPreset(-1f, 1f, 1f, -1f, -1f, 1f); break; - case "검지": SetPreset(-1f, 1f, -1f, -1f, -1f, 0f); break; - case "초기화": SetPreset(0.8f, 0.8f, 0.8f, 0.8f, 0.8f, 0.8f); break; - } - } - - private void SetPreset(float thumb, float index, float middle, float ring, float pinky, float spread) - { - if (controller.leftHandEnabled) - { - controller.leftThumbCurl = thumb; - controller.leftIndexCurl = index; - controller.leftMiddleCurl = middle; - controller.leftRingCurl = ring; - controller.leftPinkyCurl = pinky; - controller.leftSpreadFingers = spread; - } - - if (controller.rightHandEnabled) - { - controller.rightThumbCurl = thumb; - controller.rightIndexCurl = index; - controller.rightMiddleCurl = middle; - controller.rightRingCurl = ring; - controller.rightPinkyCurl = pinky; - controller.rightSpreadFingers = spread; - } - - EditorUtility.SetDirty(controller); - } -} diff --git a/Assets/Scripts/KindRetargeting/Editor/FingerShapedControllerEditor.cs.meta b/Assets/Scripts/KindRetargeting/Editor/FingerShapedControllerEditor.cs.meta deleted file mode 100644 index f9609cf4b..000000000 --- a/Assets/Scripts/KindRetargeting/Editor/FingerShapedControllerEditor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 291a583b9a953e041a119ba6c332d187 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/KindRetargeting/Editor/LimbWeightControllerEditor.cs b/Assets/Scripts/KindRetargeting/Editor/LimbWeightControllerEditor.cs deleted file mode 100644 index 491a0242c..000000000 --- a/Assets/Scripts/KindRetargeting/Editor/LimbWeightControllerEditor.cs +++ /dev/null @@ -1,185 +0,0 @@ -using UnityEngine; -using UnityEditor; -using UnityEngine.UIElements; -using UnityEditor.UIElements; - -namespace KindRetargeting -{ - [CustomEditor(typeof(LimbWeightController))] - public class LimbWeightControllerEditor : BaseRetargetingEditor - { - private const string CommonUssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss"; - - SerializedProperty maxDistance; - SerializedProperty minDistance; - SerializedProperty weightSmoothSpeed; - SerializedProperty middleWeightMultiplier; - SerializedProperty hipsMinDistance; - SerializedProperty hipsMaxDistance; - SerializedProperty props; - SerializedProperty characterRoot; - SerializedProperty groundHipsMinHeight; - SerializedProperty groundHipsMaxHeight; - SerializedProperty footHeightMinThreshold; - SerializedProperty footHeightMaxThreshold; - SerializedProperty enableLeftArmIK; - SerializedProperty enableRightArmIK; - SerializedProperty chairSeatHeightOffset; - - protected override void OnEnable() - { - base.OnEnable(); - if (serializedObject == null) return; - - maxDistance = serializedObject.FindProperty("maxDistance"); - minDistance = serializedObject.FindProperty("minDistance"); - weightSmoothSpeed = serializedObject.FindProperty("weightSmoothSpeed"); - middleWeightMultiplier = serializedObject.FindProperty("middleWeightMultiplier"); - hipsMinDistance = serializedObject.FindProperty("hipsMinDistance"); - hipsMaxDistance = serializedObject.FindProperty("hipsMaxDistance"); - props = serializedObject.FindProperty("props"); - characterRoot = serializedObject.FindProperty("characterRoot"); - groundHipsMinHeight = serializedObject.FindProperty("groundHipsMinHeight"); - groundHipsMaxHeight = serializedObject.FindProperty("groundHipsMaxHeight"); - footHeightMinThreshold = serializedObject.FindProperty("footHeightMinThreshold"); - footHeightMaxThreshold = serializedObject.FindProperty("footHeightMaxThreshold"); - enableLeftArmIK = serializedObject.FindProperty("enableLeftArmIK"); - enableRightArmIK = serializedObject.FindProperty("enableRightArmIK"); - chairSeatHeightOffset = serializedObject.FindProperty("chairSeatHeightOffset"); - } - - public override VisualElement CreateInspectorGUI() - { - if (serializedObject == null || target == null) - return new VisualElement(); - - var root = new VisualElement(); - - var commonUss = AssetDatabase.LoadAssetAtPath(CommonUssPath); - if (commonUss != null) root.styleSheets.Add(commonUss); - - // IK 활성화 설정 - var ikFoldout = new Foldout { text = "IK 활성화 설정", value = true }; - ikFoldout.Add(new PropertyField(enableLeftArmIK, "왼팔 IK 활성화")); - ikFoldout.Add(new PropertyField(enableRightArmIK, "오른팔 IK 활성화")); - root.Add(ikFoldout); - - // 거리 기반 가중치 설정 - var distFoldout = new Foldout { text = "거리 기반 가중치 설정", value = true }; - distFoldout.Add(BuildMinMaxRange("거리 범위 (가중치 1 → 0)", minDistance, maxDistance, 0f, 1f)); - root.Add(distFoldout); - - // 가중치 변화 설정 - var weightFoldout = new Foldout { text = "가중치 변화 설정", value = true }; - weightFoldout.Add(new PropertyField(weightSmoothSpeed, "가중치 변화 속도")); - root.Add(weightFoldout); - - // 허리 가중치 설정 - var hipsFoldout = new Foldout { text = "허리 가중치 설정", value = true }; - hipsFoldout.Add(BuildMinMaxRange("허리 거리 범위 (가중치 1 → 0)", hipsMinDistance, hipsMaxDistance, 0f, 1f)); - root.Add(hipsFoldout); - - // 바닥 기준 히프 보정 설정 - var groundFoldout = new Foldout { text = "바닥 기준 히프 보정 설정", value = true }; - groundFoldout.Add(BuildMinMaxRange("바닥 기준 히프 높이 범위 (가중치 0 → 1)", groundHipsMinHeight, groundHipsMaxHeight, 0f, 2f)); - root.Add(groundFoldout); - - // 발 높이 기반 가중치 설정 - var footFoldout = new Foldout { text = "발 높이 기반 가중치 설정", value = true }; - footFoldout.Add(BuildMinMaxRange("발 높이 범위 (가중치 1 → 0)", footHeightMinThreshold, footHeightMaxThreshold, 0.1f, 1f)); - root.Add(footFoldout); - - // 의자 앉기 높이 설정 - var chairFoldout = new Foldout { text = "의자 앉기 높이 설정", value = true }; - chairFoldout.Add(new PropertyField(chairSeatHeightOffset, "좌석 높이 오프셋")); - root.Add(chairFoldout); - - // 참조 설정 - var refFoldout = new Foldout { text = "참조 설정", value = true }; - refFoldout.Add(new PropertyField(props, "프랍 오브젝트")); - refFoldout.Add(new PropertyField(characterRoot, "캐릭터 루트")); - root.Add(refFoldout); - - // 변경 감지 - root.TrackSerializedObjectValue(serializedObject, so => - { - if (target == null) return; - MarkDirty(); - var script = (LimbWeightController)target; - var retargeting = script.GetComponent(); - if (retargeting != null) - EditorUtility.SetDirty(retargeting); - }); - - return root; - } - - private VisualElement BuildMinMaxRange(string label, SerializedProperty minProp, SerializedProperty maxProp, float limitMin, float limitMax) - { - var container = new VisualElement { style = { marginBottom = 4 } }; - if (minProp == null || maxProp == null) - { - container.Add(new HelpBox($"'{label}' 프로퍼티를 찾을 수 없습니다.", HelpBoxMessageType.Warning)); - return container; - } - container.Add(new Label(label) { style = { marginBottom = 2 } }); - - var row = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center } }; - - var minField = new FloatField { value = minProp.floatValue, style = { width = 50 } }; - var slider = new MinMaxSlider(minProp.floatValue, maxProp.floatValue, limitMin, limitMax); - slider.style.flexGrow = 1; - slider.style.marginLeft = slider.style.marginRight = 4; - var maxField = new FloatField { value = maxProp.floatValue, style = { width = 50 } }; - - // MinMaxSlider → sync fields + properties - slider.RegisterValueChangedCallback(evt => - { - minProp.floatValue = evt.newValue.x; - maxProp.floatValue = evt.newValue.y; - serializedObject.ApplyModifiedProperties(); - minField.SetValueWithoutNotify(evt.newValue.x); - maxField.SetValueWithoutNotify(evt.newValue.y); - }); - - // FloatField min → sync slider + property - minField.RegisterValueChangedCallback(evt => - { - float clamped = Mathf.Clamp(evt.newValue, limitMin, maxProp.floatValue); - minProp.floatValue = clamped; - serializedObject.ApplyModifiedProperties(); - slider.SetValueWithoutNotify(new Vector2(clamped, maxProp.floatValue)); - minField.SetValueWithoutNotify(clamped); - }); - - // FloatField max → sync slider + property - maxField.RegisterValueChangedCallback(evt => - { - float clamped = Mathf.Clamp(evt.newValue, minProp.floatValue, limitMax); - maxProp.floatValue = clamped; - serializedObject.ApplyModifiedProperties(); - slider.SetValueWithoutNotify(new Vector2(minProp.floatValue, clamped)); - maxField.SetValueWithoutNotify(clamped); - }); - - // Track external changes (undo, etc.) - container.TrackPropertyValue(minProp, p => - { - minField.SetValueWithoutNotify(p.floatValue); - slider.SetValueWithoutNotify(new Vector2(p.floatValue, maxProp.floatValue)); - }); - container.TrackPropertyValue(maxProp, p => - { - maxField.SetValueWithoutNotify(p.floatValue); - slider.SetValueWithoutNotify(new Vector2(minProp.floatValue, p.floatValue)); - }); - - row.Add(minField); - row.Add(slider); - row.Add(maxField); - container.Add(row); - - return container; - } - } -} diff --git a/Assets/Scripts/KindRetargeting/Editor/LimbWeightControllerEditor.cs.meta b/Assets/Scripts/KindRetargeting/Editor/LimbWeightControllerEditor.cs.meta deleted file mode 100644 index 29554f3e7..000000000 --- a/Assets/Scripts/KindRetargeting/Editor/LimbWeightControllerEditor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 199539b34f08aac41a86f4767bc49def -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs b/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs index f00dce14a..7f92c758e 100644 --- a/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs +++ b/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs @@ -144,11 +144,7 @@ public class RetargetingControlWindow : EditorWindow foreach (var s in retargetingScripts) { if (s == null) continue; - if (s.GetComponent() == null) - s.gameObject.AddComponent(); - if (s.GetComponent() == null) - s.gameObject.AddComponent(); - // PropLocationController는 CRS 내부 모듈로 이동됨 + // 모든 컴포넌트는 CRS 내부 모듈로 이동됨 EditorUtility.SetDirty(s.gameObject); } @@ -197,7 +193,7 @@ public class RetargetingControlWindow : EditorWindow panel.Add(BuildHeader(script)); // 가중치 설정 - panel.Add(BuildWeightSection(script)); + panel.Add(BuildWeightSection(script, so)); // 힙 위치 보정 panel.Add(BuildHipsSection(script, so)); @@ -229,7 +225,7 @@ public class RetargetingControlWindow : EditorWindow panel.Add(footFoldout); // 손가락 제어 설정 - panel.Add(BuildFingerControlSection(script)); + panel.Add(BuildFingerControlSection(script, so)); // 손가락 복제 설정 panel.Add(BuildFingerCopySection(script, so)); @@ -303,27 +299,22 @@ public class RetargetingControlWindow : EditorWindow // ========== Weight Settings ========== - private VisualElement BuildWeightSection(CustomRetargetingScript script) + private VisualElement BuildWeightSection(CustomRetargetingScript script, SerializedObject so) { var foldout = new Foldout { text = "가중치 설정", value = false }; - var limb = script.GetComponent(); - if (limb == null) { foldout.Add(new HelpBox("LimbWeightController가 없습니다.", HelpBoxMessageType.Warning)); return foldout; } - - var limbSO = CreateTrackedSO(limb); var container = new VisualElement(); container.Add(BuildMinMaxRange("손과 프랍과의 범위 (가중치 1 → 0)", - limbSO.FindProperty("minDistance"), limbSO.FindProperty("maxDistance"), 0f, 1f, limbSO)); + so.FindProperty("limbWeight.minDistance"), so.FindProperty("limbWeight.maxDistance"), 0f, 1f, so)); container.Add(BuildMinMaxRange("의자와 허리 거리 범위 (가중치 1 → 0)", - limbSO.FindProperty("hipsMinDistance"), limbSO.FindProperty("hipsMaxDistance"), 0f, 1f, limbSO)); + so.FindProperty("limbWeight.hipsMinDistance"), so.FindProperty("limbWeight.hipsMaxDistance"), 0f, 1f, so)); container.Add(BuildMinMaxRange("바닥과 허리 높이에 의한 블렌딩 (가중치 0 → 1)", - limbSO.FindProperty("groundHipsMinHeight"), limbSO.FindProperty("groundHipsMaxHeight"), 0f, 2f, limbSO)); + so.FindProperty("limbWeight.groundHipsMinHeight"), so.FindProperty("limbWeight.groundHipsMaxHeight"), 0f, 2f, so)); container.Add(BuildMinMaxRange("지면으로부터 발의 범위에 의한 IK 블렌딩 (가중치 1 → 0)", - limbSO.FindProperty("footHeightMinThreshold"), limbSO.FindProperty("footHeightMaxThreshold"), 0.1f, 1f, limbSO)); + so.FindProperty("limbWeight.footHeightMinThreshold"), so.FindProperty("limbWeight.footHeightMaxThreshold"), 0.1f, 1f, so)); - var smoothField = new PropertyField(limbSO.FindProperty("weightSmoothSpeed"), "가중치 변화 속도"); + var smoothField = new PropertyField(so.FindProperty("limbWeight.weightSmoothSpeed"), "가중치 변화 속도"); container.Add(smoothField); - container.Bind(limbSO); foldout.Add(container); return foldout; @@ -366,15 +357,9 @@ public class RetargetingControlWindow : EditorWindow container.Add(hz); // 의자 앉기 높이 - var limb = script.GetComponent(); - if (limb != null) - { - var limbSO = CreateTrackedSO(limb); - var chairSlider = new Slider("의자 앉기 높이", -1f, 1f) { showInputField = true, tooltip = "의자에 앉을 때 엉덩이 높이 조정 (월드 Y 기준)" }; - chairSlider.BindProperty(limbSO.FindProperty("chairSeatHeightOffset")); - container.Add(chairSlider); - chairSlider.Bind(limbSO); - } + var chairSlider = new Slider("의자 앉기 높이", -1f, 1f) { showInputField = true, tooltip = "의자에 앉을 때 엉덩이 높이 조정 (월드 Y 기준)" }; + chairSlider.BindProperty(so.FindProperty("limbWeight.chairSeatHeightOffset")); + container.Add(chairSlider); // 다리 길이 자동 보정 버튼 var autoHipsBtn = new Button(() => @@ -402,48 +387,28 @@ public class RetargetingControlWindow : EditorWindow // ========== Finger Control ========== - private VisualElement BuildFingerControlSection(CustomRetargetingScript script) + private VisualElement BuildFingerControlSection(CustomRetargetingScript script, SerializedObject so) { var foldout = new Foldout { text = "손가락 제어 설정", value = false }; - var fingerController = script.GetComponent(); - if (fingerController == null) - { - foldout.Add(new HelpBox("FingerShapedController가 없습니다.", HelpBoxMessageType.Warning)); - var addBtn = new Button(() => - { - script.gameObject.AddComponent(); - EditorUtility.SetDirty(script.gameObject); - RebuildCharacterPanels(); - }) { text = "FingerShapedController 추가" }; - foldout.Add(addBtn); - return foldout; - } - - var fso = CreateTrackedSO(fingerController); var container = new VisualElement(); // 활성화 토글 - var enableToggle = new Toggle("손가락 제어 활성화") { value = fingerController.enabled }; - enableToggle.RegisterValueChangedCallback(evt => - { - fingerController.enabled = evt.newValue; - EditorUtility.SetDirty(fingerController); - }); + var enabledProp = so.FindProperty("fingerShaped.enabled"); + var enableToggle = new PropertyField(enabledProp, "손가락 제어 활성화"); container.Add(enableToggle); // 왼손 - container.Add(BuildHandSection("왼손", "left", fso, fingerController)); + container.Add(BuildHandSection("왼손", "left", so, script.fingerShaped)); // 오른손 - container.Add(BuildHandSection("오른손", "right", fso, fingerController)); + container.Add(BuildHandSection("오른손", "right", so, script.fingerShaped)); // 프리셋 - container.Add(BuildFingerPresets(fingerController)); + container.Add(BuildFingerPresets(script, script.fingerShaped)); - container.Bind(fso); foldout.Add(container); return foldout; } - private VisualElement BuildHandSection(string label, string prefix, SerializedObject fso, FingerShapedController fc) + private VisualElement BuildHandSection(string label, string prefix, SerializedObject so, FingerShapedController fc) { var box = new VisualElement(); box.style.backgroundColor = new Color(0, 0, 0, 0.08f); @@ -455,13 +420,13 @@ public class RetargetingControlWindow : EditorWindow var header = new VisualElement { style = { flexDirection = FlexDirection.Row, alignItems = Align.Center } }; var handFoldout = new Foldout { text = label, value = false }; - var handEnabledProp = fso.FindProperty($"{prefix}HandEnabled"); + var handEnabledProp = so.FindProperty($"fingerShaped.{prefix}HandEnabled"); header.Add(new PropertyField(handEnabledProp, "활성화") { style = { flexGrow = 1 } }); var resetBtn = new Button(() => { string[] props = { "ThumbCurl", "IndexCurl", "MiddleCurl", "RingCurl", "PinkyCurl", "SpreadFingers" }; - foreach (var p in props) fso.FindProperty($"{prefix}{p}").floatValue = 0f; - fso.ApplyModifiedProperties(); + foreach (var p in props) so.FindProperty($"fingerShaped.{prefix}{p}").floatValue = 0f; + so.ApplyModifiedProperties(); }) { text = "초기화" }; resetBtn.style.width = 60; header.Add(resetBtn); @@ -477,7 +442,7 @@ public class RetargetingControlWindow : EditorWindow var col = new VisualElement { style = { alignItems = Align.Center, width = 45, marginLeft = 2, marginRight = 2 } }; col.Add(new Label(korNames[i]) { style = { fontSize = 10, color = new Color(0.6f, 0.6f, 0.6f) } }); - var prop = fso.FindProperty($"{prefix}{fingerNames[i]}Curl"); + var prop = so.FindProperty($"fingerShaped.{prefix}{fingerNames[i]}Curl"); var valLabel = new Label(prop.floatValue.ToString("F1")) { style = { fontSize = 10, color = new Color(0.6f, 0.6f, 0.6f) } }; col.Add(valLabel); @@ -494,16 +459,18 @@ public class RetargetingControlWindow : EditorWindow // 벌리기 var spreadSlider = new Slider("벌리기", -1f, 1f) { showInputField = true }; - spreadSlider.BindProperty(fso.FindProperty($"{prefix}SpreadFingers")); + spreadSlider.BindProperty(so.FindProperty($"fingerShaped.{prefix}SpreadFingers")); handFoldout.Add(spreadSlider); // 비활성 시 숨김 handFoldout.schedule.Execute(() => { - try { if (fso == null || fso.targetObject == null) return; } + try { if (so == null || so.targetObject == null) return; } catch (System.Exception) { return; } - fso.Update(); - bool enabled = fso.FindProperty($"{prefix}HandEnabled").boolValue; + so.Update(); + var enabledProp = so.FindProperty($"fingerShaped.{prefix}HandEnabled"); + if (enabledProp == null) return; + bool enabled = enabledProp.boolValue; slidersRow.style.display = enabled ? DisplayStyle.Flex : DisplayStyle.None; spreadSlider.style.display = enabled ? DisplayStyle.Flex : DisplayStyle.None; }).Every(300); @@ -512,7 +479,7 @@ public class RetargetingControlWindow : EditorWindow return box; } - private VisualElement BuildFingerPresets(FingerShapedController controller) + private VisualElement BuildFingerPresets(CustomRetargetingScript script, FingerShapedController controller) { var container = new VisualElement { style = { marginTop = 6 } }; container.Add(new Label("손 모양 프리셋") { style = { unityFontStyleAndWeight = FontStyle.Bold, marginBottom = 4 } }); @@ -524,7 +491,7 @@ public class RetargetingControlWindow : EditorWindow for (int col = 0; col < 3; col++) { string name = presets[row, col]; - var btn = new Button(() => ApplyFingerPreset(controller, name)) { text = name }; + var btn = new Button(() => ApplyFingerPreset(script, controller, name)) { text = name }; btn.style.height = 30; btn.style.width = 100; btn.style.marginLeft = btn.style.marginRight = 4; btnRow.Add(btn); } @@ -835,7 +802,7 @@ public class RetargetingControlWindow : EditorWindow return container; } - private void ApplyFingerPreset(FingerShapedController controller, string presetName) + private void ApplyFingerPreset(CustomRetargetingScript script, FingerShapedController controller, string presetName) { if (!controller.enabled) controller.enabled = true; @@ -860,7 +827,7 @@ public class RetargetingControlWindow : EditorWindow controller.rightThumbCurl = t; controller.rightIndexCurl = i; controller.rightMiddleCurl = m; controller.rightRingCurl = r; controller.rightPinkyCurl = p; controller.rightSpreadFingers = s; } - EditorUtility.SetDirty(controller); + EditorUtility.SetDirty(script); } // ========== Head Calibration ========== diff --git a/Assets/Scripts/KindRetargeting/FingerShapedController.cs b/Assets/Scripts/KindRetargeting/FingerShapedController.cs index 025e45624..533caaa49 100644 --- a/Assets/Scripts/KindRetargeting/FingerShapedController.cs +++ b/Assets/Scripts/KindRetargeting/FingerShapedController.cs @@ -1,168 +1,173 @@ using UnityEngine; using System.Collections.Generic; -[DefaultExecutionOrder(2)] -public class FingerShapedController : MonoBehaviour +namespace KindRetargeting { - private Animator animator; - private HumanPoseHandler humanPoseHandler; - - // 손가락을 제외한 모든 본의 로컬 회전 저장용 (SetHumanPose 호출 시 몸 복원용) - private Dictionary savedBoneLocalRotations = new Dictionary(); - - // 손가락을 제외한 모든 휴먼본 목록 - private static readonly HumanBodyBones[] nonFingerBones = new HumanBodyBones[] + [System.Serializable] + public class FingerShapedController { - HumanBodyBones.Hips, - HumanBodyBones.Spine, - HumanBodyBones.Chest, - HumanBodyBones.UpperChest, - HumanBodyBones.Neck, - HumanBodyBones.Head, - HumanBodyBones.LeftShoulder, - HumanBodyBones.LeftUpperArm, - HumanBodyBones.LeftLowerArm, - HumanBodyBones.LeftHand, - HumanBodyBones.RightShoulder, - HumanBodyBones.RightUpperArm, - HumanBodyBones.RightLowerArm, - HumanBodyBones.RightHand, - HumanBodyBones.LeftUpperLeg, - HumanBodyBones.LeftLowerLeg, - HumanBodyBones.LeftFoot, - HumanBodyBones.LeftToes, - HumanBodyBones.RightUpperLeg, - HumanBodyBones.RightLowerLeg, - HumanBodyBones.RightFoot, - HumanBodyBones.RightToes, - HumanBodyBones.LeftEye, - HumanBodyBones.RightEye, - HumanBodyBones.Jaw - }; + private Animator animator; + private HumanPoseHandler humanPoseHandler; - [Header("왼손 제어 값")] - [Range(-1, 1)] public float leftPinkyCurl; // 새끼손가락 구부리기 - [Range(-1, 1)] public float leftRingCurl; // 약지 구부리기 - [Range(-1, 1)] public float leftMiddleCurl; // 중지 구부리기 - [Range(-1, 1)] public float leftIndexCurl; // 검지 구부리기 - [Range(-1, 1)] public float leftThumbCurl; // 엄지 구부리기 - [Range(-1, 1)] public float leftSpreadFingers; // 손가락 벌리기 + // 손가락을 제외한 모든 본의 로컬 회전 저장용 (SetHumanPose 호출 시 몸 복원용) + private Dictionary savedBoneLocalRotations = new Dictionary(); - [Header("오른손 제어 값")] - [Range(-1, 1)] public float rightPinkyCurl; // 새끼손가락 구부리기 - [Range(-1, 1)] public float rightRingCurl; // 약지 구부리기 - [Range(-1, 1)] public float rightMiddleCurl; // 중지 구부리기 - [Range(-1, 1)] public float rightIndexCurl; // 검지 구부리기 - [Range(-1, 1)] public float rightThumbCurl; // 엄지 구부리기 - [Range(-1, 1)] public float rightSpreadFingers; // 손가락 벌리기 - - public bool leftHandEnabled = false; // 왼손 제어 활성화 상태 - public bool rightHandEnabled = false; // 오른손 제어 활성화 상태 - - private void Reset() - { - // 컴포넌트가 처음 추가될 때 자동으로 비활성화 - enabled = false; - leftHandEnabled = false; - rightHandEnabled = false; - } - - private void Awake() - { - animator = GetComponent(); - humanPoseHandler = new HumanPoseHandler(animator.avatar, animator.transform); - } - - private void OnDestroy() - { - if (humanPoseHandler != null) + // 손가락을 제외한 모든 휴먼본 목록 + private static readonly HumanBodyBones[] nonFingerBones = new HumanBodyBones[] { - humanPoseHandler.Dispose(); + HumanBodyBones.Hips, + HumanBodyBones.Spine, + HumanBodyBones.Chest, + HumanBodyBones.UpperChest, + HumanBodyBones.Neck, + HumanBodyBones.Head, + HumanBodyBones.LeftShoulder, + HumanBodyBones.LeftUpperArm, + HumanBodyBones.LeftLowerArm, + HumanBodyBones.LeftHand, + HumanBodyBones.RightShoulder, + HumanBodyBones.RightUpperArm, + HumanBodyBones.RightLowerArm, + HumanBodyBones.RightHand, + HumanBodyBones.LeftUpperLeg, + HumanBodyBones.LeftLowerLeg, + HumanBodyBones.LeftFoot, + HumanBodyBones.LeftToes, + HumanBodyBones.RightUpperLeg, + HumanBodyBones.RightLowerLeg, + HumanBodyBones.RightFoot, + HumanBodyBones.RightToes, + HumanBodyBones.LeftEye, + HumanBodyBones.RightEye, + HumanBodyBones.Jaw + }; + + public bool enabled = false; + + [Header("왼손 제어 값")] + [Range(-1, 1)] public float leftPinkyCurl; // 새끼손가락 구부리기 + [Range(-1, 1)] public float leftRingCurl; // 약지 구부리기 + [Range(-1, 1)] public float leftMiddleCurl; // 중지 구부리기 + [Range(-1, 1)] public float leftIndexCurl; // 검지 구부리기 + [Range(-1, 1)] public float leftThumbCurl; // 엄지 구부리기 + [Range(-1, 1)] public float leftSpreadFingers; // 손가락 벌리기 + + [Header("오른손 제어 값")] + [Range(-1, 1)] public float rightPinkyCurl; // 새끼손가락 구부리기 + [Range(-1, 1)] public float rightRingCurl; // 약지 구부리기 + [Range(-1, 1)] public float rightMiddleCurl; // 중지 구부리기 + [Range(-1, 1)] public float rightIndexCurl; // 검지 구부리기 + [Range(-1, 1)] public float rightThumbCurl; // 엄지 구부리기 + [Range(-1, 1)] public float rightSpreadFingers; // 손가락 벌리기 + + public bool leftHandEnabled = false; // 왼손 제어 활성화 상태 + public bool rightHandEnabled = false; // 오른손 제어 활성화 상태 + + private bool isInitialized; + + public void Initialize(Animator targetAnimator) + { + animator = targetAnimator; + if (animator == null || !animator.isHuman) return; + + humanPoseHandler = new HumanPoseHandler(animator.avatar, animator.transform); + isInitialized = true; } - } - private void Update() - { - UpdateMuscleValues(); - } - - private void UpdateMuscleValues() - { - // 1. 손가락을 제외한 모든 본의 로컬 회전 저장 (SetHumanPose 호출 전) - savedBoneLocalRotations.Clear(); - for (int i = 0; i < nonFingerBones.Length; i++) + public void Cleanup() { - Transform bone = animator.GetBoneTransform(nonFingerBones[i]); - if (bone != null) + if (humanPoseHandler != null) { - savedBoneLocalRotations[nonFingerBones[i]] = bone.localRotation; + humanPoseHandler.Dispose(); + humanPoseHandler = null; + } + isInitialized = false; + } + + public void OnUpdate() + { + if (!isInitialized || !enabled) return; + UpdateMuscleValues(); + } + + private void UpdateMuscleValues() + { + // 1. 손가락을 제외한 모든 본의 로컬 회전 저장 (SetHumanPose 호출 전) + savedBoneLocalRotations.Clear(); + for (int i = 0; i < nonFingerBones.Length; i++) + { + Transform bone = animator.GetBoneTransform(nonFingerBones[i]); + if (bone != null) + { + savedBoneLocalRotations[nonFingerBones[i]] = bone.localRotation; + } + } + + // 2. HumanPose 가져오기 및 손가락 머슬 설정 + HumanPose humanPose = new HumanPose(); + humanPoseHandler.GetHumanPose(ref humanPose); + + // 왼손 제어 + SetHandMuscles(true, leftThumbCurl, leftIndexCurl, leftMiddleCurl, leftRingCurl, + leftPinkyCurl, leftSpreadFingers, ref humanPose); + + // 오른손 제어 + SetHandMuscles(false, rightThumbCurl, rightIndexCurl, rightMiddleCurl, rightRingCurl, + rightPinkyCurl, rightSpreadFingers, ref humanPose); + + // 3. 머슬 포즈 적용 (손가락 포함 전체 본에 영향) + humanPoseHandler.SetHumanPose(ref humanPose); + + // 4. 손가락을 제외한 모든 본의 로컬 회전 복원 (본 길이 변형 방지) + foreach (var kvp in savedBoneLocalRotations) + { + Transform bone = animator.GetBoneTransform(kvp.Key); + if (bone != null) + { + bone.localRotation = kvp.Value; + } } } - // 2. HumanPose 가져오기 및 손가락 머슬 설정 - HumanPose humanPose = new HumanPose(); - humanPoseHandler.GetHumanPose(ref humanPose); - - // 왼손 제어 - SetHandMuscles(true, leftThumbCurl, leftIndexCurl, leftMiddleCurl, leftRingCurl, - leftPinkyCurl, leftSpreadFingers, ref humanPose); - - // 오른손 제어 - SetHandMuscles(false, rightThumbCurl, rightIndexCurl, rightMiddleCurl, rightRingCurl, - rightPinkyCurl, rightSpreadFingers, ref humanPose); - - // 3. 머슬 포즈 적용 (손가락 포함 전체 본에 영향) - humanPoseHandler.SetHumanPose(ref humanPose); - - // 4. 손가락을 제외한 모든 본의 로컬 회전 복원 (본 길이 변형 방지) - foreach (var kvp in savedBoneLocalRotations) + private void SetHandMuscles(bool isLeft, float thumb, float index, float middle, float ring, + float pinky, float spread, ref HumanPose humanPose) { - Transform bone = animator.GetBoneTransform(kvp.Key); - if (bone != null) - { - bone.localRotation = kvp.Value; - } + // 해당 손이 비활성화 상태면 건너뛰기 + if (isLeft && !leftHandEnabled) return; + if (!isLeft && !rightHandEnabled) return; + + int baseOffset = isLeft ? 55 : 75; // 왼손은 55부터, 오른손은 75부터 시작 + int muscleCount = humanPose.muscles.Length; + + // 엄지손가락 + if (baseOffset < muscleCount) humanPose.muscles[baseOffset] = thumb; // Thumb 1 + if (baseOffset + 1 < muscleCount) humanPose.muscles[baseOffset + 1] = thumb; // Thumb Spread + if (baseOffset + 2 < muscleCount) humanPose.muscles[baseOffset + 2] = thumb; // Thumb 2 + if (baseOffset + 3 < muscleCount) humanPose.muscles[baseOffset + 3] = thumb; // Thumb 3 + + // 검지 + if (baseOffset + 4 < muscleCount) humanPose.muscles[baseOffset + 4] = index; // Index 1 + if (baseOffset + 5 < muscleCount) humanPose.muscles[baseOffset + 5] = spread; // Index Spread + if (baseOffset + 6 < muscleCount) humanPose.muscles[baseOffset + 6] = index; // Index 2 + if (baseOffset + 7 < muscleCount) humanPose.muscles[baseOffset + 7] = index; // Index 3 + + // 중지 + if (baseOffset + 8 < muscleCount) humanPose.muscles[baseOffset + 8] = middle; // Middle 1 + if (baseOffset + 9 < muscleCount) humanPose.muscles[baseOffset + 9] = spread; // Middle Spread + if (baseOffset + 10 < muscleCount) humanPose.muscles[baseOffset + 10] = middle; // Middle 2 + if (baseOffset + 11 < muscleCount) humanPose.muscles[baseOffset + 11] = middle; // Middle 3 + + // 약지 + if (baseOffset + 12 < muscleCount) humanPose.muscles[baseOffset + 12] = ring; // Ring 1 + if (baseOffset + 13 < muscleCount) humanPose.muscles[baseOffset + 13] = spread; // Ring Spread + if (baseOffset + 14 < muscleCount) humanPose.muscles[baseOffset + 14] = ring; // Ring 2 + if (baseOffset + 15 < muscleCount) humanPose.muscles[baseOffset + 15] = ring; // Ring 3 + + // 새끼손가락 + if (baseOffset + 16 < muscleCount) humanPose.muscles[baseOffset + 16] = pinky; // Little 1 + if (baseOffset + 17 < muscleCount) humanPose.muscles[baseOffset + 17] = spread; // Little Spread + if (baseOffset + 18 < muscleCount) humanPose.muscles[baseOffset + 18] = pinky; // Little 2 + if (baseOffset + 19 < muscleCount) humanPose.muscles[baseOffset + 19] = pinky; // Little 3 } } - - private void SetHandMuscles(bool isLeft, float thumb, float index, float middle, float ring, - float pinky, float spread, ref HumanPose humanPose) - { - // 해당 손이 비활성화 상태면 건너뛰기 - if (isLeft && !leftHandEnabled) return; - if (!isLeft && !rightHandEnabled) return; - - int baseOffset = isLeft ? 55 : 75; // 왼손은 55부터, 오른손은 75부터 시작 - int muscleCount = humanPose.muscles.Length; - - // 엄지손가락 - if (baseOffset < muscleCount) humanPose.muscles[baseOffset] = thumb; // Thumb 1 - if (baseOffset + 1 < muscleCount) humanPose.muscles[baseOffset + 1] = thumb; // Thumb Spread - if (baseOffset + 2 < muscleCount) humanPose.muscles[baseOffset + 2] = thumb; // Thumb 2 - if (baseOffset + 3 < muscleCount) humanPose.muscles[baseOffset + 3] = thumb; // Thumb 3 - - // 검지 - if (baseOffset + 4 < muscleCount) humanPose.muscles[baseOffset + 4] = index; // Index 1 - if (baseOffset + 5 < muscleCount) humanPose.muscles[baseOffset + 5] = spread; // Index Spread - if (baseOffset + 6 < muscleCount) humanPose.muscles[baseOffset + 6] = index; // Index 2 - if (baseOffset + 7 < muscleCount) humanPose.muscles[baseOffset + 7] = index; // Index 3 - - // 중지 - if (baseOffset + 8 < muscleCount) humanPose.muscles[baseOffset + 8] = middle; // Middle 1 - if (baseOffset + 9 < muscleCount) humanPose.muscles[baseOffset + 9] = spread; // Middle Spread - if (baseOffset + 10 < muscleCount) humanPose.muscles[baseOffset + 10] = middle; // Middle 2 - if (baseOffset + 11 < muscleCount) humanPose.muscles[baseOffset + 11] = middle; // Middle 3 - - // 약지 - if (baseOffset + 12 < muscleCount) humanPose.muscles[baseOffset + 12] = ring; // Ring 1 - if (baseOffset + 13 < muscleCount) humanPose.muscles[baseOffset + 13] = spread; // Ring Spread - if (baseOffset + 14 < muscleCount) humanPose.muscles[baseOffset + 14] = ring; // Ring 2 - if (baseOffset + 15 < muscleCount) humanPose.muscles[baseOffset + 15] = ring; // Ring 3 - - // 새끼손가락 - if (baseOffset + 16 < muscleCount) humanPose.muscles[baseOffset + 16] = pinky; // Little 1 - if (baseOffset + 17 < muscleCount) humanPose.muscles[baseOffset + 17] = spread; // Little Spread - if (baseOffset + 18 < muscleCount) humanPose.muscles[baseOffset + 18] = pinky; // Little 2 - if (baseOffset + 19 < muscleCount) humanPose.muscles[baseOffset + 19] = pinky; // Little 3 - } } diff --git a/Assets/Scripts/KindRetargeting/LimbWeightController.cs b/Assets/Scripts/KindRetargeting/LimbWeightController.cs index 977b12a2e..10970dfb2 100644 --- a/Assets/Scripts/KindRetargeting/LimbWeightController.cs +++ b/Assets/Scripts/KindRetargeting/LimbWeightController.cs @@ -4,8 +4,8 @@ using UnityEngine; namespace KindRetargeting { - [DefaultExecutionOrder(4)] - public class LimbWeightController : MonoBehaviour + [System.Serializable] + public class LimbWeightController { [Header("거리 기반 가중치 설정")] [SerializeField, Range(0.3f, 1f)] public float maxDistance = 0.5f; // 가중치가 0이 되는 최대 거리 @@ -36,9 +36,8 @@ namespace KindRetargeting public float chairSeatHeightOffset = 0.05f; private TwoBoneIKSolver ikSolver; - private CustomRetargetingScript crs; - private Dictionary> weightLayers = new Dictionary>(); + private Transform characterRoot; List leftArmEndWeights = new List(); List rightArmEndWeights = new List(); @@ -57,8 +56,6 @@ namespace KindRetargeting public List props = new List(); - public Transform characterRoot; - // 힙스 가중치 리스트 추가 List hipsWeights = new List(); private float MasterHipsWeight = 1f; @@ -67,8 +64,63 @@ namespace KindRetargeting private float currentChairSeatOffset = 0f; private float targetChairSeatOffset = 0f; - void Update() + private bool isInitialized; + + public void Initialize(TwoBoneIKSolver ikSolver, CustomRetargetingScript crs, Transform characterRoot) { + this.ikSolver = ikSolver; + this.crs = crs; + this.characterRoot = characterRoot; + + if (ikSolver == null || crs == null) return; + + InitWeightLayers(); + + //프랍 오브젝트 찾기 + props = Object.FindObjectsByType(FindObjectsSortMode.None).Select(controller => controller.transform).ToList(); + + // 다른 캐릭터의 손을 props에 추가 + GetHand(); + + //HandDistances()에서 사용을 위한 리스트 추가 + //손 거리에 따른 웨이트 업데이트 인덱스 0번 + leftArmEndWeights.Add(0); + rightArmEndWeights.Add(0); + + // 프랍과의 거리에 따른 웨이트 업데이트 인덱스 1번 + leftArmEndWeights.Add(0); + rightArmEndWeights.Add(0); + + // 앉아있을 때 다리와의 거리에 따른 가중치 적용 인덱스 0번 + leftLegEndWeights.Add(0); + rightLegEndWeights.Add(0); + + // 다리 골 가중치 초기화 + leftLegBendWeights.Add(1f); // 기본 가중치 + rightLegBendWeights.Add(1f); // 기본 가중치 + + if (this.characterRoot == null) + { + this.characterRoot = crs.transform; + } + + // 힙스 가중치 초기화 인덱스 0번 + hipsWeights.Add(1f); // 의자 거리 기반 가중치 + + // 지면 높이 기반 가중치 초기화 인덱스 1번 + hipsWeights.Add(1f); // 지면 높이 기반 가중치 + + // 발 높이 기반 가중치 초기화 인덱스 1번 + leftLegEndWeights.Add(1f); + rightLegEndWeights.Add(1f); + + isInitialized = true; + } + + public void OnUpdate() + { + if (!isInitialized) return; + //손의 거리를 기반으로한 가중치 적용 HandDistances(); @@ -94,68 +146,15 @@ namespace KindRetargeting ApplyWeightsToFBIK(); } - void Start() - { - crs = GetComponent(); - if (crs != null) ikSolver = crs.ikSolver; - - InitWeightLayers(); - - //프랍 오브젝트 찾기 - props = FindObjectsByType(FindObjectsSortMode.None).Select(controller => controller.transform).ToList(); - - // 프랍 오브젝트 찾기 - GetHand(); - - //HandDistances();에서 사용을 위한 리스트 추가 - //손 거리에 따른 웨이트 업데이트 인덱스 0번 - leftArmEndWeights.Add(0); - rightArmEndWeights.Add(0); - - // 프랍과의 거리에 따른 웨이트 업데이트 인덱스 1번 - leftArmEndWeights.Add(0); - rightArmEndWeights.Add(0); - - // 앉아있을 때 다리와의 거리에 따른 가중치 적용 인덱스 0번 - leftLegEndWeights.Add(0); - rightLegEndWeights.Add(0); - - // 다리 골 가중치 초기화 - leftLegBendWeights.Add(1f); // 기본 가중치 - rightLegBendWeights.Add(1f); // 기본 가중치 - - // CharacterController가 있는 루트 오브젝트 찾기 - - - if (characterRoot == null) - { - characterRoot = transform; - } - - // 힙스 가중치 초기화 인덱스 0번 - hipsWeights.Add(1f); // 의자 거리 기반 가중치 - - // 지면 높이 기반 가중치 초기화 인덱스 1번 - hipsWeights.Add(1f); // 지면 높이 기반 가중치 - - // 발 높이 기반 가중치 초기화 인덱스 1번 - leftLegEndWeights.Add(1f); - rightLegEndWeights.Add(1f); - } - private void GetHand() { - // 모든 LimbWeightController 찾기 - LimbWeightController[] allControllers = FindObjectsOfType(); + // 모든 CustomRetargetingScript 찾기 (다른 캐릭터의 손을 props에 추가) + CustomRetargetingScript[] allCrs = Object.FindObjectsByType(FindObjectsSortMode.None); - foreach (LimbWeightController controller in allControllers) + foreach (CustomRetargetingScript otherCrs in allCrs) { // 자기 자신은 제외 - if (controller == this) continue; - - // CustomRetargetingScript 가져오기 - CustomRetargetingScript otherCrs = controller.GetComponent(); - if (otherCrs == null) continue; + if (otherCrs == crs) continue; // 왼손과 오른손 Transform 가져오기 Transform leftHand = otherCrs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.LeftHand); @@ -195,7 +194,7 @@ namespace KindRetargeting { Transform leftHandTransform = crs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.LeftHand); Transform rightHandTransform = crs?.sourceAnimator?.GetBoneTransform(HumanBodyBones.RightHand); - if (leftHandTransform != null && rightHandTransform != null && + if (leftHandTransform != null && rightHandTransform != null && props != null && props.Count > 0) { float Distance = Vector3.Distance(leftHandTransform.position, rightHandTransform.position); @@ -254,7 +253,7 @@ namespace KindRetargeting const float MIN_LEG_DISTANCE_RATIO = 0.3f; // 다리 길이의 30% // 거리가 멀수록 가중치 감소 - float weight = 1f - Mathf.Clamp01((horizontalDistance - MIN_LEG_DISTANCE_RATIO) / + float weight = 1f - Mathf.Clamp01((horizontalDistance - MIN_LEG_DISTANCE_RATIO) / (MAX_LEG_DISTANCE_RATIO - MIN_LEG_DISTANCE_RATIO)); weightList[weightIndex] = weight; @@ -278,7 +277,7 @@ namespace KindRetargeting minLeftDistance = Mathf.Min(minLeftDistance, distance); } - // 오른손과 프랍 사이의 최소 거리 계산 + // 오른손과 프랍 사이의 최소 거리 계산 float minRightDistance = float.MaxValue; foreach (Transform prop in props) { diff --git a/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs b/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs index 3472ea5b2..816cb3cc9 100644 --- a/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs +++ b/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs @@ -215,9 +215,6 @@ namespace KindRetargeting.Remote return; } - var limbWeight = script.GetComponent(); - var handPose = script.GetComponent(); - var data = new Dictionary { // 힙 위치 보정 (로컬) @@ -251,37 +248,25 @@ namespace KindRetargeting.Remote { "fingerCopyMode", (int)GetPrivateField(script, "fingerCopyMode") }, // 캘리브레이션 상태 - { "hasCalibrationData", script.HasCachedSettings() } + { "hasCalibrationData", script.HasCachedSettings() }, + + // LimbWeightController 데이터 + { "limbMinDistance", script.limbWeight.minDistance }, + { "limbMaxDistance", script.limbWeight.maxDistance }, + { "weightSmoothSpeed", script.limbWeight.weightSmoothSpeed }, + { "hipsMinDistance", script.limbWeight.hipsMinDistance }, + { "hipsMaxDistance", script.limbWeight.hipsMaxDistance }, + { "groundHipsMinHeight", script.limbWeight.groundHipsMinHeight }, + { "groundHipsMaxHeight", script.limbWeight.groundHipsMaxHeight }, + { "footHeightMinThreshold", script.limbWeight.footHeightMinThreshold }, + { "footHeightMaxThreshold", script.limbWeight.footHeightMaxThreshold }, + { "chairSeatHeightOffset", script.limbWeight.chairSeatHeightOffset }, }; - // LimbWeightController 데이터 - if (limbWeight != null) - { - data["limbMinDistance"] = limbWeight.minDistance; - data["limbMaxDistance"] = limbWeight.maxDistance; - data["weightSmoothSpeed"] = limbWeight.weightSmoothSpeed; - data["hipsMinDistance"] = limbWeight.hipsMinDistance; - data["hipsMaxDistance"] = limbWeight.hipsMaxDistance; - data["groundHipsMinHeight"] = limbWeight.groundHipsMinHeight; - data["groundHipsMaxHeight"] = limbWeight.groundHipsMaxHeight; - data["footHeightMinThreshold"] = limbWeight.footHeightMinThreshold; - data["footHeightMaxThreshold"] = limbWeight.footHeightMaxThreshold; - data["chairSeatHeightOffset"] = limbWeight.chairSeatHeightOffset; - } - // FingerShapedController 데이터 - if (handPose != null) - { - data["handPoseEnabled"] = handPose.enabled; - data["leftHandEnabled"] = handPose.leftHandEnabled; - data["rightHandEnabled"] = handPose.rightHandEnabled; - } - else - { - data["handPoseEnabled"] = false; - data["leftHandEnabled"] = false; - data["rightHandEnabled"] = false; - } + data["handPoseEnabled"] = script.fingerShaped.enabled; + data["leftHandEnabled"] = script.fingerShaped.leftHandEnabled; + data["rightHandEnabled"] = script.fingerShaped.rightHandEnabled; var response = new { @@ -298,9 +283,6 @@ namespace KindRetargeting.Remote var script = FindCharacter(characterId); if (script == null) return; - var limbWeight = script.GetComponent(); - var handPose = script.GetComponent(); - switch (property) { // 힙 위치 보정 @@ -363,56 +345,49 @@ namespace KindRetargeting.Remote // LimbWeightController 속성 case "limbMinDistance": - if (limbWeight != null) limbWeight.minDistance = value; + script.limbWeight.minDistance = value; break; case "limbMaxDistance": - if (limbWeight != null) limbWeight.maxDistance = value; + script.limbWeight.maxDistance = value; break; case "weightSmoothSpeed": - if (limbWeight != null) limbWeight.weightSmoothSpeed = value; + script.limbWeight.weightSmoothSpeed = value; break; case "hipsMinDistance": - if (limbWeight != null) limbWeight.hipsMinDistance = value; + script.limbWeight.hipsMinDistance = value; break; case "hipsMaxDistance": - if (limbWeight != null) limbWeight.hipsMaxDistance = value; + script.limbWeight.hipsMaxDistance = value; break; case "groundHipsMinHeight": - if (limbWeight != null) limbWeight.groundHipsMinHeight = value; + script.limbWeight.groundHipsMinHeight = value; break; case "groundHipsMaxHeight": - if (limbWeight != null) limbWeight.groundHipsMaxHeight = value; + script.limbWeight.groundHipsMaxHeight = value; break; case "footHeightMinThreshold": - if (limbWeight != null) limbWeight.footHeightMinThreshold = value; + script.limbWeight.footHeightMinThreshold = value; break; case "footHeightMaxThreshold": - if (limbWeight != null) limbWeight.footHeightMaxThreshold = value; + script.limbWeight.footHeightMaxThreshold = value; break; case "chairSeatHeightOffset": - if (limbWeight != null) limbWeight.chairSeatHeightOffset = value; + script.limbWeight.chairSeatHeightOffset = value; break; // FingerShapedController 속성 case "handPoseEnabled": - if (handPose != null) - handPose.enabled = value > 0.5f; + script.fingerShaped.enabled = value > 0.5f; break; case "leftHandEnabled": - if (handPose != null) - { - handPose.leftHandEnabled = value > 0.5f; - if (handPose.leftHandEnabled) - handPose.enabled = true; - } + script.fingerShaped.leftHandEnabled = value > 0.5f; + if (script.fingerShaped.leftHandEnabled) + script.fingerShaped.enabled = true; break; case "rightHandEnabled": - if (handPose != null) - { - handPose.rightHandEnabled = value > 0.5f; - if (handPose.rightHandEnabled) - handPose.enabled = true; - } + script.fingerShaped.rightHandEnabled = value > 0.5f; + if (script.fingerShaped.rightHandEnabled) + script.fingerShaped.enabled = true; break; default: @@ -426,8 +401,7 @@ namespace KindRetargeting.Remote var script = FindCharacter(characterId); if (script == null) return; - var handPose = script.GetComponent(); - if (handPose == null) return; + var handPose = script.fingerShaped; // 스크립트 자동 활성화 handPose.enabled = true;