diff --git a/Assets/External/EasyMotionRecorder/Scripts/MotionDataRecorder.cs b/Assets/External/EasyMotionRecorder/Scripts/MotionDataRecorder.cs index 14d0080a..dbf63f4b 100644 --- a/Assets/External/EasyMotionRecorder/Scripts/MotionDataRecorder.cs +++ b/Assets/External/EasyMotionRecorder/Scripts/MotionDataRecorder.cs @@ -12,11 +12,11 @@ using System; using System.IO; using System.Reflection; using System.Collections.Generic; +using System.Linq; #if UNITY_EDITOR using UnityEditor; #endif using EasyMotionRecorder; -using KindRetargeting; using UniHumanoid; namespace Entum @@ -100,7 +100,26 @@ namespace Entum // SessionID 생성 (인스턴스 ID 제외) SessionID = DateTime.Now.ToString("yyMMdd_HHmmss"); - _poseHandler = new HumanPoseHandler(_animator.avatar, _animator.transform); + // HumanPoseHandler 안전한 초기화 + if (_animator.avatar != null && _animator.isHuman) + { + try + { + _poseHandler = new HumanPoseHandler(_animator.avatar, _animator.transform); + Debug.Log($"HumanPoseHandler 초기화 완료 - 아바타: {_animator.avatar.name}"); + } + catch (System.Exception e) + { + Debug.LogError($"HumanPoseHandler 초기화 실패: {e.Message}"); + _poseHandler = null; + } + } + else + { + Debug.LogError($"아바타가 휴머노이드가 아니거나 Avatar가 설정되지 않았습니다. " + + $"Avatar: {_animator.avatar}, IsHuman: {_animator.isHuman}"); + _poseHandler = null; + } } // 내부 메서드들 (SavePathManager에서 호출) @@ -147,22 +166,29 @@ namespace Entum { return; } - if (FrameIndex % TargetFPS == 0) + if (FrameIndex % TargetFPS == 0 && FrameIndex > 0 && RecordedTime > 0) { - print("Motion_FPS=" + 1 / (RecordedTime / FrameIndex)); + print("Motion_FPS=" + (FrameIndex / RecordedTime)); } } else { if (Time.frameCount % Application.targetFrameRate == 0) { - print("Motion_FPS=" + 1 / Time.deltaTime); + print("Motion_FPS=" + (1 / Time.deltaTime)); } } //現在のフレームのHumanoidの姿勢を取得 + if (_poseHandler == null) + { + Debug.LogError("PoseHandler가 초기화되지 않았습니다. 녹화를 중단합니다."); + RecordEnd(); + return; + } + _poseHandler.GetHumanPose(ref _currentPose); - //posesに取得した姿勢を書き込む + //posesに取得한姿勢를書き込む var serializedPose = new HumanoidPoses.SerializeHumanoidPose(); switch (_rootBoneSystem) @@ -173,17 +199,51 @@ namespace Entum break; case MotionDataSettings.Rootbonesystem.Hipbone: - serializedPose.BodyRootPosition = _animator.GetBoneTransform(_targetRootBone).position; - serializedPose.BodyRootRotation = _animator.GetBoneTransform(_targetRootBone).rotation; - Debug.LogWarning(_animator.GetBoneTransform(_targetRootBone).position); + var hipBone = _animator.GetBoneTransform(_targetRootBone); + if (hipBone != null) + { + serializedPose.BodyRootPosition = hipBone.position; + serializedPose.BodyRootRotation = hipBone.rotation; + } + else + { + Debug.LogWarning($"타겟 루트 본 {_targetRootBone}을 찾을 수 없습니다. Object Root로 대체합니다."); + serializedPose.BodyRootPosition = _animator.transform.localPosition; + serializedPose.BodyRootRotation = _animator.transform.localRotation; + } break; default: throw new ArgumentOutOfRangeException(); } + var bodyTQ = new TQ(_currentPose.bodyPosition, _currentPose.bodyRotation); - var LeftFootTQ = new TQ(_animator.GetBoneTransform(IK_LeftFootBone).position, _animator.GetBoneTransform(IK_LeftFootBone).rotation); - var RightFootTQ = new TQ(_animator.GetBoneTransform(IK_RightFootBone).position, _animator.GetBoneTransform(IK_RightFootBone).rotation); + + // 발 본 안전성 검사 + var leftFootBone = _animator.GetBoneTransform(IK_LeftFootBone); + var rightFootBone = _animator.GetBoneTransform(IK_RightFootBone); + + TQ LeftFootTQ, RightFootTQ; + + if (leftFootBone != null) + { + LeftFootTQ = new TQ(leftFootBone.position, leftFootBone.rotation); + } + else + { + Debug.LogWarning($"왼발 본 {IK_LeftFootBone}을 찾을 수 없습니다. 기본값을 사용합니다."); + LeftFootTQ = new TQ(Vector3.zero, Quaternion.identity); + } + + if (rightFootBone != null) + { + RightFootTQ = new TQ(rightFootBone.position, rightFootBone.rotation); + } + else + { + Debug.LogWarning($"오른발 본 {IK_RightFootBone}을 찾을 수 없습니다. 기본값을 사용합니다."); + RightFootTQ = new TQ(Vector3.zero, Quaternion.identity); + } serializedPose.BodyPosition = bodyTQ.t; serializedPose.BodyRotation = bodyTQ.q; @@ -214,6 +274,13 @@ namespace Entum return; } + // PoseHandler 유효성 검사 + if (_poseHandler == null) + { + Debug.LogError("HumanPoseHandler가 초기화되지 않았습니다. 녹화를 시작할 수 없습니다."); + return; + } + // 세션 ID 생성 (인스턴스 ID 제외) SessionID = DateTime.Now.ToString("yyMMdd_HHmmss"); @@ -223,12 +290,27 @@ namespace Entum Poses.SessionID = SessionID; Poses.InstanceID = instanceID; // 인스턴스 ID 설정 + // T포즈 별도 저장 옵션이 활성화된 경우 + if (_recordTPoseAtStart) + { + try + { + RecordTPoseAsFirstFrame(); + Debug.Log("T포즈 데이터가 성공적으로 기록되었습니다."); + } + catch (System.Exception e) + { + Debug.LogError($"T포즈 녹화 중 오류 발생: {e.Message}"); + // T포즈 실패해도 일반 녹화는 계속 진행 + } + } + RecordedTime = 0f; StartTime = Time.time; FrameIndex = 0; _recording = true; - Debug.Log($"모션 녹화 시작 - 인스턴스: {instanceID}, 세션: {SessionID}"); + Debug.Log($"모션 녹화 시작 - 인스턴스: {instanceID}, 세션: {SessionID}, T포즈 옵션: {_recordTPoseAtStart}"); OnRecordStart?.Invoke(); } @@ -242,32 +324,75 @@ namespace Entum private void SetTPose(Animator animator) { - // T-포즈 설정을 위한 임시 애니메이션 클립 생성 - var tPoseClip = new AnimationClip(); - tPoseClip.name = "TPose"; - - // 모든 본을 T-포즈로 설정 - var humanBones = animator.avatar.humanDescription.human; - foreach (var bone in humanBones) + if (animator == null || animator.avatar == null) { - // HumanBodyBones enum으로 변환 - HumanBodyBones bodyBone; - if (System.Enum.TryParse(bone.humanName, out bodyBone)) + Debug.LogWarning("Animator 또는 Avatar가 null입니다. T포즈 설정을 건너뜁니다."); + return; + } + + try + { + // HumanPoseClip에서 T-포즈 데이터 로드 시도 + var humanPoseClip = Resources.Load("T-Pose.pose"); + if (humanPoseClip != null) { - var boneTransform = animator.GetBoneTransform(bodyBone); - if (boneTransform != null) - { - var curve = new AnimationCurve(); - curve.AddKey(0, 0); - tPoseClip.SetCurve(boneTransform.name, typeof(Transform), "localRotation.x", curve); - tPoseClip.SetCurve(boneTransform.name, typeof(Transform), "localRotation.y", curve); - tPoseClip.SetCurve(boneTransform.name, typeof(Transform), "localRotation.z", curve); - tPoseClip.SetCurve(boneTransform.name, typeof(Transform), "localRotation.w", curve); - } + // T포즈 데이터를 적용 + var pose = humanPoseClip.GetPose(); + SetPoseToAnimator(animator.avatar, animator.transform, pose); + + Debug.Log("T-포즈가 HumanPoseClip에서 성공적으로 로드되었습니다."); + } + else + { + // HumanPoseClip이 없는 경우 기본 T포즈 설정 + Debug.LogWarning("T-Pose.pose 리소스를 찾을 수 없습니다. 기본 T포즈를 설정합니다."); + SetDefaultTPose(animator); + } + + // UpperChest 본 위치 초기화 (카인드리타겟팅과 동일) + Transform upperChest = animator.GetBoneTransform(HumanBodyBones.UpperChest); + if (upperChest != null) + { + upperChest.localPosition = Vector3.zero; } } + catch (System.Exception e) + { + Debug.LogError($"T포즈 설정 중 오류 발생: {e.Message}"); + SetDefaultTPose(animator); + } + } + + /// + /// HumanPose를 Animator에 적용하는 내부 메서드 + /// + private void SetPoseToAnimator(Avatar avatar, Transform transform, HumanPose pose) + { + var handler = new HumanPoseHandler(avatar, transform); + handler.SetHumanPose(ref pose); + handler.Dispose(); + } + + /// + /// 기본 T포즈 설정 (HumanPoseClip이 없는 경우 백업용) + /// + private void SetDefaultTPose(Animator animator) + { + Debug.Log("기본 T포즈 설정을 수행합니다."); - animator.Play("TPose"); + // 기본 T포즈 생성 - Muscles를 0으로 설정 + var defaultTPose = new HumanPose(); + defaultTPose.bodyPosition = Vector3.zero; + defaultTPose.bodyRotation = Quaternion.identity; + + // 모든 머슬을 0으로 설정 (T포즈) + defaultTPose.muscles = new float[HumanTrait.MuscleCount]; + for (int i = 0; i < HumanTrait.MuscleCount; i++) + { + defaultTPose.muscles[i] = 0f; + } + + SetPoseToAnimator(animator.avatar, animator.transform, defaultTPose); } private void RecordTPoseData() @@ -314,6 +439,7 @@ namespace Entum { pose.HumanoidBones = new List(); + // 기존 avatar.humanDescription.human 본들 처리 var humanBones = animator.avatar.humanDescription.human; foreach (var bone in humanBones) { @@ -337,6 +463,59 @@ namespace Entum } } } + + // 손가락 본들 추가 처리 (누락된 것들을 위해) + AddFingerBones(animator, ref pose); + } + + /// + /// 손가락 본들을 추가로 처리하는 메서드 + /// + private static void AddFingerBones(Animator animator, ref HumanoidPoses.SerializeHumanoidPose pose) + { + // 이미 추가된 본들의 이름을 저장 + var existingBoneNames = new HashSet(); + foreach (var bone in pose.HumanoidBones) + { + existingBoneNames.Add(bone.Name); + } + + // 모든 손가락 본들을 정의 + var fingerBones = new HumanBodyBones[] + { + // 왼손 손가락들 + 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 + }; + + // 각 손가락 본에 대해 처리 + foreach (var fingerBone in fingerBones) + { + var boneTransform = animator.GetBoneTransform(fingerBone); + if (boneTransform != null) + { + var humanoidBone = new HumanoidPoses.SerializeHumanoidPose.HumanoidBone(); + humanoidBone.Set(animator.transform, boneTransform); + + // 중복 확인 (이미 추가되지 않은 경우만 추가) + if (!existingBoneNames.Contains(humanoidBone.Name)) + { + pose.HumanoidBones.Add(humanoidBone); + existingBoneNames.Add(humanoidBone.Name); + //Debug.Log($"손가락 본 추가: {fingerBone} -> {humanoidBone.Name}"); + } + } + } } private static bool IsElbowBone(Transform bone) @@ -654,6 +833,12 @@ namespace Entum } } + // 손가락 본들이 포함되었는지 로깅 + var fingerBoneCount = boneCurves.Keys.Count(name => + name.Contains("Thumb") || name.Contains("Index") || name.Contains("Middle") || + name.Contains("Ring") || name.Contains("Little") || name.Contains("Pinky")); + Debug.Log($"제네릭 애니메이션 클립 생성 완료 - 총 {boneCurves.Count}개 본, 손가락 본 {fingerBoneCount}개 포함"); + return clip; } #endif @@ -713,6 +898,30 @@ namespace Entum return avatarIKGoal == AvatarIKGoal.LeftFoot ? HumanBodyBones.LeftFoot : HumanBodyBones.RightFoot; } } + + /// + /// 컴포넌트가 파괴될 때 리소스를 정리합니다. + /// + private void OnDestroy() + { + // HumanPoseHandler 안전하게 정리 + if (_poseHandler != null) + { + try + { + _poseHandler.Dispose(); + Debug.Log("HumanPoseHandler가 정리되었습니다."); + } + catch (System.Exception e) + { + Debug.LogError($"HumanPoseHandler 정리 중 오류 발생: {e.Message}"); + } + finally + { + _poseHandler = null; + } + } + } } }