diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.Avatar/VrmAvatar.asset b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.Avatar/VrmAvatar.asset
index 86bf7ae1..6627dcb5 100644
--- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.Avatar/VrmAvatar.asset
+++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.Avatar/VrmAvatar.asset
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:cde489008c37f77daa3d3af0b46beb0fae0131235c2d1f49bc965e50ebb881e3
-size 87762
+oid sha256:64ec321e2f8f631855b9accfa0441a13d706ee272acd9d6cbb70ce0a25a7cc00
+size 89381
diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.Meshes/newton.baked.asset b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.Meshes/newton.baked.asset
index 2780bf80..e1dbc5a2 100644
--- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.Meshes/newton.baked.asset
+++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.Meshes/newton.baked.asset
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:9a98e96729f044f6a66bbbfeb7c1a0a95c5a605bdbe3a654a01c5a9045e8ae32
-size 3201111
+oid sha256:aca3bded3a29e6eb78e01d3f329fcfd790d96ccd33d7a6a5000727cb22861786
+size 3201078
diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.fbx.meta b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.fbx.meta
index d66ab75a..0bff8903 100644
--- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.fbx.meta
+++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.fbx.meta
@@ -269,7 +269,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: LeftFinger2Metacarpal
+ - boneName: LeftFinger2Proximal
humanName: Left Index Proximal
limit:
min: {x: 0, y: 0, z: 0}
@@ -277,7 +277,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: LeftFinger2Proximal
+ - boneName: LeftFinger2Medial
humanName: Left Index Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@@ -285,7 +285,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: LeftFinger2Medial
+ - boneName: LeftFinger2Distal
humanName: Left Index Distal
limit:
min: {x: 0, y: 0, z: 0}
@@ -293,7 +293,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: LeftFinger3Metacarpal
+ - boneName: LeftFinger3Proximal
humanName: Left Middle Proximal
limit:
min: {x: 0, y: 0, z: 0}
@@ -301,7 +301,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: LeftFinger3Proximal
+ - boneName: LeftFinger3Medial
humanName: Left Middle Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@@ -309,7 +309,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: LeftFinger3Medial
+ - boneName: LeftFinger3Distal
humanName: Left Middle Distal
limit:
min: {x: 0, y: 0, z: 0}
@@ -317,7 +317,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: LeftFinger4Metacarpal
+ - boneName: LeftFinger4Proximal
humanName: Left Ring Proximal
limit:
min: {x: 0, y: 0, z: 0}
@@ -325,7 +325,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: LeftFinger4Proximal
+ - boneName: LeftFinger4Medial
humanName: Left Ring Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@@ -333,7 +333,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: LeftFinger4Medial
+ - boneName: LeftFinger4Distal
humanName: Left Ring Distal
limit:
min: {x: 0, y: 0, z: 0}
@@ -341,7 +341,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: LeftFinger5Metacarpal
+ - boneName: LeftFinger5Proximal
humanName: Left Little Proximal
limit:
min: {x: 0, y: 0, z: 0}
@@ -349,7 +349,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: LeftFinger5Proximal
+ - boneName: LeftFinger5Medial
humanName: Left Little Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@@ -357,7 +357,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: LeftFinger5Medial
+ - boneName: LeftFinger5Distal
humanName: Left Little Distal
limit:
min: {x: 0, y: 0, z: 0}
@@ -389,7 +389,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: RightFinger2Metacarpal
+ - boneName: RightFinger2Proximal
humanName: Right Index Proximal
limit:
min: {x: 0, y: 0, z: 0}
@@ -397,7 +397,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: RightFinger2Proximal
+ - boneName: RightFinger2Medial
humanName: Right Index Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@@ -405,7 +405,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: RightFinger2Medial
+ - boneName: RightFinger2Distal
humanName: Right Index Distal
limit:
min: {x: 0, y: 0, z: 0}
@@ -413,7 +413,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: RightFinger3Metacarpal
+ - boneName: RightFinger3Proximal
humanName: Right Middle Proximal
limit:
min: {x: 0, y: 0, z: 0}
@@ -421,7 +421,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: RightFinger3Proximal
+ - boneName: RightFinger3Medial
humanName: Right Middle Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@@ -429,7 +429,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: RightFinger3Medial
+ - boneName: RightFinger3Distal
humanName: Right Middle Distal
limit:
min: {x: 0, y: 0, z: 0}
@@ -437,7 +437,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: RightFinger4Metacarpal
+ - boneName: RightFinger4Proximal
humanName: Right Ring Proximal
limit:
min: {x: 0, y: 0, z: 0}
@@ -445,7 +445,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: RightFinger4Proximal
+ - boneName: RightFinger4Medial
humanName: Right Ring Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@@ -453,7 +453,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: RightFinger4Medial
+ - boneName: RightFinger4Distal
humanName: Right Ring Distal
limit:
min: {x: 0, y: 0, z: 0}
@@ -461,7 +461,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: RightFinger5Metacarpal
+ - boneName: RightFinger5Proximal
humanName: Right Little Proximal
limit:
min: {x: 0, y: 0, z: 0}
@@ -469,7 +469,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: RightFinger5Proximal
+ - boneName: RightFinger5Medial
humanName: Right Little Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@@ -477,7 +477,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- - boneName: RightFinger5Medial
+ - boneName: RightFinger5Distal
humanName: Right Little Distal
limit:
min: {x: 0, y: 0, z: 0}
@@ -582,7 +582,7 @@ ModelImporter:
- name: LeftFinger1Metacarpal
parentName: LeftHand
position: {x: -0.027847009, y: 0.020784134, z: -0.001056083}
- rotation: {x: 0.09904506, y: -0.36964288, z: 0.23911677, w: 0.89239985}
+ rotation: {x: 0.04233702, y: -0.5728508, z: 0.3376046, w: 0.74570274}
scale: {x: 1.0000001, y: 0.99999994, z: 1.0000001}
- name: LeftFinger1Proximal
parentName: LeftFinger1Metacarpal
@@ -707,7 +707,7 @@ ModelImporter:
- name: RightFinger1Metacarpal
parentName: RightHand
position: {x: 0.027845163, y: 0.020768387, z: -0.0010154283}
- rotation: {x: 0.09904415, y: 0.36964288, z: -0.23911713, w: 0.89239985}
+ rotation: {x: 0.04219337, y: 0.5729466, z: -0.33736277, w: 0.7457468}
scale: {x: 1, y: 0.99999994, z: 0.9999998}
- name: RightFinger1Proximal
parentName: RightFinger1Metacarpal
diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.prefab b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.prefab
index 96789942..b46dd531 100644
--- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.prefab
+++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/BaseAvatar ver 2/BaseAvatar - OptiTrack ver 2.prefab
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:022bbb6b2a68737c1d189fe6822631c00a2d4f0bbb109b4583d33b64297ab260
-size 89556
+oid sha256:d426caa5686dcc01231edb83fcd338cb97ef3c81185b8a25d57f285ff9167fa8
+size 93393
diff --git a/Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceExample.vcxproj b/Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceExample.vcxproj
index 1547b996..ddd51374 100644
--- a/Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceExample.vcxproj
+++ b/Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceExample.vcxproj
@@ -68,14 +68,14 @@ copy "$(OutDir)ExampleGloveData.csv" "C:/Program Files/OptiTrack/Motive/device
Disabled
WIN32;_DEBUG;_WINDOWS;_USRDLL;ANALOGSYSTEM_IMPORTS;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;%(PreprocessorDefinitions)
false
- ..\..\include;%(AdditionalIncludeDirectories)
+ ..\include;%(AdditionalIncludeDirectories)
Windows
true
NotSet
PeripheralImport.lib;%(AdditionalDependencies)
- ..\..\lib;%(AdditionalLibraryDirectories)
+ ..\lib;%(AdditionalLibraryDirectories)
false
@@ -88,7 +88,7 @@ copy "$(OutDir)ExampleGloveData.csv" "C:/Program Files/OptiTrack/Motive/device
true
true
WIN32;NDEBUG;_WINDOWS;_USRDLL;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;ANALOGSYSTEM_IMPORTS;%(PreprocessorDefinitions)
- ..\..\include;%(AdditionalIncludeDirectories)
+ ..\include;%(AdditionalIncludeDirectories)
Windows
@@ -96,7 +96,7 @@ copy "$(OutDir)ExampleGloveData.csv" "C:/Program Files/OptiTrack/Motive/device
true
true
PeripheralImport.lib;%(AdditionalDependencies)
- ..\..\lib;%(AdditionalLibraryDirectories)
+ ..\lib;%(AdditionalLibraryDirectories)
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveAdapterSingleton.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveAdapterSingleton.cpp
index 4e2c28b2..dfad524e 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveAdapterSingleton.cpp
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveAdapterSingleton.cpp
@@ -5,7 +5,12 @@
#include
#include
+#include // stringstream 사용을 위해 추가
+#include // sqrt 함수 사용을 위해 추가
+#include // setprecision 사용을 위해 추가
+#include // std::max, std::min 사용을 위해 추가
#define WIN32_LEAN_AND_MEAN
+#define NOMINMAX // Windows min/max 매크로 비활성화
#include
// Peripheral Import
@@ -94,7 +99,7 @@ void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::DoDete
bIsDetecting = false;
NotifyConnectionFail();
}
- std::this_thread::sleep_for(std::chrono::milliseconds(20000));
+ std::this_thread::sleep_for(std::chrono::milliseconds(1)); // 🚀 실시간성 최우선 - 1ms로 최소화
}
}
@@ -170,8 +175,8 @@ bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::Connec
bIsRokokoConnected = true;
- // 로코코 장치 동적 감지 및 생성
- DetectAndCreateRokokoDevices();
+ // 초기 장치 생성은 데이터 수신 후로 지연
+ // DetectAndCreateRokokoDevices(); // 주석 처리
// 성공 메시지 출력
if (mDeviceManager) {
@@ -248,7 +253,16 @@ bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::GetLat
void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::CreateNewGloveDevice(sGloveDeviceBaseInfo& deviceInfo)
{
uint64_t gloveId = deviceInfo.gloveId;
- std::string deviceName = "RokokoGlove_" + std::to_string(deviceInfo.gloveId); // 이름을 RokokoGlove로 변경
+
+ // Actor 이름을 포함한 장치 이름 생성
+ std::string deviceName;
+ if (!deviceInfo.actorName.empty()) {
+ deviceName = deviceInfo.actorName + "_" +
+ (deviceInfo.handSide == eGloveHandSide::Left ? "Left" : "Right") +
+ "Hand";
+ } else {
+ deviceName = "RokokoGlove_" + std::to_string(deviceInfo.gloveId);
+ }
// Create device factory using the name/id/serial/client.
std::unique_ptr pDF =
@@ -427,73 +441,95 @@ void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::OnRoko
{
if (s_Instance == nullptr) return;
- try {
- // 로코코 데이터 처리
- s_Instance->ProcessRokokoData(data);
-
- // 디버그 정보 출력 (OptiTrack에서 확인 가능)
- if (s_Instance->mDeviceManager) {
- std::string debugMsg = "[RokokoGlove] Received " + std::to_string(data.size()) + " bytes from " + senderIP;
- s_Instance->mDeviceManager->MessageToHost(debugMsg.c_str(), MessageType_StatusInfo);
- }
-
- } catch (...) {
- if (s_Instance->mDeviceManager) {
- s_Instance->mDeviceManager->MessageToHost("[RokokoGlove] Error processing received data", MessageType_StatusInfo);
- }
- }
+ // 🚀 실시간성 최우선: 예외 처리 및 로그 최소화
+ s_Instance->ProcessRokokoData(data);
}
void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ProcessRokokoData(const std::vector& data)
{
try {
- // 1단계: LZ4 압축 해제 (필요한 경우)
- std::vector decompressedData;
- if (RokokoIntegration::LZ4Wrapper::IsValidLZ4Data(data.data(), static_cast(data.size()))) {
- decompressedData = RokokoIntegration::LZ4Wrapper::Decompress(data.data(), static_cast(data.size()));
- if (decompressedData.empty()) {
- if (mDeviceManager) {
- mDeviceManager->MessageToHost("[RokokoGlove] LZ4 decompression failed", MessageType_StatusInfo);
+ // 🚀 실시간성을 위해 정적 버퍼 사용 (메모리 할당 최소화)
+ static std::vector decompressedData;
+ decompressedData.clear();
+ decompressedData.reserve(data.size() * 4); // 미리 메모리 예약
+
+ // 🚀 Unity 방식: LZ4 압축 해제 (모든 로그 제거)
+ decompressedData = RokokoIntegration::LZ4Wrapper::Decompress(data.data(), static_cast(data.size()));
+
+ if (decompressedData.empty()) {
+ // 대안: LZ4 프레임 헤더 건너뛰고 압축 해제 시도
+ if (data.size() > 8) {
+ // LZ4 프레임 헤더 건너뛰기 (일반적으로 7바이트)
+ std::vector dataWithoutHeader(data.begin() + 7, data.end());
+ decompressedData = RokokoIntegration::LZ4Wrapper::Decompress(dataWithoutHeader.data(), static_cast(dataWithoutHeader.size()));
+
+ if (decompressedData.empty()) {
+ // Unity에서는 실패 시 에러 로그만 출력하고 null 반환
+ // 우리는 원본 데이터로 시도해봄
+ decompressedData = data;
}
- return;
+ } else {
+ decompressedData = data;
}
- } else {
- // LZ4 압축이 아닌 경우 원본 데이터 사용
- decompressedData = data;
}
- // 2단계: JSON 문자열로 변환
+ // 2단계: JSON 문자열로 변환 (유니티 방식 정확히 구현)
+ // Unity 방식: string text = System.Text.Encoding.UTF8.GetString(uncompressed);
std::string jsonString(decompressedData.begin(), decompressedData.end());
- // 3단계: JSON 파싱하여 Rokoko 데이터 구조로 변환
- RokokoData::LiveFrame_v4 rokokoFrame;
- if (!RokokoIntegration::RokokoDataParser::ParseLiveFrame(jsonString, rokokoFrame)) {
+ // 🚀 성능 최적화: 데이터 미리보기 로그 제거
+
+ // JSON 데이터 유효성 검사
+ if (jsonString.empty() || jsonString.length() < 10) {
if (mDeviceManager) {
- mDeviceManager->MessageToHost("[RokokoGlove] JSON parsing failed", MessageType_StatusInfo);
+ mDeviceManager->MessageToHost("[RokokoGlove] Invalid JSON data (too short)", MessageType_StatusInfo);
}
return;
}
+
+ // JSON 시작 문자 확인
+ if (jsonString[0] != '{') {
+ if (mDeviceManager) {
+ mDeviceManager->MessageToHost("[RokokoGlove] Invalid JSON data (does not start with '{')", MessageType_StatusInfo);
+ }
+
+ // 바이너리 데이터 분석 (첫 20바이트)
+ std::stringstream hexStream;
+ hexStream << "[RokokoGlove] Binary data detected: ";
+ for (size_t i = 0; i < 20 && i < decompressedData.size(); i++) {
+ char buf[8];
+ snprintf(buf, sizeof(buf), "%02X ", static_cast(decompressedData[i]));
+ hexStream << buf;
+ }
+ mDeviceManager->MessageToHost(hexStream.str().c_str(), MessageType_StatusInfo);
+ return;
+ }
+
+ // 3단계: JSON 파싱하여 Rokoko 데이터 구조로 변환
+ RokokoData::LiveFrame_v4 rokokoFrame;
+
+ // 🚀 실시간성 최우선 - 모든 로그 제거, 즉시 처리
+ if (!RokokoIntegration::RokokoDataParser::ParseLiveFrame(jsonString, rokokoFrame)) {
+ return; // 실패 시 즉시 종료
+ }
- // 4단계: 다중 장치 데이터 처리 (왼손, 오른손)
+ // 🚀 Actor 기반 동적 장치 감지 (로그 제거)
+ DetectActorsFromRokokoData(rokokoFrame);
+
+ // 🚀 첫 번째 데이터 수신 시 기본 장치도 생성 (백업)
+ if (mDetectedDevices.empty()) {
+ DetectAndCreateRokokoDevices();
+ }
+
+ // 🚀 다중 장치 데이터 처리 (왼손, 오른손) - 즉시 처리
ProcessMultipleDeviceData(rokokoFrame);
- // 5단계: 최신 프레임 업데이트
+ // 🚀 최신 프레임 업데이트 - 큐 없이 즉시 업데이트
mLastRokokoFrame = rokokoFrame;
- // 성공 메시지 출력
- if (mDeviceManager) {
- mDeviceManager->MessageToHost("[RokokoGlove] Successfully processed Rokoko data", MessageType_StatusInfo);
- }
-
- } catch (const std::exception& e) {
- if (mDeviceManager) {
- std::string errorMsg = "[RokokoGlove] Exception: " + std::string(e.what());
- mDeviceManager->MessageToHost(errorMsg.c_str(), MessageType_StatusInfo);
- }
} catch (...) {
- if (mDeviceManager) {
- mDeviceManager->MessageToHost("[RokokoGlove] Unknown exception occurred", MessageType_StatusInfo);
- }
+ // 🚀 실시간성 최우선 - 예외 발생 시 즉시 종료 (로그 제거)
+ return;
}
}
@@ -551,12 +587,16 @@ void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::Detect
mLatestGloveData.clear();
mLatestDeviceInfo.clear();
+ // 현재는 기본 2개 장치만 생성 (나중에 동적 감지로 변경)
+ // TODO: 실제 Rokoko 데이터에서 Actor 정보를 기반으로 동적 감지
+
// 왼손 장치 생성
sGloveDeviceBaseInfo leftHandInfo;
leftHandInfo.gloveId = 1;
leftHandInfo.handSide = eGloveHandSide::Left;
leftHandInfo.battery = 100;
leftHandInfo.signalStrength = 100;
+ leftHandInfo.actorName = "DefaultActor"; // Actor 이름 추가
CreateNewGloveDevice(leftHandInfo);
SetLatestDeviceInfo(leftHandInfo);
mDetectedDevices.push_back(1);
@@ -567,6 +607,7 @@ void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::Detect
rightHandInfo.handSide = eGloveHandSide::Right;
rightHandInfo.battery = 100;
rightHandInfo.signalStrength = 100;
+ rightHandInfo.actorName = "DefaultActor"; // Actor 이름 추가
CreateNewGloveDevice(rightHandInfo);
SetLatestDeviceInfo(rightHandInfo);
mDetectedDevices.push_back(2);
@@ -584,6 +625,79 @@ void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::Detect
}
}
+// 새로운 함수: 동적 Actor 기반 장치 감지
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::DetectActorsFromRokokoData(const RokokoData::LiveFrame_v4& rokokoFrame)
+{
+ try {
+ // 기존 장치 정보 백업
+ std::vector oldDevices = mDetectedDevices;
+ mDetectedDevices.clear();
+
+ // Actor별로 장치 생성
+ for (size_t actorIndex = 0; actorIndex < rokokoFrame.scene.actors.size(); actorIndex++) {
+ const auto& actor = rokokoFrame.scene.actors[actorIndex];
+ std::string actorName = actor.name;
+
+ // Actor 이름이 비어있으면 기본값 사용
+ if (actorName.empty()) {
+ actorName = "Actor" + std::to_string(actorIndex + 1);
+ }
+
+ // 왼손 장치 생성 (Actor별로 고유 ID 생성)
+ uint64_t leftHandId = (actorIndex * 2) + 1;
+ sGloveDeviceBaseInfo leftHandInfo;
+ leftHandInfo.gloveId = static_cast(leftHandId);
+ leftHandInfo.handSide = eGloveHandSide::Left;
+ leftHandInfo.battery = 100;
+ leftHandInfo.signalStrength = 100;
+ leftHandInfo.actorName = actorName;
+
+ // 새 장치인지 확인하고 생성
+ if (std::find(oldDevices.begin(), oldDevices.end(), leftHandId) == oldDevices.end()) {
+ CreateNewGloveDevice(leftHandInfo);
+ if (mDeviceManager) {
+ std::string msg = "[RokokoGlove] Created new left hand device for " + actorName + " (ID: " + std::to_string(leftHandId) + ")";
+ mDeviceManager->MessageToHost(msg.c_str(), MessageType_StatusInfo);
+ }
+ }
+ SetLatestDeviceInfo(leftHandInfo);
+ mDetectedDevices.push_back(leftHandId);
+
+ // 오른손 장치 생성
+ uint64_t rightHandId = (actorIndex * 2) + 2;
+ sGloveDeviceBaseInfo rightHandInfo;
+ rightHandInfo.gloveId = static_cast(rightHandId);
+ rightHandInfo.handSide = eGloveHandSide::Right;
+ rightHandInfo.battery = 100;
+ rightHandInfo.signalStrength = 100;
+ rightHandInfo.actorName = actorName;
+
+ // 새 장치인지 확인하고 생성
+ if (std::find(oldDevices.begin(), oldDevices.end(), rightHandId) == oldDevices.end()) {
+ CreateNewGloveDevice(rightHandInfo);
+ if (mDeviceManager) {
+ std::string msg = "[RokokoGlove] Created new right hand device for " + actorName + " (ID: " + std::to_string(rightHandId) + ")";
+ mDeviceManager->MessageToHost(msg.c_str(), MessageType_StatusInfo);
+ }
+ }
+ SetLatestDeviceInfo(rightHandInfo);
+ mDetectedDevices.push_back(rightHandId);
+ }
+
+ // 🚀 성능 최적화: 장치 감지 완료 로그 제거
+
+ } catch (const std::exception& e) {
+ if (mDeviceManager) {
+ std::string errorMsg = "[RokokoGlove] Exception in actor detection: " + std::string(e.what());
+ mDeviceManager->MessageToHost(errorMsg.c_str(), MessageType_StatusInfo);
+ }
+ } catch (...) {
+ if (mDeviceManager) {
+ mDeviceManager->MessageToHost("[RokokoGlove] Unknown exception in actor detection", MessageType_StatusInfo);
+ }
+ }
+}
+
void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ProcessMultipleDeviceData(const RokokoData::LiveFrame_v4& rokokoFrame)
{
try {
@@ -595,16 +709,23 @@ void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::Proces
return;
}
- const auto& actor = rokokoFrame.scene.actors[0];
-
- // 왼손 데이터 처리
- if (ProcessHandData(actor.body, 1, eGloveHandSide::Left)) {
- UpdateDeviceInfo(1);
- }
-
- // 오른손 데이터 처리
- if (ProcessHandData(actor.body, 2, eGloveHandSide::Right)) {
- UpdateDeviceInfo(2);
+ // 🚀 모든 Actor의 데이터를 처리 (다중 장갑 지원)
+ for (size_t actorIndex = 0; actorIndex < rokokoFrame.scene.actors.size(); actorIndex++) {
+ const auto& actor = rokokoFrame.scene.actors[actorIndex];
+
+ // Actor별 고유 ID 계산
+ uint64_t leftHandId = (actorIndex * 2) + 1;
+ uint64_t rightHandId = (actorIndex * 2) + 2;
+
+ // 왼손 데이터 처리
+ if (ProcessHandData(actor.body, leftHandId, eGloveHandSide::Left)) {
+ UpdateDeviceInfo(leftHandId);
+ }
+
+ // 오른손 데이터 처리
+ if (ProcessHandData(actor.body, rightHandId, eGloveHandSide::Right)) {
+ UpdateDeviceInfo(rightHandId);
+ }
}
} catch (const std::exception& e) {
@@ -642,12 +763,13 @@ bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::Proces
optiTrackData.nodes[i].node_id = i;
}
- // 데이터 유효성 검사
+ // 🚀 실시간성 최우선 - 모든 디버깅 제거
+
+ // 🎯 계층적 로컬 로테이션 계산 (Rokoko 월드 데이터 → 로컬 변환)
+ ConvertToTrueLocalRotations(optiTrackData.nodes, body, handSide);
+
+ // 🚀 실시간성 최우선 - 데이터 유효성 검사 (로그 제거)
if (!RokokoIntegration::RokokoDataConverter::ValidateOptiTrackData(optiTrackData)) {
- if (mDeviceManager) {
- std::string errorMsg = "[RokokoGlove] Data validation failed for device " + std::to_string(deviceId);
- mDeviceManager->MessageToHost(errorMsg.c_str(), MessageType_StatusInfo);
- }
return false;
}
@@ -659,10 +781,7 @@ bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::Proces
return true;
} catch (...) {
- if (mDeviceManager) {
- std::string errorMsg = "[RokokoGlove] Error processing hand data for device " + std::to_string(deviceId);
- mDeviceManager->MessageToHost(errorMsg.c_str(), MessageType_StatusInfo);
- }
+ // 🚀 실시간성 최우선 - 예외 발생 시 즉시 종료 (로그 제거)
return false;
}
}
@@ -725,9 +844,1438 @@ void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::MapRig
void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::MapJoint(const RokokoData::ActorJointFrame& rokokoJoint, sFingerNode& optiTrackNode)
{
- // 쿼터니언 변환
+ // Rokoko 로우 데이터를 직접 OptiTrack에 적용
optiTrackNode.quat_w = rokokoJoint.rotation.w;
optiTrackNode.quat_x = rokokoJoint.rotation.x;
optiTrackNode.quat_y = rokokoJoint.rotation.y;
optiTrackNode.quat_z = rokokoJoint.rotation.z;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 로컬 로테이션 변환 함수들
+//
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ConvertToLocalRotations(std::vector& nodes, eGloveHandSide handSide)
+{
+ // OptiTrack 손가락 배치: [Thumb(0-2), Index(3-5), Middle(6-8), Ring(9-11), Little(12-14)]
+ // 각 손가락의 계층 구조: Proximal/MP → Medial/PIP → Distal/DIP
+
+ // 손가락별로 로컬 로테이션 계산
+ for (int fingerIndex = 0; fingerIndex < 5; fingerIndex++) {
+ int baseIndex = fingerIndex * 3;
+
+ // MP 관절 (루트) - 월드 로테이션 유지
+ // 이미 설정된 값 사용
+
+ // PIP 관절 - MP 관절 기준 로컬 로테이션
+ if (baseIndex + 1 < nodes.size()) {
+ ApplyLocalRotation(nodes[baseIndex + 1], nodes[baseIndex]);
+ }
+
+ // DIP 관절 - PIP 관절 기준 로컬 로테이션
+ if (baseIndex + 2 < nodes.size()) {
+ ApplyLocalRotation(nodes[baseIndex + 2], nodes[baseIndex + 1]);
+ }
+ }
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ApplyLocalRotation(sFingerNode& childNode, const sFingerNode& parentNode)
+{
+ // 로컬 로테이션 = parent^-1 * child
+ sFingerNode parentInverse;
+ InverseQuaternion(parentNode, parentInverse);
+
+ sFingerNode localRotation;
+ MultiplyQuaternions(parentInverse, childNode, localRotation);
+
+ // 결과를 자식 노드에 적용
+ childNode.quat_w = localRotation.quat_w;
+ childNode.quat_x = localRotation.quat_x;
+ childNode.quat_y = localRotation.quat_y;
+ childNode.quat_z = localRotation.quat_z;
+
+ // 정규화
+ NormalizeQuaternion(childNode);
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::MultiplyQuaternions(const sFingerNode& q1, const sFingerNode& q2, sFingerNode& result)
+{
+ // 쿼터니언 곱셈: q1 * q2
+ result.quat_w = q1.quat_w * q2.quat_w - q1.quat_x * q2.quat_x - q1.quat_y * q2.quat_y - q1.quat_z * q2.quat_z;
+ result.quat_x = q1.quat_w * q2.quat_x + q1.quat_x * q2.quat_w + q1.quat_y * q2.quat_z - q1.quat_z * q2.quat_y;
+ result.quat_y = q1.quat_w * q2.quat_y - q1.quat_x * q2.quat_z + q1.quat_y * q2.quat_w + q1.quat_z * q2.quat_x;
+ result.quat_z = q1.quat_w * q2.quat_z + q1.quat_x * q2.quat_y - q1.quat_y * q2.quat_x + q1.quat_z * q2.quat_w;
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::InverseQuaternion(const sFingerNode& q, sFingerNode& result)
+{
+ // 쿼터니언 역원: (w, -x, -y, -z) / |q|^2
+ float norm = q.quat_w * q.quat_w + q.quat_x * q.quat_x + q.quat_y * q.quat_y + q.quat_z * q.quat_z;
+
+ if (norm > 0.0001f) { // 0으로 나누기 방지
+ result.quat_w = q.quat_w / norm;
+ result.quat_x = -q.quat_x / norm;
+ result.quat_y = -q.quat_y / norm;
+ result.quat_z = -q.quat_z / norm;
+ } else {
+ // 단위 쿼터니언으로 설정
+ result.quat_w = 1.0f;
+ result.quat_x = 0.0f;
+ result.quat_y = 0.0f;
+ result.quat_z = 0.0f;
+ }
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::NormalizeQuaternion(sFingerNode& q)
+{
+ float norm = sqrt(q.quat_w * q.quat_w + q.quat_x * q.quat_x + q.quat_y * q.quat_y + q.quat_z * q.quat_z);
+
+ if (norm > 0.0001f) { // 0으로 나누기 방지
+ q.quat_w /= norm;
+ q.quat_x /= norm;
+ q.quat_y /= norm;
+ q.quat_z /= norm;
+ } else {
+ // 단위 쿼터니언으로 설정
+ q.quat_w = 1.0f;
+ q.quat_x = 0.0f;
+ q.quat_y = 0.0f;
+ q.quat_z = 0.0f;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// T-포즈 캘리브레이션 시스템
+//
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::CalibrateTPose(uint64_t deviceId, const std::vector& tPoseData)
+{
+ try {
+ // T-포즈 기준값 저장
+ mTPoseReferences[deviceId] = tPoseData;
+ mTPoseCalibrated[deviceId] = true;
+
+ if (mDeviceManager) {
+ std::string msg = "[RokokoGlove] T-pose calibration completed for device " + std::to_string(deviceId);
+ mDeviceManager->MessageToHost(msg.c_str(), MessageType_StatusInfo);
+ }
+
+ } catch (...) {
+ if (mDeviceManager) {
+ std::string errorMsg = "[RokokoGlove] Error during T-pose calibration for device " + std::to_string(deviceId);
+ mDeviceManager->MessageToHost(errorMsg.c_str(), MessageType_StatusInfo);
+ }
+ }
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ApplyTPoseOffset(std::vector& nodes, uint64_t deviceId)
+{
+ if (!IsTPoseCalibrated(deviceId)) {
+ // T-포즈 캘리브레이션이 없으면 현재 데이터를 T-포즈로 사용
+ CalibrateTPose(deviceId, nodes);
+
+ // 첫 프레임은 중립 상태로 설정
+ for (auto& node : nodes) {
+ node.quat_w = 1.0f;
+ node.quat_x = 0.0f;
+ node.quat_y = 0.0f;
+ node.quat_z = 0.0f;
+ }
+ return;
+ }
+
+ try {
+ const auto& tPoseRef = mTPoseReferences[deviceId];
+
+ if (tPoseRef.size() != nodes.size()) {
+ if (mDeviceManager) {
+ mDeviceManager->MessageToHost("[RokokoGlove] T-pose reference size mismatch", MessageType_StatusInfo);
+ }
+ return;
+ }
+
+ // 각 관절에 대해 T-포즈 기준 오프셋 계산
+ for (size_t i = 0; i < nodes.size(); i++) {
+ // 오프셋 = T-pose^-1 * current
+ sFingerNode tPoseInverse;
+ InverseQuaternion(tPoseRef[i], tPoseInverse);
+
+ sFingerNode offsetRotation;
+ MultiplyQuaternions(tPoseInverse, nodes[i], offsetRotation);
+
+ // 결과를 노드에 적용
+ nodes[i].quat_w = offsetRotation.quat_w;
+ nodes[i].quat_x = offsetRotation.quat_x;
+ nodes[i].quat_y = offsetRotation.quat_y;
+ nodes[i].quat_z = offsetRotation.quat_z;
+
+ // 정규화
+ NormalizeQuaternion(nodes[i]);
+ }
+
+ } catch (...) {
+ if (mDeviceManager) {
+ std::string errorMsg = "[RokokoGlove] Error applying T-pose offset for device " + std::to_string(deviceId);
+ mDeviceManager->MessageToHost(errorMsg.c_str(), MessageType_StatusInfo);
+ }
+ }
+}
+
+bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::IsTPoseCalibrated(uint64_t deviceId) const
+{
+ auto it = mTPoseCalibrated.find(deviceId);
+ return (it != mTPoseCalibrated.end() && it->second);
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ResetTPoseCalibration(uint64_t deviceId)
+{
+ mTPoseReferences.erase(deviceId);
+ mTPoseCalibrated[deviceId] = false;
+
+ if (mDeviceManager) {
+ std::string msg = "[RokokoGlove] T-pose calibration reset for device " + std::to_string(deviceId);
+ mDeviceManager->MessageToHost(msg.c_str(), MessageType_StatusInfo);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 손목 기준 로컬 변환 시스템
+//
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ConvertToWristLocalRotations(std::vector& nodes, const RokokoData::Body& body, eGloveHandSide handSide)
+{
+ try {
+ // 손목 로테이션 가져오기
+ sFingerNode wristRotation;
+ if (!GetWristRotation(body, handSide, wristRotation)) {
+ if (mDeviceManager) {
+ mDeviceManager->MessageToHost("[RokokoGlove] Failed to get wrist rotation", MessageType_StatusInfo);
+ }
+ return;
+ }
+
+ // 손목 로테이션의 역원 계산
+ sFingerNode wristInverse;
+ InverseQuaternion(wristRotation, wristInverse);
+
+ // 모든 손가락에 대해 손목 기준 로컬 로테이션 계산
+ for (auto& node : nodes) {
+ // 로컬 로테이션 = 손목^-1 * 손가락
+ sFingerNode localRotation;
+ MultiplyQuaternions(wristInverse, node, localRotation);
+
+ // 결과 적용
+ node.quat_w = localRotation.quat_w;
+ node.quat_x = localRotation.quat_x;
+ node.quat_y = localRotation.quat_y;
+ node.quat_z = localRotation.quat_z;
+
+ // 정규화
+ NormalizeQuaternion(node);
+ }
+
+ if (mDeviceManager) {
+ std::string handStr = (handSide == eGloveHandSide::Left) ? "Left" : "Right";
+ std::string msg = "[RokokoGlove] Applied wrist-relative rotations for " + handStr + " hand";
+ mDeviceManager->MessageToHost(msg.c_str(), MessageType_StatusInfo);
+ }
+
+ } catch (...) {
+ if (mDeviceManager) {
+ mDeviceManager->MessageToHost("[RokokoGlove] Error in wrist-relative rotation conversion", MessageType_StatusInfo);
+ }
+ }
+}
+
+bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::GetWristRotation(const RokokoData::Body& body, eGloveHandSide handSide, sFingerNode& wristRotation)
+{
+ try {
+ // 🎯 실제 손목 데이터 사용! (이제 body.leftHand, body.rightHand가 있음)
+ std::shared_ptr wristJoint;
+
+ if (handSide == eGloveHandSide::Left) {
+ // 왼손 실제 손목 데이터 사용
+ wristJoint = body.leftHand;
+ } else {
+ // 오른손 실제 손목 데이터 사용
+ wristJoint = body.rightHand;
+ }
+
+ if (wristJoint) {
+ wristRotation.quat_w = wristJoint->rotation.w;
+ wristRotation.quat_x = wristJoint->rotation.x;
+ wristRotation.quat_y = wristJoint->rotation.y;
+ wristRotation.quat_z = wristJoint->rotation.z;
+ return true;
+ }
+
+ // 데이터가 없으면 단위 쿼터니언 사용
+ wristRotation.quat_w = 1.0f;
+ wristRotation.quat_x = 0.0f;
+ wristRotation.quat_y = 0.0f;
+ wristRotation.quat_z = 0.0f;
+
+ if (mDeviceManager) {
+ std::string handStr = (handSide == eGloveHandSide::Left) ? "Left" : "Right";
+ std::string msg = "[RokokoGlove] " + handStr + " reference joint not available, using identity rotation";
+ mDeviceManager->MessageToHost(msg.c_str(), MessageType_StatusInfo);
+ }
+
+ return true;
+
+ } catch (...) {
+ // 에러 시 단위 쿼터니언
+ wristRotation.quat_w = 1.0f;
+ wristRotation.quat_x = 0.0f;
+ wristRotation.quat_y = 0.0f;
+ wristRotation.quat_z = 0.0f;
+ return false;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 진짜 로컬 로테이션 변환 시스템 (계층적)
+//
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ConvertToTrueLocalRotations(std::vector& nodes, const RokokoData::Body& body, eGloveHandSide handSide)
+{
+ try {
+ // 모든 손가락의 관절 데이터 가져오기
+ std::vector> allFingers;
+ if (!GetFingerJointRotations(body, handSide, allFingers)) {
+ if (mDeviceManager) {
+ mDeviceManager->MessageToHost("[RokokoGlove] Failed to get finger joint rotations", MessageType_StatusInfo);
+ }
+ return;
+ }
+
+ // 각 손가락별로 계층적 로컬 로테이션 계산
+ int nodeIndex = 0;
+ for (const auto& fingerJoints : allFingers) {
+ if (fingerJoints.size() >= 3) { // MP, PIP, DIP 최소 3개
+ CalculateFingerLocalRotations(fingerJoints, nodes, nodeIndex, body, handSide);
+ nodeIndex += 3; // OptiTrack은 손가락당 3개 노드 (MP, PIP, DIP)
+ }
+ }
+
+ // 🚀 실시간성을 위해 로그 메시지 제거
+
+ } catch (...) {
+ if (mDeviceManager) {
+ mDeviceManager->MessageToHost("[RokokoGlove] Error in true local rotation conversion", MessageType_StatusInfo);
+ }
+ }
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::CalculateFingerLocalRotations(const std::vector& fingerJoints, std::vector& outputNodes, int startIndex, const RokokoData::Body& body, eGloveHandSide handSide)
+{
+ try {
+ if (fingerJoints.size() < 3 || startIndex + 2 >= outputNodes.size()) {
+ return;
+ }
+
+ // 🎯 핵심: 실제 손목 로테이션을 부모로 사용!
+ sFingerNode wristRotation;
+ GetWristRotation(body, handSide, wristRotation); // body 파라미터 사용
+ sFingerNode parentRotation = wristRotation; // 첫 번째 손가락 관절의 부모는 손목
+
+ // 각 관절별로 부모 기준 로컬 로테이션 계산
+ for (int i = 0; i < 3 && (startIndex + i) < outputNodes.size(); i++) {
+ if (i < fingerJoints.size() && fingerJoints[i]) {
+ // 현재 관절의 월드 로테이션
+ sFingerNode currentWorld;
+ currentWorld.quat_w = fingerJoints[i]->rotation.w;
+ currentWorld.quat_x = fingerJoints[i]->rotation.x;
+ currentWorld.quat_y = fingerJoints[i]->rotation.y;
+ currentWorld.quat_z = fingerJoints[i]->rotation.z;
+
+ // 부모 로테이션의 역원
+ sFingerNode parentInverse;
+ InverseQuaternion(parentRotation, parentInverse);
+
+ // 로컬 로테이션 = 부모^-1 × 현재
+ sFingerNode localRotation;
+ MultiplyQuaternions(parentInverse, currentWorld, localRotation);
+
+ // 🔧 좌표계 변환 적용 (Rokoko → OptiTrack) - 손별 대칭성 보정
+ ConvertRokokoToOptiTrackCoordinates(localRotation, handSide);
+
+ // 🎯 손가락별 축 순서 변경 적용 (엄지와 일반 손가락 분리)
+ sFingerNode finalRotation = localRotation;
+
+ // 엄지손가락인지 확인 (startIndex == 0이면 엄지)
+ bool isThumb = (startIndex == 0);
+
+ if (isThumb) {
+ // 🖐️ 엄지손가락 전용 축 변환
+ if (handSide == eGloveHandSide::Left) {
+ // 🤚 왼손 엄지: 특별한 축 변환
+ outputNodes[startIndex + i].quat_w = finalRotation.quat_w; // W는 그대로
+ outputNodes[startIndex + i].quat_x = finalRotation.quat_y; // X ← Z
+ outputNodes[startIndex + i].quat_y = -finalRotation.quat_z; // Y ← Y (그대로)
+ outputNodes[startIndex + i].quat_z = -finalRotation.quat_x; // Z ← X (엄지 전용)
+ } else {
+ // 🖐️ 오른손 엄지: 특별한 축 변환
+ outputNodes[startIndex + i].quat_w = finalRotation.quat_w; // W는 그대로
+ outputNodes[startIndex + i].quat_x = -finalRotation.quat_y; // X ← Z (엄지 전용)
+ outputNodes[startIndex + i].quat_y = -finalRotation.quat_z; // Y ← Y (그대로)
+ outputNodes[startIndex + i].quat_z = finalRotation.quat_x; // Z ← -X (엄지 전용)
+ }
+ } else {
+ // 🤚 일반 손가락 축 변환
+ if (handSide == eGloveHandSide::Left) {
+ // 🤚 왼손 일반 손가락: x y z → z y x (기존 방식)
+ outputNodes[startIndex + i].quat_w = finalRotation.quat_w; // W는 그대로
+ outputNodes[startIndex + i].quat_x = finalRotation.quat_z; // X ← Z
+ outputNodes[startIndex + i].quat_y = -finalRotation.quat_y; // Y ← -Y
+ outputNodes[startIndex + i].quat_z = -finalRotation.quat_x; // Z ← -X
+ } else {
+ // 🖐️ 오른손 일반 손가락: 대칭적 축 매핑 (미러링 보정)
+ outputNodes[startIndex + i].quat_w = finalRotation.quat_w; // W는 그대로
+ outputNodes[startIndex + i].quat_x = -finalRotation.quat_z; // X ← -Z (미러링)
+ outputNodes[startIndex + i].quat_y = finalRotation.quat_y; // Y ← Y (그대로)
+ outputNodes[startIndex + i].quat_z = finalRotation.quat_x; // Z ← X (미러링)
+ }
+ }
+
+ // 정규화
+ NormalizeQuaternion(outputNodes[startIndex + i]);
+
+ // 다음 계산을 위해 현재 관절을 부모로 설정
+ parentRotation = currentWorld;
+ }
+ }
+
+ } catch (...) {
+ // 에러 시 기본값 설정
+ for (int i = 0; i < 3 && (startIndex + i) < outputNodes.size(); i++) {
+ outputNodes[startIndex + i].quat_w = 1.0f;
+ outputNodes[startIndex + i].quat_x = 0.0f;
+ outputNodes[startIndex + i].quat_y = 0.0f;
+ outputNodes[startIndex + i].quat_z = 0.0f;
+ }
+ }
+}
+
+bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::GetFingerJointRotations(const RokokoData::Body& body, eGloveHandSide handSide, std::vector>& allFingers)
+{
+ try {
+ allFingers.clear();
+
+ if (handSide == eGloveHandSide::Left) {
+ // 왼손 손가락들 (5개 손가락)
+ allFingers.resize(5);
+
+ // 엄지
+ allFingers[0].push_back(body.leftThumbProximal.get());
+ allFingers[0].push_back(body.leftThumbMedial.get());
+ allFingers[0].push_back(body.leftThumbDistal.get());
+
+ // 검지
+ allFingers[1].push_back(body.leftIndexProximal.get());
+ allFingers[1].push_back(body.leftIndexMedial.get());
+ allFingers[1].push_back(body.leftIndexDistal.get());
+
+ // 중지
+ allFingers[2].push_back(body.leftMiddleProximal.get());
+ allFingers[2].push_back(body.leftMiddleMedial.get());
+ allFingers[2].push_back(body.leftMiddleDistal.get());
+
+ // 약지
+ allFingers[3].push_back(body.leftRingProximal.get());
+ allFingers[3].push_back(body.leftRingMedial.get());
+ allFingers[3].push_back(body.leftRingDistal.get());
+
+ // 새끼
+ allFingers[4].push_back(body.leftLittleProximal.get());
+ allFingers[4].push_back(body.leftLittleMedial.get());
+ allFingers[4].push_back(body.leftLittleDistal.get());
+
+ } else {
+ // 오른손 손가락들 (5개 손가락)
+ allFingers.resize(5);
+
+ // 엄지
+ allFingers[0].push_back(body.rightThumbProximal.get());
+ allFingers[0].push_back(body.rightThumbMedial.get());
+ allFingers[0].push_back(body.rightThumbDistal.get());
+
+ // 검지
+ allFingers[1].push_back(body.rightIndexProximal.get());
+ allFingers[1].push_back(body.rightIndexMedial.get());
+ allFingers[1].push_back(body.rightIndexDistal.get());
+
+ // 중지
+ allFingers[2].push_back(body.rightMiddleProximal.get());
+ allFingers[2].push_back(body.rightMiddleMedial.get());
+ allFingers[2].push_back(body.rightMiddleDistal.get());
+
+ // 약지
+ allFingers[3].push_back(body.rightRingProximal.get());
+ allFingers[3].push_back(body.rightRingMedial.get());
+ allFingers[3].push_back(body.rightRingDistal.get());
+
+ // 새끼
+ allFingers[4].push_back(body.rightLittleProximal.get());
+ allFingers[4].push_back(body.rightLittleMedial.get());
+ allFingers[4].push_back(body.rightLittleDistal.get());
+ }
+
+ return true;
+
+ } catch (...) {
+ allFingers.clear();
+ return false;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 좌표계 변환 및 디버깅 시스템
+//
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ConvertRokokoToOptiTrackCoordinates(sFingerNode& node, eGloveHandSide handSide)
+{
+ // 🎯 손별 좌표계 변환 (왼손/오른손 대칭성 보정)
+ //
+ // 로코코: Unity 좌표계 (왼손 좌표계, Y-up, Z-forward)
+ // OptiTrack: 오른손 좌표계 (Y-up, Z-backward)
+
+ float original_x = node.quat_x;
+ float original_z = node.quat_z;
+
+ if (handSide == eGloveHandSide::Left) {
+ // 🤚 왼손: 미러링 변환 필요 (좌표계 차이로 인한 대칭성 보정)
+ node.quat_x = -original_x; // X축 반전 (좌우 미러링)
+ node.quat_z = -original_z; // Z축 반전 (앞뒤 미러링)
+ // Y, W는 그대로 유지
+
+ } else {
+ // 🖐️ 오른손: 직접 매핑 (OptiTrack과 좌표계 일치)
+ // X, Z 축 반전하지 않음
+ node.quat_x = original_x; // X축 그대로
+ node.quat_z = original_z; // Z축 그대로
+ // Y, W는 그대로 유지
+ }
+}
+
+// 🚀 ApplyThumbOffset 함수 제거됨 - 인라인으로 처리
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::LogRotationData(const std::string& label, const sFingerNode& rotation)
+{
+ if (mDeviceManager) {
+ std::stringstream ss;
+ ss << "[RokokoGlove] " << label << " - W:" << std::fixed << std::setprecision(3) << rotation.quat_w
+ << " X:" << rotation.quat_x << " Y:" << rotation.quat_y << " Z:" << rotation.quat_z;
+ mDeviceManager->MessageToHost(ss.str().c_str(), MessageType_StatusInfo);
+ }
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::DebugCoordinateSystem(const RokokoData::Body& body, eGloveHandSide handSide)
+{
+ // 🚀 실시간성 최우선 - 모든 디버깅 제거 (함수 비활성화)
+ return;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 절대 로컬 로테이션 시스템 (손목에 완전히 독립적)
+//
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ConvertToAbsoluteLocalRotations(std::vector& nodes, const RokokoData::Body& body, eGloveHandSide handSide)
+{
+ try {
+ // 모든 손가락의 관절 데이터 가져오기
+ std::vector> allFingers;
+ if (!GetFingerJointRotations(body, handSide, allFingers)) {
+ if (mDeviceManager) {
+ mDeviceManager->MessageToHost("[RokokoGlove] Failed to get finger joint rotations for absolute local", MessageType_StatusInfo);
+ }
+ return;
+ }
+
+ // 각 손가락별로 절대 로컬 로테이션 계산
+ int nodeIndex = 0;
+ for (const auto& fingerJoints : allFingers) {
+ if (fingerJoints.size() >= 3) { // MP, PIP, DIP 최소 3개
+ CalculateAbsoluteLocalForFinger(fingerJoints, nodes, nodeIndex, handSide);
+ nodeIndex += 3; // OptiTrack은 손가락당 3개 노드 (MP, PIP, DIP)
+ }
+ }
+
+ if (mDeviceManager) {
+ std::string handStr = (handSide == eGloveHandSide::Left) ? "Left" : "Right";
+ std::string msg = "[RokokoGlove] Applied absolute local rotations for " + handStr + " hand";
+ mDeviceManager->MessageToHost(msg.c_str(), MessageType_StatusInfo);
+ }
+
+ } catch (...) {
+ if (mDeviceManager) {
+ mDeviceManager->MessageToHost("[RokokoGlove] Error in absolute local rotation conversion", MessageType_StatusInfo);
+ }
+ }
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::CalculateAbsoluteLocalForFinger(const std::vector& fingerJoints, std::vector& outputNodes, int startIndex, eGloveHandSide handSide)
+{
+ try {
+ if (fingerJoints.size() < 3 || startIndex + 2 >= outputNodes.size()) {
+ return;
+ }
+
+ // 각 관절별로 절대 로컬 로테이션 계산
+ for (int i = 0; i < 3 && (startIndex + i) < outputNodes.size(); i++) {
+ if (i < fingerJoints.size() && fingerJoints[i]) {
+ // 현재 관절의 월드 로테이션을 가져옴
+ sFingerNode worldRotation;
+ worldRotation.quat_w = fingerJoints[i]->rotation.w;
+ worldRotation.quat_x = fingerJoints[i]->rotation.x;
+ worldRotation.quat_y = fingerJoints[i]->rotation.y;
+ worldRotation.quat_z = fingerJoints[i]->rotation.z;
+
+ // 좌표계 변환 적용
+ ConvertRokokoToOptiTrackCoordinates(worldRotation, handSide);
+
+ // 절대적인 로컬 로테이션 계산
+ sFingerNode localRotation;
+ if (i == 0) {
+ // 첫 번째 관절 (MP): 기본 T-포즈 기준으로 로컬 변환
+ // T-포즈 = (0, 0, 0, 1) 즉, 단위 쿼터니언
+ localRotation = worldRotation; // 첫 번째는 월드 그대로 사용
+ } else {
+ // 나머지 관절들: 이전 관절 기준으로 상대 로테이션 계산
+ sFingerNode parentWorld;
+ parentWorld.quat_w = fingerJoints[i-1]->rotation.w;
+ parentWorld.quat_x = fingerJoints[i-1]->rotation.x;
+ parentWorld.quat_y = fingerJoints[i-1]->rotation.y;
+ parentWorld.quat_z = fingerJoints[i-1]->rotation.z;
+
+ // 부모도 좌표계 변환
+ ConvertRokokoToOptiTrackCoordinates(parentWorld, handSide);
+
+ // 상대 로테이션 = parent^-1 * child
+ localRotation = CalculateRelativeRotation(parentWorld, worldRotation);
+ }
+
+ // 결과를 출력 노드에 저장
+ outputNodes[startIndex + i].quat_w = localRotation.quat_w;
+ outputNodes[startIndex + i].quat_x = localRotation.quat_x;
+ outputNodes[startIndex + i].quat_y = localRotation.quat_y;
+ outputNodes[startIndex + i].quat_z = localRotation.quat_z;
+
+ // 정규화
+ NormalizeQuaternion(outputNodes[startIndex + i]);
+ }
+ }
+
+ } catch (...) {
+ // 에러 시 기본값 설정
+ for (int i = 0; i < 3 && (startIndex + i) < outputNodes.size(); i++) {
+ outputNodes[startIndex + i].quat_w = 1.0f;
+ outputNodes[startIndex + i].quat_x = 0.0f;
+ outputNodes[startIndex + i].quat_y = 0.0f;
+ outputNodes[startIndex + i].quat_z = 0.0f;
+ }
+ }
+}
+
+sFingerNode OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::CalculateRelativeRotation(const sFingerNode& parent, const sFingerNode& child)
+{
+ try {
+ // 상대 로테이션 = parent^-1 * child
+ sFingerNode parentInverse;
+ InverseQuaternion(parent, parentInverse);
+
+ sFingerNode result;
+ MultiplyQuaternions(parentInverse, child, result);
+
+ // 정규화
+ NormalizeQuaternion(result);
+
+ return result;
+
+ } catch (...) {
+ // 에러 시 단위 쿼터니언 반환
+ sFingerNode identity;
+ identity.quat_w = 1.0f;
+ identity.quat_x = 0.0f;
+ identity.quat_y = 0.0f;
+ identity.quat_z = 0.0f;
+ return identity;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 가상 손 구조 시스템 (정확한 계층적 로컬 로테이션)
+//
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ConvertToUnityRokokoMethod(std::vector& nodes, const RokokoData::Body& body, eGloveHandSide handSide)
+{
+ try {
+ // Unity 방식: 원시 Rokoko 데이터에 T-포즈 오프셋 적용
+ std::vector> allFingers;
+ if (!GetFingerJointRotations(body, handSide, allFingers)) {
+ if (mDeviceManager) {
+ mDeviceManager->MessageToHost("[RokokoGlove] Failed to get finger joint rotations for Unity method", MessageType_StatusInfo);
+ }
+ return;
+ }
+
+ // 각 손가락별로 Unity 방식 적용
+ int nodeIndex = 0;
+ for (int fingerIndex = 0; fingerIndex < allFingers.size(); fingerIndex++) {
+ const auto& fingerJoints = allFingers[fingerIndex];
+ if (fingerJoints.size() >= 3) { // MP, PIP, DIP 최소 3개
+ for (int jointIndex = 0; jointIndex < 3 && (nodeIndex + jointIndex) < nodes.size(); jointIndex++) {
+ if (jointIndex < fingerJoints.size() && fingerJoints[jointIndex]) {
+ // Unity 방식 로테이션 적용
+ ApplyUnityRokokoRotation(nodes[nodeIndex + jointIndex], fingerJoints[jointIndex], fingerIndex, jointIndex, handSide);
+ } else {
+ // 데이터가 없으면 단위 쿼터니언
+ nodes[nodeIndex + jointIndex].quat_w = 1.0f;
+ nodes[nodeIndex + jointIndex].quat_x = 0.0f;
+ nodes[nodeIndex + jointIndex].quat_y = 0.0f;
+ nodes[nodeIndex + jointIndex].quat_z = 0.0f;
+ }
+ // 노드 ID 설정
+ nodes[nodeIndex + jointIndex].node_id = nodeIndex + jointIndex;
+ }
+ nodeIndex += 3; // OptiTrack은 손가락당 3개 노드
+ }
+ }
+
+ if (mDeviceManager) {
+ std::string handStr = (handSide == eGloveHandSide::Left) ? "Left" : "Right";
+ std::string msg = "[RokokoGlove] Applied Unity Rokoko method for " + handStr + " hand";
+ mDeviceManager->MessageToHost(msg.c_str(), MessageType_StatusInfo);
+ }
+
+ } catch (...) {
+ if (mDeviceManager) {
+ mDeviceManager->MessageToHost("[RokokoGlove] Error in Unity Rokoko method conversion", MessageType_StatusInfo);
+ }
+ }
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::BuildVirtualHandFromRokoko(VirtualHand& virtualHand, const RokokoData::Body& body, eGloveHandSide handSide)
+{
+ try {
+ // 손목 로테이션 설정 (기준점)
+ virtualHand.wristRotation.quat_w = 1.0f;
+ virtualHand.wristRotation.quat_x = 0.0f;
+ virtualHand.wristRotation.quat_y = 0.0f;
+ virtualHand.wristRotation.quat_z = 0.0f;
+
+ // 각 손가락의 월드 로테이션 설정
+ if (handSide == eGloveHandSide::Left) {
+ // 왼손 엄지
+ if (body.leftThumbProximal) {
+ virtualHand.thumb.mp.worldRotation.quat_w = body.leftThumbProximal->rotation.w;
+ virtualHand.thumb.mp.worldRotation.quat_x = body.leftThumbProximal->rotation.x;
+ virtualHand.thumb.mp.worldRotation.quat_y = body.leftThumbProximal->rotation.y;
+ virtualHand.thumb.mp.worldRotation.quat_z = body.leftThumbProximal->rotation.z;
+ virtualHand.thumb.mp.parent = nullptr; // 손목이 부모
+ }
+ if (body.leftThumbMedial) {
+ virtualHand.thumb.pip.worldRotation.quat_w = body.leftThumbMedial->rotation.w;
+ virtualHand.thumb.pip.worldRotation.quat_x = body.leftThumbMedial->rotation.x;
+ virtualHand.thumb.pip.worldRotation.quat_y = body.leftThumbMedial->rotation.y;
+ virtualHand.thumb.pip.worldRotation.quat_z = body.leftThumbMedial->rotation.z;
+ virtualHand.thumb.pip.parent = &virtualHand.thumb.mp;
+ }
+ if (body.leftThumbDistal) {
+ virtualHand.thumb.dip.worldRotation.quat_w = body.leftThumbDistal->rotation.w;
+ virtualHand.thumb.dip.worldRotation.quat_x = body.leftThumbDistal->rotation.x;
+ virtualHand.thumb.dip.worldRotation.quat_y = body.leftThumbDistal->rotation.y;
+ virtualHand.thumb.dip.worldRotation.quat_z = body.leftThumbDistal->rotation.z;
+ virtualHand.thumb.dip.parent = &virtualHand.thumb.pip;
+ }
+
+ // 왼손 검지
+ if (body.leftIndexProximal) {
+ virtualHand.index.mp.worldRotation.quat_w = body.leftIndexProximal->rotation.w;
+ virtualHand.index.mp.worldRotation.quat_x = body.leftIndexProximal->rotation.x;
+ virtualHand.index.mp.worldRotation.quat_y = body.leftIndexProximal->rotation.y;
+ virtualHand.index.mp.worldRotation.quat_z = body.leftIndexProximal->rotation.z;
+ virtualHand.index.mp.parent = nullptr;
+ }
+ if (body.leftIndexMedial) {
+ virtualHand.index.pip.worldRotation.quat_w = body.leftIndexMedial->rotation.w;
+ virtualHand.index.pip.worldRotation.quat_x = body.leftIndexMedial->rotation.x;
+ virtualHand.index.pip.worldRotation.quat_y = body.leftIndexMedial->rotation.y;
+ virtualHand.index.pip.worldRotation.quat_z = body.leftIndexMedial->rotation.z;
+ virtualHand.index.pip.parent = &virtualHand.index.mp;
+ }
+ if (body.leftIndexDistal) {
+ virtualHand.index.dip.worldRotation.quat_w = body.leftIndexDistal->rotation.w;
+ virtualHand.index.dip.worldRotation.quat_x = body.leftIndexDistal->rotation.x;
+ virtualHand.index.dip.worldRotation.quat_y = body.leftIndexDistal->rotation.y;
+ virtualHand.index.dip.worldRotation.quat_z = body.leftIndexDistal->rotation.z;
+ virtualHand.index.dip.parent = &virtualHand.index.pip;
+ }
+
+ // 같은 방식으로 중지, 약지, 새끼 (여기서는 간략화)
+ // TODO: 나머지 손가락들도 동일하게 구현
+
+ } else {
+ // 오른손 - 같은 구조, 다른 데이터
+ // TODO: 오른손 구현
+ }
+
+ } catch (...) {
+ // 에러 시 기본값 설정
+ }
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::CalculateHierarchicalLocalRotations(VirtualHand& virtualHand)
+{
+ try {
+ // 각 손가락의 계층적 로컬 로테이션 계산
+
+ // 엄지 손가락
+ virtualHand.thumb.mp.localRotation = CalculateLocalRotationFromParent(virtualHand.thumb.mp, virtualHand.thumb.mp); // 첫 번째는 손목 기준
+ virtualHand.thumb.pip.localRotation = CalculateLocalRotationFromParent(virtualHand.thumb.mp, virtualHand.thumb.pip);
+ virtualHand.thumb.dip.localRotation = CalculateLocalRotationFromParent(virtualHand.thumb.pip, virtualHand.thumb.dip);
+
+ // 검지 손가락
+ virtualHand.index.mp.localRotation = CalculateLocalRotationFromParent(virtualHand.index.mp, virtualHand.index.mp);
+ virtualHand.index.pip.localRotation = CalculateLocalRotationFromParent(virtualHand.index.mp, virtualHand.index.pip);
+ virtualHand.index.dip.localRotation = CalculateLocalRotationFromParent(virtualHand.index.pip, virtualHand.index.dip);
+
+ // TODO: 나머지 손가락들
+
+ } catch (...) {
+ // 에러 시 기본값
+ }
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ExtractLocalRotationsToNodes(const VirtualHand& virtualHand, std::vector& nodes)
+{
+ try {
+ if (nodes.size() < 15) return;
+
+ // OptiTrack 노드 순서에 맞게 추출
+ // 엄지: 0, 1, 2
+ nodes[0] = virtualHand.thumb.mp.localRotation;
+ nodes[1] = virtualHand.thumb.pip.localRotation;
+ nodes[2] = virtualHand.thumb.dip.localRotation;
+
+ // 검지: 3, 4, 5
+ nodes[3] = virtualHand.index.mp.localRotation;
+ nodes[4] = virtualHand.index.pip.localRotation;
+ nodes[5] = virtualHand.index.dip.localRotation;
+
+ // TODO: 나머지 손가락들 (6~14)
+
+ // 노드 ID 설정
+ for (int i = 0; i < 15; i++) {
+ nodes[i].node_id = i;
+ NormalizeQuaternion(nodes[i]);
+ }
+
+ } catch (...) {
+ // 에러 시 기본값
+ }
+}
+
+sFingerNode OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::CalculateLocalRotationFromParent(const VirtualJoint& parent, const VirtualJoint& child)
+{
+ try {
+ if (parent.parent == nullptr) {
+ // 첫 번째 관절은 월드 로테이션 그대로 (손목 기준)
+ return child.worldRotation;
+ } else {
+ // 부모 기준 로컬 로테이션 = parent^-1 * child
+ sFingerNode parentInverse;
+ InverseQuaternion(parent.worldRotation, parentInverse);
+
+ sFingerNode localRotation;
+ MultiplyQuaternions(parentInverse, child.worldRotation, localRotation);
+
+ return localRotation;
+ }
+
+ } catch (...) {
+ // 에러 시 단위 쿼터니언
+ sFingerNode identity;
+ identity.quat_w = 1.0f;
+ identity.quat_x = 0.0f;
+ identity.quat_y = 0.0f;
+ identity.quat_z = 0.0f;
+ return identity;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// Unity 방식 Rokoko 데이터 처리 (원시 데이터 + T-포즈 오프셋)
+//
+sFingerNode OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::GetUnityTPoseOffset(int fingerIndex, int jointIndex, eGloveHandSide handSide)
+{
+ // Unity의 SmartsuitTPose 데이터 (정확히 동일)
+ sFingerNode offset;
+
+ if (handSide == eGloveHandSide::Left) {
+ // 왼손 T-포즈 오프셋 (Unity Actor.cs에서 복사)
+ if (fingerIndex == 0) { // 엄지
+ if (jointIndex == 0) { // Proximal
+ offset.quat_w = -0.092f; offset.quat_x = -0.561f; offset.quat_y = -0.701f; offset.quat_z = 0.430f;
+ } else { // Intermediate, Distal
+ offset.quat_w = -0.271f; offset.quat_x = -0.653f; offset.quat_y = -0.653f; offset.quat_z = 0.271f;
+ }
+ } else { // 검지, 중지, 약지, 새끼
+ offset.quat_w = -0.500f; offset.quat_x = -0.500f; offset.quat_y = -0.500f; offset.quat_z = 0.500f;
+ }
+ } else {
+ // 오른손 T-포즈 오프셋 (Unity Actor.cs에서 복사)
+ if (fingerIndex == 0) { // 엄지
+ if (jointIndex == 0) { // Proximal
+ offset.quat_w = 0.092f; offset.quat_x = 0.561f; offset.quat_y = -0.701f; offset.quat_z = 0.430f;
+ } else { // Intermediate, Distal
+ offset.quat_w = 0.271f; offset.quat_x = 0.653f; offset.quat_y = -0.653f; offset.quat_z = 0.271f;
+ }
+ } else { // 검지, 중지, 약지, 새끼
+ offset.quat_w = 0.500f; offset.quat_x = 0.500f; offset.quat_y = -0.500f; offset.quat_z = 0.500f;
+ }
+ }
+
+ return offset;
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ApplyUnityRokokoRotation(sFingerNode& node, const RokokoData::ActorJointFrame* jointFrame, int fingerIndex, int jointIndex, eGloveHandSide handSide)
+{
+ try {
+ // 1. Rokoko 원시 월드 로테이션
+ sFingerNode worldRotation;
+ worldRotation.quat_w = jointFrame->rotation.w;
+ worldRotation.quat_x = jointFrame->rotation.x;
+ worldRotation.quat_y = jointFrame->rotation.y;
+ worldRotation.quat_z = jointFrame->rotation.z;
+
+ // 2. Unity T-포즈 오프셋
+ sFingerNode tposeOffset = GetUnityTPoseOffset(fingerIndex, jointIndex, handSide);
+
+ // 3. Unity Actor.cs 공식 정확히 따라하기
+ // Unity: boneTransform.rotation = parentRotation * worldRotation * offset
+ // OptiTrack: 각 손가락은 독립 센서이므로 worldRotation * offset만 적용
+
+ // 🎯 핵심: Unity는 worldRotation을 그대로 사용 (로컬 변환 안함!)
+ // 손가락이 손목에 따라 움직이는 것은 Unity Transform 계층에서 자동 처리됨
+ sFingerNode finalRotation;
+ MultiplyQuaternions(worldRotation, tposeOffset, finalRotation);
+
+ // 4. 결과 적용
+ node.quat_w = finalRotation.quat_w;
+ node.quat_x = finalRotation.quat_x;
+ node.quat_y = finalRotation.quat_y;
+ node.quat_z = finalRotation.quat_z;
+
+ // 5. 정규화
+ NormalizeQuaternion(node);
+
+ } catch (...) {
+ // 에러 시 단위 쿼터니언
+ node.quat_w = 1.0f;
+ node.quat_x = 0.0f;
+ node.quat_y = 0.0f;
+ node.quat_z = 0.0f;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// 가상 Unity 아바타 시뮬레이션 시스템
+//
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ConvertToVirtualUnityAvatar(std::vector& nodes, const RokokoData::Body& body, eGloveHandSide handSide)
+{
+ try {
+ // 1. 가상 Unity 아바타 초기화
+ VirtualUnityAvatar avatar;
+ InitializeVirtualUnityAvatar(avatar);
+
+ // 2. Rokoko 데이터를 가상 아바타에 적용 (월드 로테이션)
+ ApplyRokokoDataToVirtualAvatar(avatar, body);
+
+ // 3. Unity 방식으로 로컬 로테이션 계산
+ CalculateLocalRotationsFromWorldRotations(avatar);
+
+ // 4. 로컬 로테이션을 OptiTrack 노드로 추출
+ ExtractLocalRotationsFromVirtualAvatar(avatar, nodes, handSide);
+
+ if (mDeviceManager) {
+ std::string handStr = (handSide == eGloveHandSide::Left) ? "Left" : "Right";
+ std::string msg = "[RokokoGlove] Applied Virtual Unity Avatar for " + handStr + " hand";
+ mDeviceManager->MessageToHost(msg.c_str(), MessageType_StatusInfo);
+ }
+
+ } catch (...) {
+ if (mDeviceManager) {
+ mDeviceManager->MessageToHost("[RokokoGlove] Error in Virtual Unity Avatar conversion", MessageType_StatusInfo);
+ }
+ }
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::InitializeVirtualUnityAvatar(VirtualUnityAvatar& avatar)
+{
+ try {
+ // Root Transform 초기화
+ avatar.root.name = "Root";
+ avatar.root.parent = nullptr;
+ avatar.root.localRotation.quat_w = 1.0f; // Identity
+ avatar.root.localRotation.quat_x = 0.0f;
+ avatar.root.localRotation.quat_y = 0.0f;
+ avatar.root.localRotation.quat_z = 0.0f;
+ avatar.root.worldRotation.quat_w = 1.0f;
+ avatar.root.worldRotation.quat_x = 0.0f;
+ avatar.root.worldRotation.quat_y = 0.0f;
+ avatar.root.worldRotation.quat_z = 0.0f;
+
+ // === 전체 스켈레톤 계층 구조 생성 (Unity Humanoid와 동일) ===
+
+ // 1. 허리 (최상위 부모)
+ avatar.hips = new VirtualTransform();
+ avatar.hips->name = "Hips";
+ avatar.hips->parent = &avatar.root;
+ avatar.hips->localRotation.quat_w = 1.0f;
+ avatar.hips->localRotation.quat_x = 0.0f;
+ avatar.hips->localRotation.quat_y = 0.0f;
+ avatar.hips->localRotation.quat_z = 0.0f;
+
+ // 2. 스파인 (허리의 자식)
+ avatar.spine = new VirtualTransform();
+ avatar.spine->name = "Spine";
+ avatar.spine->parent = avatar.hips;
+ avatar.spine->localRotation.quat_w = 1.0f;
+ avatar.spine->localRotation.quat_x = 0.0f;
+ avatar.spine->localRotation.quat_y = 0.0f;
+ avatar.spine->localRotation.quat_z = 0.0f;
+
+ // 3. 가슴 (스파인의 자식)
+ avatar.chest = new VirtualTransform();
+ avatar.chest->name = "Chest";
+ avatar.chest->parent = avatar.spine;
+ avatar.chest->localRotation.quat_w = 1.0f;
+ avatar.chest->localRotation.quat_x = 0.0f;
+ avatar.chest->localRotation.quat_y = 0.0f;
+ avatar.chest->localRotation.quat_z = 0.0f;
+
+ // 4. 어깨들 (가슴의 자식)
+ avatar.leftShoulder = new VirtualTransform();
+ avatar.leftShoulder->name = "LeftShoulder";
+ avatar.leftShoulder->parent = avatar.chest;
+ avatar.leftShoulder->localRotation.quat_w = 1.0f;
+ avatar.leftShoulder->localRotation.quat_x = 0.0f;
+ avatar.leftShoulder->localRotation.quat_y = 0.0f;
+ avatar.leftShoulder->localRotation.quat_z = 0.0f;
+
+ avatar.rightShoulder = new VirtualTransform();
+ avatar.rightShoulder->name = "RightShoulder";
+ avatar.rightShoulder->parent = avatar.chest;
+ avatar.rightShoulder->localRotation.quat_w = 1.0f;
+ avatar.rightShoulder->localRotation.quat_x = 0.0f;
+ avatar.rightShoulder->localRotation.quat_y = 0.0f;
+ avatar.rightShoulder->localRotation.quat_z = 0.0f;
+
+ // 5. 팔 계층 (어깨 → 상완 → 하완 → 손)
+ avatar.leftUpperArm = new VirtualTransform();
+ avatar.leftUpperArm->name = "LeftUpperArm";
+ avatar.leftUpperArm->parent = avatar.leftShoulder;
+ avatar.leftUpperArm->localRotation.quat_w = 1.0f;
+ avatar.leftUpperArm->localRotation.quat_x = 0.0f;
+ avatar.leftUpperArm->localRotation.quat_y = 0.0f;
+ avatar.leftUpperArm->localRotation.quat_z = 0.0f;
+
+ avatar.leftLowerArm = new VirtualTransform();
+ avatar.leftLowerArm->name = "LeftLowerArm";
+ avatar.leftLowerArm->parent = avatar.leftUpperArm;
+ avatar.leftLowerArm->localRotation.quat_w = 1.0f;
+ avatar.leftLowerArm->localRotation.quat_x = 0.0f;
+ avatar.leftLowerArm->localRotation.quat_y = 0.0f;
+ avatar.leftLowerArm->localRotation.quat_z = 0.0f;
+
+ avatar.leftHand = new VirtualTransform();
+ avatar.leftHand->name = "LeftHand";
+ avatar.leftHand->parent = avatar.leftLowerArm;
+ avatar.leftHand->localRotation.quat_w = 1.0f;
+ avatar.leftHand->localRotation.quat_x = 0.0f;
+ avatar.leftHand->localRotation.quat_y = 0.0f;
+ avatar.leftHand->localRotation.quat_z = 0.0f;
+
+ // 오른쪽 팔도 동일
+ avatar.rightUpperArm = new VirtualTransform();
+ avatar.rightUpperArm->name = "RightUpperArm";
+ avatar.rightUpperArm->parent = avatar.rightShoulder;
+ avatar.rightUpperArm->localRotation.quat_w = 1.0f;
+ avatar.rightUpperArm->localRotation.quat_x = 0.0f;
+ avatar.rightUpperArm->localRotation.quat_y = 0.0f;
+ avatar.rightUpperArm->localRotation.quat_z = 0.0f;
+
+ avatar.rightLowerArm = new VirtualTransform();
+ avatar.rightLowerArm->name = "RightLowerArm";
+ avatar.rightLowerArm->parent = avatar.rightUpperArm;
+ avatar.rightLowerArm->localRotation.quat_w = 1.0f;
+ avatar.rightLowerArm->localRotation.quat_x = 0.0f;
+ avatar.rightLowerArm->localRotation.quat_y = 0.0f;
+ avatar.rightLowerArm->localRotation.quat_z = 0.0f;
+
+ avatar.rightHand = new VirtualTransform();
+ avatar.rightHand->name = "RightHand";
+ avatar.rightHand->parent = avatar.rightLowerArm;
+ avatar.rightHand->localRotation.quat_w = 1.0f;
+ avatar.rightHand->localRotation.quat_x = 0.0f;
+ avatar.rightHand->localRotation.quat_y = 0.0f;
+ avatar.rightHand->localRotation.quat_z = 0.0f;
+
+ // 왼손 손가락 계층 구조 생성
+ for (int i = 0; i < 3; i++) {
+ // 엄지
+ avatar.leftThumb[i] = new VirtualTransform();
+ avatar.leftThumb[i]->name = "LeftThumb" + std::to_string(i);
+ avatar.leftThumb[i]->parent = (i == 0) ? avatar.leftHand : avatar.leftThumb[i-1];
+ avatar.leftThumb[i]->localRotation.quat_w = 1.0f;
+ avatar.leftThumb[i]->localRotation.quat_x = 0.0f;
+ avatar.leftThumb[i]->localRotation.quat_y = 0.0f;
+ avatar.leftThumb[i]->localRotation.quat_z = 0.0f;
+
+ // 검지
+ avatar.leftIndex[i] = new VirtualTransform();
+ avatar.leftIndex[i]->name = "LeftIndex" + std::to_string(i);
+ avatar.leftIndex[i]->parent = (i == 0) ? avatar.leftHand : avatar.leftIndex[i-1];
+ avatar.leftIndex[i]->localRotation.quat_w = 1.0f;
+ avatar.leftIndex[i]->localRotation.quat_x = 0.0f;
+ avatar.leftIndex[i]->localRotation.quat_y = 0.0f;
+ avatar.leftIndex[i]->localRotation.quat_z = 0.0f;
+
+ // 중지
+ avatar.leftMiddle[i] = new VirtualTransform();
+ avatar.leftMiddle[i]->name = "LeftMiddle" + std::to_string(i);
+ avatar.leftMiddle[i]->parent = (i == 0) ? avatar.leftHand : avatar.leftMiddle[i-1];
+ avatar.leftMiddle[i]->localRotation.quat_w = 1.0f;
+ avatar.leftMiddle[i]->localRotation.quat_x = 0.0f;
+ avatar.leftMiddle[i]->localRotation.quat_y = 0.0f;
+ avatar.leftMiddle[i]->localRotation.quat_z = 0.0f;
+
+ // 약지
+ avatar.leftRing[i] = new VirtualTransform();
+ avatar.leftRing[i]->name = "LeftRing" + std::to_string(i);
+ avatar.leftRing[i]->parent = (i == 0) ? avatar.leftHand : avatar.leftRing[i-1];
+ avatar.leftRing[i]->localRotation.quat_w = 1.0f;
+ avatar.leftRing[i]->localRotation.quat_x = 0.0f;
+ avatar.leftRing[i]->localRotation.quat_y = 0.0f;
+ avatar.leftRing[i]->localRotation.quat_z = 0.0f;
+
+ // 새끼
+ avatar.leftLittle[i] = new VirtualTransform();
+ avatar.leftLittle[i]->name = "LeftLittle" + std::to_string(i);
+ avatar.leftLittle[i]->parent = (i == 0) ? avatar.leftHand : avatar.leftLittle[i-1];
+ avatar.leftLittle[i]->localRotation.quat_w = 1.0f;
+ avatar.leftLittle[i]->localRotation.quat_x = 0.0f;
+ avatar.leftLittle[i]->localRotation.quat_y = 0.0f;
+ avatar.leftLittle[i]->localRotation.quat_z = 0.0f;
+ }
+
+ // 오른손도 동일하게 (간략화)
+ for (int i = 0; i < 3; i++) {
+ avatar.rightThumb[i] = new VirtualTransform();
+ avatar.rightThumb[i]->name = "RightThumb" + std::to_string(i);
+ avatar.rightThumb[i]->parent = (i == 0) ? avatar.rightHand : avatar.rightThumb[i-1];
+ avatar.rightThumb[i]->localRotation.quat_w = 1.0f;
+ avatar.rightThumb[i]->localRotation.quat_x = 0.0f;
+ avatar.rightThumb[i]->localRotation.quat_y = 0.0f;
+ avatar.rightThumb[i]->localRotation.quat_z = 0.0f;
+
+ avatar.rightIndex[i] = new VirtualTransform();
+ avatar.rightIndex[i]->name = "RightIndex" + std::to_string(i);
+ avatar.rightIndex[i]->parent = (i == 0) ? avatar.rightHand : avatar.rightIndex[i-1];
+ avatar.rightIndex[i]->localRotation.quat_w = 1.0f;
+ avatar.rightIndex[i]->localRotation.quat_x = 0.0f;
+ avatar.rightIndex[i]->localRotation.quat_y = 0.0f;
+ avatar.rightIndex[i]->localRotation.quat_z = 0.0f;
+
+ // 나머지 손가락들도 필요시 추가...
+ }
+
+ } catch (...) {
+ // 에러 시 기본값 설정
+ }
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ApplyRokokoDataToVirtualAvatar(VirtualUnityAvatar& avatar, const RokokoData::Body& body)
+{
+ try {
+ // === Rokoko 전신 + 손가락 데이터를 가상 아바타에 적용 ===
+ // Unity BodyFrame과 동일한 구조로 전신 스켈레톤 데이터 적용
+
+ // 1. 전신 스켈레톤 데이터 적용
+ if (body.hip) {
+ avatar.hips->worldRotation.quat_w = body.hip->rotation.w;
+ avatar.hips->worldRotation.quat_x = body.hip->rotation.x;
+ avatar.hips->worldRotation.quat_y = body.hip->rotation.y;
+ avatar.hips->worldRotation.quat_z = body.hip->rotation.z;
+ }
+
+ if (body.spine) {
+ avatar.spine->worldRotation.quat_w = body.spine->rotation.w;
+ avatar.spine->worldRotation.quat_x = body.spine->rotation.x;
+ avatar.spine->worldRotation.quat_y = body.spine->rotation.y;
+ avatar.spine->worldRotation.quat_z = body.spine->rotation.z;
+ }
+
+ if (body.chest) {
+ avatar.chest->worldRotation.quat_w = body.chest->rotation.w;
+ avatar.chest->worldRotation.quat_x = body.chest->rotation.x;
+ avatar.chest->worldRotation.quat_y = body.chest->rotation.y;
+ avatar.chest->worldRotation.quat_z = body.chest->rotation.z;
+ }
+
+ // 2. 어깨 데이터 적용
+ if (body.leftShoulder) {
+ avatar.leftShoulder->worldRotation.quat_w = body.leftShoulder->rotation.w;
+ avatar.leftShoulder->worldRotation.quat_x = body.leftShoulder->rotation.x;
+ avatar.leftShoulder->worldRotation.quat_y = body.leftShoulder->rotation.y;
+ avatar.leftShoulder->worldRotation.quat_z = body.leftShoulder->rotation.z;
+ }
+
+ if (body.rightShoulder) {
+ avatar.rightShoulder->worldRotation.quat_w = body.rightShoulder->rotation.w;
+ avatar.rightShoulder->worldRotation.quat_x = body.rightShoulder->rotation.x;
+ avatar.rightShoulder->worldRotation.quat_y = body.rightShoulder->rotation.y;
+ avatar.rightShoulder->worldRotation.quat_z = body.rightShoulder->rotation.z;
+ }
+
+ // 3. 팔 데이터 적용
+ if (body.leftUpperArm) {
+ avatar.leftUpperArm->worldRotation.quat_w = body.leftUpperArm->rotation.w;
+ avatar.leftUpperArm->worldRotation.quat_x = body.leftUpperArm->rotation.x;
+ avatar.leftUpperArm->worldRotation.quat_y = body.leftUpperArm->rotation.y;
+ avatar.leftUpperArm->worldRotation.quat_z = body.leftUpperArm->rotation.z;
+ }
+
+ if (body.leftLowerArm) {
+ avatar.leftLowerArm->worldRotation.quat_w = body.leftLowerArm->rotation.w;
+ avatar.leftLowerArm->worldRotation.quat_x = body.leftLowerArm->rotation.x;
+ avatar.leftLowerArm->worldRotation.quat_y = body.leftLowerArm->rotation.y;
+ avatar.leftLowerArm->worldRotation.quat_z = body.leftLowerArm->rotation.z;
+ }
+
+ if (body.leftHand) {
+ avatar.leftHand->worldRotation.quat_w = body.leftHand->rotation.w;
+ avatar.leftHand->worldRotation.quat_x = body.leftHand->rotation.x;
+ avatar.leftHand->worldRotation.quat_y = body.leftHand->rotation.y;
+ avatar.leftHand->worldRotation.quat_z = body.leftHand->rotation.z;
+ }
+
+ // 오른쪽 팔도 동일
+ if (body.rightUpperArm) {
+ avatar.rightUpperArm->worldRotation.quat_w = body.rightUpperArm->rotation.w;
+ avatar.rightUpperArm->worldRotation.quat_x = body.rightUpperArm->rotation.x;
+ avatar.rightUpperArm->worldRotation.quat_y = body.rightUpperArm->rotation.y;
+ avatar.rightUpperArm->worldRotation.quat_z = body.rightUpperArm->rotation.z;
+ }
+
+ if (body.rightLowerArm) {
+ avatar.rightLowerArm->worldRotation.quat_w = body.rightLowerArm->rotation.w;
+ avatar.rightLowerArm->worldRotation.quat_x = body.rightLowerArm->rotation.x;
+ avatar.rightLowerArm->worldRotation.quat_y = body.rightLowerArm->rotation.y;
+ avatar.rightLowerArm->worldRotation.quat_z = body.rightLowerArm->rotation.z;
+ }
+
+ if (body.rightHand) {
+ avatar.rightHand->worldRotation.quat_w = body.rightHand->rotation.w;
+ avatar.rightHand->worldRotation.quat_x = body.rightHand->rotation.x;
+ avatar.rightHand->worldRotation.quat_y = body.rightHand->rotation.y;
+ avatar.rightHand->worldRotation.quat_z = body.rightHand->rotation.z;
+ }
+
+ // 4. 손가락 데이터 적용
+
+ // 왼손 엄지
+ if (body.leftThumbProximal) {
+ avatar.leftThumb[0]->worldRotation.quat_w = body.leftThumbProximal->rotation.w;
+ avatar.leftThumb[0]->worldRotation.quat_x = body.leftThumbProximal->rotation.x;
+ avatar.leftThumb[0]->worldRotation.quat_y = body.leftThumbProximal->rotation.y;
+ avatar.leftThumb[0]->worldRotation.quat_z = body.leftThumbProximal->rotation.z;
+ }
+ if (body.leftThumbMedial) {
+ avatar.leftThumb[1]->worldRotation.quat_w = body.leftThumbMedial->rotation.w;
+ avatar.leftThumb[1]->worldRotation.quat_x = body.leftThumbMedial->rotation.x;
+ avatar.leftThumb[1]->worldRotation.quat_y = body.leftThumbMedial->rotation.y;
+ avatar.leftThumb[1]->worldRotation.quat_z = body.leftThumbMedial->rotation.z;
+ }
+ if (body.leftThumbDistal) {
+ avatar.leftThumb[2]->worldRotation.quat_w = body.leftThumbDistal->rotation.w;
+ avatar.leftThumb[2]->worldRotation.quat_x = body.leftThumbDistal->rotation.x;
+ avatar.leftThumb[2]->worldRotation.quat_y = body.leftThumbDistal->rotation.y;
+ avatar.leftThumb[2]->worldRotation.quat_z = body.leftThumbDistal->rotation.z;
+ }
+
+ // 왼손 검지
+ if (body.leftIndexProximal) {
+ avatar.leftIndex[0]->worldRotation.quat_w = body.leftIndexProximal->rotation.w;
+ avatar.leftIndex[0]->worldRotation.quat_x = body.leftIndexProximal->rotation.x;
+ avatar.leftIndex[0]->worldRotation.quat_y = body.leftIndexProximal->rotation.y;
+ avatar.leftIndex[0]->worldRotation.quat_z = body.leftIndexProximal->rotation.z;
+ }
+ if (body.leftIndexMedial) {
+ avatar.leftIndex[1]->worldRotation.quat_w = body.leftIndexMedial->rotation.w;
+ avatar.leftIndex[1]->worldRotation.quat_x = body.leftIndexMedial->rotation.x;
+ avatar.leftIndex[1]->worldRotation.quat_y = body.leftIndexMedial->rotation.y;
+ avatar.leftIndex[1]->worldRotation.quat_z = body.leftIndexMedial->rotation.z;
+ }
+ if (body.leftIndexDistal) {
+ avatar.leftIndex[2]->worldRotation.quat_w = body.leftIndexDistal->rotation.w;
+ avatar.leftIndex[2]->worldRotation.quat_x = body.leftIndexDistal->rotation.x;
+ avatar.leftIndex[2]->worldRotation.quat_y = body.leftIndexDistal->rotation.y;
+ avatar.leftIndex[2]->worldRotation.quat_z = body.leftIndexDistal->rotation.z;
+ }
+
+ // TODO: 나머지 손가락들과 오른손도 추가
+
+ } catch (...) {
+ // 에러 시 기본값 유지
+ }
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::CalculateLocalRotationsFromWorldRotations(VirtualUnityAvatar& avatar)
+{
+ try {
+ // Unity 공식: localRotation = Quaternion.Inverse(parent.rotation) * child.rotation
+
+ // === 전체 스켈레톤 계층적 로컬 로테이션 계산 ===
+
+ // 1. 몸통 계층 계산
+ if (avatar.hips && avatar.hips->parent) {
+ sFingerNode parentInverse;
+ InverseQuaternion(avatar.hips->parent->worldRotation, parentInverse);
+ MultiplyQuaternions(parentInverse, avatar.hips->worldRotation, avatar.hips->localRotation);
+ }
+
+ if (avatar.spine && avatar.spine->parent) {
+ sFingerNode parentInverse;
+ InverseQuaternion(avatar.spine->parent->worldRotation, parentInverse);
+ MultiplyQuaternions(parentInverse, avatar.spine->worldRotation, avatar.spine->localRotation);
+ }
+
+ if (avatar.chest && avatar.chest->parent) {
+ sFingerNode parentInverse;
+ InverseQuaternion(avatar.chest->parent->worldRotation, parentInverse);
+ MultiplyQuaternions(parentInverse, avatar.chest->worldRotation, avatar.chest->localRotation);
+ }
+
+ // 2. 어깨 계층 계산
+ if (avatar.leftShoulder && avatar.leftShoulder->parent) {
+ sFingerNode parentInverse;
+ InverseQuaternion(avatar.leftShoulder->parent->worldRotation, parentInverse);
+ MultiplyQuaternions(parentInverse, avatar.leftShoulder->worldRotation, avatar.leftShoulder->localRotation);
+ }
+
+ if (avatar.rightShoulder && avatar.rightShoulder->parent) {
+ sFingerNode parentInverse;
+ InverseQuaternion(avatar.rightShoulder->parent->worldRotation, parentInverse);
+ MultiplyQuaternions(parentInverse, avatar.rightShoulder->worldRotation, avatar.rightShoulder->localRotation);
+ }
+
+ // 3. 팔 계층 계산 (상완 → 하완 → 손)
+ if (avatar.leftUpperArm && avatar.leftUpperArm->parent) {
+ sFingerNode parentInverse;
+ InverseQuaternion(avatar.leftUpperArm->parent->worldRotation, parentInverse);
+ MultiplyQuaternions(parentInverse, avatar.leftUpperArm->worldRotation, avatar.leftUpperArm->localRotation);
+ }
+
+ if (avatar.leftLowerArm && avatar.leftLowerArm->parent) {
+ sFingerNode parentInverse;
+ InverseQuaternion(avatar.leftLowerArm->parent->worldRotation, parentInverse);
+ MultiplyQuaternions(parentInverse, avatar.leftLowerArm->worldRotation, avatar.leftLowerArm->localRotation);
+ }
+
+ if (avatar.leftHand && avatar.leftHand->parent) {
+ sFingerNode parentInverse;
+ InverseQuaternion(avatar.leftHand->parent->worldRotation, parentInverse);
+ MultiplyQuaternions(parentInverse, avatar.leftHand->worldRotation, avatar.leftHand->localRotation);
+ }
+
+ // 오른쪽 팔도 동일
+ if (avatar.rightUpperArm && avatar.rightUpperArm->parent) {
+ sFingerNode parentInverse;
+ InverseQuaternion(avatar.rightUpperArm->parent->worldRotation, parentInverse);
+ MultiplyQuaternions(parentInverse, avatar.rightUpperArm->worldRotation, avatar.rightUpperArm->localRotation);
+ }
+
+ if (avatar.rightLowerArm && avatar.rightLowerArm->parent) {
+ sFingerNode parentInverse;
+ InverseQuaternion(avatar.rightLowerArm->parent->worldRotation, parentInverse);
+ MultiplyQuaternions(parentInverse, avatar.rightLowerArm->worldRotation, avatar.rightLowerArm->localRotation);
+ }
+
+ if (avatar.rightHand && avatar.rightHand->parent) {
+ sFingerNode parentInverse;
+ InverseQuaternion(avatar.rightHand->parent->worldRotation, parentInverse);
+ MultiplyQuaternions(parentInverse, avatar.rightHand->worldRotation, avatar.rightHand->localRotation);
+ }
+
+ // 4. 손가락 계층 계산 (이제 손을 올바른 부모로 사용)
+ for (int i = 0; i < 3; i++) {
+ if (avatar.leftThumb[i] && avatar.leftThumb[i]->parent) {
+ sFingerNode parentInverse;
+ InverseQuaternion(avatar.leftThumb[i]->parent->worldRotation, parentInverse);
+ MultiplyQuaternions(parentInverse, avatar.leftThumb[i]->worldRotation, avatar.leftThumb[i]->localRotation);
+ }
+
+ if (avatar.leftIndex[i] && avatar.leftIndex[i]->parent) {
+ sFingerNode parentInverse;
+ InverseQuaternion(avatar.leftIndex[i]->parent->worldRotation, parentInverse);
+ MultiplyQuaternions(parentInverse, avatar.leftIndex[i]->worldRotation, avatar.leftIndex[i]->localRotation);
+ }
+
+ // TODO: 나머지 손가락들과 오른손도 추가
+ }
+
+ } catch (...) {
+ // 에러 시 기본값 유지
+ }
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ExtractLocalRotationsFromVirtualAvatar(const VirtualUnityAvatar& avatar, std::vector& nodes, eGloveHandSide handSide)
+{
+ try {
+ if (nodes.size() < 15) return;
+
+ if (handSide == eGloveHandSide::Left) {
+ // 왼손 엄지: 0, 1, 2
+ for (int i = 0; i < 3; i++) {
+ if (avatar.leftThumb[i]) {
+ nodes[i] = avatar.leftThumb[i]->localRotation;
+ nodes[i].node_id = i;
+ NormalizeQuaternion(nodes[i]);
+ }
+ }
+
+ // 왼손 검지: 3, 4, 5
+ for (int i = 0; i < 3; i++) {
+ if (avatar.leftIndex[i]) {
+ nodes[3 + i] = avatar.leftIndex[i]->localRotation;
+ nodes[3 + i].node_id = 3 + i;
+ NormalizeQuaternion(nodes[3 + i]);
+ }
+ }
+
+ // TODO: 나머지 손가락들 (6~14)
+ } else {
+ // 오른손 처리
+ // TODO: 오른손 구현
+ }
+
+ } catch (...) {
+ // 에러 시 기본값
+ }
+}
+
+void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::UpdateWorldRotationFromParent(VirtualTransform* transform)
+{
+ // Unity 공식: rotation = parent.rotation * localRotation
+ try {
+ if (transform && transform->parent) {
+ MultiplyQuaternions(transform->parent->worldRotation, transform->localRotation, transform->worldRotation);
+ }
+ } catch (...) {
+ // 에러 시 기본값 유지
+ }
}
\ No newline at end of file
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveAdapterSingleton.h b/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveAdapterSingleton.h
index 6f35ad9c..67029d97 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveAdapterSingleton.h
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveAdapterSingleton.h
@@ -134,9 +134,13 @@ namespace OptiTrackPluginDevices
* Example data map for storing aggregated device data.
*/
uint16_t mDeviceCount = 0;
- std::vector mDetectedDevices;
- std::unordered_map mLatestGloveData;
- std::unordered_map mLatestDeviceInfo;
+ std::vector mDetectedDevices;
+ std::unordered_map mLatestGloveData;
+ std::unordered_map mLatestDeviceInfo;
+
+ // T-포즈 캘리브레이션 데이터 (로컬 로테이션 계산을 위해 필요)
+ std::unordered_map> mTPoseReferences; // 장치별 T-포즈 기준값
+ std::unordered_map mTPoseCalibrated; // 장치별 캘리브레이션 상태
/**
* Rokoko integration members
@@ -173,14 +177,9 @@ namespace OptiTrackPluginDevices
*/
void UpdateDeviceInfo(uint64_t deviceId);
- /**
- * Detect and create Rokoko devices dynamically
- */
+ // Dynamic Device Detection and Management
void DetectAndCreateRokokoDevices();
-
- /**
- * Process data for multiple devices (left/right hand)
- */
+ void DetectActorsFromRokokoData(const RokokoData::LiveFrame_v4& rokokoFrame);
void ProcessMultipleDeviceData(const RokokoData::LiveFrame_v4& rokokoFrame);
/**
@@ -202,6 +201,128 @@ namespace OptiTrackPluginDevices
* Map individual joint data
*/
void MapJoint(const RokokoData::ActorJointFrame& rokokoJoint, sFingerNode& optiTrackNode);
+
+ // 로컬 로테이션 변환 함수들
+ void ConvertToLocalRotations(std::vector& nodes, eGloveHandSide handSide);
+ void ApplyLocalRotation(sFingerNode& childNode, const sFingerNode& parentNode);
+
+ // 쿼터니언 연산 헬퍼 함수들
+ void MultiplyQuaternions(const sFingerNode& q1, const sFingerNode& q2, sFingerNode& result);
+ void InverseQuaternion(const sFingerNode& q, sFingerNode& result);
+ void NormalizeQuaternion(sFingerNode& q);
+
+ // T-포즈 캘리브레이션 시스템
+ void CalibrateTPose(uint64_t deviceId, const std::vector& tPoseData);
+ void ApplyTPoseOffset(std::vector& nodes, uint64_t deviceId);
+ bool IsTPoseCalibrated(uint64_t deviceId) const;
+ void ResetTPoseCalibration(uint64_t deviceId);
+
+ // 손목 기준 로컬 변환 시스템 (기존)
+ void ConvertToWristLocalRotations(std::vector& nodes, const RokokoData::Body& body, eGloveHandSide handSide);
+ bool GetWristRotation(const RokokoData::Body& body, eGloveHandSide handSide, sFingerNode& wristRotation);
+
+ // 진짜 로컬 로테이션 변환 시스템 (계층적)
+ void ConvertToTrueLocalRotations(std::vector& nodes, const RokokoData::Body& body, eGloveHandSide handSide);
+ void CalculateFingerLocalRotations(const std::vector& fingerJoints, std::vector& outputNodes, int startIndex, const RokokoData::Body& body, eGloveHandSide handSide);
+ bool GetFingerJointRotations(const RokokoData::Body& body, eGloveHandSide handSide, std::vector>& allFingers);
+
+ // 좌표계 변환 및 디버깅
+ void ConvertRokokoToOptiTrackCoordinates(sFingerNode& node, eGloveHandSide handSide);
+ void LogRotationData(const std::string& label, const sFingerNode& rotation);
+ void DebugCoordinateSystem(const RokokoData::Body& body, eGloveHandSide handSide);
+
+ // 절대 로컬 로테이션 시스템 (손목에 완전히 독립적)
+ void ConvertToAbsoluteLocalRotations(std::vector& nodes, const RokokoData::Body& body, eGloveHandSide handSide);
+ void CalculateAbsoluteLocalForFinger(const std::vector& fingerJoints, std::vector& outputNodes, int startIndex, eGloveHandSide handSide);
+ sFingerNode CalculateRelativeRotation(const sFingerNode& parent, const sFingerNode& child);
+
+ // Unity 방식 Rokoko 데이터 처리 (원시 데이터 + T-포즈 오프셋)
+ void ConvertToUnityRokokoMethod(std::vector& nodes, const RokokoData::Body& body, eGloveHandSide handSide);
+ sFingerNode GetUnityTPoseOffset(int fingerIndex, int jointIndex, eGloveHandSide handSide);
+ void ApplyUnityRokokoRotation(sFingerNode& node, const RokokoData::ActorJointFrame* jointFrame, int fingerIndex, int jointIndex, eGloveHandSide handSide);
+
+ // 가상 Unity 아바타 시뮬레이션 시스템
+ void ConvertToVirtualUnityAvatar(std::vector& nodes, const RokokoData::Body& body, eGloveHandSide handSide);
+
+ // Unity Transform 시뮬레이션 구조
+ struct VirtualTransform {
+ sFingerNode localRotation; // Unity localRotation
+ sFingerNode worldRotation; // Unity rotation (world)
+ VirtualTransform* parent; // Unity parent transform
+ std::vector children; // Unity children
+ std::string name; // 디버깅용
+ };
+
+ struct VirtualUnityAvatar {
+ // 전체 스켈레톤 구조 (Unity Humanoid와 동일한 계층)
+ VirtualTransform root;
+
+ // 몸통 구조
+ VirtualTransform* hips; // 허리 (Root)
+ VirtualTransform* spine; // 스파인
+ VirtualTransform* chest; // 가슴
+
+ // 어깨 구조
+ VirtualTransform* leftShoulder; // 왼쪽 어깨
+ VirtualTransform* rightShoulder; // 오른쪽 어깨
+
+ // 팔 구조
+ VirtualTransform* leftUpperArm; // 왼쪽 상완
+ VirtualTransform* leftLowerArm; // 왼쪽 하완
+ VirtualTransform* leftHand; // 왼쪽 손
+
+ VirtualTransform* rightUpperArm; // 오른쪽 상완
+ VirtualTransform* rightLowerArm; // 오른쪽 하완
+ VirtualTransform* rightHand; // 오른쪽 손
+
+ // 손가락 구조 (Unity 계층과 동일)
+ VirtualTransform* leftThumb[3]; // Proximal, Intermediate, Distal
+ VirtualTransform* leftIndex[3];
+ VirtualTransform* leftMiddle[3];
+ VirtualTransform* leftRing[3];
+ VirtualTransform* leftLittle[3];
+
+ VirtualTransform* rightThumb[3];
+ VirtualTransform* rightIndex[3];
+ VirtualTransform* rightMiddle[3];
+ VirtualTransform* rightRing[3];
+ VirtualTransform* rightLittle[3];
+ };
+
+ // 가상 Unity 아바타 헬퍼 함수들
+ void InitializeVirtualUnityAvatar(VirtualUnityAvatar& avatar);
+ void ApplyRokokoDataToVirtualAvatar(VirtualUnityAvatar& avatar, const RokokoData::Body& body);
+ void CalculateLocalRotationsFromWorldRotations(VirtualUnityAvatar& avatar);
+ void ExtractLocalRotationsFromVirtualAvatar(const VirtualUnityAvatar& avatar, std::vector& nodes, eGloveHandSide handSide);
+ void UpdateWorldRotationFromParent(VirtualTransform* transform);
+
+ // 가상 손 노드 구조
+ struct VirtualJoint {
+ sFingerNode worldRotation;
+ sFingerNode localRotation;
+ VirtualJoint* parent;
+ };
+
+ struct VirtualFinger {
+ VirtualJoint mp; // Metacarpophalangeal
+ VirtualJoint pip; // Proximal Interphalangeal
+ VirtualJoint dip; // Distal Interphalangeal
+ };
+
+ struct VirtualHand {
+ sFingerNode wristRotation;
+ VirtualFinger thumb;
+ VirtualFinger index;
+ VirtualFinger middle;
+ VirtualFinger ring;
+ VirtualFinger little;
+ };
+
+ // 가상 손 구조 헬퍼 함수들
+ void BuildVirtualHandFromRokoko(VirtualHand& virtualHand, const RokokoData::Body& body, eGloveHandSide handSide);
+ void CalculateHierarchicalLocalRotations(VirtualHand& virtualHand);
+ void ExtractLocalRotationsToNodes(const VirtualHand& virtualHand, std::vector& nodes);
+ sFingerNode CalculateLocalRotationFromParent(const VirtualJoint& parent, const VirtualJoint& child);
protected:
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/GloveDataFormat.h b/Optitrack Rokoko Glove/RokokoGloveDevice/GloveDataFormat.h
index d434bca8..193eb2a6 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/GloveDataFormat.h
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/GloveDataFormat.h
@@ -74,6 +74,7 @@ struct sGloveDeviceBaseInfo
int battery = 0 ;
int signalStrength= 0;
eGloveHandSide handSide = eGloveHandSide::Unknown;
+ std::string actorName = ""; // Actor 이름 (다중 장비 구분용)
};
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/LZ4Wrapper.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice/LZ4Wrapper.cpp
index 8623e5e7..302fe07c 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/LZ4Wrapper.cpp
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/LZ4Wrapper.cpp
@@ -3,9 +3,48 @@
//======================================================================================================
#include "LZ4Wrapper.h"
-#include "lz4.h"
#include
#include
+#include
+
+// Unity LZ4 DLL 동적 로딩
+static HMODULE lz4Module = nullptr;
+static decltype(&Unity_LZ4_uncompressSize) pUnity_LZ4_uncompressSize = nullptr;
+static decltype(&Unity_LZ4_decompress) pUnity_LZ4_decompress = nullptr;
+
+static bool LoadUnityLZ4()
+{
+ if (lz4Module == nullptr) {
+ // 다양한 경로에서 Unity LZ4 DLL 로드 시도
+ const wchar_t* dllPaths[] = {
+ L"lz4.dll", // 현재 디렉토리
+ L".\\lz4.dll", // 명시적 현재 디렉토리
+ L"x64\\Debug\\lz4.dll", // 디버그 폴더
+ L"C:\\Users\\user\\Documents\\Streamingle_URP\\Assets\\External\\Rokoko\\Scripts\\Plugins\\LZ4\\x86_64\\lz4.dll" // Unity 절대 경로
+ };
+
+ for (const auto& path : dllPaths) {
+ lz4Module = LoadLibrary(path);
+ if (lz4Module != nullptr) {
+ // DLL 로드 성공 - 함수 포인터 획득
+ pUnity_LZ4_uncompressSize = (decltype(pUnity_LZ4_uncompressSize))GetProcAddress(lz4Module, "Unity_LZ4_uncompressSize");
+ pUnity_LZ4_decompress = (decltype(pUnity_LZ4_decompress))GetProcAddress(lz4Module, "Unity_LZ4_decompress");
+
+ if (pUnity_LZ4_uncompressSize != nullptr && pUnity_LZ4_decompress != nullptr) {
+ // 성공
+ break;
+ } else {
+ // 함수를 찾지 못함 - DLL 언로드하고 다음 시도
+ FreeLibrary(lz4Module);
+ lz4Module = nullptr;
+ pUnity_LZ4_uncompressSize = nullptr;
+ pUnity_LZ4_decompress = nullptr;
+ }
+ }
+ }
+ }
+ return (lz4Module != nullptr && pUnity_LZ4_uncompressSize != nullptr && pUnity_LZ4_decompress != nullptr);
+}
namespace RokokoIntegration
{
@@ -17,33 +56,38 @@ namespace RokokoIntegration
return {};
}
- // LZ4 데이터 유효성 검사
- if (!IsValidLZ4Data(compressedData, compressedSize)) {
+ // Unity LZ4 DLL 로드
+ if (!LoadUnityLZ4()) {
return {};
}
- // 압축 해제된 크기 추정
- int decompressedSize = GetDecompressedSize(compressedData, compressedSize);
- if (decompressedSize <= 0) {
+ // Unity 방식 정확히 구현
+ // 1. Unity_LZ4_uncompressSize로 압축 해제된 크기 구하기
+ int uncompressedSize = pUnity_LZ4_uncompressSize(
+ reinterpret_cast(compressedData),
+ compressedSize
+ );
+
+ if (uncompressedSize <= 0) {
return {};
}
// 크기 유효성 검사
- if (!ValidateDecompressedSize(compressedSize, decompressedSize)) {
+ if (uncompressedSize > MAX_DECOMPRESSED_SIZE) {
return {};
}
- // 압축 해제 실행
- std::vector decompressed(decompressedSize);
- int actualSize = LZ4_decompress_safe(
+ // 2. Unity_LZ4_decompress로 압축 해제
+ std::vector decompressed(uncompressedSize);
+ int result = pUnity_LZ4_decompress(
reinterpret_cast(compressedData),
- reinterpret_cast(decompressed.data()),
compressedSize,
- decompressedSize
+ reinterpret_cast(decompressed.data()),
+ uncompressedSize
);
- // 압축 해제 결과 검증
- if (actualSize != decompressedSize) {
+ // Unity에서는 result != 0이면 실패
+ if (result != 0) {
return {};
}
@@ -61,20 +105,37 @@ namespace RokokoIntegration
return false;
}
- // LZ4 매직 넘버 확인 (간단한 검증)
- // 실제로는 더 정교한 검증이 필요할 수 있음
+ // Rokoko Studio의 LZ4 데이터는 일반적으로 JSON이 아니므로
+ // 첫 번째 바이트가 '{'가 아닌 경우 LZ4로 간주
+ if (size > 0 && data[0] == '{') {
+ return false; // JSON 데이터는 LZ4가 아님
+ }
+
+ // 기본적인 LZ4 매직 넘버 확인
+ // LZ4 프레임 헤더의 일부 패턴 확인
+ if (size >= 4) {
+ // LZ4 프레임 헤더의 매직 넘버 (0x184D2204)
+ if (data[0] == 0x04 && data[1] == 0x22 && data[2] == 0x4D && data[3] == 0x18) {
+ return true;
+ }
+ }
+
+ // 매직 넘버가 없어도 압축된 데이터로 간주 (Rokoko Studio 특성상)
return true;
}
int LZ4Wrapper::GetDecompressedSize(const uint8_t* compressedData, int compressedSize)
{
try {
- // LZ4에서 압축 해제된 크기 추정
- int decompressedSize = LZ4_decompress_safe(
+ // Unity LZ4 DLL 로드
+ if (!LoadUnityLZ4()) {
+ return -1;
+ }
+
+ // Unity 방식으로 압축 해제된 크기 구하기
+ int decompressedSize = pUnity_LZ4_uncompressSize(
reinterpret_cast(compressedData),
- nullptr,
- compressedSize,
- 0
+ compressedSize
);
return decompressedSize;
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/LZ4Wrapper.h b/Optitrack Rokoko Glove/RokokoGloveDevice/LZ4Wrapper.h
index 50e6ac23..120230f3 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/LZ4Wrapper.h
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/LZ4Wrapper.h
@@ -10,6 +10,12 @@
#include
#include
+// Unity LZ4 함수들 선언
+extern "C" {
+ int Unity_LZ4_uncompressSize(const char* srcBuffer, int srcSize);
+ int Unity_LZ4_decompress(const char* src, int srcSize, char* dst, int dstCapacity);
+}
+
namespace RokokoIntegration
{
class LZ4Wrapper
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoData.h b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoData.h
index b4abbf0e..8f166964 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoData.h
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoData.h
@@ -37,9 +37,39 @@ namespace RokokoData
Vector4Frame rotation;
};
- // Body structure containing finger joints
+ // Body structure containing full skeleton (Unity BodyFrame와 동일)
struct Body
{
+ // 전신 스켈레톤 데이터 (Unity BodyFrame과 동일)
+ std::shared_ptr hip;
+ std::shared_ptr spine;
+ std::shared_ptr chest;
+ std::shared_ptr neck;
+ std::shared_ptr head;
+
+ std::shared_ptr leftShoulder;
+ std::shared_ptr leftUpperArm;
+ std::shared_ptr leftLowerArm;
+ std::shared_ptr leftHand;
+
+ std::shared_ptr rightShoulder;
+ std::shared_ptr rightUpperArm;
+ std::shared_ptr rightLowerArm;
+ std::shared_ptr rightHand;
+
+ // 다리 데이터 (필요시)
+ std::shared_ptr leftUpLeg;
+ std::shared_ptr leftLeg;
+ std::shared_ptr leftFoot;
+ std::shared_ptr leftToe;
+ std::shared_ptr leftToeEnd;
+
+ std::shared_ptr rightUpLeg;
+ std::shared_ptr rightLeg;
+ std::shared_ptr rightFoot;
+ std::shared_ptr rightToe;
+ std::shared_ptr rightToeEnd;
+
// Left hand finger joints
std::shared_ptr leftThumbProximal;
std::shared_ptr leftThumbMedial;
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataParser.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataParser.cpp
index 54338487..6cb73c01 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataParser.cpp
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataParser.cpp
@@ -14,101 +14,173 @@ namespace RokokoIntegration
bool RokokoDataParser::ParseLiveFrame(const std::string& jsonString, RokokoData::LiveFrame_v4& frame)
{
try {
- // 간단한 JSON 파싱 구현 (실제로는 nlohmann/json 라이브러리 사용 권장)
- // 현재는 기본 구조만 생성
+ // nlohmann/json을 사용하여 JSON 파싱
+ nlohmann::json j = nlohmann::json::parse(jsonString);
- // 기본 프레임 데이터 초기화
- frame.scene.actors.clear();
- frame.scene.actors.push_back(RokokoData::ActorData());
+ // Unity JsonLiveSerializerV3.cs와 동일한 구조로 파싱
- // 기본 손가락 데이터 구조 생성
- auto& actor = frame.scene.actors[0];
-
- // 왼손 데이터 초기화 (모든 손가락)
- // 엄지
- actor.body.leftThumbMedial = std::make_shared();
- actor.body.leftThumbDistal = std::make_shared();
- actor.body.leftThumbTip = std::make_shared();
- // 검지
- actor.body.leftIndexMedial = std::make_shared();
- actor.body.leftIndexDistal = std::make_shared();
- actor.body.leftIndexTip = std::make_shared();
- // 중지
- actor.body.leftMiddleMedial = std::make_shared();
- actor.body.leftMiddleDistal = std::make_shared();
- actor.body.leftMiddleTip = std::make_shared();
- // 약지
- actor.body.leftRingMedial = std::make_shared();
- actor.body.leftRingDistal = std::make_shared();
- actor.body.leftRingTip = std::make_shared();
- // 새끼
- actor.body.leftLittleMedial = std::make_shared();
- actor.body.leftLittleDistal = std::make_shared();
- actor.body.leftLittleTip = std::make_shared();
-
- // 오른손 데이터 초기화 (모든 손가락)
- // 엄지
- actor.body.rightThumbMedial = std::make_shared();
- actor.body.rightThumbDistal = std::make_shared();
- actor.body.rightThumbTip = std::make_shared();
- // 검지
- actor.body.rightIndexMedial = std::make_shared();
- actor.body.rightIndexDistal = std::make_shared();
- actor.body.rightIndexTip = std::make_shared();
- // 중지
- actor.body.rightMiddleMedial = std::make_shared();
- actor.body.rightMiddleDistal = std::make_shared();
- actor.body.rightMiddleTip = std::make_shared();
- // 약지
- actor.body.rightRingMedial = std::make_shared();
- actor.body.rightRingDistal = std::make_shared();
- actor.body.rightRingTip = std::make_shared();
- // 새끼
- actor.body.rightLittleMedial = std::make_shared();
- actor.body.rightLittleDistal = std::make_shared();
- actor.body.rightLittleTip = std::make_shared();
-
- // 기본 쿼터니언 값 설정 (정규화된 단위 쿼터니언)
- RokokoData::Vector4Frame defaultRotation = {1.0f, 0.0f, 0.0f, 0.0f};
-
- // 왼손 기본값 설정
- actor.body.leftThumbMedial->rotation = defaultRotation;
- actor.body.leftThumbDistal->rotation = defaultRotation;
- actor.body.leftThumbTip->rotation = defaultRotation;
- actor.body.leftIndexMedial->rotation = defaultRotation;
- actor.body.leftIndexDistal->rotation = defaultRotation;
- actor.body.leftIndexTip->rotation = defaultRotation;
- actor.body.leftMiddleMedial->rotation = defaultRotation;
- actor.body.leftMiddleDistal->rotation = defaultRotation;
- actor.body.leftMiddleTip->rotation = defaultRotation;
- actor.body.leftRingMedial->rotation = defaultRotation;
- actor.body.leftRingDistal->rotation = defaultRotation;
- actor.body.leftRingTip->rotation = defaultRotation;
- actor.body.leftLittleMedial->rotation = defaultRotation;
- actor.body.leftLittleDistal->rotation = defaultRotation;
- actor.body.leftLittleTip->rotation = defaultRotation;
-
- // 오른손 기본값 설정
- actor.body.rightThumbMedial->rotation = defaultRotation;
- actor.body.rightThumbDistal->rotation = defaultRotation;
- actor.body.rightThumbTip->rotation = defaultRotation;
- actor.body.rightIndexMedial->rotation = defaultRotation;
- actor.body.rightIndexDistal->rotation = defaultRotation;
- actor.body.rightIndexTip->rotation = defaultRotation;
- actor.body.rightMiddleMedial->rotation = defaultRotation;
- actor.body.rightMiddleDistal->rotation = defaultRotation;
- actor.body.rightMiddleTip->rotation = defaultRotation;
- actor.body.rightRingMedial->rotation = defaultRotation;
- actor.body.rightRingDistal->rotation = defaultRotation;
- actor.body.rightRingTip->rotation = defaultRotation;
- actor.body.rightLittleMedial->rotation = defaultRotation;
- actor.body.rightLittleDistal->rotation = defaultRotation;
- actor.body.rightLittleTip->rotation = defaultRotation;
+ // Scene 데이터 파싱
+ if (j.contains("scene")) {
+ auto& scene = j["scene"];
+
+ // 타임스탬프
+ if (scene.contains("timestamp")) {
+ frame.timestamp = scene["timestamp"];
+ }
+
+ // Actors 파싱
+ if (scene.contains("actors") && scene["actors"].is_array()) {
+ auto& actors = scene["actors"];
+ for (const auto& actorJson : actors) {
+ RokokoData::ActorData actor;
+
+ // Actor 기본 정보
+ if (actorJson.contains("name")) {
+ actor.name = actorJson["name"];
+ }
+
+ // Body 데이터 파싱
+ if (actorJson.contains("body")) {
+ ParseBodyFrame(actorJson["body"], actor.body);
+ }
+
+ frame.scene.actors.push_back(actor);
+ }
+ }
+ }
return true;
- } catch (...) {
+ } catch (const nlohmann::json::exception& e) {
+ // JSON 파싱 에러 처리
+ std::cerr << "JSON parsing error: " << e.what() << std::endl;
return false;
+ } catch (...) {
+ // 기타 예외 처리
+ std::cerr << "Unknown error during JSON parsing" << std::endl;
+ return false;
+ }
+ }
+
+ void RokokoDataParser::ParseBodyFrame(const nlohmann::json& bodyJson, RokokoData::Body& body)
+ {
+ // Unity JsonLiveSerializerV3.cs의 BodyFrame 구조와 동일하게 파싱
+
+ // === 전신 스켈레톤 데이터 파싱 (Unity BodyFrame과 동일) ===
+
+ // 몸통 파싱
+ ParseFingerJoint(bodyJson, "hip", body.hip);
+ ParseFingerJoint(bodyJson, "spine", body.spine);
+ ParseFingerJoint(bodyJson, "chest", body.chest);
+ ParseFingerJoint(bodyJson, "neck", body.neck);
+ ParseFingerJoint(bodyJson, "head", body.head);
+
+ // 왼쪽 팔 파싱
+ ParseFingerJoint(bodyJson, "leftShoulder", body.leftShoulder);
+ ParseFingerJoint(bodyJson, "leftUpperArm", body.leftUpperArm);
+ ParseFingerJoint(bodyJson, "leftLowerArm", body.leftLowerArm);
+ ParseFingerJoint(bodyJson, "leftHand", body.leftHand);
+
+ // 오른쪽 팔 파싱
+ ParseFingerJoint(bodyJson, "rightShoulder", body.rightShoulder);
+ ParseFingerJoint(bodyJson, "rightUpperArm", body.rightUpperArm);
+ ParseFingerJoint(bodyJson, "rightLowerArm", body.rightLowerArm);
+ ParseFingerJoint(bodyJson, "rightHand", body.rightHand);
+
+ // 다리 파싱 (필요시)
+ ParseFingerJoint(bodyJson, "leftUpLeg", body.leftUpLeg);
+ ParseFingerJoint(bodyJson, "leftLeg", body.leftLeg);
+ ParseFingerJoint(bodyJson, "leftFoot", body.leftFoot);
+ ParseFingerJoint(bodyJson, "leftToe", body.leftToe);
+ ParseFingerJoint(bodyJson, "leftToeEnd", body.leftToeEnd);
+
+ ParseFingerJoint(bodyJson, "rightUpLeg", body.rightUpLeg);
+ ParseFingerJoint(bodyJson, "rightLeg", body.rightLeg);
+ ParseFingerJoint(bodyJson, "rightFoot", body.rightFoot);
+ ParseFingerJoint(bodyJson, "rightToe", body.rightToe);
+ ParseFingerJoint(bodyJson, "rightToeEnd", body.rightToeEnd);
+
+ // === 손가락 데이터 파싱 ===
+
+ // 왼손 손가락 데이터 파싱
+ ParseFingerJoint(bodyJson, "leftThumbProximal", body.leftThumbProximal);
+ ParseFingerJoint(bodyJson, "leftThumbMedial", body.leftThumbMedial);
+ ParseFingerJoint(bodyJson, "leftThumbDistal", body.leftThumbDistal);
+ ParseFingerJoint(bodyJson, "leftThumbTip", body.leftThumbTip);
+
+ ParseFingerJoint(bodyJson, "leftIndexProximal", body.leftIndexProximal);
+ ParseFingerJoint(bodyJson, "leftIndexMedial", body.leftIndexMedial);
+ ParseFingerJoint(bodyJson, "leftIndexDistal", body.leftIndexDistal);
+ ParseFingerJoint(bodyJson, "leftIndexTip", body.leftIndexTip);
+
+ ParseFingerJoint(bodyJson, "leftMiddleProximal", body.leftMiddleProximal);
+ ParseFingerJoint(bodyJson, "leftMiddleMedial", body.leftMiddleMedial);
+ ParseFingerJoint(bodyJson, "leftMiddleDistal", body.leftMiddleDistal);
+ ParseFingerJoint(bodyJson, "leftMiddleTip", body.leftMiddleTip);
+
+ ParseFingerJoint(bodyJson, "leftRingProximal", body.leftRingProximal);
+ ParseFingerJoint(bodyJson, "leftRingMedial", body.leftRingMedial);
+ ParseFingerJoint(bodyJson, "leftRingDistal", body.leftRingDistal);
+ ParseFingerJoint(bodyJson, "leftRingTip", body.leftRingTip);
+
+ ParseFingerJoint(bodyJson, "leftLittleProximal", body.leftLittleProximal);
+ ParseFingerJoint(bodyJson, "leftLittleMedial", body.leftLittleMedial);
+ ParseFingerJoint(bodyJson, "leftLittleDistal", body.leftLittleDistal);
+ ParseFingerJoint(bodyJson, "leftLittleTip", body.leftLittleTip);
+
+ // 오른손 손가락 데이터 파싱
+ ParseFingerJoint(bodyJson, "rightThumbProximal", body.rightThumbProximal);
+ ParseFingerJoint(bodyJson, "rightThumbMedial", body.rightThumbMedial);
+ ParseFingerJoint(bodyJson, "rightThumbDistal", body.rightThumbDistal);
+ ParseFingerJoint(bodyJson, "rightThumbTip", body.rightThumbTip);
+
+ ParseFingerJoint(bodyJson, "rightIndexProximal", body.rightIndexProximal);
+ ParseFingerJoint(bodyJson, "rightIndexMedial", body.rightIndexMedial);
+ ParseFingerJoint(bodyJson, "rightIndexDistal", body.rightIndexDistal);
+ ParseFingerJoint(bodyJson, "rightIndexTip", body.rightIndexTip);
+
+ ParseFingerJoint(bodyJson, "rightMiddleProximal", body.rightMiddleProximal);
+ ParseFingerJoint(bodyJson, "rightMiddleMedial", body.rightMiddleMedial);
+ ParseFingerJoint(bodyJson, "rightMiddleDistal", body.rightMiddleDistal);
+ ParseFingerJoint(bodyJson, "rightMiddleTip", body.rightMiddleTip);
+
+ ParseFingerJoint(bodyJson, "rightRingProximal", body.rightRingProximal);
+ ParseFingerJoint(bodyJson, "rightRingMedial", body.rightRingMedial);
+ ParseFingerJoint(bodyJson, "rightRingDistal", body.rightRingDistal);
+ ParseFingerJoint(bodyJson, "rightRingTip", body.rightRingTip);
+
+ ParseFingerJoint(bodyJson, "rightLittleProximal", body.rightLittleProximal);
+ ParseFingerJoint(bodyJson, "rightLittleMedial", body.rightLittleMedial);
+ ParseFingerJoint(bodyJson, "rightLittleDistal", body.rightLittleDistal);
+ ParseFingerJoint(bodyJson, "rightLittleTip", body.rightLittleTip);
+ }
+
+ void RokokoDataParser::ParseFingerJoint(const nlohmann::json& bodyJson, const std::string& jointName, std::shared_ptr& joint)
+ {
+ if (bodyJson.contains(jointName)) {
+ auto& jointJson = bodyJson[jointName];
+
+ joint = std::make_shared();
+
+ // Position 파싱
+ if (jointJson.contains("position")) {
+ auto& pos = jointJson["position"];
+ joint->position.x = pos.value("x", 0.0f);
+ joint->position.y = pos.value("y", 0.0f);
+ joint->position.z = pos.value("z", 0.0f);
+ }
+
+ // Rotation 파싱
+ if (jointJson.contains("rotation")) {
+ auto& rot = jointJson["rotation"];
+ joint->rotation.x = rot.value("x", 0.0f);
+ joint->rotation.y = rot.value("y", 0.0f);
+ joint->rotation.z = rot.value("z", 0.0f);
+ joint->rotation.w = rot.value("w", 1.0f);
+
+ // 쿼터니언 정규화
+ NormalizeQuaternion(joint->rotation.x, joint->rotation.y, joint->rotation.z, joint->rotation.w);
+ }
}
}
@@ -122,7 +194,7 @@ namespace RokokoIntegration
const auto& actor = frame.scene.actors[0];
- // 손가락 데이터 검증
+ // 손가락 데이터 검증 (왼손 엄지손가락만 체크)
if (!actor.body.leftThumbMedial || !actor.body.leftThumbDistal || !actor.body.leftThumbTip) {
return false;
}
@@ -148,19 +220,57 @@ namespace RokokoIntegration
const auto& actor = frame.scene.actors[0];
- // 왼손 데이터 추출
- if (actor.body.leftThumbMedial) {
- leftHandFingers.push_back(*actor.body.leftThumbMedial);
- }
- if (actor.body.leftThumbDistal) {
- leftHandFingers.push_back(*actor.body.leftThumbDistal);
- }
- if (actor.body.leftThumbTip) {
- leftHandFingers.push_back(*actor.body.leftThumbTip);
- }
+ // 왼손 데이터 추출 (20개 관절)
+ if (actor.body.leftThumbProximal) leftHandFingers.push_back(*actor.body.leftThumbProximal);
+ if (actor.body.leftThumbMedial) leftHandFingers.push_back(*actor.body.leftThumbMedial);
+ if (actor.body.leftThumbDistal) leftHandFingers.push_back(*actor.body.leftThumbDistal);
+ if (actor.body.leftThumbTip) leftHandFingers.push_back(*actor.body.leftThumbTip);
- // 오른손 데이터는 현재 구현되지 않음 (기본값 사용)
- // 실제 구현에서는 actor.body.rightThumb* 데이터 사용
+ if (actor.body.leftIndexProximal) leftHandFingers.push_back(*actor.body.leftIndexProximal);
+ if (actor.body.leftIndexMedial) leftHandFingers.push_back(*actor.body.leftIndexMedial);
+ if (actor.body.leftIndexDistal) leftHandFingers.push_back(*actor.body.leftIndexDistal);
+ if (actor.body.leftIndexTip) leftHandFingers.push_back(*actor.body.leftIndexTip);
+
+ if (actor.body.leftMiddleProximal) leftHandFingers.push_back(*actor.body.leftMiddleProximal);
+ if (actor.body.leftMiddleMedial) leftHandFingers.push_back(*actor.body.leftMiddleMedial);
+ if (actor.body.leftMiddleDistal) leftHandFingers.push_back(*actor.body.leftMiddleDistal);
+ if (actor.body.leftMiddleTip) leftHandFingers.push_back(*actor.body.leftMiddleTip);
+
+ if (actor.body.leftRingProximal) leftHandFingers.push_back(*actor.body.leftRingProximal);
+ if (actor.body.leftRingMedial) leftHandFingers.push_back(*actor.body.leftRingMedial);
+ if (actor.body.leftRingDistal) leftHandFingers.push_back(*actor.body.leftRingDistal);
+ if (actor.body.leftRingTip) leftHandFingers.push_back(*actor.body.leftRingTip);
+
+ if (actor.body.leftLittleProximal) leftHandFingers.push_back(*actor.body.leftLittleProximal);
+ if (actor.body.leftLittleMedial) leftHandFingers.push_back(*actor.body.leftLittleMedial);
+ if (actor.body.leftLittleDistal) leftHandFingers.push_back(*actor.body.leftLittleDistal);
+ if (actor.body.leftLittleTip) leftHandFingers.push_back(*actor.body.leftLittleTip);
+
+ // 오른손 데이터 추출 (20개 관절)
+ if (actor.body.rightThumbProximal) rightHandFingers.push_back(*actor.body.rightThumbProximal);
+ if (actor.body.rightThumbMedial) rightHandFingers.push_back(*actor.body.rightThumbMedial);
+ if (actor.body.rightThumbDistal) rightHandFingers.push_back(*actor.body.rightThumbDistal);
+ if (actor.body.rightThumbTip) rightHandFingers.push_back(*actor.body.rightThumbTip);
+
+ if (actor.body.rightIndexProximal) rightHandFingers.push_back(*actor.body.rightIndexProximal);
+ if (actor.body.rightIndexMedial) rightHandFingers.push_back(*actor.body.rightIndexMedial);
+ if (actor.body.rightIndexDistal) rightHandFingers.push_back(*actor.body.rightIndexDistal);
+ if (actor.body.rightIndexTip) rightHandFingers.push_back(*actor.body.rightIndexTip);
+
+ if (actor.body.rightMiddleProximal) rightHandFingers.push_back(*actor.body.rightMiddleProximal);
+ if (actor.body.rightMiddleMedial) rightHandFingers.push_back(*actor.body.rightMiddleMedial);
+ if (actor.body.rightMiddleDistal) rightHandFingers.push_back(*actor.body.rightMiddleDistal);
+ if (actor.body.rightMiddleTip) rightHandFingers.push_back(*actor.body.rightMiddleTip);
+
+ if (actor.body.rightRingProximal) rightHandFingers.push_back(*actor.body.rightRingProximal);
+ if (actor.body.rightRingMedial) rightHandFingers.push_back(*actor.body.rightRingMedial);
+ if (actor.body.rightRingDistal) rightHandFingers.push_back(*actor.body.rightRingDistal);
+ if (actor.body.rightRingTip) rightHandFingers.push_back(*actor.body.rightRingTip);
+
+ if (actor.body.rightLittleProximal) rightHandFingers.push_back(*actor.body.rightLittleProximal);
+ if (actor.body.rightLittleMedial) rightHandFingers.push_back(*actor.body.rightLittleMedial);
+ if (actor.body.rightLittleDistal) rightHandFingers.push_back(*actor.body.rightLittleDistal);
+ if (actor.body.rightLittleTip) rightHandFingers.push_back(*actor.body.rightLittleTip);
return true;
@@ -197,7 +307,7 @@ namespace RokokoIntegration
// 쿼터니언 길이 체크 (정규화된 경우 1.0에 가까워야 함)
float length = std::sqrt(x * x + y * y + z * z + w * w);
- if (std::abs(length - 1.0f) > 0.1f) {
+ if (length < 0.1f || length > 10.0f) {
return false;
}
@@ -207,15 +317,13 @@ namespace RokokoIntegration
void RokokoDataParser::NormalizeQuaternion(float& x, float& y, float& z, float& w)
{
float length = std::sqrt(x * x + y * y + z * z + w * w);
-
- if (length > 0.0f) {
- float invLength = 1.0f / length;
- x *= invLength;
- y *= invLength;
- z *= invLength;
- w *= invLength;
+ if (length > 0.0001f) {
+ x /= length;
+ y /= length;
+ z /= length;
+ w /= length;
} else {
- // 유효하지 않은 쿼터니언인 경우 기본값 설정
+ // 기본값 설정
x = 0.0f;
y = 0.0f;
z = 0.0f;
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataParser.h b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataParser.h
index 7d5ef00c..5f0ab931 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataParser.h
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataParser.h
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
// Forward declarations for JSON data structures
namespace RokokoData
@@ -19,6 +20,7 @@ namespace RokokoData
struct ActorJointFrame;
struct ActorData;
struct LiveFrame_v4;
+ struct Body;
}
namespace RokokoIntegration
@@ -53,6 +55,22 @@ namespace RokokoIntegration
std::vector& rightHandFingers);
private:
+ /**
+ * Parses body frame data from JSON
+ * @param bodyJson JSON object containing body data
+ * @param body Output body data structure
+ */
+ static void ParseBodyFrame(const nlohmann::json& bodyJson, RokokoData::Body& body);
+
+ /**
+ * Parses individual finger joint data from JSON
+ * @param bodyJson JSON object containing body data
+ * @param jointName Name of the joint to parse
+ * @param joint Output joint data structure
+ */
+ static void ParseFingerJoint(const nlohmann::json& bodyJson, const std::string& jointName,
+ std::shared_ptr& joint);
+
/**
* Parses actor data from JSON
* @param actorJson JSON object containing actor data
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoGloveDevice.vcxproj b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoGloveDevice.vcxproj
index e2c634af..6d0a016d 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoGloveDevice.vcxproj
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoGloveDevice.vcxproj
@@ -72,14 +72,15 @@ if exist "$(OutDir)ExampleGloveData.csv" copy "$(OutDir)ExampleGloveData.csv" "C
stdcpp17
WIN32;_DEBUG;_WINDOWS;_USRDLL;ANALOGSYSTEM_IMPORTS;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;%(PreprocessorDefinitions)
false
- ..\..\include;%(AdditionalIncludeDirectories)
+ ..\include;..\external;..\external\nlohmann;%(AdditionalIncludeDirectories)
+ /utf-8 %(AdditionalOptions)
Windows
true
NotSet
PeripheralImport.lib;ws2_32.lib;%(AdditionalDependencies)
- ..\..\lib;%(AdditionalLibraryDirectories)
+ ..\lib;%(AdditionalLibraryDirectories)
false
@@ -93,7 +94,8 @@ if exist "$(OutDir)ExampleGloveData.csv" copy "$(OutDir)ExampleGloveData.csv" "C
true
stdcpp17
WIN32;NDEBUG;_WINDOWS;_USRDLL;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;ANALOGSYSTEM_IMPORTS;%(PreprocessorDefinitions)
- ..\..\include;%(AdditionalIncludeDirectories)
+ ..\include;..\external;..\external\nlohmann;%(AdditionalIncludeDirectories)
+ /utf-8 %(AdditionalOptions)
Windows
@@ -101,7 +103,7 @@ if exist "$(OutDir)ExampleGloveData.csv" copy "$(OutDir)ExampleGloveData.csv" "C
true
true
PeripheralImport.lib;ws2_32.lib;%(AdditionalDependencies)
- ..\..\lib;%(AdditionalLibraryDirectories)
+ ..\lib;%(AdditionalLibraryDirectories)
@@ -111,7 +113,7 @@ if exist "$(OutDir)ExampleGloveData.csv" copy "$(OutDir)ExampleGloveData.csv" "C
-
+
@@ -130,7 +132,7 @@ if exist "$(OutDir)ExampleGloveData.csv" copy "$(OutDir)ExampleGloveData.csv" "C
-
+
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoUDPReceiver.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoUDPReceiver.cpp
index f447e404..6397d889 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoUDPReceiver.cpp
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoUDPReceiver.cpp
@@ -195,8 +195,7 @@ namespace RokokoIntegration
}
}
- // CPU 사용률 조절
- std::this_thread::sleep_for(std::chrono::milliseconds(1));
+ // 🚀 실시간성 최우선 - sleep 제거 (CPU 사용률보다 반응성 우선)
} catch (...) {
SetError("Exception in receive thread");
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/ExampleGloveAdapterSingleton.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/ExampleGloveAdapterSingleton.obj
index eb625e2b..a87fe54f 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/ExampleGloveAdapterSingleton.obj
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/ExampleGloveAdapterSingleton.obj
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:2bcc5341ff6326f2e644dac6941ceae06ffe113f020d4979d420d1d13567565d
-size 1900385
+oid sha256:f6fe6dc43c5014ad1c43821b887eb31362e61a429f919c8da111600d0eb6a1be
+size 3541367
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/ExampleGloveDevice.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/ExampleGloveDevice.obj
index a64bd2a6..4f954f7f 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/ExampleGloveDevice.obj
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/ExampleGloveDevice.obj
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5e828db2ae2584af3aa2382a807e639350239607b54e3e1317f14f121dbe23ac
-size 411123
+oid sha256:aefc3a79f1f9eebfecc2c884bf5d189fb4c4a379571d52ac12e0ae22df1f20c2
+size 627589
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/GloveDeviceBase.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/GloveDeviceBase.obj
index e758fcb3..67eeda28 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/GloveDeviceBase.obj
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/GloveDeviceBase.obj
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:ba3436308df0b6cd5bd9da32aad30a949aee5ff01b804ead4f611c8afb07f7c0
-size 368143
+oid sha256:361885b9262a598997ded5a79fd40e4ef77bf3f22c1b95da10511bdc7217b151
+size 371467
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/LZ4Wrapper.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/LZ4Wrapper.obj
index 764b2526..70f36089 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/LZ4Wrapper.obj
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/LZ4Wrapper.obj
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7f57f67431c5e5c23f44a3a5c9785f204269c47e10b20c8853287abf491eeb27
-size 169548
+oid sha256:6e12ddee47cc5ddd5ed91cb71704538e18e217faa69e806305a8db4e37937019
+size 188054
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoDataConverter.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoDataConverter.obj
index 22293387..69394e01 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoDataConverter.obj
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoDataConverter.obj
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:0ec59716322b316b1bca098666afe786abce44a88683bd8e49571e5385d72d16
-size 408468
+oid sha256:49545fe193fda88a99aff30873978e48c31eea5963920edc865c2c9d98173131
+size 408444
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoDataParser.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoDataParser.obj
index 17d34595..0df6470d 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoDataParser.obj
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoDataParser.obj
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:156f16eafecd6b0b8bb5a9b47e3a58364ee96e47030faa04b96612c5b80c4c50
-size 379349
+oid sha256:c0326dd5cd75fd6a3ebd76c855dcf0edf6248b6f4d3087ea05790d537aa92ebd
+size 5909881
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.command.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.command.1.tlog
index 1b64c85a..8f384092 100644
Binary files a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.command.1.tlog and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.command.1.tlog differ
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.read.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.read.1.tlog
index 030b673a..3c957845 100644
Binary files a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.read.1.tlog and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.read.1.tlog differ
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.write.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.write.1.tlog
index c3665854..ebcef3ae 100644
Binary files a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.write.1.tlog and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.write.1.tlog differ
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.command.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.command.1.tlog
index 9be77f59..8500ffda 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.command.1.tlog
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.command.1.tlog
@@ -1,2 +1,2 @@
-^C:\USERS\QSCFT\ONEDRIVE\문서\OPTITRACK\MOTIVE\PERIPHERALAPI\EXAMPLE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV
-C:\Users\qscft\OneDrive\문서\OptiTrack\Motive\PeripheralAPI\example\x64\Debug\ExampleGloveData.csv
+^C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV
+C:\Users\user\Documents\Streamingle_URP\Optitrack Rokoko Glove\x64\Debug\ExampleGloveData.csv
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.read.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.read.1.tlog
index 120ee5e5..91d479f4 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.read.1.tlog
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.read.1.tlog
@@ -1 +1 @@
-^C:\USERS\QSCFT\ONEDRIVE\문서\OPTITRACK\MOTIVE\PERIPHERALAPI\EXAMPLE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV
+^C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.write.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.write.1.tlog
index 849c02ff..78e57e7f 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.write.1.tlog
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.write.1.tlog
@@ -1,2 +1,2 @@
-^C:\USERS\QSCFT\ONEDRIVE\문서\OPTITRACK\MOTIVE\PERIPHERALAPI\EXAMPLE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV
-C:\USERS\QSCFT\ONEDRIVE\문서\OPTITRACK\MOTIVE\PERIPHERALAPI\EXAMPLE\X64\DEBUG\EXAMPLEGLOVEDATA.CSV
+^C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV
+C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\X64\DEBUG\EXAMPLEGLOVEDATA.CSV
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.lastbuildstate b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.lastbuildstate
index ca77c90a..27ddd87a 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.lastbuildstate
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.lastbuildstate
@@ -1,2 +1,2 @@
-PlatformToolSet=v142:VCToolArchitecture=Native64Bit:VCToolsVersion=14.29.30133:TargetPlatformVersion=8.1:VcpkgTriplet=x64-windows:
-Debug|x64|C:\Users\qscft\OneDrive\문서\OptiTrack\Motive\PeripheralAPI\example\|
+PlatformToolSet=v142:VCToolArchitecture=Native64Bit:VCToolsVersion=14.29.30133:TargetPlatformVersion=8.1:
+Debug|x64|C:\Users\user\Documents\Streamingle_URP\Optitrack Rokoko Glove\|
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.write.1u.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.write.1u.tlog
index 4698b933..f0a27aab 100644
Binary files a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.write.1u.tlog and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.write.1u.tlog differ
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.command.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.command.1.tlog
index cc64a2f4..f6dbb298 100644
Binary files a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.command.1.tlog and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.command.1.tlog differ
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.read.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.read.1.tlog
index 693ff8ac..46e9fee1 100644
Binary files a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.read.1.tlog and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.read.1.tlog differ
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.write.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.write.1.tlog
index 47acec86..6af6286f 100644
Binary files a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.write.1.tlog and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.write.1.tlog differ
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.dll.recipe b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.dll.recipe
index 339ec984..68ea365b 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.dll.recipe
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.dll.recipe
@@ -2,7 +2,7 @@
- C:\Users\qscft\OneDrive\문서\OptiTrack\Motive\PeripheralAPI\example\x64\Debug\RokokoGloveDevice.dll
+ C:\Users\user\Documents\Streamingle_URP\Optitrack Rokoko Glove\x64\Debug\RokokoGloveDevice.dll
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.ilk b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.ilk
index 87b69ffa..d231c607 100644
Binary files a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.ilk and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.ilk differ
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoUDPReceiver.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoUDPReceiver.obj
index 39c1db9e..5e4a9096 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoUDPReceiver.obj
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoUDPReceiver.obj
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:80b256c2ede8a3d4285192069d2a25e2ae56d17900bef555e26c3aef751f8edf
-size 674337
+oid sha256:3c96bfc1e905899005cf8aa8b3ce0489912f5e198c8d11f2d3ca4e880bfcf512
+size 602380
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/dllmain.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/dllmain.obj
index d78fb7c8..36df90ed 100644
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/dllmain.obj
+++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/dllmain.obj
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:09ee07f42e48385a35cb9f33f6bcdcc760bada2d85e79f0311a055f61b161817
-size 278719
+oid sha256:53756e5e0ff8e744dfa7226344ed02efd644183a50a47a73624d86ea9321c36e
+size 435135
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/lz4.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/lz4.obj
deleted file mode 100644
index 4e918ddb..00000000
--- a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/lz4.obj
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:90c2abd5142ada866a1336c9189e7ec65e0d68dccf3e4f4d05c4b0d97ecc75ba
-size 134890
diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/vc142.idb b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/vc142.idb
index 366c488b..ac87499a 100644
Binary files a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/vc142.idb and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/vc142.idb differ
diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/SimpleDeviceExample.vcxproj b/Optitrack Rokoko Glove/SimpleDeviceExample/SimpleDeviceExample.vcxproj
index fa15da70..87636826 100644
--- a/Optitrack Rokoko Glove/SimpleDeviceExample/SimpleDeviceExample.vcxproj
+++ b/Optitrack Rokoko Glove/SimpleDeviceExample/SimpleDeviceExample.vcxproj
@@ -67,14 +67,14 @@ copy "$(OutDir)SimpleDeviceExample.dll" "C:/Program Files/OptiTrack/Motive/dev
Disabled
WIN32;_DEBUG;_WINDOWS;_USRDLL;ANALOGSYSTEM_IMPORTS;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;%(PreprocessorDefinitions)
false
- ..\..\include;%(AdditionalIncludeDirectories)
+ ..\include;%(AdditionalIncludeDirectories)
Windows
true
NotSet
PeripheralImport.lib;%(AdditionalDependencies)
- ..\..\lib;%(AdditionalLibraryDirectories)
+ ..\lib;%(AdditionalLibraryDirectories)
@@ -84,7 +84,7 @@ copy "$(OutDir)SimpleDeviceExample.dll" "C:/Program Files/OptiTrack/Motive/dev
true
true
WIN32;NDEBUG;_WINDOWS;_USRDLL;ANALOGSYSTEM_IMPORTS;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;%(PreprocessorDefinitions)
- ..\..\include;%(AdditionalIncludeDirectories)
+ ..\include;%(AdditionalIncludeDirectories)
Windows
@@ -92,7 +92,7 @@ copy "$(OutDir)SimpleDeviceExample.dll" "C:/Program Files/OptiTrack/Motive/dev
true
true
PeripheralImport.lib;%(AdditionalDependencies)
- ..\..\lib;%(AdditionalLibraryDirectories)
+ ..\lib;%(AdditionalLibraryDirectories)
diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/SimpleDeviceExample.lastbuildstate b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/SimpleDeviceExample.lastbuildstate
index ca77c90a..27ddd87a 100644
--- a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/SimpleDeviceExample.lastbuildstate
+++ b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/SimpleDeviceExample.lastbuildstate
@@ -1,2 +1,2 @@
-PlatformToolSet=v142:VCToolArchitecture=Native64Bit:VCToolsVersion=14.29.30133:TargetPlatformVersion=8.1:VcpkgTriplet=x64-windows:
-Debug|x64|C:\Users\qscft\OneDrive\문서\OptiTrack\Motive\PeripheralAPI\example\|
+PlatformToolSet=v142:VCToolArchitecture=Native64Bit:VCToolsVersion=14.29.30133:TargetPlatformVersion=8.1:
+Debug|x64|C:\Users\user\Documents\Streamingle_URP\Optitrack Rokoko Glove\|
diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/dllmain.obj b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/dllmain.obj
deleted file mode 100644
index 3fc151b9..00000000
--- a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/dllmain.obj
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:d866e6a7e9d9cc51f0cf06aafbea365578bc1fe030bc1b4b3e2a7eb2eb4bd050
-size 88247
diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/vc142.idb b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/vc142.idb
index 6d9dea15..17aca725 100644
Binary files a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/vc142.idb and b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/vc142.idb differ
diff --git a/Optitrack Rokoko Glove/external/nlohmann/json.hpp b/Optitrack Rokoko Glove/external/nlohmann/json.hpp
new file mode 100644
index 00000000..8b72ea65
--- /dev/null
+++ b/Optitrack Rokoko Glove/external/nlohmann/json.hpp
@@ -0,0 +1,24765 @@
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.11.3
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+/****************************************************************************\
+ * Note on documentation: The source files contain links to the online *
+ * documentation of the public API at https://json.nlohmann.me. This URL *
+ * contains the most recent documentation and should also be applicable to *
+ * previous versions; documentation for deprecated functions is not *
+ * removed, but marked deprecated. See "Generate documentation" section in *
+ * file docs/README.md. *
+\****************************************************************************/
+
+#ifndef INCLUDE_NLOHMANN_JSON_HPP_
+#define INCLUDE_NLOHMANN_JSON_HPP_
+
+#include // all_of, find, for_each
+#include // nullptr_t, ptrdiff_t, size_t
+#include // hash, less
+#include // initializer_list
+#ifndef JSON_NO_IO
+ #include // istream, ostream
+#endif // JSON_NO_IO
+#include // random_access_iterator_tag
+#include // unique_ptr
+#include // string, stoi, to_string
+#include // declval, forward, move, pair, swap
+#include // vector
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.11.3
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+#include
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.11.3
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+// This file contains all macro definitions affecting or depending on the ABI
+
+#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
+ #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
+ #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 11 || NLOHMANN_JSON_VERSION_PATCH != 3
+ #warning "Already included a different version of the library!"
+ #endif
+ #endif
+#endif
+
+#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
+#define NLOHMANN_JSON_VERSION_MINOR 11 // NOLINT(modernize-macro-to-enum)
+#define NLOHMANN_JSON_VERSION_PATCH 3 // NOLINT(modernize-macro-to-enum)
+
+#ifndef JSON_DIAGNOSTICS
+ #define JSON_DIAGNOSTICS 0
+#endif
+
+#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
+ #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
+#endif
+
+#if JSON_DIAGNOSTICS
+ #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
+#else
+ #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
+#endif
+
+#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
+ #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
+#else
+ #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
+#endif
+
+#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
+ #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
+#endif
+
+// Construct the namespace ABI tags component
+#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b) json_abi ## a ## b
+#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b) \
+ NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b)
+
+#define NLOHMANN_JSON_ABI_TAGS \
+ NLOHMANN_JSON_ABI_TAGS_CONCAT( \
+ NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
+ NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON)
+
+// Construct the namespace version component
+#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
+ _v ## major ## _ ## minor ## _ ## patch
+#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
+ NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
+
+#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
+#define NLOHMANN_JSON_NAMESPACE_VERSION
+#else
+#define NLOHMANN_JSON_NAMESPACE_VERSION \
+ NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
+ NLOHMANN_JSON_VERSION_MINOR, \
+ NLOHMANN_JSON_VERSION_PATCH)
+#endif
+
+// Combine namespace components
+#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
+#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
+ NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
+
+#ifndef NLOHMANN_JSON_NAMESPACE
+#define NLOHMANN_JSON_NAMESPACE \
+ nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
+ NLOHMANN_JSON_ABI_TAGS, \
+ NLOHMANN_JSON_NAMESPACE_VERSION)
+#endif
+
+#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
+#define NLOHMANN_JSON_NAMESPACE_BEGIN \
+ namespace nlohmann \
+ { \
+ inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
+ NLOHMANN_JSON_ABI_TAGS, \
+ NLOHMANN_JSON_NAMESPACE_VERSION) \
+ {
+#endif
+
+#ifndef NLOHMANN_JSON_NAMESPACE_END
+#define NLOHMANN_JSON_NAMESPACE_END \
+ } /* namespace (inline namespace) NOLINT(readability/namespace) */ \
+ } // namespace nlohmann
+#endif
+
+// #include
+// __ _____ _____ _____
+// __| | __| | | | JSON for Modern C++
+// | | |__ | | | | | | version 3.11.3
+// |_____|_____|_____|_|___| https://github.com/nlohmann/json
+//
+// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann
+// SPDX-License-Identifier: MIT
+
+
+
+#include // transform
+#include // array
+#include // forward_list
+#include // inserter, front_inserter, end
+#include