diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Prefabs/Client - OptiTrack.prefab b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Prefabs/Client - OptiTrack.prefab index 9dc81a0ad..97689afd9 100644 --- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Prefabs/Client - OptiTrack.prefab +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Prefabs/Client - OptiTrack.prefab @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4d28138cba0db2a44f9c41ad868dbfdea158c28bbc7b7460500b72c5f851bf05 -size 5115 +oid sha256:43ef0abb5666904f8ce55aa84e14e8fbb95098937c4ff895a7fbb67a83254084 +size 5049 diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/Editor/UXML/OptitrackStreamingClientEditor.uxml b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/Editor/UXML/OptitrackStreamingClientEditor.uxml index 6eaf999ba..1e5c09f4c 100644 --- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/Editor/UXML/OptitrackStreamingClientEditor.uxml +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/Editor/UXML/OptitrackStreamingClientEditor.uxml @@ -15,7 +15,6 @@ - @@ -51,14 +50,6 @@ - - - - - - - - diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackSkeletonAnimator_Mingle.cs b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackSkeletonAnimator_Mingle.cs index a707be120..52b3a327f 100644 --- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackSkeletonAnimator_Mingle.cs +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackSkeletonAnimator_Mingle.cs @@ -91,6 +91,23 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour HumanBodyBones.RightToes, }; + private static readonly string[] s_fingerBoneNameTokens = + { + "Thumb", + "Index", + "Middle", + "Ring", + "Pinky", + "Little", + "Finger", + }; + + private const bool k_EnableResetPoseFrameFilter = true; + private const bool k_ResetPoseComparePositions = false; + private const float k_ResetPosePositionTolerance = 0.025f; + private const float k_ResetPoseRotationToleranceDegrees = 5f; + private const int k_ResetPoseMinimumBodyBones = 8; + // 스파인/넥 체인 Transform 캐시 (GetSpineChainTransforms 매 호출 List 할당 방지) private List m_spineChainCache = new List(); @@ -341,6 +358,9 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour return; // ── NatNet 실제 프레임 간격 계산 (하드웨어 타이머 — 렌더 프레임 등락과 완전 독립) ── + if (ShouldSkipResetPoseFrame()) + return; + if (m_hasLastFrameTimestamp && frameTs.m_ticks != m_lastFrameTimestamp.m_ticks) { float measuredDt = frameTs.SecondsSince(m_lastFrameTimestamp); @@ -647,6 +667,66 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour /// 두 OptiTrack 프레임 사이를 시간 기반으로 보간합니다. /// m_snapshotPositions/Orientations를 보간된 결과로 덮어씁니다. /// + private bool ShouldSkipResetPoseFrame() + { + if (!k_EnableResetPoseFrameFilter || !m_isRestPoseCached || m_skeletonDef == null) + return false; + + int testedBodyBoneCount = 0; + float positionTolerance = Mathf.Max(0.0f, k_ResetPosePositionTolerance); + float rotationTolerance = Mathf.Max(0.0f, k_ResetPoseRotationToleranceDegrees); + + for (int i = 0; i < m_skeletonDef.Bones.Count; ++i) + { + OptitrackSkeletonDefinition.BoneDefinition bone = m_skeletonDef.Bones[i]; + if (!m_boneIdToMappingIndex.TryGetValue(bone.Id, out int mappingIndex)) + continue; + + OptiTrackBoneMapping mapping = boneMappings[mappingIndex]; + if (!mapping.isMapped) + continue; + + if (IsFingerBoneName(mapping.optiTrackBoneName)) + continue; + + if (!m_restLocalRotations.TryGetValue(mapping.optiTrackBoneName, out Quaternion restRot) || + !m_snapshotOrientations.TryGetValue(bone.Id, out Quaternion frameRot)) + { + return false; + } + + if (Quaternion.Angle(restRot, frameRot) > rotationTolerance) + return false; + + if (k_ResetPoseComparePositions && + mapping.applyPosition && + m_restLocalPositions.TryGetValue(mapping.optiTrackBoneName, out Vector3 restPos) && + m_snapshotPositions.TryGetValue(bone.Id, out Vector3 framePos) && + Vector3.Distance(restPos, framePos) > positionTolerance) + { + return false; + } + + ++testedBodyBoneCount; + } + + return testedBodyBoneCount >= k_ResetPoseMinimumBodyBones; + } + + private static bool IsFingerBoneName(string boneName) + { + if (string.IsNullOrEmpty(boneName)) + return false; + + for (int i = 0; i < s_fingerBoneNameTokens.Length; ++i) + { + if (boneName.IndexOf(s_fingerBoneNameTokens[i], StringComparison.InvariantCultureIgnoreCase) >= 0) + return true; + } + + return false; + } + private void InterpolateSnapshots(OptitrackHiResTimer.Timestamp frameTs) { // 새 프레임 감지 (타임스탬프가 변경되었으면 새 OptiTrack 프레임이 도착한 것) diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs index 01925a4da..27509ca0c 100644 --- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs @@ -341,9 +341,6 @@ public class OptitrackStreamingClient : MonoBehaviour [Range(0.05f, 5.0f)] public float ReplayFreshnessSeconds = 0.35f; - [Tooltip("Actor/skeleton name to use when the NatNet command channel is unavailable and names cannot be read from MODELDEF. Must match OptitrackSkeletonAnimator_Mingle.SkeletonAssetName.")] - public string DirectSyntheticSkeletonName = "002"; - [Tooltip("Controls whether skeleton data is streamed with local or global coordinates.")] public StreamingCoordinatesValues SkeletonCoordinates = StreamingCoordinatesValues.Local; @@ -390,10 +387,6 @@ public class OptitrackStreamingClient : MonoBehaviour [Tooltip("Mirrors streamed skeleton bones and retargeted avatar motion left/right.")] public bool MirrorMode = false; - [Header("Skeleton Frame Filter")] - [Tooltip("Strict skeleton frame filter.\nON: drops the whole skeleton frame when any bone is untracked or invalid.\nOFF: valid bones update and invalid bones keep the previous pose. Recommended for live use.")] - public bool EnableSkeletonFrameFilter = false; - #region Private fields //private UInt16 ServerCommandPort = NatNetConstants.DefaultCommandPort; //private UInt16 ServerDataPort = NatNetConstants.DefaultDataPort; @@ -821,6 +814,12 @@ public class OptitrackStreamingClient : MonoBehaviour return; } + if (m_directNatNetConnected && ConnectionType == ClientConnectionType.Multicast) + { + Debug.Log(GetType().FullName + ": direct UDP receiver is already active; reconnect request skipped.", this); + return; + } + Debug.Log("OptiTrack: reconnect requested."); // 湲곗〈 ?곌껐 ?뺣━ (StopAllCoroutines ?ы븿) @@ -1919,6 +1918,15 @@ public class OptitrackStreamingClient : MonoBehaviour m_lastReplayFrameDeliveryTimestamp.AgeSeconds <= ReplayFreshnessSeconds; } + private bool HasRecentStreamingFrame(float thresholdSeconds) + { + OptitrackHiResTimer.Timestamp liveTimestamp; + liveTimestamp.m_ticks = Interlocked.Read(ref m_lastFrameDeliveryTimestamp.m_ticks); + bool liveFresh = m_receivedFrameSinceConnect && liveTimestamp.AgeSeconds < thresholdSeconds; + bool replayFresh = IsReplayFrameFresh(); + return liveFresh || replayFresh; + } + private void StartDirectFrameReceiver(string localAddress, string multicastAddress, UInt16 dataPort) { StopDirectFrameReceiver(); @@ -2046,6 +2054,8 @@ public class OptitrackStreamingClient : MonoBehaviour { m_replayReceivedFrameSinceConnect = true; Interlocked.Exchange(ref m_lastReplayFrameDeliveryTimestamp.m_ticks, frameTimestamp.m_ticks); + m_receivedFrameSinceConnect = true; + Interlocked.Exchange(ref m_lastFrameDeliveryTimestamp.m_ticks, frameTimestamp.m_ticks); } else { @@ -2212,27 +2222,8 @@ public class OptitrackStreamingClient : MonoBehaviour return; } - if (EnableSkeletonFrameFilter && boneCount != skelDef.Bones.Count) - return; - OptitrackSkeletonState skelState = GetOrCreateSkeletonState(skeletonId); - if (EnableSkeletonFrameFilter) - { - for (int b = 0; b < boneCount; b++) - { - int boneSkelId, boneId; - DirectDecodeId(stagedBones[b].Id, out boneSkelId, out boneId); - if (boneSkelId != skeletonId || - !skelDef.BoneIdToParentIdMap.ContainsKey(boneId) || - !IsSkeletonBoneTracked(stagedBones[b]) || - !IsBoneDataUsable(stagedBones[b])) - { - return; - } - } - } - for (int b = 0; b < boneCount; b++) { sRigidBodyData boneData = stagedBones[b]; @@ -2332,8 +2323,6 @@ public class OptitrackStreamingClient : MonoBehaviour private string ChooseSyntheticSkeletonName(int skeletonId) { - if (!string.IsNullOrWhiteSpace(DirectSyntheticSkeletonName)) - return DirectSyntheticSkeletonName.Trim(); return "Skeleton" + skeletonId; } @@ -3035,7 +3024,6 @@ public class OptitrackStreamingClient : MonoBehaviour // The coroutine is stopped on disconnect and restarted on connect. YieldInstruction checkIntervalYield = new WaitForSeconds( kHealthCheckIntervalSeconds ); OptitrackHiResTimer.Timestamp connectionInitiatedTimestamp = OptitrackHiResTimer.Now(); - OptitrackHiResTimer.Timestamp lastFrameReceivedTimestamp; bool wasReceivingFrames = false; bool warnedPendingFirstFrame = false; @@ -3043,7 +3031,7 @@ public class OptitrackStreamingClient : MonoBehaviour { yield return checkIntervalYield; - if ( m_receivedFrameSinceConnect == false ) + if ( m_receivedFrameSinceConnect == false && !IsReplayFrameFresh() ) { // Still waiting for first frame. Warn exactly once if this takes too long. if ( connectionInitiatedTimestamp.AgeSeconds > kRecentFrameThresholdSeconds ) @@ -3063,8 +3051,7 @@ public class OptitrackStreamingClient : MonoBehaviour else { // We've received at least one frame, do ongoing checks for changes in connection health. - lastFrameReceivedTimestamp.m_ticks = Interlocked.Read( ref m_lastFrameDeliveryTimestamp.m_ticks ); - bool receivedRecentFrame = lastFrameReceivedTimestamp.AgeSeconds < kRecentFrameThresholdSeconds; + bool receivedRecentFrame = HasRecentStreamingFrame(kRecentFrameThresholdSeconds); if ( wasReceivingFrames == false && receivedRecentFrame == true ) { @@ -3078,6 +3065,11 @@ public class OptitrackStreamingClient : MonoBehaviour // Transition: Good health -> bad health. wasReceivingFrames = false; Debug.LogWarning( GetType().FullName + ": No streaming frames received from the server recently.", this ); + if (m_directNatNetConnected) + { + Debug.LogWarning(GetType().FullName + ": direct UDP receiver stays active; automatic reconnect is skipped to avoid interrupting live/replay multicast.", this); + continue; + } if ( AutoReconnect ) { Debug.Log( GetType().FullName + ": starting automatic reconnect.", this ); @@ -3199,15 +3191,6 @@ public class OptitrackStreamingClient : MonoBehaviour continue; } - // === ?ㅼ펷?덊넠 ?꾨젅???꾪꽣 (EnableSkeletonFrameFilter) === - // Motive媛€ 鍮?遺€遺??ㅼ펷?덊넠 ?섏씠濡쒕뱶瑜??대낫?????덈떎. - // ON : 蹂?媛쒖닔媛€ ?ㅻⅤ硫??꾨젅???꾩껜 ?먭린 (吏곸쟾 ?ъ쫰 ?좎? ???⑤┝/遺€遺꾪봽?덉엫 諛⑹?) - // OFF: ?ㅼ뼱??蹂?媛쒖닔留뚰겮 洹몃?濡?泥섎━ (紐⑥뀡 ?딄? 諛⑹?) - if (EnableSkeletonFrameFilter && skelRbCount != skelDef.Bones.Count) - { - continue; - } - sRigidBodyData[] stagedBones = GetSkeletonFrameScratch(skeletonId, skelRbCount); for (int boneIdx = 0; boneIdx < skelRbCount; ++boneIdx) { @@ -3215,34 +3198,6 @@ public class OptitrackStreamingClient : MonoBehaviour NatNetException.ThrowIfNotOK( result, "NatNet_Frame_Skeleton_GetRigidBody failed." ); } - // ON ???뚮쭔: 蹂??섎굹?쇰룄 ?몃옒???ㅽ뙣/?먯긽?대㈃ ?꾨젅???꾩껜 ?먭린 - if (EnableSkeletonFrameFilter) - { - bool isValidSkeletonFrame = true; - for (int boneIdx = 0; boneIdx < skelRbCount; ++boneIdx) - { - sRigidBodyData boneData = stagedBones[boneIdx]; - // In the context of frame data (unlike in the definition data), this ID value is a - // packed composite of both the asset/entity (skeleton) ID and member (bone) ID. - Int32 boneSkelId, boneId; - NaturalPoint.NatNetLib.NativeMethods.NatNet_DecodeID( boneData.Id, out boneSkelId, out boneId ); - - if (boneSkelId != skeletonId || - !skelDef.BoneIdToParentIdMap.ContainsKey(boneId) || - !IsSkeletonBoneTracked(boneData) || - !IsBoneDataUsable(boneData)) - { - isValidSkeletonFrame = false; - break; - } - } - - if (!isValidSkeletonFrame) - { - continue; - } - } - // === 湲€濡쒕쾶 ?몃옖?ㅽ뤌 而ㅻ컠 === // ?꾪꽣 OFF ?먯꽌???먯긽(NaN쨌0荑쇳꽣?덉뼵)쨌誘몃ℓ??蹂몄? 媛쒕퀎濡?嫄대꼫?곗뼱 吏곸쟾 ?ъ쫰瑜?蹂댁〈?쒕떎. for (int boneIdx = 0; boneIdx < skelRbCount; ++boneIdx) @@ -3890,12 +3845,6 @@ public class OptitrackStreamingClient : MonoBehaviour return scratch; } - /// Motive "???꾨젅???몃옒?밸맖" 鍮꾪듃(0x01). ?꾧꺽 ?꾪꽣(ON)?먯꽌留??꾨젅???먭린 議곌굔?쇰줈 ?곗씤?? - private static bool IsSkeletonBoneTracked(sRigidBodyData boneData) - { - return (boneData.Params & 0x01) != 0; - } - /// /// 蹂??곗씠?곌? ?ъ쫰濡?而ㅻ컠?대룄 ?덉쟾?쒖? 寃€?ы븳??醫뚰몴 ?좏븳 + 荑쇳꽣?덉뼵 ?뺤긽). /// ?몃옒??鍮꾪듃?€ 臾닿? ???꾪꽣 OFF?먯꽌???먯긽 蹂몄씠 吏곸쟾 ?ъ쫰瑜???뼱?곗? 紐삵븯寃?留됰뒗 ?덉쟾?μ튂.