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?먯꽌???먯긽 蹂몄씠 吏곸쟾 ?ъ쫰瑜???뼱?곗? 紐삵븯寃?留됰뒗 ?덉쟾?μ튂.