Fix : 옵티 미러 옵션 추가

This commit is contained in:
qsxft258@gmail.com 2026-03-22 15:34:13 +09:00
parent 783ab14f71
commit 6793635c7c

View File

@ -331,6 +331,10 @@ public class OptitrackStreamingClient : MonoBehaviour
//[Tooltip("Timecode Provider")] //[Tooltip("Timecode Provider")]
//public bool TimecodeProvider = false; //public bool TimecodeProvider = false;
[Header("Mirror Mode")]
[Tooltip("모든 스켈레톤 본과 리지드바디의 좌우를 거울 반전합니다.")]
public bool MirrorMode = false;
#region Private fields #region Private fields
//private UInt16 ServerCommandPort = NatNetConstants.DefaultCommandPort; //private UInt16 ServerCommandPort = NatNetConstants.DefaultCommandPort;
@ -361,6 +365,9 @@ public class OptitrackStreamingClient : MonoBehaviour
/// <summary>Maps from a streamed skeleton's ID to its most recent available pose data.</summary> /// <summary>Maps from a streamed skeleton's ID to its most recent available pose data.</summary>
private Dictionary<Int32, OptitrackSkeletonState> m_latestSkeletonStates = new Dictionary<Int32, OptitrackSkeletonState>(); private Dictionary<Int32, OptitrackSkeletonState> m_latestSkeletonStates = new Dictionary<Int32, OptitrackSkeletonState>();
/// <summary>MirrorMode용: 스켈레톤 ID → (boneId → mirrorBoneId) 매핑 캐시.</summary>
private Dictionary<Int32, Dictionary<Int32, Int32>> m_mirrorBoneIdMaps = new Dictionary<Int32, Dictionary<Int32, Int32>>();
/// <summary>Maps from a streamed trained markerset's ID to its most recent available pose data.</summary> /// <summary>Maps from a streamed trained markerset's ID to its most recent available pose data.</summary>
private Dictionary<Int32, OptitrackTMarkersetState> m_latestTMarkersetStates = new Dictionary<Int32, OptitrackTMarkersetState>(); // trained markerset added private Dictionary<Int32, OptitrackTMarkersetState> m_latestTMarkersetStates = new Dictionary<Int32, OptitrackTMarkersetState>(); // trained markerset added
@ -868,8 +875,20 @@ public class OptitrackStreamingClient : MonoBehaviour
rbState = new OptitrackRigidBodyState(); rbState = new OptitrackRigidBodyState();
RigidBodyDataToState( rbData, OptitrackHiResTimer.Now(), rbState ); 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; return rbState;
@ -914,15 +933,80 @@ public class OptitrackStreamingClient : MonoBehaviour
deliveryTimestamp = state.DeliveryTimestamp; deliveryTimestamp = state.DeliveryTimestamp;
posOut.Clear(); posOut.Clear();
oriOut.Clear(); oriOut.Clear();
if ( MirrorMode )
{
Dictionary<Int32, Int32> 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 ) foreach ( var kvp in state.BonePoses )
{ {
posOut[kvp.Key] = kvp.Value.Position; posOut[kvp.Key] = kvp.Value.Position;
oriOut[kvp.Key] = kvp.Value.Orientation; oriOut[kvp.Key] = kvp.Value.Orientation;
} }
}
return true; return true;
} }
} }
/// <summary>
/// 본 이름의 L/R 접두사를 반전한 이름을 반환. 대칭 본이면 null.
/// 예: "LUArm" → "RUArm", "RShin" → "LShin", "Hip" → null
/// </summary>
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;
}
/// <summary>
/// 스켈레톤 정의를 기반으로 boneId → mirrorBoneId 매핑을 빌드하고 캐시에 저장.
/// m_frameDataUpdateLock 내에서 호출해야 함.
/// </summary>
private Dictionary<Int32, Int32> 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<string, Int32>( skelDef.Bones.Count );
foreach ( var bone in skelDef.Bones )
nameToId[bone.Name] = bone.Id;
var map = new Dictionary<Int32, Int32>( 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;
}
/// <summary>YZ 평면 기준 회전 미러: X, Z 성분 부호 반전.</summary>
private static Quaternion MirrorOrientation( Quaternion q )
=> new Quaternion( -q.x, q.y, -q.z, q.w );
/// <summary>YZ 평면 기준 위치 미러: X 성분 부호 반전.</summary>
private static Vector3 MirrorPosition( Vector3 pos )
=> new Vector3( -pos.x, pos.y, pos.z );
/// <summary>Get the most recently received state for the specified trained markerset.</summary> /// <summary>Get the most recently received state for the specified trained markerset.</summary>
/// <param name="tmarkersetId"> /// <param name="tmarkersetId">
@ -1113,6 +1197,7 @@ public class OptitrackStreamingClient : MonoBehaviour
m_rigidBodyDefinitions.Clear(); m_rigidBodyDefinitions.Clear();
m_skeletonDefinitions.Clear(); m_skeletonDefinitions.Clear();
m_tmarkersetDefinitions.Clear(); m_tmarkersetDefinitions.Clear();
m_mirrorBoneIdMaps.Clear(); // 스켈레톤 정의 변경 시 mirror map 캐시 무효화
// ---------------------------------- // ----------------------------------
// - Translate Rigid Body Definitions // - Translate Rigid Body Definitions