diff --git a/Assets/External/EasyMotionRecorder/Scripts/README_SavePathManager.md b/Assets/External/EasyMotionRecorder/Scripts/README_SavePathManager.md deleted file mode 100644 index c92f951d..00000000 --- a/Assets/External/EasyMotionRecorder/Scripts/README_SavePathManager.md +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3f1ea9e9ccd7d22e9936bbfd0520c5a9155b6e38fa1376cc4c22711f20a8cd74 -size 1997 diff --git a/Assets/Motion/T-Pose_20250726_024111.json b/Assets/Motion/T-Pose_20250726_024111.json new file mode 100644 index 00000000..c5fd37de --- /dev/null +++ b/Assets/Motion/T-Pose_20250726_024111.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91a93fa75768dea987ffabb5f8a3d200cdf390d7f5db7e1bca4fe51b65b7ed27 +size 26266 diff --git a/Assets/External/EasyMotionRecorder/Scripts/README_SavePathManager.md.meta b/Assets/Motion/T-Pose_20250726_024111.json.meta similarity index 75% rename from Assets/External/EasyMotionRecorder/Scripts/README_SavePathManager.md.meta rename to Assets/Motion/T-Pose_20250726_024111.json.meta index 65dfcca7..45d0f0fb 100644 --- a/Assets/External/EasyMotionRecorder/Scripts/README_SavePathManager.md.meta +++ b/Assets/Motion/T-Pose_20250726_024111.json.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d52d79965c0c87f4dbc7d4ea99597abe +guid: 963c54edbba1b4b45bed1b3a96506b08 TextScriptImporter: externalObjects: {} userData: diff --git a/Assets/Scenes/PoseRotationBaker.unity b/Assets/Scenes/PoseRotationBaker.unity new file mode 100644 index 00000000..74ad63f6 --- /dev/null +++ b/Assets/Scenes/PoseRotationBaker.unity @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2655d396610361690c26276d36d503a8e4eabc8b7439d05e4095e484623643a7 +size 1365193452 diff --git a/Assets/Scenes/PoseRotationBaker.unity.meta b/Assets/Scenes/PoseRotationBaker.unity.meta new file mode 100644 index 00000000..16e45ede --- /dev/null +++ b/Assets/Scenes/PoseRotationBaker.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4aa63adf1196d0743ac835945bd69261 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/PoseRecorderWindow.cs b/Assets/Scripts/Editor/PoseRecorderWindow.cs new file mode 100644 index 00000000..e7656485 --- /dev/null +++ b/Assets/Scripts/Editor/PoseRecorderWindow.cs @@ -0,0 +1,373 @@ +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; +using System.IO; +using System; + +namespace Streamingle.Editor +{ + public class PoseRecorderWindow : EditorWindow + { + private GameObject selectedAvatar; + private Animator avatarAnimator; + private string poseName = "NewPose"; + private string savePath = "Assets/Recordings/"; + + // 휴먼본 본들 (Animator HumanBodyBones 사용) + private readonly HumanBodyBones[] humanBodyBones = { + 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 + }; + + // 손가락 본들 + private readonly HumanBodyBones[] fingerBones = { + // 왼손 손가락 + HumanBodyBones.LeftThumbProximal, + HumanBodyBones.LeftThumbIntermediate, + HumanBodyBones.LeftThumbDistal, + HumanBodyBones.LeftIndexProximal, + HumanBodyBones.LeftIndexIntermediate, + HumanBodyBones.LeftIndexDistal, + HumanBodyBones.LeftMiddleProximal, + HumanBodyBones.LeftMiddleIntermediate, + HumanBodyBones.LeftMiddleDistal, + HumanBodyBones.LeftRingProximal, + HumanBodyBones.LeftRingIntermediate, + HumanBodyBones.LeftRingDistal, + HumanBodyBones.LeftLittleProximal, + HumanBodyBones.LeftLittleIntermediate, + HumanBodyBones.LeftLittleDistal, + // 오른손 손가락 + HumanBodyBones.RightThumbProximal, + HumanBodyBones.RightThumbIntermediate, + HumanBodyBones.RightThumbDistal, + HumanBodyBones.RightIndexProximal, + HumanBodyBones.RightIndexIntermediate, + HumanBodyBones.RightIndexDistal, + HumanBodyBones.RightMiddleProximal, + HumanBodyBones.RightMiddleIntermediate, + HumanBodyBones.RightMiddleDistal, + HumanBodyBones.RightRingProximal, + HumanBodyBones.RightRingIntermediate, + HumanBodyBones.RightRingDistal, + HumanBodyBones.RightLittleProximal, + HumanBodyBones.RightLittleIntermediate, + HumanBodyBones.RightLittleDistal + }; + + [MenuItem("Tools/Pose Recorder")] + public static void ShowWindow() + { + GetWindow("포즈 기록기"); + } + + private void OnEnable() + { + // 선택된 GameObject가 아바타인지 확인 + if (Selection.activeGameObject != null) + { + selectedAvatar = Selection.activeGameObject; + avatarAnimator = selectedAvatar.GetComponent(); + if (avatarAnimator == null) + { + avatarAnimator = selectedAvatar.GetComponentInChildren(); + } + } + } + + private Vector2 scrollPosition; + + private void OnGUI() + { + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); + + GUILayout.Label("아바타 포즈 기록기", EditorStyles.boldLabel); + EditorGUILayout.Space(); + + // 아바타 선택 + EditorGUILayout.BeginHorizontal(); + GUILayout.Label("아바타:", GUILayout.Width(60)); + selectedAvatar = (GameObject)EditorGUILayout.ObjectField(selectedAvatar, typeof(GameObject), true); + EditorGUILayout.EndHorizontal(); + + if (selectedAvatar != null) + { + avatarAnimator = selectedAvatar.GetComponent(); + if (avatarAnimator == null) + { + avatarAnimator = selectedAvatar.GetComponentInChildren(); + } + + if (avatarAnimator == null) + { + EditorGUILayout.HelpBox("선택된 오브젝트에 Animator가 없습니다!", MessageType.Error); + EditorGUILayout.EndScrollView(); + return; + } + + EditorGUILayout.HelpBox($"아바타: {selectedAvatar.name}", MessageType.Info); + } + else + { + EditorGUILayout.HelpBox("아바타를 선택해주세요.", MessageType.Warning); + } + + EditorGUILayout.Space(); + + // 포즈 이름 입력 + EditorGUILayout.BeginHorizontal(); + GUILayout.Label("포즈 이름:", GUILayout.Width(60)); + poseName = EditorGUILayout.TextField(poseName); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + + // 저장 경로 설정 + EditorGUILayout.BeginHorizontal(); + GUILayout.Label("저장 경로:", GUILayout.Width(60)); + savePath = EditorGUILayout.TextField(savePath); + if (GUILayout.Button("폴더 선택", GUILayout.Width(80))) + { + string newPath = EditorUtility.OpenFolderPanel("저장 폴더 선택", savePath, ""); + if (!string.IsNullOrEmpty(newPath)) + { + // Assets 폴더 내부로 경로 변환 + if (newPath.StartsWith(Application.dataPath)) + { + savePath = "Assets" + newPath.Substring(Application.dataPath.Length) + "/"; + } + else + { + savePath = "Assets/Recordings/"; + } + } + } + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + + // 버튼들 + EditorGUI.BeginDisabledGroup(selectedAvatar == null || avatarAnimator == null); + + if (GUILayout.Button("현재 포즈 기록 (전체)", GUILayout.Height(30))) + { + RecordCurrentPose(true); + } + + if (GUILayout.Button("현재 포즈 기록 (본체만)", GUILayout.Height(30))) + { + RecordCurrentPose(false); + } + + EditorGUILayout.Space(); + + if (GUILayout.Button("T-Pose 기록", GUILayout.Height(30))) + { + RecordTPose(); + } + + if (GUILayout.Button("A-Pose 기록", GUILayout.Height(30))) + { + RecordAPose(); + } + + EditorGUI.EndDisabledGroup(); + + EditorGUILayout.Space(); + + // 정보 표시 + if (selectedAvatar != null && avatarAnimator != null) + { + EditorGUILayout.LabelField("기록 가능한 본들:", EditorStyles.boldLabel); + + // 본체 본들 + EditorGUILayout.LabelField("본체 본들:", EditorStyles.boldLabel); + EditorGUILayout.BeginVertical("box"); + + int foundBodyBones = 0; + foreach (HumanBodyBones humanBone in humanBodyBones) + { + Transform bone = avatarAnimator.GetBoneTransform(humanBone); + if (bone != null) + { + EditorGUILayout.LabelField($"✓ {humanBone}"); + foundBodyBones++; + } + } + EditorGUILayout.EndVertical(); + EditorGUILayout.LabelField($"본체 본: {foundBodyBones}개"); + + EditorGUILayout.Space(); + + // 손가락 본들 + EditorGUILayout.LabelField("손가락 본들:", EditorStyles.boldLabel); + EditorGUILayout.BeginVertical("box"); + + int foundFingerBones = 0; + foreach (HumanBodyBones fingerBone in fingerBones) + { + Transform bone = avatarAnimator.GetBoneTransform(fingerBone); + if (bone != null) + { + EditorGUILayout.LabelField($"✓ {fingerBone}"); + foundFingerBones++; + } + } + EditorGUILayout.EndVertical(); + EditorGUILayout.LabelField($"손가락 본: {foundFingerBones}개"); + + EditorGUILayout.Space(); + EditorGUILayout.LabelField($"총 {foundBodyBones + foundFingerBones}개 본 발견", EditorStyles.boldLabel); + } + + EditorGUILayout.EndScrollView(); + } + + private void RecordCurrentPose(bool includeFingers = true) + { + if (selectedAvatar == null || avatarAnimator == null) + { + EditorUtility.DisplayDialog("오류", "아바타를 선택해주세요.", "확인"); + return; + } + + PoseData poseData = new PoseData(); + poseData.avatarName = selectedAvatar.name; + poseData.poseName = poseName; + poseData.recordedTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + + int recordedBones = 0; + + // 본체 본들 기록 + foreach (HumanBodyBones humanBone in humanBodyBones) + { + Transform bone = avatarAnimator.GetBoneTransform(humanBone); + if (bone != null) + { + BonePoseData boneData = new BonePoseData(); + boneData.boneName = humanBone.ToString(); + boneData.localPosition = bone.localPosition; + boneData.localRotation = bone.localEulerAngles; + boneData.localScale = bone.localScale; + + poseData.bonePoses.Add(boneData); + recordedBones++; + } + } + + // 손가락 본들 기록 (옵션) + if (includeFingers) + { + int fingerBonesCount = 0; + foreach (HumanBodyBones fingerBone in fingerBones) + { + Transform bone = avatarAnimator.GetBoneTransform(fingerBone); + if (bone != null) + { + BonePoseData boneData = new BonePoseData(); + boneData.boneName = fingerBone.ToString(); + boneData.localPosition = bone.localPosition; + boneData.localRotation = bone.localEulerAngles; + boneData.localScale = bone.localScale; + + poseData.bonePoses.Add(boneData); + recordedBones++; + fingerBonesCount++; + } + } + + if (fingerBonesCount > 0) + { + UnityEngine.Debug.Log($"손가락 본 {fingerBonesCount}개 기록됨"); + } + } + + if (recordedBones > 0) + { + SavePoseData(poseData); + string message = includeFingers ? + $"포즈가 기록되었습니다!\n본체 본: {recordedBones - fingerBones.Length}개\n손가락 본: {fingerBones.Length}개\n총 {recordedBones}개 본" : + $"포즈가 기록되었습니다!\n본체 본: {recordedBones}개"; + EditorUtility.DisplayDialog("성공", message, "확인"); + } + else + { + EditorUtility.DisplayDialog("오류", "기록할 본을 찾을 수 없습니다.", "확인"); + } + } + + private void RecordTPose() + { + poseName = "T-Pose"; + RecordCurrentPose(true); // 손가락 포함 + } + + private void RecordAPose() + { + poseName = "A-Pose"; + RecordCurrentPose(true); // 손가락 포함 + } + + + + private void SavePoseData(PoseData poseData) + { + // 저장 폴더 생성 + if (!Directory.Exists(savePath)) + { + Directory.CreateDirectory(savePath); + } + + // 파일명 생성 + string fileName = $"{poseData.poseName}_{DateTime.Now:yyyyMMdd_HHmmss}.json"; + string filePath = Path.Combine(savePath, fileName); + + // JSON으로 저장 + string json = JsonUtility.ToJson(poseData, true); + File.WriteAllText(filePath, json); + + // Unity 에디터에서 파일 새로고침 + AssetDatabase.Refresh(); + + UnityEngine.Debug.Log($"포즈가 저장되었습니다: {filePath}"); + } + } + + [System.Serializable] + public class BonePoseData + { + public string boneName; + public Vector3 localPosition; + public Vector3 localRotation; + public Vector3 localScale; + } + + [System.Serializable] + public class PoseData + { + public string avatarName; + public string poseName; + public string recordedTime; + public List bonePoses = new List(); + } +} \ No newline at end of file diff --git a/Assets/Scripts/Editor/PoseRecorderWindow.cs.meta b/Assets/Scripts/Editor/PoseRecorderWindow.cs.meta new file mode 100644 index 00000000..ccf28449 --- /dev/null +++ b/Assets/Scripts/Editor/PoseRecorderWindow.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 03b437e4b8ab0fa4191196f14605c61c \ No newline at end of file diff --git a/Assets/Scripts/Editor/PoseRotationBaker.cs b/Assets/Scripts/Editor/PoseRotationBaker.cs new file mode 100644 index 00000000..959bb228 --- /dev/null +++ b/Assets/Scripts/Editor/PoseRotationBaker.cs @@ -0,0 +1,499 @@ +using UnityEngine; +using UnityEditor; +using System.Collections.Generic; +using System.IO; +using System; + +namespace Streamingle.Editor +{ + public class PoseRotationBaker : EditorWindow + { + private GameObject selectedAvatar; + private Animator avatarAnimator; + private string jsonFilePath = ""; + private PoseData loadedPoseData; + private Vector2 scrollPosition; + + // 베이크 옵션 + private bool applyToAllBones = true; + private bool applyToSpecificBones = false; + private List specificBones = new List(); + + // 미리보기 옵션 + private bool showBoneInfo = true; + + [MenuItem("Tools/Pose Rotation Baker")] + public static void ShowWindow() + { + GetWindow("포즈 로테이션 베이커"); + } + + private void OnEnable() + { + if (Selection.activeGameObject != null) + { + selectedAvatar = Selection.activeGameObject; + avatarAnimator = selectedAvatar.GetComponent(); + if (avatarAnimator == null) + { + avatarAnimator = selectedAvatar.GetComponentInChildren(); + } + } + } + + private void OnGUI() + { + scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition); + + GUILayout.Label("포즈 로테이션 베이커", EditorStyles.boldLabel); + EditorGUILayout.Space(); + + // 아바타 선택 + EditorGUILayout.BeginHorizontal(); + GUILayout.Label("아바타:", GUILayout.Width(60)); + selectedAvatar = (GameObject)EditorGUILayout.ObjectField(selectedAvatar, typeof(GameObject), true); + EditorGUILayout.EndHorizontal(); + + if (selectedAvatar != null) + { + avatarAnimator = selectedAvatar.GetComponent(); + if (avatarAnimator == null) + { + avatarAnimator = selectedAvatar.GetComponentInChildren(); + } + + if (avatarAnimator == null) + { + EditorGUILayout.HelpBox("선택된 오브젝트에 Animator가 없습니다!", MessageType.Error); + EditorGUILayout.EndScrollView(); + return; + } + + EditorGUILayout.HelpBox($"아바타: {selectedAvatar.name}", MessageType.Info); + } + else + { + EditorGUILayout.HelpBox("아바타를 선택해주세요.", MessageType.Warning); + } + + EditorGUILayout.Space(); + + // JSON 파일 로드 + EditorGUILayout.LabelField("포즈 데이터 로드:", EditorStyles.boldLabel); + EditorGUILayout.BeginHorizontal(); + GUILayout.Label("JSON 파일:", GUILayout.Width(60)); + jsonFilePath = EditorGUILayout.TextField(jsonFilePath); + if (GUILayout.Button("파일 선택", GUILayout.Width(80))) + { + string path = EditorUtility.OpenFilePanel("포즈 JSON 파일 선택", "", "json"); + if (!string.IsNullOrEmpty(path)) + { + jsonFilePath = path; + LoadPoseData(); + } + } + EditorGUILayout.EndHorizontal(); + + if (loadedPoseData != null) + { + EditorGUILayout.HelpBox($"로드된 포즈: {loadedPoseData.poseName}\n아바타: {loadedPoseData.avatarName}\n본 개수: {loadedPoseData.bonePoses.Count}개", MessageType.Info); + } + + EditorGUILayout.Space(); + + // 베이크 옵션 + EditorGUILayout.LabelField("베이크 옵션:", EditorStyles.boldLabel); + EditorGUILayout.BeginVertical("box"); + + applyToAllBones = EditorGUILayout.Toggle("모든 본에 적용", applyToAllBones); + applyToSpecificBones = EditorGUILayout.Toggle("특정 본만 적용", applyToSpecificBones); + + if (applyToSpecificBones) + { + EditorGUILayout.Space(); + EditorGUILayout.LabelField("적용할 본들:"); + EditorGUILayout.BeginVertical("box"); + + if (loadedPoseData != null) + { + foreach (var bonePose in loadedPoseData.bonePoses) + { + bool isSelected = specificBones.Contains(bonePose.boneName); + bool newSelection = EditorGUILayout.Toggle(bonePose.boneName, isSelected); + + if (newSelection != isSelected) + { + if (newSelection) + { + specificBones.Add(bonePose.boneName); + } + else + { + specificBones.Remove(bonePose.boneName); + } + } + } + } + EditorGUILayout.EndVertical(); + } + + EditorGUILayout.EndVertical(); + + EditorGUILayout.Space(); + + // 미리보기 옵션 + EditorGUILayout.LabelField("미리보기 옵션:", EditorStyles.boldLabel); + EditorGUILayout.BeginVertical("box"); + + showBoneInfo = EditorGUILayout.Toggle("본 정보 표시", showBoneInfo); + + EditorGUILayout.EndVertical(); + + EditorGUILayout.Space(); + + // 버튼들 + EditorGUI.BeginDisabledGroup(selectedAvatar == null || avatarAnimator == null || loadedPoseData == null); + + if (GUILayout.Button("본 회전 베이크 (메쉬 고정)", GUILayout.Height(30))) + { + BakeWithRiggingData(); + } + + EditorGUI.EndDisabledGroup(); + + EditorGUILayout.Space(); + + // 정보 표시 + if (loadedPoseData != null && showBoneInfo) + { + EditorGUILayout.LabelField("포즈 데이터 정보:", EditorStyles.boldLabel); + EditorGUILayout.BeginVertical("box"); + + foreach (var bonePose in loadedPoseData.bonePoses) + { + Transform bone = FindBoneByName(selectedAvatar, bonePose.boneName); + if (bone != null) + { + EditorGUILayout.LabelField($"✓ {bonePose.boneName}"); + EditorGUILayout.LabelField($" 회전: {bonePose.localRotation}"); + } + else + { + EditorGUILayout.LabelField($"✗ {bonePose.boneName} (찾을 수 없음)"); + } + } + + EditorGUILayout.EndVertical(); + } + + EditorGUILayout.EndScrollView(); + } + + private void LoadPoseData() + { + if (string.IsNullOrEmpty(jsonFilePath) || !File.Exists(jsonFilePath)) + { + EditorUtility.DisplayDialog("오류", "JSON 파일을 찾을 수 없습니다.", "확인"); + return; + } + + try + { + string json = File.ReadAllText(jsonFilePath); + loadedPoseData = JsonUtility.FromJson(json); + + if (loadedPoseData != null && loadedPoseData.bonePoses.Count > 0) + { + UnityEngine.Debug.Log($"포즈 데이터 로드 완료: {loadedPoseData.poseName}, {loadedPoseData.bonePoses.Count}개 본"); + } + else + { + EditorUtility.DisplayDialog("오류", "유효하지 않은 포즈 데이터입니다.", "확인"); + loadedPoseData = null; + } + } + catch (Exception e) + { + EditorUtility.DisplayDialog("오류", $"파일 로드 중 오류 발생: {e.Message}", "확인"); + loadedPoseData = null; + } + } + + private Transform FindBoneByName(GameObject targetAvatar, string boneName) + { + if (targetAvatar == null) return null; + + // HumanBodyBones enum으로 찾기 + if (System.Enum.TryParse(boneName, out HumanBodyBones humanBone)) + { + Animator targetAnimator = targetAvatar.GetComponent(); + if (targetAnimator == null) + { + targetAnimator = targetAvatar.GetComponentInChildren(); + } + return targetAnimator?.GetBoneTransform(humanBone); + } + + // 이름으로 직접 찾기 + Transform bone = targetAvatar.transform.Find(boneName); + if (bone == null) + { + bone = FindChildRecursively(targetAvatar.transform, boneName); + } + + return bone; + } + + private Transform FindChildRecursively(Transform parent, string childName) + { + foreach (Transform child in parent) + { + if (child.name == childName) + return child; + + Transform result = FindChildRecursively(child, childName); + if (result != null) + return result; + } + return null; + } + + // 본 회전 베이크 (메쉬 고정) + private void BakeWithRiggingData() + { + if (loadedPoseData == null || selectedAvatar == null) + { + EditorUtility.DisplayDialog("오류", "포즈 데이터와 아바타를 선택해주세요.", "확인"); + return; + } + + if (!EditorUtility.DisplayDialog("본 회전 베이크", + "본의 로테이션을 변경하되 메쉬는 원래 모양을 유지합니다.\n\n" + + "본은 회전하지만 스킨메쉬렌더러는 변형되지 않습니다.", "베이크 실행", "취소")) + return; + + try + { + // 1단계: 아바타 복사본 생성 + GameObject originalAvatar = selectedAvatar; + GameObject copiedAvatar = Instantiate(originalAvatar); + copiedAvatar.name = originalAvatar.name + "_Baked_" + DateTime.Now.ToString("yyyyMMdd_HHmmss"); + + // 복사본을 씬에 추가 + copiedAvatar.transform.SetParent(originalAvatar.transform.parent); + copiedAvatar.transform.position = originalAvatar.transform.position; + copiedAvatar.transform.rotation = originalAvatar.transform.rotation; + copiedAvatar.transform.localScale = originalAvatar.transform.localScale; + + // 2단계: 모든 스킨메쉬렌더러 찾기 + SkinnedMeshRenderer[] allSkinnedMeshes = copiedAvatar.GetComponentsInChildren(); + if (allSkinnedMeshes.Length == 0) + { + EditorUtility.DisplayDialog("오류", "스킨메쉬렌더러를 찾을 수 없습니다.", "확인"); + DestroyImmediate(copiedAvatar); + return; + } + + UnityEngine.Debug.Log($"복사본 아바타: {copiedAvatar.name}, 스킨메쉬 개수: {allSkinnedMeshes.Length}개"); + + // 각 스킨메쉬렌더러별 메쉬 복사본 생성 + Dictionary copiedMeshes = new Dictionary(); + Dictionary storedBindPoses = new Dictionary(); + Dictionary storedBones = new Dictionary(); + + foreach (SkinnedMeshRenderer skinnedMesh in allSkinnedMeshes) + { + UnityEngine.Debug.Log($"스킨메쉬 처리 중: {skinnedMesh.name}"); + + // 메쉬 복사본 생성 + Mesh originalMesh = skinnedMesh.sharedMesh; + Mesh copiedMesh = Instantiate(originalMesh); + copiedMesh.name = originalMesh.name + "_Baked_" + DateTime.Now.ToString("yyyyMMdd_HHmmss"); + + // 복사된 메쉬를 스킨메쉬렌더러에 할당 + skinnedMesh.sharedMesh = copiedMesh; + copiedMeshes[skinnedMesh] = copiedMesh; + + // 원래 바인드포즈와 본 정보 저장 + Matrix4x4[] bindPoses = copiedMesh.bindposes; + Transform[] bones = skinnedMesh.bones; + + storedBindPoses[skinnedMesh] = bindPoses; + storedBones[skinnedMesh] = bones; + + UnityEngine.Debug.Log($"메쉬 복사 완료: {copiedMesh.name}, 바인드포즈: {bindPoses.Length}개, 본: {bones.Length}개"); + } + + // 4단계: 본의 원래 상태 기록 + Dictionary originalParents = new Dictionary(); + Dictionary originalBoneRotations = new Dictionary(); + List allBones = GetAllBones(copiedAvatar); + + UnityEngine.Debug.Log($"본 상태 기록 시작: {allBones.Count}개 본"); + foreach (Transform bone in allBones) + { + originalParents[bone] = bone.parent; + originalBoneRotations[bone] = bone.localEulerAngles; + bone.SetParent(copiedAvatar.transform); // 모든 본을 아바타의 직접 자식으로 + } + + // 5단계: JSON 데이터로 본 로테이션 적용 + int appliedBones = 0; + foreach (var bonePose in loadedPoseData.bonePoses) + { + if (applyToSpecificBones && !specificBones.Contains(bonePose.boneName)) + continue; + + Transform bone = FindBoneByName(copiedAvatar, bonePose.boneName); + if (bone != null) + { + UnityEngine.Debug.Log($"본 로테이션 적용: {bonePose.boneName} - {bone.localEulerAngles} → {bonePose.localRotation}"); + bone.localEulerAngles = bonePose.localRotation; + appliedBones++; + } + } + + // 5.5단계: 본 변형을 메쉬에 강제 반영 (바인드포즈 보존) + UnityEngine.Debug.Log("본 변형을 메쉬에 강제 반영 시작"); + foreach (SkinnedMeshRenderer skinnedMesh in allSkinnedMeshes) + { + // 1단계: 원본 메쉬의 바인드포즈와 본 가중치 백업 + Mesh originalMesh = copiedMeshes[skinnedMesh]; + Matrix4x4[] originalBindPoses = originalMesh.bindposes; + BoneWeight[] originalBoneWeights = originalMesh.boneWeights; + Transform[] originalBones = storedBones[skinnedMesh]; + Matrix4x4[] originalStoredBindPoses = storedBindPoses[skinnedMesh]; + + UnityEngine.Debug.Log($"바인드포즈 백업 완료: {skinnedMesh.name}, 바인드포즈: {originalBindPoses.Length}개, 본가중치: {originalBoneWeights.Length}개"); + + // 2단계: BakeMesh로 본 변형 반영 + Mesh tempMesh = new Mesh(); + skinnedMesh.BakeMesh(tempMesh); + + UnityEngine.Debug.Log($"BakeMesh 완료: {skinnedMesh.name}, 버텍스: {tempMesh.vertices.Length}개"); + + // 3단계: 바인드포즈 조정을 통한 메쉬 고정 (X축 -90도 보정 포함) + Matrix4x4[] newBindPoses = new Matrix4x4[originalBindPoses.Length]; + + // X축 -90도 회전 보정 행렬 + Matrix4x4 correctionMatrix = Matrix4x4.Rotate(Quaternion.Euler(90f, 0f, 0f)); + + // 각 본의 회전 변화량을 바인드포즈에 반영 + for (int boneIndex = 0; boneIndex < originalBones.Length; boneIndex++) + { + Transform bone = originalBones[boneIndex]; + Matrix4x4 originalBindPose = originalStoredBindPoses[boneIndex]; + + // 현재 본의 Transform의 역행렬에 X축 -90도 보정 적용 + Matrix4x4 currentBoneTransform = bone.localToWorldMatrix; + Matrix4x4 newBindPose = (correctionMatrix * currentBoneTransform).inverse; + + newBindPoses[boneIndex] = newBindPose; + + UnityEngine.Debug.Log($"바인드포즈 조정: {bone.name} (X축 -90도 보정 적용)"); + } + + // 원래 버텍스와 조정된 바인드포즈 사용 + tempMesh.vertices = originalMesh.vertices; + tempMesh.bindposes = newBindPoses; + tempMesh.boneWeights = originalBoneWeights; + + // 블렌드쉐입 데이터 보존 + if (originalMesh.blendShapeCount > 0) + { + UnityEngine.Debug.Log($"블렌드쉐입 복사 시작: {originalMesh.blendShapeCount}개"); + + for (int i = 0; i < originalMesh.blendShapeCount; i++) + { + string shapeName = originalMesh.GetBlendShapeName(i); + int frameCount = originalMesh.GetBlendShapeFrameCount(i); + + for (int frameIndex = 0; frameIndex < frameCount; frameIndex++) + { + float frameWeight = originalMesh.GetBlendShapeFrameWeight(i, frameIndex); + + Vector3[] deltaVertices = new Vector3[originalMesh.vertexCount]; + Vector3[] deltaNormals = new Vector3[originalMesh.vertexCount]; + Vector3[] deltaTangents = new Vector3[originalMesh.vertexCount]; + + originalMesh.GetBlendShapeFrameVertices(i, frameIndex, deltaVertices, deltaNormals, deltaTangents); + + tempMesh.AddBlendShapeFrame(shapeName, frameWeight, deltaVertices, deltaNormals, deltaTangents); + } + } + + UnityEngine.Debug.Log($"블렌드쉐입 복사 완료: {tempMesh.blendShapeCount}개"); + } + + // 4단계: SkinnedMeshRenderer에 조정된 메쉬 적용 + tempMesh.RecalculateNormals(); + tempMesh.RecalculateBounds(); + + // 조정된 메쉬를 SkinnedMeshRenderer에 적용 + skinnedMesh.sharedMesh = tempMesh; + + UnityEngine.Debug.Log($"메쉬 고정 완료: {skinnedMesh.name}, 바인드포즈 조정됨"); + } + + // 6단계: 완료 (5.5단계에서 모든 작업 완료) + UnityEngine.Debug.Log("모든 스킨메쉬 처리 완료"); + + // 7단계: 원래 계층 구조 복원 + UnityEngine.Debug.Log("원래 계층 구조 복원 시작"); + foreach (Transform bone in allBones) + { + if (originalParents.ContainsKey(bone)) + { + bone.SetParent(originalParents[bone]); + } + } + + EditorUtility.SetDirty(copiedAvatar); + + EditorUtility.DisplayDialog("베이크 완료", + $"본 회전 베이크가 완료되었습니다!\n" + + $"적용된 본: {appliedBones}개\n" + + $"바인드포즈 조정된 메쉬: {allSkinnedMeshes.Length}개\n\n" + + $"복사본 아바타가 생성되었습니다: {copiedAvatar.name}\n\n" + + "본은 회전했지만 바인드포즈 조정으로 메쉬는 변형되지 않습니다.", "확인"); + + UnityEngine.Debug.Log($"본 회전 베이크 완료: {appliedBones}개 본"); + + // 생성된 아바타를 선택 + Selection.activeGameObject = copiedAvatar; + } + catch (Exception e) + { + EditorUtility.DisplayDialog("오류", $"베이크 중 오류가 발생했습니다: {e.Message}", "확인"); + UnityEngine.Debug.LogError($"베이크 오류: {e.Message}"); + } + } + + // 모든 본을 가져오는 헬퍼 메서드 + private List GetAllBones(GameObject avatar) + { + List bones = new List(); + Transform[] allTransforms = avatar.GetComponentsInChildren(); + + foreach (Transform t in allTransforms) + { + if (t != avatar.transform) // 아바타 루트는 제외 + { + bones.Add(t); + } + } + + return bones; + } + } + + // 리깅 데이터 백업을 위한 컴포넌트 + [System.Serializable] + public class RiggingDataBackup : MonoBehaviour + { + public BoneWeight[] boneWeights; + public Matrix4x4[] bindPoses; + public Transform[] originalBones; + public Vector3[] originalVertices; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Editor/PoseRotationBaker.cs.meta b/Assets/Scripts/Editor/PoseRotationBaker.cs.meta new file mode 100644 index 00000000..53defdc0 --- /dev/null +++ b/Assets/Scripts/Editor/PoseRotationBaker.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 150464f3f9d1e5c49a568fbcada754b7 \ No newline at end of file