Fix : 필터링 시스템 추가 업데이트 패치
This commit is contained in:
parent
25c3c128e4
commit
9f93d398ad
BIN
Assets/External/OptiTrack Unity Plugin/OptiTrack/Prefabs/Client - OptiTrack.prefab
(Stored with Git LFS)
vendored
BIN
Assets/External/OptiTrack Unity Plugin/OptiTrack/Prefabs/Client - OptiTrack.prefab
(Stored with Git LFS)
vendored
Binary file not shown.
@ -15,7 +15,6 @@
|
||||
<uie:PropertyField binding-path="ServerAddress" label="Live Motive IP"/>
|
||||
<uie:PropertyField binding-path="EnableReplayPriority" label="Use MMRP Replay Priority"/>
|
||||
<uie:PropertyField binding-path="ReplayServerAddress" label="MMRP Replay IP"/>
|
||||
<uie:PropertyField binding-path="DirectSyntheticSkeletonName" label="Fallback Actor Name"/>
|
||||
<uie:PropertyField binding-path="ConnectionType" label="Connection Type"/>
|
||||
<uie:PropertyField binding-path="SkeletonCoordinates" label="Skeleton Coordinates"/>
|
||||
<uie:PropertyField binding-path="TMarkersetCoordinates" label="TMarkerset Coordinates"/>
|
||||
@ -51,14 +50,6 @@
|
||||
</ui:Foldout>
|
||||
</ui:VisualElement>
|
||||
|
||||
<!-- Skeleton Frame Filter -->
|
||||
<ui:VisualElement class="section">
|
||||
<ui:Foldout text="Skeleton Frame Filter" value="true" class="section-foldout">
|
||||
<uie:PropertyField binding-path="EnableSkeletonFrameFilter" label="Enable Strict Skeleton Frame Filter"/>
|
||||
<ui:HelpBox message-type="Info" text="ON: drop the whole skeleton frame if any bone is untracked or invalid. OFF is recommended for live use: valid bones update, invalid bones keep the last pose."/>
|
||||
</ui:Foldout>
|
||||
</ui:VisualElement>
|
||||
|
||||
<!-- NatNet Version -->
|
||||
<ui:VisualElement class="section">
|
||||
<ui:Foldout text="NatNet Version" value="false" class="section-foldout">
|
||||
|
||||
@ -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<Transform> m_spineChainCache = new List<Transform>();
|
||||
@ -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를 보간된 결과로 덮어씁니다.
|
||||
/// </summary>
|
||||
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 프레임이 도착한 것)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/// <summary>Motive "???꾨젅???몃옒?밸맖" 鍮꾪듃(0x01). ?꾧꺽 ?꾪꽣(ON)?먯꽌留??꾨젅???먭린 議곌굔?쇰줈 ?곗씤??</summary>
|
||||
private static bool IsSkeletonBoneTracked(sRigidBodyData boneData)
|
||||
{
|
||||
return (boneData.Params & 0x01) != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 蹂??곗씠?곌? ?ъ쫰濡?而ㅻ컠?대룄 ?덉쟾?쒖? 寃?ы븳??醫뚰몴 ?좏븳 + 荑쇳꽣?덉뼵 ?뺤긽).
|
||||
/// ?몃옒??鍮꾪듃? 臾닿? ???꾪꽣 OFF?먯꽌???먯긽 蹂몄씠 吏곸쟾 ?ъ쫰瑜???뼱?곗? 紐삵븯寃?留됰뒗 ?덉쟾?μ튂.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user