diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs index 0283712c7..ce86a1d0b 100644 --- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs @@ -331,6 +331,10 @@ public class OptitrackStreamingClient : MonoBehaviour //[Tooltip("Timecode Provider")] //public bool TimecodeProvider = false; + [Header("Mirror Mode")] + [Tooltip("모든 스켈레톤 본과 리지드바디의 좌우를 거울 반전합니다.")] + public bool MirrorMode = false; + #region Private fields //private UInt16 ServerCommandPort = NatNetConstants.DefaultCommandPort; @@ -361,6 +365,9 @@ public class OptitrackStreamingClient : MonoBehaviour /// Maps from a streamed skeleton's ID to its most recent available pose data. private Dictionary m_latestSkeletonStates = new Dictionary(); + /// MirrorMode용: 스켈레톤 ID → (boneId → mirrorBoneId) 매핑 캐시. + private Dictionary> m_mirrorBoneIdMaps = new Dictionary>(); + /// Maps from a streamed trained markerset's ID to its most recent available pose data. private Dictionary m_latestTMarkersetStates = new Dictionary(); // trained markerset added @@ -868,8 +875,20 @@ public class OptitrackStreamingClient : MonoBehaviour rbState = new OptitrackRigidBodyState(); RigidBodyDataToState( rbData, OptitrackHiResTimer.Now(), rbState ); + } - + if ( MirrorMode && rbState != null ) + { + rbState = new OptitrackRigidBodyState + { + DeliveryTimestamp = rbState.DeliveryTimestamp, + IsTracked = rbState.IsTracked, + Pose = new OptitrackPose + { + Position = MirrorPosition( rbState.Pose.Position ), + Orientation = MirrorOrientation( rbState.Pose.Orientation ), + } + }; } return rbState; @@ -914,15 +933,80 @@ public class OptitrackStreamingClient : MonoBehaviour deliveryTimestamp = state.DeliveryTimestamp; posOut.Clear(); oriOut.Clear(); - foreach ( var kvp in state.BonePoses ) + + if ( MirrorMode ) { - posOut[kvp.Key] = kvp.Value.Position; - oriOut[kvp.Key] = kvp.Value.Orientation; + Dictionary mirrorMap = GetOrBuildMirrorBoneIdMap( skeletonId ); + foreach ( var kvp in state.BonePoses ) + { + Int32 targetId = mirrorMap != null && mirrorMap.TryGetValue( kvp.Key, out Int32 mid ) ? mid : kvp.Key; + posOut[targetId] = MirrorPosition( kvp.Value.Position ); + oriOut[targetId] = MirrorOrientation( kvp.Value.Orientation ); + } + } + else + { + foreach ( var kvp in state.BonePoses ) + { + posOut[kvp.Key] = kvp.Value.Position; + oriOut[kvp.Key] = kvp.Value.Orientation; + } } return true; } } + /// + /// 본 이름의 L/R 접두사를 반전한 이름을 반환. 대칭 본이면 null. + /// 예: "LUArm" → "RUArm", "RShin" → "LShin", "Hip" → null + /// + private static string GetMirrorBoneName( string name ) + { + if ( string.IsNullOrEmpty( name ) || name.Length < 2 ) return null; + if ( name[0] == 'L' && char.IsUpper( name[1] ) ) return "R" + name.Substring( 1 ); + if ( name[0] == 'R' && char.IsUpper( name[1] ) ) return "L" + name.Substring( 1 ); + return null; + } + + /// + /// 스켈레톤 정의를 기반으로 boneId → mirrorBoneId 매핑을 빌드하고 캐시에 저장. + /// m_frameDataUpdateLock 내에서 호출해야 함. + /// + private Dictionary GetOrBuildMirrorBoneIdMap( Int32 skeletonId ) + { + if ( m_mirrorBoneIdMaps.TryGetValue( skeletonId, out var cached ) ) + return cached; + + OptitrackSkeletonDefinition skelDef = GetSkeletonDefinitionById( skeletonId ); + if ( skelDef == null ) return null; + + // 이름 → ID 룩업 테이블 + var nameToId = new Dictionary( skelDef.Bones.Count ); + foreach ( var bone in skelDef.Bones ) + nameToId[bone.Name] = bone.Id; + + var map = new Dictionary( skelDef.Bones.Count ); + foreach ( var bone in skelDef.Bones ) + { + string mirrorName = GetMirrorBoneName( bone.Name ); + if ( mirrorName != null && nameToId.TryGetValue( mirrorName, out Int32 mirrorId ) ) + map[bone.Id] = mirrorId; + else + map[bone.Id] = bone.Id; // 대칭 본은 자기 자신에 매핑 + } + + m_mirrorBoneIdMaps[skeletonId] = map; + return map; + } + + /// YZ 평면 기준 회전 미러: X, Z 성분 부호 반전. + private static Quaternion MirrorOrientation( Quaternion q ) + => new Quaternion( -q.x, q.y, -q.z, q.w ); + + /// YZ 평면 기준 위치 미러: X 성분 부호 반전. + private static Vector3 MirrorPosition( Vector3 pos ) + => new Vector3( -pos.x, pos.y, pos.z ); + /// Get the most recently received state for the specified trained markerset. /// @@ -1113,6 +1197,7 @@ public class OptitrackStreamingClient : MonoBehaviour m_rigidBodyDefinitions.Clear(); m_skeletonDefinitions.Clear(); m_tmarkersetDefinitions.Clear(); + m_mirrorBoneIdMaps.Clear(); // 스켈레톤 정의 변경 시 mirror map 캐시 무효화 // ---------------------------------- // - Translate Rigid Body Definitions