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