Optimize: OptiTrack 플러그인 Motive 부하 최적화
- SetProperty 원격 명령을 최초 연결에서만 전송, 재연결 시 스킵 - 정의 재조회(UpdateDefinitions) 쿨다운 5초→15초, 최대 10회 제한 - DrawMarkers/DrawTMarkersetMarkers/DrawCameras/DrawForcePlates 락 범위 축소 - RecordOnPlay 재연결 루프에서 녹화 시작/종료 스킵, 성공 후에만 재시작 - SubscribeMarkers 실패 시 10초 쿨다운 (매 프레임 재시도 방지) - OnNatNetFrameReceived 내 GetSkeletonDefinitionById를 본 루프 밖으로 이동 - GetMarkerName assetID→이름 캐시 도입 (3중 선형 탐색 제거) - OptitrackRigidBody Update+OnBeforeRender 이중 NatNet 호출 제거 (캐싱) - OptitrackSkeletonAnimator_Mingle 스켈레톤 체크 주기 0.1초→1초+지수 백오프 - ToggleRecording 녹화 상태 추적으로 2중 명령 제거 - ResetStreamingSubscriptions 중복 명령 축소 - GetLatestTMarkMarkerStates 디버그 로그 잔재 제거 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f0b6a55649
commit
5aa805e16a
@ -39,7 +39,12 @@ public class OptitrackRigidBody : MonoBehaviour
|
|||||||
private bool m_isRigidBodyFound = false;
|
private bool m_isRigidBodyFound = false;
|
||||||
private int m_resolvedRigidBodyId = -1;
|
private int m_resolvedRigidBodyId = -1;
|
||||||
|
|
||||||
private const float k_RetryInterval = 1.0f;
|
private const float k_RetryInterval = 3.0f; // Motive 재조회 부하 완화 (기존 1초 → 3초)
|
||||||
|
|
||||||
|
// Update()에서 조회한 포즈를 캐시 — OnBeforeRender()에서 재사용하여 이중 NatNet 호출 방지
|
||||||
|
private Vector3 m_cachedPosition;
|
||||||
|
private Quaternion m_cachedRotation;
|
||||||
|
private bool m_hasCachedPose = false;
|
||||||
|
|
||||||
public bool isRigidBodyFound
|
public bool isRigidBodyFound
|
||||||
{
|
{
|
||||||
@ -129,37 +134,35 @@ public class OptitrackRigidBody : MonoBehaviour
|
|||||||
if (m_streamingClient == null || m_resolvedRigidBodyId == -1)
|
if (m_streamingClient == null || m_resolvedRigidBodyId == -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// NatNet 호출은 Update()에서 1회만 — 캐시하여 OnBeforeRender()에서 재사용
|
||||||
OptitrackRigidBodyState rbState = m_streamingClient.GetLatestRigidBodyState(m_resolvedRigidBodyId, useNetworkCompensation);
|
OptitrackRigidBodyState rbState = m_streamingClient.GetLatestRigidBodyState(m_resolvedRigidBodyId, useNetworkCompensation);
|
||||||
if (rbState != null)
|
if (rbState != null)
|
||||||
{
|
{
|
||||||
m_isRigidBodyFound = rbState.IsTracked;
|
m_isRigidBodyFound = rbState.IsTracked;
|
||||||
if (m_isRigidBodyFound)
|
if (m_isRigidBodyFound)
|
||||||
{
|
{
|
||||||
transform.localPosition = rbState.Pose.Position;
|
m_cachedPosition = rbState.Pose.Position;
|
||||||
transform.localRotation = rbState.Pose.Orientation;
|
m_cachedRotation = rbState.Pose.Orientation;
|
||||||
|
m_hasCachedPose = true;
|
||||||
|
transform.localPosition = m_cachedPosition;
|
||||||
|
transform.localRotation = m_cachedRotation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_isRigidBodyFound = false;
|
m_isRigidBodyFound = false;
|
||||||
|
m_hasCachedPose = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void UpdatePose()
|
void UpdatePose()
|
||||||
{
|
{
|
||||||
if (m_streamingClient == null || m_resolvedRigidBodyId == -1)
|
// OnBeforeRender용: NatNet 재호출 없이 캐시된 포즈 적용 (렌더링 직전 최신 적용)
|
||||||
return;
|
if (m_hasCachedPose && m_isRigidBodyFound)
|
||||||
|
|
||||||
OptitrackRigidBodyState rbState = m_streamingClient.GetLatestRigidBodyState(m_resolvedRigidBodyId, useNetworkCompensation);
|
|
||||||
if (rbState != null)
|
|
||||||
{
|
{
|
||||||
m_isRigidBodyFound = rbState.IsTracked;
|
transform.localPosition = m_cachedPosition;
|
||||||
if (m_isRigidBodyFound)
|
transform.localRotation = m_cachedRotation;
|
||||||
{
|
|
||||||
transform.localPosition = rbState.Pose.Position;
|
|
||||||
transform.localRotation = rbState.Pose.Orientation;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,12 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
|
|||||||
[HideInInspector]
|
[HideInInspector]
|
||||||
public bool isSkeletonFound = false;
|
public bool isSkeletonFound = false;
|
||||||
|
|
||||||
private const float k_SkeletonCheckInterval = 0.1f;
|
// 스켈레톤 연결 체크 주기: 발견 전 1초(+백오프), 발견 후 2초 (기존 0.1초에서 완화)
|
||||||
|
private const float k_SkeletonCheckIntervalDefault = 1.0f;
|
||||||
|
private const float k_SkeletonCheckIntervalConnected = 2.0f;
|
||||||
|
private const float k_SkeletonCheckIntervalMax = 10.0f; // 백오프 최대치
|
||||||
|
private float m_currentCheckInterval = k_SkeletonCheckIntervalDefault;
|
||||||
|
private int m_definitionRefreshRequests = 0; // 연속 재조회 요청 횟수 (백오프 계산용)
|
||||||
|
|
||||||
private Coroutine m_checkCoroutine;
|
private Coroutine m_checkCoroutine;
|
||||||
private bool m_initialized = false;
|
private bool m_initialized = false;
|
||||||
@ -518,16 +523,38 @@ public class OptitrackSkeletonAnimator_Mingle : MonoBehaviour
|
|||||||
RebuildBoneIdMapping();
|
RebuildBoneIdMapping();
|
||||||
Debug.Log($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}' 연결 성공");
|
Debug.Log($"[OptiTrack] 스켈레톤 '{SkeletonAssetName}' 연결 성공");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 발견됨 → 체크 주기 완화, 백오프 리셋
|
||||||
|
if (isSkeletonFound)
|
||||||
|
{
|
||||||
|
m_currentCheckInterval = k_SkeletonCheckIntervalConnected;
|
||||||
|
m_definitionRefreshRequests = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 스켈레톤 정의를 찾지 못함 → 서버에 정의 재조회 요청
|
// 스켈레톤 정의를 찾지 못함 → 지수 백오프로 재조회 요청 빈도 제한
|
||||||
isSkeletonFound = false;
|
isSkeletonFound = false;
|
||||||
|
m_definitionRefreshRequests++;
|
||||||
|
|
||||||
|
// 처음 3회까지는 즉시 요청, 이후 백오프 적용
|
||||||
|
if (m_definitionRefreshRequests <= 3)
|
||||||
|
{
|
||||||
|
StreamingClient.RequestDefinitionRefresh();
|
||||||
|
m_currentCheckInterval = k_SkeletonCheckIntervalDefault;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 3회 초과 시 점진적 백오프 (2초 → 4초 → 8초 → 10초 상한)
|
||||||
|
m_currentCheckInterval = Mathf.Min(
|
||||||
|
k_SkeletonCheckIntervalDefault * Mathf.Pow(2f, m_definitionRefreshRequests - 3),
|
||||||
|
k_SkeletonCheckIntervalMax);
|
||||||
StreamingClient.RequestDefinitionRefresh();
|
StreamingClient.RequestDefinitionRefresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
yield return new WaitForSeconds(k_SkeletonCheckInterval);
|
yield return new WaitForSeconds(m_currentCheckInterval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -345,6 +345,8 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
private bool m_hasDrawnCameras = false;
|
private bool m_hasDrawnCameras = false;
|
||||||
private bool m_hasDrawnForcePlates = false;
|
private bool m_hasDrawnForcePlates = false;
|
||||||
private bool m_subscribedToMarkers = false;
|
private bool m_subscribedToMarkers = false;
|
||||||
|
private float m_markerSubscribeRetryCooldown = 0f; // 마커 구독 실패 시 재시도 쿨다운
|
||||||
|
private const float k_MarkerSubscribeRetryInterval = 10f; // 10초 간격 재시도
|
||||||
|
|
||||||
private OptitrackHiResTimer.Timestamp m_lastFrameDeliveryTimestamp;
|
private OptitrackHiResTimer.Timestamp m_lastFrameDeliveryTimestamp;
|
||||||
private Coroutine m_connectionHealthCoroutine = null;
|
private Coroutine m_connectionHealthCoroutine = null;
|
||||||
@ -398,12 +400,25 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private object m_frameDataUpdateLock = new object();
|
private object m_frameDataUpdateLock = new object();
|
||||||
|
|
||||||
|
/// <summary>assetID → assetName 캐시 (GetMarkerName 내 3중 선형 탐색 제거용).</summary>
|
||||||
|
private Dictionary<Int32, string> m_assetIdToNameCache = new Dictionary<Int32, string>();
|
||||||
|
|
||||||
// 중간에 새 스켈레톤이 생성된 경우 정의 재조회 플래그 (NatNet 스레드에서도 씀 → volatile)
|
// 중간에 새 스켈레톤이 생성된 경우 정의 재조회 플래그 (NatNet 스레드에서도 씀 → volatile)
|
||||||
private volatile bool m_pendingDefinitionRefresh = false;
|
private volatile bool m_pendingDefinitionRefresh = false;
|
||||||
private float m_definitionRefreshCooldown = 0f;
|
private float m_definitionRefreshCooldown = 0f;
|
||||||
|
|
||||||
// 자동 재연결 진행 중 여부 (중복 재연결 방지)
|
// 자동 재연결 진행 중 여부 (중복 재연결 방지)
|
||||||
private bool m_isReconnecting = false;
|
private bool m_isReconnecting = false;
|
||||||
|
|
||||||
|
// 녹화 상태 추적 (ToggleRecording에서 불필요한 이중 명령 방지)
|
||||||
|
private bool m_isRecording = false;
|
||||||
|
|
||||||
|
// SetProperty 원격 명령을 최초 연결에서만 전송 (재연결 시 Motive 설정 반복 변경 방지)
|
||||||
|
private bool m_hasAppliedServerSettings = false;
|
||||||
|
|
||||||
|
// 정의 재조회 횟수 제한 (Motive 부하 방지: 최대 횟수 초과 시 수동 트리거 필요)
|
||||||
|
private int m_definitionRefreshCount = 0;
|
||||||
|
private const int k_MaxAutoDefinitionRefreshes = 10;
|
||||||
#endregion Private fields
|
#endregion Private fields
|
||||||
|
|
||||||
|
|
||||||
@ -421,57 +436,68 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
{
|
{
|
||||||
if (DrawMarkers)
|
if (DrawMarkers)
|
||||||
{
|
{
|
||||||
|
// 마커 구독 실패 시 매 프레임 재시도 방지 — 쿨다운 적용
|
||||||
if (m_client != null && ConnectionType == ClientConnectionType.Unicast && !m_subscribedToMarkers)
|
if (m_client != null && ConnectionType == ClientConnectionType.Unicast && !m_subscribedToMarkers)
|
||||||
|
{
|
||||||
|
if (m_markerSubscribeRetryCooldown <= 0f)
|
||||||
{
|
{
|
||||||
SubscribeMarkers();
|
SubscribeMarkers();
|
||||||
}
|
if (!m_subscribedToMarkers)
|
||||||
|
m_markerSubscribeRetryCooldown = k_MarkerSubscribeRetryInterval;
|
||||||
List<Int32> markerIds = new List<Int32>();
|
|
||||||
//Debug.Log("markers: " + m_latestMarkerStates.Count);
|
|
||||||
lock (m_frameDataUpdateLock)
|
|
||||||
{
|
|
||||||
// Move existing spheres and create new ones if necessary
|
|
||||||
foreach (KeyValuePair<Int32, OptitrackMarkerState> markerEntry in m_latestMarkerStates)
|
|
||||||
{
|
|
||||||
if (m_latestMarkerSpheres.ContainsKey( markerEntry.Key ))
|
|
||||||
{
|
|
||||||
m_latestMarkerSpheres[markerEntry.Key].transform.position = markerEntry.Value.Position;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var sphere = GameObject.CreatePrimitive( PrimitiveType.Cube );
|
m_markerSubscribeRetryCooldown -= Time.deltaTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 락 범위 최소화: 데이터만 복사 → 락 해제 후 GameObject 생성/파괴
|
||||||
|
var markerSnapshot = new Dictionary<Int32, (Vector3 pos, float size, string name, bool isActive)>();
|
||||||
|
lock (m_frameDataUpdateLock)
|
||||||
|
{
|
||||||
|
foreach (var markerEntry in m_latestMarkerStates)
|
||||||
|
{
|
||||||
|
markerSnapshot[markerEntry.Key] = (
|
||||||
|
markerEntry.Value.Position,
|
||||||
|
markerEntry.Value.Size,
|
||||||
|
markerEntry.Value.Name,
|
||||||
|
markerEntry.Value.IsActive
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 락 밖에서 GameObject 업데이트/생성
|
||||||
|
var activeIds = new HashSet<Int32>(markerSnapshot.Count);
|
||||||
|
foreach (var kvp in markerSnapshot)
|
||||||
|
{
|
||||||
|
activeIds.Add(kvp.Key);
|
||||||
|
if (m_latestMarkerSpheres.ContainsKey(kvp.Key))
|
||||||
|
{
|
||||||
|
m_latestMarkerSpheres[kvp.Key].transform.position = kvp.Value.pos;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var sphere = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||||
sphere.transform.parent = this.transform;
|
sphere.transform.parent = this.transform;
|
||||||
sphere.transform.localScale = new Vector3( markerEntry.Value.Size, markerEntry.Value.Size, markerEntry.Value.Size );
|
sphere.transform.localScale = new Vector3(kvp.Value.size, kvp.Value.size, kvp.Value.size);
|
||||||
sphere.transform.position = markerEntry.Value.Position;
|
sphere.transform.position = kvp.Value.pos;
|
||||||
sphere.name = markerEntry.Value.Name;
|
sphere.name = kvp.Value.name;
|
||||||
if (markerEntry.Value.IsActive)
|
if (kvp.Value.isActive)
|
||||||
{
|
|
||||||
// Make active markers cyan colored
|
|
||||||
sphere.GetComponent<Renderer>().material.SetColor("_Color", Color.cyan);
|
sphere.GetComponent<Renderer>().material.SetColor("_Color", Color.cyan);
|
||||||
|
m_latestMarkerSpheres[kvp.Key] = sphere;
|
||||||
}
|
}
|
||||||
m_latestMarkerSpheres[markerEntry.Key] = sphere;
|
|
||||||
}
|
}
|
||||||
markerIds.Add( markerEntry.Key );
|
// 락 밖에서 stale 오브젝트 제거
|
||||||
}
|
var staleIds = new List<Int32>();
|
||||||
// find spheres to remove that weren't in the previous frame
|
foreach (var sphereEntry in m_latestMarkerSpheres)
|
||||||
List<Int32> markerSphereIdsToDelete = new List<Int32>();
|
|
||||||
foreach (KeyValuePair<Int32, GameObject> markerSphereEntry in m_latestMarkerSpheres)
|
|
||||||
{
|
{
|
||||||
if (!markerIds.Contains( markerSphereEntry.Key ))
|
if (!activeIds.Contains(sphereEntry.Key))
|
||||||
|
staleIds.Add(sphereEntry.Key);
|
||||||
|
}
|
||||||
|
foreach (var id in staleIds)
|
||||||
{
|
{
|
||||||
// stale marker, tag for removal
|
Destroy(m_latestMarkerSpheres[id]);
|
||||||
markerSphereIdsToDelete.Add( markerSphereEntry.Key );
|
m_latestMarkerSpheres.Remove(id);
|
||||||
}
|
|
||||||
}
|
|
||||||
// remove stale spheres
|
|
||||||
foreach(Int32 markerId in markerSphereIdsToDelete)
|
|
||||||
{
|
|
||||||
if(m_latestMarkerSpheres.ContainsKey(markerId))
|
|
||||||
{
|
|
||||||
Destroy( m_latestMarkerSpheres[markerId] );
|
|
||||||
m_latestMarkerSpheres.Remove( markerId );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -485,17 +511,13 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//Draw the camera positions once on startup.
|
// 락 밖에서 카메라 지오메트리 생성 (1회성 — 데이터는 m_cameraDefinitions에 이미 복사됨)
|
||||||
if (DrawCameras && !m_hasDrawnCameras )
|
if (DrawCameras && !m_hasDrawnCameras )
|
||||||
{
|
{
|
||||||
if (m_client.ServerAppVersion >= new Version(3, 0, 0))
|
if (m_client.ServerAppVersion >= new Version(3, 0, 0))
|
||||||
{
|
{
|
||||||
lock (m_frameDataUpdateLock)
|
// m_cameraDefinitions는 메인 스레드의 UpdateDefinitions()에서 채워짐 — 락 불필요
|
||||||
{
|
|
||||||
var cameraGroup = new GameObject("Cameras");
|
var cameraGroup = new GameObject("Cameras");
|
||||||
//cameraGroup.transform.parent = this.transform; //Adds the camera group as a child of the streaming client
|
|
||||||
|
|
||||||
// Create the geometry for cameras.
|
|
||||||
foreach (OptitrackCameraDefinition camera in m_cameraDefinitions)
|
foreach (OptitrackCameraDefinition camera in m_cameraDefinitions)
|
||||||
{
|
{
|
||||||
var geometry = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
var geometry = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||||
@ -507,29 +529,20 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
geometry.GetComponent<Renderer>().material.SetColor("_Color", Color.black);
|
geometry.GetComponent<Renderer>().material.SetColor("_Color", Color.black);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.LogWarning("Drawing cameras is only supported in Motive 3.0+.");
|
Debug.LogWarning("Drawing cameras is only supported in Motive 3.0+.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
m_hasDrawnCameras = true;
|
m_hasDrawnCameras = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 락 밖에서 포스 플레이트 지오메트리 생성 (1회성)
|
||||||
//Draw the camera positions once on startup.
|
|
||||||
if (DrawForcePlates && !m_hasDrawnForcePlates)
|
if (DrawForcePlates && !m_hasDrawnForcePlates)
|
||||||
{
|
{
|
||||||
lock (m_frameDataUpdateLock)
|
var forcePlateGroup = new GameObject("Force Plates");
|
||||||
{
|
|
||||||
var cameraGroup = new GameObject("Force Plates");
|
|
||||||
//cameraGroup.transform.parent = this.transform; //Adds the camera group as a child of the streaming client
|
|
||||||
|
|
||||||
// Create the geometry for cameras.
|
|
||||||
foreach (OptitrackForcePlateDefinition plate in m_forcePlateDefinitions)
|
foreach (OptitrackForcePlateDefinition plate in m_forcePlateDefinitions)
|
||||||
{
|
{
|
||||||
// Corner Locations (Adjusted for Unity world space)
|
|
||||||
Vector3 p0 = new Vector3(-plate.Corners[0], plate.Corners[1], plate.Corners[2]);
|
Vector3 p0 = new Vector3(-plate.Corners[0], plate.Corners[1], plate.Corners[2]);
|
||||||
Vector3 p1 = new Vector3(-plate.Corners[3], plate.Corners[4], plate.Corners[5]);
|
Vector3 p1 = new Vector3(-plate.Corners[3], plate.Corners[4], plate.Corners[5]);
|
||||||
Vector3 p2 = new Vector3(-plate.Corners[6], plate.Corners[7], plate.Corners[8]);
|
Vector3 p2 = new Vector3(-plate.Corners[6], plate.Corners[7], plate.Corners[8]);
|
||||||
@ -537,21 +550,17 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
Vector3 pAverage = (p0 + p1 + p2 + p3) / 4;
|
Vector3 pAverage = (p0 + p1 + p2 + p3) / 4;
|
||||||
|
|
||||||
var geometry = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
var geometry = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||||
geometry.transform.parent = cameraGroup.transform;
|
geometry.transform.parent = forcePlateGroup.transform;
|
||||||
geometry.transform.localScale = new Vector3(plate.Length * 0.0254f, 0.03f, plate.Width * 0.0254f); // inches to meters
|
geometry.transform.localScale = new Vector3(plate.Length * 0.0254f, 0.03f, plate.Width * 0.0254f);
|
||||||
geometry.transform.position = pAverage; // Corner of the plate
|
geometry.transform.position = pAverage;
|
||||||
geometry.transform.rotation = Quaternion.LookRotation(p2 - p1); //Quaternion.identity;
|
geometry.transform.rotation = Quaternion.LookRotation(p2 - p1);
|
||||||
geometry.name = plate.SerialNumber;
|
geometry.name = plate.SerialNumber;
|
||||||
geometry.GetComponent<Renderer>().material.SetColor("_Color", Color.blue);
|
geometry.GetComponent<Renderer>().material.SetColor("_Color", Color.blue);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_hasDrawnForcePlates = true;
|
m_hasDrawnForcePlates = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//if (TimecodeProvider)
|
//if (TimecodeProvider)
|
||||||
//{
|
//{
|
||||||
// Debug.Log("");
|
// Debug.Log("");
|
||||||
@ -560,57 +569,53 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
// Trained Markerset Markers if requested to draw // trained markerset added
|
// Trained Markerset Markers if requested to draw // trained markerset added
|
||||||
if (DrawTMarkersetMarkers)
|
if (DrawTMarkersetMarkers)
|
||||||
{
|
{
|
||||||
//if (m_client != null && ConnectionType == ClientConnectionType.Unicast && !m_subscribedToTMarkMarkers)
|
// 락 범위 최소화: 데이터만 복사 → 락 해제 후 GameObject 생성/파괴
|
||||||
//{
|
var tmarkSnapshot = new Dictionary<Int32, (Vector3 pos, float size, string name, bool isActive)>();
|
||||||
// SubscribeTMarkMarkers();
|
|
||||||
//}
|
|
||||||
|
|
||||||
List<Int32> tmarkmarkerIds = new List<Int32>();
|
|
||||||
//Debug.Log("tmark states: " + m_latestTMarkMarkerStates.Count);
|
|
||||||
lock (m_frameDataUpdateLock)
|
lock (m_frameDataUpdateLock)
|
||||||
{
|
{
|
||||||
// Move existing spheres and create new ones if necessary
|
foreach (var markerEntry in m_latestTMarkMarkerStates)
|
||||||
foreach (KeyValuePair<Int32, OptitrackMarkerState> markerEntry in m_latestTMarkMarkerStates)
|
|
||||||
{
|
{
|
||||||
if (m_latestTMarkMarkerSpheres.ContainsKey(markerEntry.Key))
|
tmarkSnapshot[markerEntry.Key] = (
|
||||||
|
markerEntry.Value.Position,
|
||||||
|
markerEntry.Value.Size,
|
||||||
|
markerEntry.Value.Name,
|
||||||
|
markerEntry.Value.IsActive
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 락 밖에서 GameObject 업데이트/생성
|
||||||
|
var activeTMarkIds = new HashSet<Int32>(tmarkSnapshot.Count);
|
||||||
|
foreach (var kvp in tmarkSnapshot)
|
||||||
{
|
{
|
||||||
m_latestTMarkMarkerSpheres[markerEntry.Key].transform.position = markerEntry.Value.Position;
|
activeTMarkIds.Add(kvp.Key);
|
||||||
|
if (m_latestTMarkMarkerSpheres.ContainsKey(kvp.Key))
|
||||||
|
{
|
||||||
|
m_latestTMarkMarkerSpheres[kvp.Key].transform.position = kvp.Value.pos;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||||
cube.transform.parent = this.transform;
|
cube.transform.parent = this.transform;
|
||||||
cube.transform.localScale = new Vector3(markerEntry.Value.Size, markerEntry.Value.Size, markerEntry.Value.Size);
|
cube.transform.localScale = new Vector3(kvp.Value.size, kvp.Value.size, kvp.Value.size);
|
||||||
cube.transform.position = markerEntry.Value.Position;
|
cube.transform.position = kvp.Value.pos;
|
||||||
cube.name = markerEntry.Value.Name;
|
cube.name = kvp.Value.name;
|
||||||
if (markerEntry.Value.IsActive)
|
if (kvp.Value.isActive)
|
||||||
{
|
|
||||||
// Make active markers cyan colored
|
|
||||||
cube.GetComponent<Renderer>().material.SetColor("_Color", Color.cyan);
|
cube.GetComponent<Renderer>().material.SetColor("_Color", Color.cyan);
|
||||||
|
m_latestTMarkMarkerSpheres[kvp.Key] = cube;
|
||||||
}
|
}
|
||||||
m_latestTMarkMarkerSpheres[markerEntry.Key] = cube;
|
|
||||||
}
|
}
|
||||||
tmarkmarkerIds.Add(markerEntry.Key);
|
// 락 밖에서 stale 오브젝트 제거
|
||||||
}
|
var staleTMarkIds = new List<Int32>();
|
||||||
// find spheres to remove that weren't in the previous frame
|
foreach (var cubeEntry in m_latestTMarkMarkerSpheres)
|
||||||
List<Int32> markerCubeIdsToDelete = new List<Int32>();
|
|
||||||
foreach (KeyValuePair<Int32, GameObject> markerCubeEntry in m_latestTMarkMarkerSpheres)
|
|
||||||
{
|
{
|
||||||
if (!tmarkmarkerIds.Contains(markerCubeEntry.Key))
|
if (!activeTMarkIds.Contains(cubeEntry.Key))
|
||||||
|
staleTMarkIds.Add(cubeEntry.Key);
|
||||||
|
}
|
||||||
|
foreach (var id in staleTMarkIds)
|
||||||
{
|
{
|
||||||
// stale marker, tag for removal
|
Destroy(m_latestTMarkMarkerSpheres[id]);
|
||||||
markerCubeIdsToDelete.Add(markerCubeEntry.Key);
|
m_latestTMarkMarkerSpheres.Remove(id);
|
||||||
}
|
|
||||||
}
|
|
||||||
// remove stale spheres
|
|
||||||
foreach (Int32 markerId in markerCubeIdsToDelete)
|
|
||||||
{
|
|
||||||
if (m_latestTMarkMarkerSpheres.ContainsKey(markerId))
|
|
||||||
{
|
|
||||||
Destroy(m_latestTMarkMarkerSpheres[markerId]);
|
|
||||||
m_latestTMarkMarkerSpheres.Remove(markerId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -624,13 +629,26 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 미등록 스켈레톤이 프레임에 등장했을 때 정의 자동 재조회 (Motive 중간 생성 대응)
|
// 미등록 스켈레톤이 프레임에 등장했을 때 정의 자동 재조회 (Motive 중간 생성 대응)
|
||||||
|
// 쿨다운 15초 + 최대 횟수 제한으로 Motive DataDescription 요청 폭풍 방지
|
||||||
if (m_pendingDefinitionRefresh && m_definitionRefreshCooldown <= 0f && m_client != null && !SkipDataDescriptions)
|
if (m_pendingDefinitionRefresh && m_definitionRefreshCooldown <= 0f && m_client != null && !SkipDataDescriptions)
|
||||||
{
|
{
|
||||||
m_pendingDefinitionRefresh = false;
|
m_pendingDefinitionRefresh = false;
|
||||||
m_definitionRefreshCooldown = 5f;
|
|
||||||
|
if (m_definitionRefreshCount < k_MaxAutoDefinitionRefreshes)
|
||||||
|
{
|
||||||
|
m_definitionRefreshCooldown = 15f;
|
||||||
|
m_definitionRefreshCount++;
|
||||||
try { UpdateDefinitions(); }
|
try { UpdateDefinitions(); }
|
||||||
catch (Exception ex) { Debug.LogException(ex, this); }
|
catch (Exception ex) { Debug.LogException(ex, this); }
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 최대 횟수 초과 — 경고 로그 출력 후 자동 재조회 중단
|
||||||
|
m_definitionRefreshCooldown = 60f; // 1분 후 다시 시도 허용
|
||||||
|
Debug.LogWarning(GetType().FullName + ": 자동 정의 재조회 최대 횟수(" + k_MaxAutoDefinitionRefreshes + "회) 초과. Motive에서 에셋을 확인하세요.", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (m_definitionRefreshCooldown > 0f)
|
||||||
m_definitionRefreshCooldown -= Time.deltaTime;
|
m_definitionRefreshCooldown -= Time.deltaTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -666,7 +684,9 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
{
|
{
|
||||||
if(m_client != null)
|
if(m_client != null)
|
||||||
{
|
{
|
||||||
return m_client.RequestCommand("StartRecording");
|
bool result = m_client.RequestCommand("StartRecording");
|
||||||
|
if (result) m_isRecording = true;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -679,7 +699,9 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
{
|
{
|
||||||
if (m_client != null)
|
if (m_client != null)
|
||||||
{
|
{
|
||||||
return m_client.RequestCommand("StopRecording");
|
bool result = m_client.RequestCommand("StopRecording");
|
||||||
|
if (result) m_isRecording = false;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -730,6 +752,9 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
if (m_receivedFrameSinceConnect)
|
if (m_receivedFrameSinceConnect)
|
||||||
{
|
{
|
||||||
Debug.Log(GetType().FullName + ": 재연결 성공.", this);
|
Debug.Log(GetType().FullName + ": 재연결 성공.", this);
|
||||||
|
// 재연결 성공 후에만 녹화 재시작
|
||||||
|
if (RecordOnPlay)
|
||||||
|
StartRecording();
|
||||||
m_isReconnecting = false;
|
m_isReconnecting = false;
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
@ -744,7 +769,7 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
}
|
}
|
||||||
if (m_client != null)
|
if (m_client != null)
|
||||||
{
|
{
|
||||||
if (RecordOnPlay) StopRecording();
|
// 재연결 루프 중에는 녹화 중지 명령 생략 — Motive 디스크 I/O 반복 방지
|
||||||
try { m_client.NativeFrameReceived -= OnNatNetFrameReceived; } catch (System.Exception) { }
|
try { m_client.NativeFrameReceived -= OnNatNetFrameReceived; } catch (System.Exception) { }
|
||||||
try { m_client.Disconnect(); } catch (System.Exception) { }
|
try { m_client.Disconnect(); } catch (System.Exception) { }
|
||||||
try { m_client.Dispose(); } catch (System.Exception) { }
|
try { m_client.Dispose(); } catch (System.Exception) { }
|
||||||
@ -833,21 +858,20 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
{
|
{
|
||||||
if (m_client != null)
|
if (m_client != null)
|
||||||
{
|
{
|
||||||
// Note: There's no direct way to check if recording is active,
|
// 상태 추적 기반 토글 — 기존: "일단 Start → 실패하면 Stop" 이중 명령 → 1회 명령으로 개선
|
||||||
// so we'll try to start recording first, and if it fails, try to stop
|
if (m_isRecording)
|
||||||
bool startResult = StartRecording();
|
|
||||||
if (!startResult)
|
|
||||||
{
|
|
||||||
// If start failed, try to stop (might already be recording)
|
|
||||||
bool stopResult = StopRecording();
|
|
||||||
if (stopResult)
|
|
||||||
{
|
{
|
||||||
|
if (StopRecording())
|
||||||
Debug.Log("OptiTrack: 레코딩을 중지했습니다.");
|
Debug.Log("OptiTrack: 레코딩을 중지했습니다.");
|
||||||
}
|
else
|
||||||
|
Debug.LogWarning("OptiTrack: 레코딩 중지에 실패했습니다.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (StartRecording())
|
||||||
Debug.Log("OptiTrack: 레코딩을 시작했습니다.");
|
Debug.Log("OptiTrack: 레코딩을 시작했습니다.");
|
||||||
|
else
|
||||||
|
Debug.LogWarning("OptiTrack: 레코딩 시작에 실패했습니다.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -1193,7 +1217,6 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
public List<OptitrackMarkerState> GetLatestTMarkMarkerStates() // trained markerset added
|
public List<OptitrackMarkerState> GetLatestTMarkMarkerStates() // trained markerset added
|
||||||
{
|
{
|
||||||
List<OptitrackMarkerState> tmarkmarkerStates = new List<OptitrackMarkerState>();
|
List<OptitrackMarkerState> tmarkmarkerStates = new List<OptitrackMarkerState>();
|
||||||
Debug.Log("GetLatestTMarkMarker: " + m_latestTMarkMarkerStates.Count);
|
|
||||||
|
|
||||||
lock (m_frameDataUpdateLock)
|
lock (m_frameDataUpdateLock)
|
||||||
{
|
{
|
||||||
@ -1239,10 +1262,14 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
}
|
}
|
||||||
m_dataDescs = m_client.GetDataDescriptions(descriptionTypeMask);
|
m_dataDescs = m_client.GetDataDescriptions(descriptionTypeMask);
|
||||||
|
|
||||||
|
// 정의를 성공적으로 받았으므로 자동 재조회 카운터 리셋
|
||||||
|
m_definitionRefreshCount = 0;
|
||||||
|
|
||||||
m_rigidBodyDefinitions.Clear();
|
m_rigidBodyDefinitions.Clear();
|
||||||
m_skeletonDefinitions.Clear();
|
m_skeletonDefinitions.Clear();
|
||||||
m_tmarkersetDefinitions.Clear();
|
m_tmarkersetDefinitions.Clear();
|
||||||
m_mirrorBoneIdMaps.Clear(); // 스켈레톤 정의 변경 시 mirror map 캐시 무효화
|
m_mirrorBoneIdMaps.Clear(); // 스켈레톤 정의 변경 시 mirror map 캐시 무효화
|
||||||
|
m_assetIdToNameCache.Clear(); // assetID→이름 캐시 무효화
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// - Translate Rigid Body Definitions
|
// - Translate Rigid Body Definitions
|
||||||
@ -1546,6 +1573,9 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
m_client = new NatNetClient();
|
m_client = new NatNetClient();
|
||||||
m_client.Connect( connType, localAddr, serverAddr );
|
m_client.Connect( connType, localAddr, serverAddr );
|
||||||
|
|
||||||
|
// SetProperty는 최초 연결에서만 전송 — 재연결 시 Motive 글로벌 설정 반복 변경 방지
|
||||||
|
if ( !m_hasAppliedServerSettings )
|
||||||
|
{
|
||||||
// Remotely change the Skeleton Coordinate property to Global/Local
|
// Remotely change the Skeleton Coordinate property to Global/Local
|
||||||
if (SkeletonCoordinates == StreamingCoordinatesValues.Global)
|
if (SkeletonCoordinates == StreamingCoordinatesValues.Global)
|
||||||
m_client.RequestCommand("SetProperty,,Skeleton Coordinates,false");
|
m_client.RequestCommand("SetProperty,,Skeleton Coordinates,false");
|
||||||
@ -1559,6 +1589,13 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
m_client.RequestCommand("SetProperty,,Bone Naming Convention,1");
|
m_client.RequestCommand("SetProperty,,Bone Naming Convention,1");
|
||||||
else if (BoneNamingConvention == OptitrackBoneNameConvention.BVH)
|
else if (BoneNamingConvention == OptitrackBoneNameConvention.BVH)
|
||||||
m_client.RequestCommand("SetProperty,,Bone Naming Convention,2");
|
m_client.RequestCommand("SetProperty,,Bone Naming Convention,2");
|
||||||
|
|
||||||
|
m_hasAppliedServerSettings = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.Log(GetType().FullName + ": 재연결 — SetProperty 명령 스킵 (최초 연결에서 이미 적용됨).", this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch ( Exception ex )
|
catch ( Exception ex )
|
||||||
{
|
{
|
||||||
@ -1568,7 +1605,8 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetProperty 명령이 서버에 적용될 때까지 대기 (메인 스레드 블락 없이)
|
// SetProperty 명령이 서버에 적용될 때까지 대기 (재연결 시 SetProperty 스킵했으면 대기 불필요)
|
||||||
|
if (!m_isReconnecting)
|
||||||
yield return new UnityEngine.WaitForSeconds( 0.1f );
|
yield return new UnityEngine.WaitForSeconds( 0.1f );
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -1592,7 +1630,8 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
SubscribeTMarkerset(tmark.Value, tmark.Key);
|
SubscribeTMarkerset(tmark.Value, tmark.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RecordOnPlay)
|
// 재연결 중에는 녹화 시작 스킵 — Motive의 Take 파일 반복 열기/닫기 방지
|
||||||
|
if (RecordOnPlay && !m_isReconnecting)
|
||||||
StartRecording();
|
StartRecording();
|
||||||
|
|
||||||
byte[] NatNetVersion = m_client.ServerDescription.NatNetVersion;
|
byte[] NatNetVersion = m_client.ServerDescription.NatNetVersion;
|
||||||
@ -1783,6 +1822,19 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
result = NaturalPoint.NatNetLib.NativeMethods.NatNet_Frame_Skeleton_GetRigidBodyCount( pFrame, skelIdx, out skelRbCount );
|
result = NaturalPoint.NatNetLib.NativeMethods.NatNet_Frame_Skeleton_GetRigidBodyCount( pFrame, skelIdx, out skelRbCount );
|
||||||
NatNetException.ThrowIfNotOK( result, "NatNet_Frame_Skeleton_GetRigidBodyCount failed." );
|
NatNetException.ThrowIfNotOK( result, "NatNet_Frame_Skeleton_GetRigidBodyCount failed." );
|
||||||
|
|
||||||
|
// 스켈레톤 정의 검색을 본 루프 밖에서 1회만 수행 (기존: 매 본마다 선형 탐색)
|
||||||
|
OptitrackSkeletonDefinition skelDef = GetSkeletonDefinitionById(skeletonId);
|
||||||
|
if (skelDef == null)
|
||||||
|
{
|
||||||
|
// Motive에서 중간에 스켈레톤이 생성된 경우 — 메인 스레드에서 정의 재조회 예약 (중복 로그 방지)
|
||||||
|
if (!m_pendingDefinitionRefresh && m_definitionRefreshCount < k_MaxAutoDefinitionRefreshes)
|
||||||
|
{
|
||||||
|
Debug.LogWarning(GetType().FullName + ": 알 수 없는 스켈레톤 ID " + skeletonId + " — 정의 재조회 예약됨.", this);
|
||||||
|
m_pendingDefinitionRefresh = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (int boneIdx = 0; boneIdx < skelRbCount; ++boneIdx)
|
for (int boneIdx = 0; boneIdx < skelRbCount; ++boneIdx)
|
||||||
{
|
{
|
||||||
sRigidBodyData boneData = new sRigidBodyData();
|
sRigidBodyData boneData = new sRigidBodyData();
|
||||||
@ -1814,16 +1866,6 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
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);
|
||||||
|
|
||||||
OptitrackSkeletonDefinition skelDef = GetSkeletonDefinitionById(skeletonId);
|
|
||||||
if (skelDef == null)
|
|
||||||
{
|
|
||||||
// Motive에서 중간에 스켈레톤이 생성된 경우 — 메인 스레드에서 정의 재조회 예약
|
|
||||||
if (!m_pendingDefinitionRefresh)
|
|
||||||
Debug.LogWarning(GetType().FullName + ": 알 수 없는 스켈레톤 ID " + skeletonId + " — 정의 재조회 예약됨.", this);
|
|
||||||
m_pendingDefinitionRefresh = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Int32 pId = skelDef.BoneIdToParentIdMap[boneId];
|
Int32 pId = skelDef.BoneIdToParentIdMap[boneId];
|
||||||
if (pId != 0)
|
if (pId != 0)
|
||||||
{
|
{
|
||||||
@ -1849,8 +1891,14 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
// Ensure we have a state corresponding to this tmarkerset ID.
|
// Ensure we have a state corresponding to this tmarkerset ID.
|
||||||
OptitrackTMarkersetState tmarkState = GetOrCreateTMarkersetState(tmarkersetId);
|
OptitrackTMarkersetState tmarkState = GetOrCreateTMarkersetState(tmarkersetId);
|
||||||
|
|
||||||
// Enumerate this tmarkerset's bone rigid bodies.
|
// TMarkerset 정의 검색을 본 루프 밖에서 1회만 수행 (기존: 매 본마다 선형 탐색)
|
||||||
Int32 tmarkRbCount = m_dataDescs.AssetDescriptions[tmarkIdx].RigidBodyCount;
|
Int32 tmarkRbCount = m_dataDescs.AssetDescriptions[tmarkIdx].RigidBodyCount;
|
||||||
|
OptitrackTMarkersetDefinition tmarkDef = GetTMarkersetDefinitionById(tmarkersetId);
|
||||||
|
if (tmarkDef == null)
|
||||||
|
{
|
||||||
|
Debug.LogError(GetType().FullName + ": OnNatNetFrameReceived, no corresponding tmarkerset definition for received tmarkerset frame data.", this);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (int boneIdx = 0; boneIdx < tmarkRbCount; ++boneIdx)
|
for (int boneIdx = 0; boneIdx < tmarkRbCount; ++boneIdx)
|
||||||
{
|
{
|
||||||
@ -1858,13 +1906,9 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
result = NaturalPoint.NatNetLib.NativeMethods.NatNet_Frame_TMarkerset_GetRigidBody(pFrame, tmarkIdx, boneIdx, out boneData);
|
result = NaturalPoint.NatNetLib.NativeMethods.NatNet_Frame_TMarkerset_GetRigidBody(pFrame, tmarkIdx, boneIdx, out boneData);
|
||||||
NatNetException.ThrowIfNotOK(result, "NatNet_Frame_TMarkerset_GetRigidBody failed.");
|
NatNetException.ThrowIfNotOK(result, "NatNet_Frame_TMarkerset_GetRigidBody failed.");
|
||||||
|
|
||||||
// In the context of frame data (unlike in the definition data), this ID value is a
|
|
||||||
// packed composite of both the asset/entity (tmarkerset) ID and member (bone) ID.
|
|
||||||
Int32 boneTMarkId, boneId;
|
Int32 boneTMarkId, boneId;
|
||||||
NaturalPoint.NatNetLib.NativeMethods.NatNet_DecodeID(boneData.Id, out boneTMarkId, out boneId);
|
NaturalPoint.NatNetLib.NativeMethods.NatNet_DecodeID(boneData.Id, out boneTMarkId, out boneId);
|
||||||
|
|
||||||
// TODO: Could pre-populate this map when the definitions are retrieved.
|
|
||||||
// Should never allocate after the first frame, at least.
|
|
||||||
if (tmarkState.BonePoses.ContainsKey(boneId) == false)
|
if (tmarkState.BonePoses.ContainsKey(boneId) == false)
|
||||||
{
|
{
|
||||||
tmarkState.BonePoses[boneId] = new OptitrackPose();
|
tmarkState.BonePoses[boneId] = new OptitrackPose();
|
||||||
@ -1883,13 +1927,6 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
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);
|
||||||
|
|
||||||
OptitrackTMarkersetDefinition tmarkDef = GetTMarkersetDefinitionById(tmarkersetId);
|
|
||||||
if (tmarkDef == null)
|
|
||||||
{
|
|
||||||
Debug.LogError(GetType().FullName + ": OnNatNetFrameReceived, no corresponding tmarkerset definition for received tmarkerset frame data.", this);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Int32 pId = tmarkDef.BoneIdToParentIdMap[boneId];
|
Int32 pId = tmarkDef.BoneIdToParentIdMap[boneId];
|
||||||
if (pId != -1)
|
if (pId != -1)
|
||||||
{
|
{
|
||||||
@ -1970,54 +2007,43 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
|
|
||||||
private string GetMarkerName( sMarker marker )
|
private string GetMarkerName( sMarker marker )
|
||||||
{
|
{
|
||||||
int hashKey = marker.Id.GetHashCode();
|
int assetID = marker.Id >> 16; // high word = Asset ID Number
|
||||||
int assetID = marker.Id.GetHashCode() >> 16; // high word = Asset ID Number
|
int memberID = marker.Id & 0x00ffff; // low word = Member ID Number (constraint number)
|
||||||
int memberID = marker.Id.GetHashCode() & 0x00ffff; // low word = Member ID Number (constraint number)
|
|
||||||
|
|
||||||
// Figure out the asset name if it exists.
|
// assetID→이름 캐시 사용 (기존: 매 마커마다 3개 리스트 선형 탐색)
|
||||||
string assetName = "";
|
string assetName;
|
||||||
|
if (!m_assetIdToNameCache.TryGetValue(assetID, out assetName))
|
||||||
|
{
|
||||||
|
assetName = "";
|
||||||
OptitrackRigidBodyDefinition rigidBodyDef = GetRigidBodyDefinitionById( assetID );
|
OptitrackRigidBodyDefinition rigidBodyDef = GetRigidBodyDefinitionById( assetID );
|
||||||
OptitrackSkeletonDefinition skeletonDef = GetSkeletonDefinitionById( assetID );
|
|
||||||
OptitrackTMarkersetDefinition tmarkersetDef = GetTMarkersetDefinitionById( assetID );
|
|
||||||
|
|
||||||
if (rigidBodyDef != null)
|
if (rigidBodyDef != null)
|
||||||
{
|
|
||||||
assetName = rigidBodyDef.Name;
|
assetName = rigidBodyDef.Name;
|
||||||
}
|
else
|
||||||
else if (skeletonDef != null)
|
|
||||||
{
|
{
|
||||||
|
OptitrackSkeletonDefinition skeletonDef = GetSkeletonDefinitionById( assetID );
|
||||||
|
if (skeletonDef != null)
|
||||||
assetName = skeletonDef.Name;
|
assetName = skeletonDef.Name;
|
||||||
}
|
else
|
||||||
else if (tmarkersetDef != null)
|
|
||||||
{
|
{
|
||||||
|
OptitrackTMarkersetDefinition tmarkersetDef = GetTMarkersetDefinitionById( assetID );
|
||||||
|
if (tmarkersetDef != null)
|
||||||
assetName = tmarkersetDef.Name;
|
assetName = tmarkersetDef.Name;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
m_assetIdToNameCache[assetID] = assetName;
|
||||||
|
}
|
||||||
|
|
||||||
// Figure out if the marker is labeled or active
|
|
||||||
bool IsLabeled = (marker.Params & 0x10) == 0;
|
bool IsLabeled = (marker.Params & 0x10) == 0;
|
||||||
bool IsActive = (marker.Params & 0x20) != 0;
|
bool IsActive = (marker.Params & 0x20) != 0;
|
||||||
string name = "";
|
|
||||||
|
|
||||||
// Go through the possible naming conventions for the markers
|
|
||||||
// Check different Active/Passive Labeled/Unlabeled Configurations.
|
|
||||||
if (IsActive && !IsLabeled)
|
if (IsActive && !IsLabeled)
|
||||||
{
|
return "Active " + marker.Id.ToString();
|
||||||
name = "Active " + marker.Id.ToString();
|
|
||||||
}
|
|
||||||
else if (IsActive && IsLabeled)
|
else if (IsActive && IsLabeled)
|
||||||
{
|
return "Active " + marker.Id.ToString() + " (" + assetName + " Member ID: " + memberID + " )";
|
||||||
name = "Active " + marker.Id.ToString() +" (" + assetName + " Member ID: " + memberID + " )";
|
|
||||||
}
|
|
||||||
else if (!IsActive && !IsLabeled)
|
else if (!IsActive && !IsLabeled)
|
||||||
{
|
return "Passive " + marker.Id.ToString();
|
||||||
name = "Passive " + marker.Id.ToString();
|
else
|
||||||
}
|
return "Passive (" + assetName + " Member ID: " + memberID + ")";
|
||||||
else if (!IsActive && IsLabeled)
|
|
||||||
{
|
|
||||||
name = "Passive (" + assetName + " Member ID: " + memberID + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RigidBodyDataToState(sRigidBodyData rbData, OptitrackHiResTimer.Timestamp timestamp, OptitrackRigidBodyState rbState)
|
private void RigidBodyDataToState(sRigidBodyData rbData, OptitrackHiResTimer.Timestamp timestamp, OptitrackRigidBodyState rbState)
|
||||||
@ -2033,8 +2059,8 @@ public class OptitrackStreamingClient : MonoBehaviour
|
|||||||
|
|
||||||
private void ResetStreamingSubscriptions()
|
private void ResetStreamingSubscriptions()
|
||||||
{
|
{
|
||||||
m_client.RequestCommand( "SubscribeToData" ); // Clear all filters
|
// 1개 명령으로 통합: "SubscribeToData"는 모든 필터를 클리어하고 기본 상태(구독 없음)로 리셋
|
||||||
m_client.RequestCommand( "SubscribeToData,AllTypes,None" ); // Unsubscribe from all data by default
|
m_client.RequestCommand( "SubscribeToData" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user