Fix : 옵티 스크립트 업데이트 및 프리펩 업데이트 및 필터 시스템추가
This commit is contained in:
parent
3745b37e85
commit
4b31d6de06
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.
@ -121,7 +121,9 @@ public class OptitrackStreamingClientEditor : Editor
|
|||||||
runtimeInfo.Clear();
|
runtimeInfo.Clear();
|
||||||
|
|
||||||
AddInfoRow(runtimeInfo, "Server Address", client.ServerAddress);
|
AddInfoRow(runtimeInfo, "Server Address", client.ServerAddress);
|
||||||
AddInfoRow(runtimeInfo, "Local Address", client.LocalAddress);
|
AddInfoRow(runtimeInfo, "Local Address", string.IsNullOrEmpty(client.ResolvedLocalAddress)
|
||||||
|
? client.LocalAddress
|
||||||
|
: client.ResolvedLocalAddress);
|
||||||
AddInfoRow(runtimeInfo, "Connection Type", client.ConnectionType.ToString());
|
AddInfoRow(runtimeInfo, "Connection Type", client.ConnectionType.ToString());
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(client.ServerNatNetVersion))
|
if (!string.IsNullOrEmpty(client.ServerNatNetVersion))
|
||||||
|
|||||||
@ -13,7 +13,6 @@
|
|||||||
<ui:VisualElement class="section">
|
<ui:VisualElement class="section">
|
||||||
<ui:Foldout text="Connection Settings" value="true" class="section-foldout">
|
<ui:Foldout text="Connection Settings" value="true" class="section-foldout">
|
||||||
<uie:PropertyField binding-path="ServerAddress" label="Server Address"/>
|
<uie:PropertyField binding-path="ServerAddress" label="Server Address"/>
|
||||||
<uie:PropertyField binding-path="LocalAddress" label="Local Address"/>
|
|
||||||
<uie:PropertyField binding-path="ConnectionType" label="Connection Type"/>
|
<uie:PropertyField binding-path="ConnectionType" label="Connection Type"/>
|
||||||
<uie:PropertyField binding-path="SkeletonCoordinates" label="Skeleton Coordinates"/>
|
<uie:PropertyField binding-path="SkeletonCoordinates" label="Skeleton Coordinates"/>
|
||||||
<uie:PropertyField binding-path="TMarkersetCoordinates" label="TMarkerset Coordinates"/>
|
<uie:PropertyField binding-path="TMarkersetCoordinates" label="TMarkerset Coordinates"/>
|
||||||
|
|||||||
@ -66,13 +66,14 @@ public class OptitrackFaceDevice : MonoBehaviour
|
|||||||
|
|
||||||
// Resolved device names (1 or 2: base, base_A+_B)
|
// Resolved device names (1 or 2: base, base_A+_B)
|
||||||
private List<string> _resolvedDeviceNames = new List<string>(2);
|
private List<string> _resolvedDeviceNames = new List<string>(2);
|
||||||
|
private Dictionary<string, float> _channelSnapshot = new Dictionary<string, float>();
|
||||||
private float _lastResolveAttempt = -10f;
|
private float _lastResolveAttempt = -10f;
|
||||||
private const float k_ResolveRetrySeconds = 1.0f;
|
private const float k_ResolveRetrySeconds = 1.0f;
|
||||||
|
|
||||||
void Start()
|
void Start()
|
||||||
{
|
{
|
||||||
if (streamingClient == null)
|
if (streamingClient == null)
|
||||||
streamingClient = FindObjectOfType<OptitrackStreamingClient>();
|
streamingClient = FindFirstObjectByType<OptitrackStreamingClient>();
|
||||||
|
|
||||||
if (streamingClient != null && !streamingClient.ReceiveDevices)
|
if (streamingClient != null && !streamingClient.ReceiveDevices)
|
||||||
{
|
{
|
||||||
@ -163,16 +164,10 @@ public class OptitrackFaceDevice : MonoBehaviour
|
|||||||
int appliedBs = 0;
|
int appliedBs = 0;
|
||||||
for (int i = 0; i < _resolvedDeviceNames.Count; ++i)
|
for (int i = 0; i < _resolvedDeviceNames.Count; ++i)
|
||||||
{
|
{
|
||||||
var state = streamingClient.GetLatestDeviceState(_resolvedDeviceNames[i]);
|
if (!streamingClient.FillDeviceChannelSnapshot(_resolvedDeviceNames[i], _channelSnapshot))
|
||||||
if (state == null) continue;
|
continue;
|
||||||
|
|
||||||
// Snapshot to a local list to avoid holding the lock during apply.
|
foreach (var kv in _channelSnapshot)
|
||||||
// ChannelValues is a Dictionary updated by the NatNet thread; iterating
|
|
||||||
// it while it mutates would throw. The streaming client's GetLatestDeviceState
|
|
||||||
// returns the live reference, so we iterate quickly under no lock — accept
|
|
||||||
// occasional torn reads (one frame stale value) over the perf cost of
|
|
||||||
// copying the dict every frame.
|
|
||||||
foreach (var kv in state.ChannelValues)
|
|
||||||
{
|
{
|
||||||
string name = kv.Key;
|
string name = kv.Key;
|
||||||
float v = kv.Value;
|
float v = kv.Value;
|
||||||
|
|||||||
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@ -312,8 +313,17 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
public string ServerAddress = "127.0.0.1";
|
public string ServerAddress = "127.0.0.1";
|
||||||
|
|
||||||
[Tooltip("Must be on the same network as the Streaming IP (Local Interface) in Motive.")]
|
[Tooltip("Must be on the same network as the Streaming IP (Local Interface) in Motive.")]
|
||||||
|
[HideInInspector]
|
||||||
public string LocalAddress = "127.0.0.1";
|
public string LocalAddress = "127.0.0.1";
|
||||||
|
|
||||||
|
[Tooltip("Automatically selects the local IPv4 interface that routes to ServerAddress. LocalAddress is used as a fallback if detection fails.")]
|
||||||
|
[HideInInspector]
|
||||||
|
public bool AutoDetectLocalAddress = true;
|
||||||
|
|
||||||
|
[Tooltip("The local IPv4 address selected for the active connection.")]
|
||||||
|
[HideInInspector]
|
||||||
|
public string ResolvedLocalAddress = "";
|
||||||
|
|
||||||
[Tooltip("Unicast performs subscription reducing your overall data set in some applications.")]
|
[Tooltip("Unicast performs subscription reducing your overall data set in some applications.")]
|
||||||
public ClientConnectionType ConnectionType;
|
public ClientConnectionType ConnectionType;
|
||||||
|
|
||||||
@ -340,7 +350,7 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
[Tooltip("Draws force plate visuals in the viewport for debugging and other uses.")]
|
[Tooltip("Draws force plate visuals in the viewport for debugging and other uses.")]
|
||||||
public bool DrawForcePlates = false;
|
public bool DrawForcePlates = false;
|
||||||
|
|
||||||
[Tooltip("Receive analog device data (e.g. iFacialMocap face streams). Each frame the full sFrameOfMocapData is marshaled (~200KB), so leave off if no devices are needed.")]
|
[Tooltip("Receive analog device data (e.g. iFacialMocap face streams). Leave off if no devices are needed.")]
|
||||||
public bool ReceiveDevices = false;
|
public bool ReceiveDevices = false;
|
||||||
|
|
||||||
[Tooltip("Motive will record when the Unity project is played.")]
|
[Tooltip("Motive will record when the Unity project is played.")]
|
||||||
@ -401,6 +411,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>Reusable staging buffers used to validate a complete skeleton frame before committing it.</summary>
|
||||||
|
private Dictionary<Int32, sRigidBodyData[]> m_skeletonFrameScratch = new Dictionary<Int32, sRigidBodyData[]>();
|
||||||
|
|
||||||
/// <summary>MirrorMode용: 스켈레톤 ID → (boneId → mirrorBoneId) 매핑 캐시.</summary>
|
/// <summary>MirrorMode용: 스켈레톤 ID → (boneId → mirrorBoneId) 매핑 캐시.</summary>
|
||||||
private Dictionary<Int32, Dictionary<Int32, Int32>> m_mirrorBoneIdMaps = new Dictionary<Int32, Dictionary<Int32, Int32>>();
|
private Dictionary<Int32, Dictionary<Int32, Int32>> m_mirrorBoneIdMaps = new Dictionary<Int32, Dictionary<Int32, Int32>>();
|
||||||
|
|
||||||
@ -771,6 +784,7 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
|
|
||||||
for (int attempt = 1; attempt <= maxAttempts; attempt++)
|
for (int attempt = 1; attempt <= maxAttempts; attempt++)
|
||||||
{
|
{
|
||||||
|
m_receivedFrameSinceConnect = false;
|
||||||
float delay = (attempt == 1) ? 1.0f : 5.0f;
|
float delay = (attempt == 1) ? 1.0f : 5.0f;
|
||||||
Debug.Log(string.Format("{0}: 재연결 시도 {1}/{2} — {3}초 후...", GetType().FullName, attempt, maxAttempts, delay), this);
|
Debug.Log(string.Format("{0}: 재연결 시도 {1}/{2} — {3}초 후...", GetType().FullName, attempt, maxAttempts, delay), this);
|
||||||
yield return new WaitForSeconds(delay);
|
yield return new WaitForSeconds(delay);
|
||||||
@ -1103,30 +1117,62 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
/// <returns>The most recent available state, or null if none available.</returns>
|
/// <returns>The most recent available state, or null if none available.</returns>
|
||||||
public OptitrackTMarkersetState GetLatestTMarkersetState(Int32 tmarkersetId)
|
public OptitrackTMarkersetState GetLatestTMarkersetState(Int32 tmarkersetId)
|
||||||
{
|
{
|
||||||
OptitrackTMarkersetState tmarState;
|
|
||||||
|
|
||||||
lock (m_frameDataUpdateLock)
|
lock (m_frameDataUpdateLock)
|
||||||
{
|
{
|
||||||
m_latestTMarkersetStates.TryGetValue(tmarkersetId, out tmarState);
|
if (!m_latestTMarkersetStates.TryGetValue(tmarkersetId, out var source) || source == null)
|
||||||
}
|
return null;
|
||||||
|
|
||||||
if ( MirrorMode && tmarState != null && tmarState.BonePoses != null )
|
var snapshot = new OptitrackTMarkersetState
|
||||||
{
|
|
||||||
var mirrored = new OptitrackTMarkersetState
|
|
||||||
{
|
{
|
||||||
BonePoses = new Dictionary<Int32, OptitrackPose>( tmarState.BonePoses.Count ),
|
BonePoses = CopyPoseDictionary(source.BonePoses, MirrorMode),
|
||||||
LocalBonePoses = tmarState.LocalBonePoses,
|
LocalBonePoses = CopyPoseDictionary(source.LocalBonePoses, MirrorMode),
|
||||||
};
|
};
|
||||||
foreach ( var kvp in tmarState.BonePoses )
|
return snapshot;
|
||||||
mirrored.BonePoses[kvp.Key] = new OptitrackPose
|
|
||||||
{
|
|
||||||
Position = MirrorPosition( kvp.Value.Position ),
|
|
||||||
Orientation = MirrorOrientation( kvp.Value.Orientation ),
|
|
||||||
};
|
|
||||||
tmarState = mirrored;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return tmarState;
|
private static Dictionary<Int32, OptitrackPose> CopyPoseDictionary(
|
||||||
|
Dictionary<Int32, OptitrackPose> source, bool mirror)
|
||||||
|
{
|
||||||
|
var snapshot = new Dictionary<Int32, OptitrackPose>(source != null ? source.Count : 0);
|
||||||
|
if (source == null)
|
||||||
|
return snapshot;
|
||||||
|
|
||||||
|
foreach (var kvp in source)
|
||||||
|
{
|
||||||
|
snapshot[kvp.Key] = new OptitrackPose
|
||||||
|
{
|
||||||
|
Position = mirror ? MirrorPosition(kvp.Value.Position) : kvp.Value.Position,
|
||||||
|
Orientation = mirror ? MirrorOrientation(kvp.Value.Orientation) : kvp.Value.Orientation,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies trained markerset poses while holding the NatNet frame lock.
|
||||||
|
/// </summary>
|
||||||
|
public bool FillTMarkersetSnapshot(Int32 tmarkersetId, bool useLocalBonePoses,
|
||||||
|
Dictionary<Int32, Vector3> posOut, Dictionary<Int32, Quaternion> oriOut)
|
||||||
|
{
|
||||||
|
lock (m_frameDataUpdateLock)
|
||||||
|
{
|
||||||
|
if (!m_latestTMarkersetStates.TryGetValue(tmarkersetId, out var state) || state == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var source = useLocalBonePoses ? state.LocalBonePoses : state.BonePoses;
|
||||||
|
if (source == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
posOut.Clear();
|
||||||
|
oriOut.Clear();
|
||||||
|
foreach (var kvp in source)
|
||||||
|
{
|
||||||
|
posOut[kvp.Key] = MirrorMode ? MirrorPosition(kvp.Value.Position) : kvp.Value.Position;
|
||||||
|
oriOut[kvp.Key] = MirrorMode ? MirrorOrientation(kvp.Value.Orientation) : kvp.Value.Orientation;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1550,42 +1596,38 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// - Device Definitions (generic analog: iFacialMocap, NIDAQ, etc.)
|
// - Device Definitions (generic analog: iFacialMocap, NIDAQ, etc.)
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
if (m_dataDescs.DeviceDescriptions != null)
|
lock (m_frameDataUpdateLock)
|
||||||
{
|
{
|
||||||
for (int devIdx = 0; devIdx < m_dataDescs.DeviceDescriptions.Count; ++devIdx)
|
if (m_dataDescs.DeviceDescriptions != null)
|
||||||
{
|
{
|
||||||
sDeviceDescription dev = m_dataDescs.DeviceDescriptions[devIdx];
|
for (int devIdx = 0; devIdx < m_dataDescs.DeviceDescriptions.Count; ++devIdx)
|
||||||
|
|
||||||
// dev.ChannelNames is a fixed-size 32 array (NatNet C# wrapper limit).
|
|
||||||
// Plugin can register devices with > 32 channels (Motive stores them all),
|
|
||||||
// but only the first 32 names survive the wrapper marshaling.
|
|
||||||
// IMPORTANT: ChannelCount is preserved as the full count reported by Motive
|
|
||||||
// (could be > 32, e.g. iFacialMocap primary = 54). Downstream consumers
|
|
||||||
// need this to match the wire frame's nCh (which may also be > 32 if Motive
|
|
||||||
// doesn't truncate broadcast). ChannelNames is clamped to the marshaled
|
|
||||||
// array length to avoid IndexOutOfRangeException.
|
|
||||||
int marshalNameCount = (dev.ChannelNames != null)
|
|
||||||
? Math.Min(dev.ChannelCount, dev.ChannelNames.Length)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
OptitrackDeviceDefinition deviceDef = new OptitrackDeviceDefinition
|
|
||||||
{
|
{
|
||||||
Id = dev.Id,
|
sDeviceDescription dev = m_dataDescs.DeviceDescriptions[devIdx];
|
||||||
Name = dev.Name,
|
|
||||||
SerialNumber = dev.SerialNo,
|
|
||||||
DeviceType = dev.DeviceType,
|
|
||||||
ChannelDataType = dev.ChannelDataType,
|
|
||||||
ChannelCount = dev.ChannelCount,
|
|
||||||
ChannelNames = new List<string>(marshalNameCount),
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; i < marshalNameCount; ++i)
|
// dev.ChannelNames is a fixed-size 32 array (NatNet C# wrapper limit).
|
||||||
{
|
int marshalNameCount = (dev.ChannelNames != null)
|
||||||
deviceDef.ChannelNames.Add(dev.ChannelNames[i]);
|
? Math.Min(dev.ChannelCount, dev.ChannelNames.Length)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
OptitrackDeviceDefinition deviceDef = new OptitrackDeviceDefinition
|
||||||
|
{
|
||||||
|
Id = dev.Id,
|
||||||
|
Name = dev.Name,
|
||||||
|
SerialNumber = dev.SerialNo,
|
||||||
|
DeviceType = dev.DeviceType,
|
||||||
|
ChannelDataType = dev.ChannelDataType,
|
||||||
|
ChannelCount = dev.ChannelCount,
|
||||||
|
ChannelNames = new List<string>(marshalNameCount),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < marshalNameCount; ++i)
|
||||||
|
{
|
||||||
|
deviceDef.ChannelNames.Add(dev.ChannelNames[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_deviceDefinitions.Add(deviceDef);
|
||||||
|
m_deviceNameToId[deviceDef.Name] = deviceDef.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_deviceDefinitions.Add(deviceDef);
|
|
||||||
m_deviceNameToId[deviceDef.Name] = deviceDef.Id;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1725,6 +1767,7 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
|
|
||||||
private System.Collections.IEnumerator ConnectCoroutine()
|
private System.Collections.IEnumerator ConnectCoroutine()
|
||||||
{
|
{
|
||||||
|
m_receivedFrameSinceConnect = false;
|
||||||
IPAddress serverAddr;
|
IPAddress serverAddr;
|
||||||
IPAddress localAddr;
|
IPAddress localAddr;
|
||||||
NatNetConnectionType connType;
|
NatNetConnectionType connType;
|
||||||
@ -1732,7 +1775,8 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
serverAddr = IPAddress.Parse( ServerAddress );
|
serverAddr = IPAddress.Parse( ServerAddress );
|
||||||
localAddr = IPAddress.Parse( LocalAddress );
|
localAddr = ResolveLocalAddress( serverAddr );
|
||||||
|
ResolvedLocalAddress = localAddr.ToString();
|
||||||
connType = ConnectionType == ClientConnectionType.Unicast
|
connType = ConnectionType == ClientConnectionType.Unicast
|
||||||
? NatNetConnectionType.NatNetConnectionType_Unicast
|
? NatNetConnectionType.NatNetConnectionType_Unicast
|
||||||
: NatNetConnectionType.NatNetConnectionType_Multicast;
|
: NatNetConnectionType.NatNetConnectionType_Multicast;
|
||||||
@ -1795,6 +1839,8 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
SubscribeSkeleton(skel.Value, skel.Key);
|
SubscribeSkeleton(skel.Value, skel.Key);
|
||||||
foreach (KeyValuePair<string, MonoBehaviour> tmark in m_tmarkersets) // trained markerset added
|
foreach (KeyValuePair<string, MonoBehaviour> tmark in m_tmarkersets) // trained markerset added
|
||||||
SubscribeTMarkerset(tmark.Value, tmark.Key);
|
SubscribeTMarkerset(tmark.Value, tmark.Key);
|
||||||
|
if (DrawTMarkersetMarkers)
|
||||||
|
SubscribeTMarkMarkers();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 재연결 중에는 녹화 시작 스킵 — Motive의 Take 파일 반복 열기/닫기 방지
|
// 재연결 중에는 녹화 시작 스킵 — Motive의 Take 파일 반복 열기/닫기 방지
|
||||||
@ -1954,13 +2000,6 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
result = NaturalPoint.NatNetLib.NativeMethods.NatNet_Frame_GetRigidBody( pFrame, rbIdx, out rbData );
|
result = NaturalPoint.NatNetLib.NativeMethods.NatNet_Frame_GetRigidBody( pFrame, rbIdx, out rbData );
|
||||||
NatNetException.ThrowIfNotOK( result, "NatNet_Frame_GetRigidBody failed." );
|
NatNetException.ThrowIfNotOK( result, "NatNet_Frame_GetRigidBody failed." );
|
||||||
|
|
||||||
bool bTrackedThisFrame = (rbData.Params & 0x01) != 0;
|
|
||||||
|
|
||||||
if (bTrackedThisFrame == false)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we have a state corresponding to this rigid body ID.
|
// Ensure we have a state corresponding to this rigid body ID.
|
||||||
OptitrackRigidBodyState rbState = GetOrCreateRigidBodyState( rbData.Id );
|
OptitrackRigidBodyState rbState = GetOrCreateRigidBodyState( rbData.Id );
|
||||||
RigidBodyDataToState(rbData, OptitrackHiResTimer.Now(), rbState);
|
RigidBodyDataToState(rbData, OptitrackHiResTimer.Now(), rbState);
|
||||||
@ -1982,7 +2021,6 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
|
|
||||||
// Ensure we have a state corresponding to this skeleton ID.
|
// Ensure we have a state corresponding to this skeleton ID.
|
||||||
OptitrackSkeletonState skelState = GetOrCreateSkeletonState( skeletonId );
|
OptitrackSkeletonState skelState = GetOrCreateSkeletonState( skeletonId );
|
||||||
skelState.DeliveryTimestamp = frameTimestamp;
|
|
||||||
|
|
||||||
// Enumerate this skeleton's bone rigid bodies.
|
// Enumerate this skeleton's bone rigid bodies.
|
||||||
Int32 skelRbCount;
|
Int32 skelRbCount;
|
||||||
@ -2002,17 +2040,47 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Motive can occasionally emit an empty or partial skeleton payload. Do not let
|
||||||
|
// one bad bone overwrite the previous valid pose: stage and validate first.
|
||||||
|
if (skelRbCount != skelDef.Bones.Count)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sRigidBodyData[] stagedBones = GetSkeletonFrameScratch(skeletonId, skelRbCount);
|
||||||
|
bool isValidSkeletonFrame = true;
|
||||||
for (int boneIdx = 0; boneIdx < skelRbCount; ++boneIdx)
|
for (int boneIdx = 0; boneIdx < skelRbCount; ++boneIdx)
|
||||||
{
|
{
|
||||||
sRigidBodyData boneData = new sRigidBodyData();
|
result = NaturalPoint.NatNetLib.NativeMethods.NatNet_Frame_Skeleton_GetRigidBody( pFrame, skelIdx, boneIdx, out stagedBones[boneIdx] );
|
||||||
result = NaturalPoint.NatNetLib.NativeMethods.NatNet_Frame_Skeleton_GetRigidBody( pFrame, skelIdx, boneIdx, out boneData );
|
|
||||||
NatNetException.ThrowIfNotOK( result, "NatNet_Frame_Skeleton_GetRigidBody failed." );
|
NatNetException.ThrowIfNotOK( result, "NatNet_Frame_Skeleton_GetRigidBody failed." );
|
||||||
|
|
||||||
|
sRigidBodyData boneData = stagedBones[boneIdx];
|
||||||
// In the context of frame data (unlike in the definition data), this ID value is a
|
// 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.
|
// packed composite of both the asset/entity (skeleton) ID and member (bone) ID.
|
||||||
Int32 boneSkelId, boneId;
|
Int32 boneSkelId, boneId;
|
||||||
NaturalPoint.NatNetLib.NativeMethods.NatNet_DecodeID( boneData.Id, out boneSkelId, out boneId );
|
NaturalPoint.NatNetLib.NativeMethods.NatNet_DecodeID( boneData.Id, out boneSkelId, out boneId );
|
||||||
|
|
||||||
|
if (boneSkelId != skeletonId ||
|
||||||
|
!skelDef.BoneIdToParentIdMap.ContainsKey(boneId) ||
|
||||||
|
!IsValidSkeletonBoneData(boneData))
|
||||||
|
{
|
||||||
|
isValidSkeletonFrame = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isValidSkeletonFrame)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit global transforms only after the whole payload has passed validation.
|
||||||
|
for (int boneIdx = 0; boneIdx < skelRbCount; ++boneIdx)
|
||||||
|
{
|
||||||
|
sRigidBodyData boneData = stagedBones[boneIdx];
|
||||||
|
Int32 boneSkelId, boneId;
|
||||||
|
NaturalPoint.NatNetLib.NativeMethods.NatNet_DecodeID( boneData.Id, out boneSkelId, out boneId );
|
||||||
|
|
||||||
// TODO: Could pre-populate this map when the definitions are retrieved.
|
// TODO: Could pre-populate this map when the definitions are retrieved.
|
||||||
// Should never allocate after the first frame, at least.
|
// Should never allocate after the first frame, at least.
|
||||||
if (skelState.BonePoses.ContainsKey( boneId ) == false)
|
if (skelState.BonePoses.ContainsKey( boneId ) == false)
|
||||||
@ -2029,7 +2097,17 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
Quaternion boneOri = new Quaternion(-boneData.QX, boneData.QY, boneData.QZ, -boneData.QW);
|
Quaternion boneOri = new Quaternion(-boneData.QX, boneData.QY, boneData.QZ, -boneData.QW);
|
||||||
skelState.BonePoses[boneId].Position = bonePos;
|
skelState.BonePoses[boneId].Position = bonePos;
|
||||||
skelState.BonePoses[boneId].Orientation = boneOri;
|
skelState.BonePoses[boneId].Orientation = boneOri;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive locals in a second pass so parent order in the payload does not matter.
|
||||||
|
for (int boneIdx = 0; boneIdx < skelRbCount; ++boneIdx)
|
||||||
|
{
|
||||||
|
sRigidBodyData boneData = stagedBones[boneIdx];
|
||||||
|
Int32 boneSkelId, boneId;
|
||||||
|
NaturalPoint.NatNetLib.NativeMethods.NatNet_DecodeID( boneData.Id, out boneSkelId, out boneId );
|
||||||
|
|
||||||
|
Vector3 bonePos = skelState.BonePoses[boneId].Position;
|
||||||
|
Quaternion boneOri = skelState.BonePoses[boneId].Orientation;
|
||||||
Vector3 parentBonePos = new Vector3(0,0,0);
|
Vector3 parentBonePos = new Vector3(0,0,0);
|
||||||
Quaternion parentBoneOri = new Quaternion(0,0,0,1);
|
Quaternion parentBoneOri = new Quaternion(0,0,0,1);
|
||||||
|
|
||||||
@ -2042,6 +2120,8 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
skelState.LocalBonePoses[boneId].Position = bonePos - parentBonePos;
|
skelState.LocalBonePoses[boneId].Position = bonePos - parentBonePos;
|
||||||
skelState.LocalBonePoses[boneId].Orientation = Quaternion.Inverse(parentBoneOri) * boneOri;
|
skelState.LocalBonePoses[boneId].Orientation = Quaternion.Inverse(parentBoneOri) * boneOri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
skelState.DeliveryTimestamp = frameTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------
|
// -----------------------------------------------------
|
||||||
@ -2050,6 +2130,7 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
// m_dataDescs를 로컬 변수로 캡처: UpdateDefinitions()(메인 스레드)가 참조를 교체해도 안전
|
// m_dataDescs를 로컬 변수로 캡처: UpdateDefinitions()(메인 스레드)가 참조를 교체해도 안전
|
||||||
// null 체크: SkipDataDescriptions=true 또는 UpdateDefinitions() 미완료/실패 시 크래시 방지
|
// null 체크: SkipDataDescriptions=true 또는 UpdateDefinitions() 미완료/실패 시 크래시 방지
|
||||||
var dataDescsSnapshot = m_dataDescs;
|
var dataDescsSnapshot = m_dataDescs;
|
||||||
|
m_latestTMarkMarkerStates.Clear();
|
||||||
if (dataDescsSnapshot != null && dataDescsSnapshot.AssetDescriptions != null)
|
if (dataDescsSnapshot != null && dataDescsSnapshot.AssetDescriptions != null)
|
||||||
for (int tmarkIdx = 0; tmarkIdx < dataDescsSnapshot.AssetDescriptions.Count; ++tmarkIdx)
|
for (int tmarkIdx = 0; tmarkIdx < dataDescsSnapshot.AssetDescriptions.Count; ++tmarkIdx)
|
||||||
{
|
{
|
||||||
@ -2112,8 +2193,6 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
NatNetException.ThrowIfNotOK(result, "NatNet_Frame_TMarkerset_GetMarkerCount failed.");*/
|
NatNetException.ThrowIfNotOK(result, "NatNet_Frame_TMarkerset_GetMarkerCount failed.");*/
|
||||||
//Debug.Log("tmark marker count: " + tmarkMarkerCount); // working finally
|
//Debug.Log("tmark marker count: " + tmarkMarkerCount); // working finally
|
||||||
|
|
||||||
m_latestTMarkMarkerStates.Clear();
|
|
||||||
|
|
||||||
// Update Trained Markerset Marker data
|
// Update Trained Markerset Marker data
|
||||||
for (int markerIdx = 0; markerIdx < tmarkMarkerCount; ++markerIdx)
|
for (int markerIdx = 0; markerIdx < tmarkMarkerCount; ++markerIdx)
|
||||||
{
|
{
|
||||||
@ -2232,10 +2311,14 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
devState.DeliveryTimestamp = frameTimestamp;
|
devState.DeliveryTimestamp = frameTimestamp;
|
||||||
|
|
||||||
int chCount = Math.Min(devData.ChannelCount, devDef.ChannelCount);
|
int chCount = Math.Min(devData.ChannelCount, devDef.ChannelCount);
|
||||||
|
chCount = Math.Min(chCount, devData.ChannelData != null ? devData.ChannelData.Length : 0);
|
||||||
|
chCount = Math.Min(chCount, devDef.ChannelNames != null ? devDef.ChannelNames.Count : 0);
|
||||||
for (int ch = 0; ch < chCount; ++ch)
|
for (int ch = 0; ch < chCount; ++ch)
|
||||||
{
|
{
|
||||||
// Use subframe 0 — face data is one sample per frame.
|
// Use subframe 0 — face data is one sample per frame.
|
||||||
sAnalogChannelData chData = devData.ChannelData[ch];
|
sAnalogChannelData chData = devData.ChannelData[ch];
|
||||||
|
if (chData.Values == null || chData.Values.Length == 0)
|
||||||
|
continue;
|
||||||
float value = chData.Values[0];
|
float value = chData.Values[0];
|
||||||
devState.ChannelValues[devDef.ChannelNames[ch]] = value;
|
devState.ChannelValues[devDef.ChannelNames[ch]] = value;
|
||||||
}
|
}
|
||||||
@ -2270,7 +2353,7 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
Monitor.Enter( m_frameDataUpdateLock );
|
Monitor.Enter( m_frameDataUpdateLock );
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return m_latestDeviceStates.TryGetValue(deviceId, out var s) ? s : null;
|
return m_latestDeviceStates.TryGetValue(deviceId, out var state) ? CopyDeviceState(state) : null;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -2286,8 +2369,8 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (m_deviceNameToId.TryGetValue(deviceName, out Int32 id) &&
|
if (m_deviceNameToId.TryGetValue(deviceName, out Int32 id) &&
|
||||||
m_latestDeviceStates.TryGetValue(id, out var s))
|
m_latestDeviceStates.TryGetValue(id, out var state))
|
||||||
return s;
|
return CopyDeviceState(state);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@ -2296,6 +2379,38 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static OptitrackDeviceState CopyDeviceState(OptitrackDeviceState source)
|
||||||
|
{
|
||||||
|
if (source == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new OptitrackDeviceState
|
||||||
|
{
|
||||||
|
Id = source.Id,
|
||||||
|
Name = source.Name,
|
||||||
|
FrameNumber = source.FrameNumber,
|
||||||
|
DeliveryTimestamp = source.DeliveryTimestamp,
|
||||||
|
ChannelValues = new Dictionary<string, float>(source.ChannelValues),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Copies a device's latest channel values into a caller-owned dictionary.</summary>
|
||||||
|
public bool FillDeviceChannelSnapshot(string deviceName, Dictionary<string, float> channelValuesOut)
|
||||||
|
{
|
||||||
|
lock (m_frameDataUpdateLock)
|
||||||
|
{
|
||||||
|
channelValuesOut.Clear();
|
||||||
|
if (!m_deviceNameToId.TryGetValue(deviceName, out Int32 id) ||
|
||||||
|
!m_latestDeviceStates.TryGetValue(id, out var state) ||
|
||||||
|
state == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (var kvp in state.ChannelValues)
|
||||||
|
channelValuesOut[kvp.Key] = kvp.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>Returns a snapshot of currently registered device definitions.</summary>
|
/// <summary>Returns a snapshot of currently registered device definitions.</summary>
|
||||||
public List<OptitrackDeviceDefinition> GetDeviceDefinitions()
|
public List<OptitrackDeviceDefinition> GetDeviceDefinitions()
|
||||||
@ -2487,7 +2602,7 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
{
|
{
|
||||||
if (m_client != null && ConnectionType == ClientConnectionType.Unicast)
|
if (m_client != null && ConnectionType == ClientConnectionType.Unicast)
|
||||||
{
|
{
|
||||||
bool subscribeSucceeded4 = m_client.RequestCommand("SubscribeToData, TrainedMarkersetMarkers,All", 2000, 3);
|
bool subscribeSucceeded4 = m_client.RequestCommand("SubscribeToData,TrainedMarkersetMarkers,All", 2000, 3);
|
||||||
//Debug.Log("TMMarkers: " + subscribeSucceeded4);
|
//Debug.Log("TMMarkers: " + subscribeSucceeded4);
|
||||||
|
|
||||||
// Log a warning on the first failure.
|
// Log a warning on the first failure.
|
||||||
@ -2557,6 +2672,71 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IPAddress ResolveLocalAddress(IPAddress serverAddress)
|
||||||
|
{
|
||||||
|
if (!AutoDetectLocalAddress)
|
||||||
|
return IPAddress.Parse(LocalAddress);
|
||||||
|
|
||||||
|
if (IPAddress.IsLoopback(serverAddress))
|
||||||
|
return IPAddress.Loopback;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
|
||||||
|
{
|
||||||
|
socket.Connect(new IPEndPoint(serverAddress, NatNetConstants.DefaultCommandPort));
|
||||||
|
if (socket.LocalEndPoint is IPEndPoint endpoint &&
|
||||||
|
endpoint.Address.AddressFamily == AddressFamily.InterNetwork &&
|
||||||
|
!IPAddress.Any.Equals(endpoint.Address))
|
||||||
|
{
|
||||||
|
LocalAddress = endpoint.Address.ToString();
|
||||||
|
return endpoint.Address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogWarning(GetType().FullName + ": 로컬 주소 자동 탐지 실패. LocalAddress fallback을 사용합니다. " + ex.Message, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return IPAddress.Parse(LocalAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sRigidBodyData[] GetSkeletonFrameScratch(Int32 skeletonId, Int32 boneCount)
|
||||||
|
{
|
||||||
|
if (!m_skeletonFrameScratch.TryGetValue(skeletonId, out var scratch) || scratch.Length != boneCount)
|
||||||
|
{
|
||||||
|
scratch = new sRigidBodyData[boneCount];
|
||||||
|
m_skeletonFrameScratch[skeletonId] = scratch;
|
||||||
|
}
|
||||||
|
return scratch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValidSkeletonBoneData(sRigidBodyData boneData)
|
||||||
|
{
|
||||||
|
if ((boneData.Params & 0x01) == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
float quaternionMagnitudeSquared =
|
||||||
|
boneData.QX * boneData.QX +
|
||||||
|
boneData.QY * boneData.QY +
|
||||||
|
boneData.QZ * boneData.QZ +
|
||||||
|
boneData.QW * boneData.QW;
|
||||||
|
|
||||||
|
return IsFinite(boneData.X) &&
|
||||||
|
IsFinite(boneData.Y) &&
|
||||||
|
IsFinite(boneData.Z) &&
|
||||||
|
IsFinite(boneData.QX) &&
|
||||||
|
IsFinite(boneData.QY) &&
|
||||||
|
IsFinite(boneData.QZ) &&
|
||||||
|
IsFinite(boneData.QW) &&
|
||||||
|
quaternionMagnitudeSquared > 0.000001f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsFinite(float value)
|
||||||
|
{
|
||||||
|
return !float.IsNaN(value) && !float.IsInfinity(value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the <see cref="OptitrackRigidBodyState"/> corresponding to the provided <paramref name="rigidBodyId"/>.
|
/// Returns the <see cref="OptitrackRigidBodyState"/> corresponding to the provided <paramref name="rigidBodyId"/>.
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright © 2016 NaturalPoint Inc.
|
Copyright © 2016 NaturalPoint Inc.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -53,6 +53,8 @@ public class OptitrackTrainedMarkerset : MonoBehaviour
|
|||||||
private Dictionary<string, Transform> m_cachedBoneNameMap = new Dictionary<string, Transform>(); // Optitrack's skeleton's bone names
|
private Dictionary<string, Transform> m_cachedBoneNameMap = new Dictionary<string, Transform>(); // Optitrack's skeleton's bone names
|
||||||
|
|
||||||
private Dictionary<Transform, Transform> m_transformMap = new Dictionary<Transform, Transform>();
|
private Dictionary<Transform, Transform> m_transformMap = new Dictionary<Transform, Transform>();
|
||||||
|
private Dictionary<Int32, Vector3> m_snapshotPositions = new Dictionary<Int32, Vector3>();
|
||||||
|
private Dictionary<Int32, Quaternion> m_snapshotOrientations = new Dictionary<Int32, Quaternion>();
|
||||||
|
|
||||||
#endregion Private fields
|
#endregion Private fields
|
||||||
|
|
||||||
@ -115,36 +117,29 @@ public class OptitrackTrainedMarkerset : MonoBehaviour
|
|||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
OptitrackTMarkersetState tmarState = StreamingClient.GetLatestTMarkersetState( m_tmarkersetDef.Id );
|
bool useLocalBonePoses = StreamingClient.TMarkersetCoordinates == StreamingCoordinatesValues.Global;
|
||||||
if (tmarState != null)
|
if (StreamingClient.FillTMarkersetSnapshot(m_tmarkersetDef.Id, useLocalBonePoses,
|
||||||
|
m_snapshotPositions, m_snapshotOrientations))
|
||||||
{
|
{
|
||||||
// Update the transforms of the bone GameObjects.
|
// Update the transforms of the bone GameObjects.
|
||||||
for (int i = 0; i < m_tmarkersetDef.Bones.Count; ++i)
|
for (int i = 0; i < m_tmarkersetDef.Bones.Count; ++i)
|
||||||
{
|
{
|
||||||
Int32 boneId = m_tmarkersetDef.Bones[i].Id;
|
Int32 boneId = m_tmarkersetDef.Bones[i].Id;
|
||||||
|
|
||||||
OptitrackPose bonePose;
|
|
||||||
GameObject boneObject;
|
GameObject boneObject;
|
||||||
|
|
||||||
bool foundPose = false;
|
|
||||||
if (StreamingClient.TMarkersetCoordinates == StreamingCoordinatesValues.Global)
|
|
||||||
{
|
|
||||||
// Use global tmarkerset coordinates
|
|
||||||
foundPose = tmarState.LocalBonePoses.TryGetValue(boneId, out bonePose);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Use local tmarkerset coordinates
|
|
||||||
foundPose = tmarState.BonePoses.TryGetValue(boneId, out bonePose);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool foundObject = m_boneObjectMap.TryGetValue(boneId, out boneObject);
|
bool foundObject = m_boneObjectMap.TryGetValue(boneId, out boneObject);
|
||||||
if (foundPose && foundObject)
|
if (foundObject &&
|
||||||
|
m_snapshotPositions.TryGetValue(boneId, out Vector3 position) &&
|
||||||
|
m_snapshotOrientations.TryGetValue(boneId, out Quaternion orientation))
|
||||||
{
|
{
|
||||||
boneObject.transform.localPosition = bonePose.Position;
|
boneObject.transform.localPosition = position;
|
||||||
boneObject.transform.localRotation = bonePose.Orientation;
|
boneObject.transform.localRotation = orientation;
|
||||||
m_transformMap[boneObject.transform].transform.localPosition = bonePose.Position;
|
if (m_transformMap.TryGetValue(boneObject.transform, out Transform target))
|
||||||
m_transformMap[boneObject.transform].transform.localRotation = bonePose.Orientation;
|
{
|
||||||
|
target.localPosition = position;
|
||||||
|
target.localRotation = orientation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user