diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..d4489992 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6722fe768e744cfd4a3e35c1d77d853e7d495a6ec5e05b15e449ed77a075e404 +size 372 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/ExampleGloveAdapterSingleton.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/ExampleGloveAdapterSingleton.cpp new file mode 100644 index 00000000..d2f09d31 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/ExampleGloveAdapterSingleton.cpp @@ -0,0 +1,2279 @@ +//====================================================================================================== +// Copyright 2023, NaturalPoint Inc. +// Modified 2025 for Rokoko Glove Integration +//====================================================================================================== + +#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 +#include "ExampleGloveAdapterSingleton.h" +#include "IDeviceManager.h" +#include "ExampleGloveDevice.h" +#include "LZ4Wrapper.h" +using namespace AnalogSystem; + +// Rokoko Integration - 기존 시뮬레이션 관련 정의를 주석 처리 +// #define SIM_DEVICE_COUNT 2 +// #define SIM_DEVICE_RATE 100 + + +OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ExampleGloveAdapterSingleton(AnalogSystem::IDeviceManager* pDeviceManager) +{ + s_Instance = this; + mDeviceManager = pDeviceManager; + + // 기존 시뮬레이션 코드를 주석 처리 (로코코 통신으로 대체) + // mGloveSimulator = new SimulatedPluginDevices::HardwareSimulator(); + + mGloveDataMutex = new std::recursive_mutex(); + mDetectedDevices.clear(); + mLatestGloveData.clear(); + + // start detection thread + mDetectionThread = std::thread(&ExampleGloveAdapterSingleton::DoDetectionThread, this); +} + + +OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::~ExampleGloveAdapterSingleton() +{ + mDetectedDevices.clear(); + mLatestGloveData.clear(); + delete mGloveDataMutex; + bIsConnected = false; + bIsDetecting = false; + s_Instance = nullptr; + + if (mDetectionThread.joinable()) + { + mDetectionThread.join(); + } + + // 기존 시뮬레이션 코드를 주석 처리 + // mGloveSimulator->Shutdown(); + // delete mGloveSimulator; + + // 로코코 연결 해제 + DisconnectFromRokoko(); +} + + +bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ClientShutdown() +{ + bIsConnected = false; + bIsDetecting = false; + + // 기존 시뮬레이션 코드를 주석 처리 + // if (mGloveSimulator != nullptr) + // { + // mGloveSimulator->Shutdown(); + // } + + // 로코코 연결 해제 + DisconnectFromRokoko(); + + return false; +} + +/////////////////////////////////////////////////////////////////////////////// +// +// Rokoko Glove Host Detection Thread +// +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::DoDetectionThread() +{ + while (!bIsConnected && bIsDetecting) + { + // Try reconnecting + if (s_Instance->mConnectionAttemptCount < s_Instance->kMaxConnectionAttempt) { + bIsConnected = ConnectToHost(); + s_Instance->mConnectionAttemptCount++; + } + else { + bIsDetecting = false; + NotifyConnectionFail(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); // 🚀 실시간성 최우선 - 1ms로 최소화 + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// Rokoko Integration - Connection Management +// +bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ConnectToHost() +{ + // Check if glove server address is defined in Motive's settings. + if ((mServerAddress.empty())) { + char* szServerAddress = new char[MAX_PATH]; + if (mDeviceManager->GetProperty("Glove Server Address", &szServerAddress)) + { + std::string str(szServerAddress); + mServerAddress = str; + } + delete[] szServerAddress; + } + + /* + * [Glove SDK Placeholder] + * SDK call to connect to the host at 'mServerAddress'. + */ + bIsConnected = true; + + // 로코코 UDP 통신 시작 + if (ConnectToRokoko()) { + if (mDeviceManager) { + mDeviceManager->MessageToHost("[RokokoGlove] Successfully connected to Rokoko Studio", MessageType_StatusInfo); + } + return true; + } else { + if (mDeviceManager) { + mDeviceManager->MessageToHost("[RokokoGlove] Failed to connect to Rokoko Studio", MessageType_StatusInfo); + } + // 로코코 연결 실패 시 연결 실패로 처리 + bIsConnected = false; + return false; + } +} + +bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ConnectToRokoko() +{ + try { + // 로코코 UDP 리시버 초기화 + mRokokoReceiver = std::make_unique(); + + // UDP 리시버 초기화 (포트 14043) + if (!mRokokoReceiver->Initialize(14043)) { + // 에러 메시지 출력 + std::string errorMsg = "Failed to initialize Rokoko UDP receiver: " + mRokokoReceiver->GetLastError(); + if (mDeviceManager) { + mDeviceManager->MessageToHost(errorMsg.c_str(), MessageType_StatusInfo); + } + return false; + } + + // 데이터 콜백 설정 + mRokokoReceiver->SetDataCallback([this](const std::vector& data, const std::string& senderIP) { + this->OnRokokoDataReceived(data, senderIP); + }); + + // UDP 수신 시작 + if (!mRokokoReceiver->StartListening()) { + std::string errorMsg = "Failed to start Rokoko UDP listening: " + mRokokoReceiver->GetLastError(); + if (mDeviceManager) { + mDeviceManager->MessageToHost(errorMsg.c_str(), MessageType_StatusInfo); + } + return false; + } + + bIsRokokoConnected = true; + + // 초기 장치 생성은 데이터 수신 후로 지연 + // DetectAndCreateRokokoDevices(); // 주석 처리 + + // 성공 메시지 출력 + if (mDeviceManager) { + mDeviceManager->MessageToHost("Successfully connected to Rokoko Studio via UDP on port 14043", MessageType_StatusInfo); + } + + return true; + + } catch (...) { + if (mDeviceManager) { + mDeviceManager->MessageToHost("Exception occurred while connecting to Rokoko", MessageType_StatusInfo); + } + return false; + } +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::DisconnectFromRokoko() +{ + if (mRokokoReceiver) { + mRokokoReceiver->StopListening(); + mRokokoReceiver.reset(); + } + bIsRokokoConnected = false; + + if (mDeviceManager) { + mDeviceManager->MessageToHost("Disconnected from Rokoko Studio", MessageType_StatusInfo); + } +} + + +bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::IsConnected() +{ + return bIsConnected; +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::SetLatestData(const sGloveDeviceData& gloveFingerData) +{ + s_Instance->mLatestGloveData[gloveFingerData.gloveId] = gloveFingerData; +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::SetDeviceManager(AnalogSystem::IDeviceManager* pDeviceManager) +{ + if (s_Instance == nullptr) return; + if (pDeviceManager != nullptr) + { + s_Instance->mDeviceManager = pDeviceManager; + } +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::SetLatestDeviceInfo(const sGloveDeviceBaseInfo& deviceInfo) +{ + s_Instance->mLatestDeviceInfo[deviceInfo.gloveId] = deviceInfo; +} + +bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::GetLatestData(const uint64_t mDeviceSerial, sGloveDeviceData& gloveFingerData) +{ + if (s_Instance == nullptr || !s_Instance -> bIsConnected) return false; + + // FIX: Always initialize result and use lock_guard for exception safety + bool res = false; + + // FIX: Use lock_guard instead of try_lock + manual unlock + std::lock_guard lock(*s_Instance->mGloveDataMutex); + + // Iterate through the glove data table and update + auto iter = s_Instance->mLatestGloveData.find(mDeviceSerial); + if (iter != s_Instance->mLatestGloveData.end()) + { + gloveFingerData = iter->second; + res = true; + } + + return res; +} + + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::CreateNewGloveDevice(sGloveDeviceBaseInfo& deviceInfo) +{ + uint64_t gloveId = deviceInfo.gloveId; + + // 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 = + std::make_unique(deviceName, deviceInfo); + pDF->mDeviceIndex = s_Instance->mCurrentDeviceIndex; + s_Instance->mDeviceManager->AddDevice(std::move(pDF)); + s_Instance->mCurrentDeviceIndex++; + + // 로코코 장치 생성 성공 메시지 + if (s_Instance->mDeviceManager) { + std::string successMsg = "[RokokoGlove] Created new device: " + deviceName; + s_Instance->mDeviceManager->MessageToHost(successMsg.c_str(), MessageType_StatusInfo); + } + + return; +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::NotifyConnectionFail() +{ + char* szServerAddress = new char[MAX_PATH]; + char szMessage[MAX_PATH]; + if (mDeviceManager->GetProperty("Glove Server Address", &szServerAddress)) { + if (!(strlen(szServerAddress) == 0)) + { + sprintf_s(szMessage, "[RokokoGlove] Failed to connect to Rokoko Studio on %s", szServerAddress); + mDeviceManager->MessageToHost(szMessage, MessageType_StatusInfo); + } + else + { + sprintf_s(szMessage, "[RokokoGlove] Failed to connect to Rokoko Studio on localhost"); + mDeviceManager->MessageToHost(szMessage, MessageType_StatusInfo); + } + } + delete[] szServerAddress; +} + +/////////////////////////////////////////////////////////////////////////////// +// +// Legacy Simulation Support (Commented Out) +// +/* +* Methods in this section were used for simulated hardware. +* Now replaced by Rokoko UDP communication. +*/ +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::StartSimulatedHardware(int deviceCount) +{ + // mGloveSimulator->StartData(); // 기존 시뮬레이션 코드를 주석 처리 +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::RegisterSDKCallbacks() +{ + // s_Instance->mGloveSimulator->RegisterFrameDataCallback(OnDataCallback); // 기존 시뮬레이션 코드를 주석 처리 + // s_Instance->mGloveSimulator->RegisterDeviceInfoCallback(OnDeviceInfoCallback); // 기존 시뮬레이션 코드를 주석 처리 + + // Add simulated glove devices + // s_Instance->mGloveSimulator->AddSimulatedGlove(1, 15, 1); // 기존 시뮬레이션 코드를 주석 처리 + // s_Instance->mGloveSimulator->AddSimulatedGlove(2, 15, 2); // 기존 시뮬레이션 코드를 주석 처리 +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::OnDataCallback(std::vector& gloveAllFingerData) +{ + // 기존 시뮬레이션 콜백을 주석 처리 (로코코 UDP 데이터 처리로 대체) + /* + if (s_Instance == nullptr) return; + + // [Glove SDK Placeholder] + // Data update callbacks to Keep the latest glove data map up-to-date. + s_Instance->mGloveDataMutex->lock(); + for (auto gloveFingerData : gloveAllFingerData) { + // Convert simulated data into glove data format. + sGloveDeviceData newGloveData = ConvertDataFormat(gloveFingerData); + s_Instance->SetLatestData(newGloveData); + } + s_Instance->mGloveDataMutex->unlock(); + */ + + // 로코코 UDP 데이터는 OnRokokoDataReceived에서 처리됨 +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::OnDeviceInfoCallback(std::vector& newGloveInfo) +{ + // 기존 시뮬레이션 콜백을 주석 처리 (로코코 장치 정보로 대체) + /* + if (s_Instance == nullptr) return; + + // Create new gloves + for (SimulatedPluginDevices::SimulatedDeviceInfo glove : newGloveInfo) + { + s_Instance->mDeviceManager->MessageToHost(" [ExampleGloveDevice] New device(s) detected.", MessageType_StatusInfo); + sGloveDeviceBaseInfo newGloveDevice = ConvertDeviceInfoFormat(glove); + s_Instance->CreateNewGloveDevice(newGloveDevice); + } + */ + + // 로코코 장치 정보는 ConnectToRokoko에서 처리됨 +} + +/////////////////////////////////////////////////////////////////////////////// +// +// Legacy Data Conversion (Commented Out) +// +/* +* Methods in this section were used for converting simulated data. +* Now replaced by Rokoko data conversion. +*/ +sGloveDeviceBaseInfo OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ConvertDeviceInfoFormat(SimulatedPluginDevices::SimulatedDeviceInfo& glove) +{ + // 기존 시뮬레이션 데이터 변환을 주석 처리 (로코코 데이터로 대체) + /* + sGloveDeviceBaseInfo ret; + + ret.battery = glove.mBattery; + ret.gloveId = glove.mDeviceSerial; + ret.signalStrength = glove.mSignalStrength; + ret.handSide = (glove.mHandSide == 1) ? eGloveHandSide::Left : + (glove.mHandSide == 2) ? eGloveHandSide::Right : + eGloveHandSide::Unknown; + + return ret; + */ + + // 로코코 장치 정보는 기본값으로 설정 + sGloveDeviceBaseInfo ret; + ret.battery = 100; // 기본 배터리 100% + ret.gloveId = 1; // 기본 장갑 ID + ret.signalStrength = 100; // 기본 신호 강도 100% + ret.handSide = eGloveHandSide::Left; // 기본 왼손 + + return ret; +} + +sGloveDeviceData OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ConvertDataFormat(const SimulatedPluginDevices::SimulatedGloveFrameData& gloveFrameData) +{ + // 기존 시뮬레이션 데이터 변환을 주석 처리 (로코코 데이터로 대체) + /* + sGloveDeviceData ret; + ret.gloveId = gloveFrameData.mDeviceSerial; + ret.timestamp = 0; + + sFingerNode defaultNodes = { 0, 0, 0, 1 }; + ret.nodes = std::vector(15, defaultNodes); + + int node_iter = 0; + for (auto& fingerNode : ret.nodes) + { + fingerNode.node_id = node_iter; + fingerNode.quat_w = gloveFrameData.gloveFingerData[node_iter].quat_w; + fingerNode.quat_x = gloveFrameData.gloveFingerData[node_iter].quat_x; + fingerNode.quat_y = gloveFrameData.gloveFingerData[node_iter].quat_y; + fingerNode.quat_z = gloveFrameData.gloveFingerData[node_iter].quat_z; + node_iter++; + } + + return ret; + */ + + // 로코코 데이터는 ProcessRokokoData에서 처리됨 + // 기본 빈 데이터 반환 + sGloveDeviceData ret; + ret.gloveId = 1; + ret.timestamp = 0; + ret.nodes.clear(); + + return ret; +} + +/////////////////////////////////////////////////////////////////////////////// +// +// Rokoko Integration - UDP Data Processing +// +/* +* Methods in this section handle Rokoko UDP data reception and processing. +*/ + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::OnRokokoDataReceived(const std::vector& data, const std::string& senderIP) +{ + if (s_Instance == nullptr) return; + + // 🚀 실시간성 최우선: 예외 처리 및 로그 최소화 + s_Instance->ProcessRokokoData(data); +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ProcessRokokoData(const std::vector& data) +{ + try { + // 🚀 실시간성을 위해 정적 버퍼 사용 (메모리 할당 최소화) + 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; + } + } else { + decompressedData = data; + } + } + + // 2단계: JSON 문자열로 변환 (유니티 방식 정확히 구현) + // Unity 방식: string text = System.Text.Encoding.UTF8.GetString(uncompressed); + std::string jsonString(decompressedData.begin(), decompressedData.end()); + + // 🚀 성능 최적화: 데이터 미리보기 로그 제거 + + // JSON 데이터 유효성 검사 + if (jsonString.empty() || jsonString.length() < 10) { + if (mDeviceManager) { + 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; // 실패 시 즉시 종료 + } + + // 🚀 Actor 기반 동적 장치 감지 (로그 제거) + DetectActorsFromRokokoData(rokokoFrame); + + // 🚀 첫 번째 데이터 수신 시 기본 장치도 생성 (백업) + if (mDetectedDevices.empty()) { + DetectAndCreateRokokoDevices(); + } + + // 🚀 다중 장치 데이터 처리 (왼손, 오른손) - 즉시 처리 + ProcessMultipleDeviceData(rokokoFrame); + + // 🚀 최신 프레임 업데이트 - 큐 없이 즉시 업데이트 + mLastRokokoFrame = rokokoFrame; + + } catch (...) { + // 🚀 실시간성 최우선 - 예외 발생 시 즉시 종료 (로그 제거) + return; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// +// Device Information Management +// +/* +* Methods in this section handle device information updates and management. +*/ + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::UpdateDeviceInfo(uint64_t deviceId) +{ + try { + // 장치 정보 업데이트 (배터리, 신호 강도 등) + auto deviceInfoIter = mLatestDeviceInfo.find(deviceId); + if (deviceInfoIter != mLatestDeviceInfo.end()) { + // 기존 장치 정보 업데이트 + sGloveDeviceBaseInfo& deviceInfo = deviceInfoIter->second; + + // 배터리 상태 업데이트 (실제로는 Rokoko 데이터에서 가져와야 함) + deviceInfo.battery = 100; // 기본값 + + // 신호 강도 업데이트 (UDP 연결 상태 기반) + if (bIsRokokoConnected) { + deviceInfo.signalStrength = 100; // 연결됨 + } else { + deviceInfo.signalStrength = 0; // 연결 안됨 + } + + // 장치 정보 저장 + SetLatestDeviceInfo(deviceInfo); + } + + } catch (...) { + if (mDeviceManager) { + mDeviceManager->MessageToHost("[RokokoGlove] Error updating device info", MessageType_StatusInfo); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// +// Dynamic Device Detection and Management +// +/* +* Methods in this section handle dynamic device detection and multi-device data processing. +*/ + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::DetectAndCreateRokokoDevices() +{ + try { + // 기존 장치 정보 초기화 + mDetectedDevices.clear(); + 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); + + // 오른손 장치 생성 + sGloveDeviceBaseInfo rightHandInfo; + rightHandInfo.gloveId = 2; + rightHandInfo.handSide = eGloveHandSide::Right; + rightHandInfo.battery = 100; + rightHandInfo.signalStrength = 100; + rightHandInfo.actorName = "DefaultActor"; // Actor 이름 추가 + CreateNewGloveDevice(rightHandInfo); + SetLatestDeviceInfo(rightHandInfo); + mDetectedDevices.push_back(2); + + // 장치 감지 완료 메시지 + if (mDeviceManager) { + std::string msg = "[RokokoGlove] Detected " + std::to_string(mDetectedDevices.size()) + " devices (Left & Right Hand)"; + mDeviceManager->MessageToHost(msg.c_str(), MessageType_StatusInfo); + } + + } catch (...) { + if (mDeviceManager) { + mDeviceManager->MessageToHost("[RokokoGlove] Error during device detection", MessageType_StatusInfo); + } + } +} + +// 새로운 함수: 동적 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 { + // 검증: actors가 존재하는지 확인 + if (rokokoFrame.scene.actors.empty()) { + if (mDeviceManager) { + mDeviceManager->MessageToHost("[RokokoGlove] No actors found in frame", MessageType_StatusInfo); + } + return; + } + + // 🚀 모든 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) { + if (mDeviceManager) { + std::string errorMsg = "[RokokoGlove] Exception in multi-device processing: " + std::string(e.what()); + mDeviceManager->MessageToHost(errorMsg.c_str(), MessageType_StatusInfo); + } + } catch (...) { + if (mDeviceManager) { + mDeviceManager->MessageToHost("[RokokoGlove] Unknown exception in multi-device processing", MessageType_StatusInfo); + } + } +} + +bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ProcessHandData(const RokokoData::Body& body, uint64_t deviceId, eGloveHandSide handSide) +{ + try { + // 손가락 데이터를 OptiTrack 형식으로 변환 + sGloveDeviceData optiTrackData; + optiTrackData.gloveId = deviceId; + optiTrackData.timestamp = 0.0; + optiTrackData.nodes.resize(15); // 5개 손가락 × 3개 관절 + + // 손가락별 데이터 매핑 + if (handSide == eGloveHandSide::Left) { + // 왼손 데이터 매핑 + MapLeftHandJoints(body, optiTrackData.nodes); + } else { + // 오른손 데이터 매핑 + MapRightHandJoints(body, optiTrackData.nodes); + } + + // 노드 ID 설정 + for (int i = 0; i < 15; i++) { + optiTrackData.nodes[i].node_id = i; + } + + // 🚀 실시간성 최우선 - 모든 디버깅 제거 + + // 🎯 계층적 로컬 로테이션 계산 (Rokoko 월드 데이터 → 로컬 변환) + ConvertToTrueLocalRotations(optiTrackData.nodes, body, handSide); + + // 🚀 실시간성 최우선 - 데이터 유효성 검사 (로그 제거) + if (!RokokoIntegration::RokokoDataConverter::ValidateOptiTrackData(optiTrackData)) { + return false; + } + + // OptiTrack 데이터 저장 + mGloveDataMutex->lock(); + SetLatestData(optiTrackData); + mGloveDataMutex->unlock(); + + return true; + + } catch (...) { + // 🚀 실시간성 최우선 - 예외 발생 시 즉시 종료 (로그 제거) + return false; + } +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::MapLeftHandJoints(const RokokoData::Body& body, std::vector& nodes) +{ + // 엄지손가락 (Thumb) + if (body.leftThumbMedial) MapJoint(*body.leftThumbMedial, nodes[0]); // MP + if (body.leftThumbDistal) MapJoint(*body.leftThumbDistal, nodes[1]); // PIP + if (body.leftThumbTip) MapJoint(*body.leftThumbTip, nodes[2]); // DIP + + // 검지손가락 (Index) + if (body.leftIndexMedial) MapJoint(*body.leftIndexMedial, nodes[3]); // MP + if (body.leftIndexDistal) MapJoint(*body.leftIndexDistal, nodes[4]); // PIP + if (body.leftIndexTip) MapJoint(*body.leftIndexTip, nodes[5]); // DIP + + // 중지손가락 (Middle) + if (body.leftMiddleMedial) MapJoint(*body.leftMiddleMedial, nodes[6]); // MP + if (body.leftMiddleDistal) MapJoint(*body.leftMiddleDistal, nodes[7]); // PIP + if (body.leftMiddleTip) MapJoint(*body.leftMiddleTip, nodes[8]); // DIP + + // 약지손가락 (Ring) + if (body.leftRingMedial) MapJoint(*body.leftRingMedial, nodes[9]); // MP + if (body.leftRingDistal) MapJoint(*body.leftRingDistal, nodes[10]); // PIP + if (body.leftRingTip) MapJoint(*body.leftRingTip, nodes[11]); // DIP + + // 새끼손가락 (Little) + if (body.leftLittleMedial) MapJoint(*body.leftLittleMedial, nodes[12]); // MP + if (body.leftLittleDistal) MapJoint(*body.leftLittleDistal, nodes[13]); // PIP + if (body.leftLittleTip) MapJoint(*body.leftLittleTip, nodes[14]); // DIP +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::MapRightHandJoints(const RokokoData::Body& body, std::vector& nodes) +{ + // 엄지손가락 (Thumb) + if (body.rightThumbMedial) MapJoint(*body.rightThumbMedial, nodes[0]); // MP + if (body.rightThumbDistal) MapJoint(*body.rightThumbDistal, nodes[1]); // PIP + if (body.rightThumbTip) MapJoint(*body.rightThumbTip, nodes[2]); // DIP + + // 검지손가락 (Index) + if (body.rightIndexMedial) MapJoint(*body.rightIndexMedial, nodes[3]); // MP + if (body.rightIndexDistal) MapJoint(*body.rightIndexDistal, nodes[4]); // PIP + if (body.rightIndexTip) MapJoint(*body.rightIndexTip, nodes[5]); // DIP + + // 중지손가락 (Middle) + if (body.rightMiddleMedial) MapJoint(*body.rightMiddleMedial, nodes[6]); // MP + if (body.rightMiddleDistal) MapJoint(*body.rightMiddleDistal, nodes[7]); // PIP + if (body.rightMiddleTip) MapJoint(*body.rightMiddleTip, nodes[8]); // DIP + + // 약지손가락 (Ring) + if (body.rightRingMedial) MapJoint(*body.rightRingMedial, nodes[9]); // MP + if (body.rightRingDistal) MapJoint(*body.rightRingDistal, nodes[10]); // PIP + if (body.rightRingTip) MapJoint(*body.rightRingTip, nodes[11]); // DIP + + // 새끼손가락 (Little) + if (body.rightLittleMedial) MapJoint(*body.rightLittleMedial, nodes[12]); // MP + if (body.rightLittleDistal) MapJoint(*body.rightLittleDistal, nodes[13]); // PIP + if (body.rightLittleTip) MapJoint(*body.rightLittleTip, nodes[14]); // DIP +} + +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가 있음) + // ✅ OPTIMIZATION: Use optional reference instead of shared_ptr + const std::optional& wristJoint = + (handSide == eGloveHandSide::Left) ? body.leftHand : body.rightHand; + + if (wristJoint.has_value()) { + 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_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 (미러링) + } + } + + // 정규화 + 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(); + + // ✅ OPTIMIZATION: Use const_cast to get mutable pointer from const optional (no heap allocation) + if (handSide == eGloveHandSide::Left) { + // 왼손 손가락들 (5개 손가락) + allFingers.resize(5); + + // 엄지 + allFingers[0].push_back(const_cast(&*body.leftThumbProximal)); + allFingers[0].push_back(const_cast(&*body.leftThumbMedial)); + allFingers[0].push_back(const_cast(&*body.leftThumbDistal)); + + // 검지 + allFingers[1].push_back(const_cast(&*body.leftIndexProximal)); + allFingers[1].push_back(const_cast(&*body.leftIndexMedial)); + allFingers[1].push_back(const_cast(&*body.leftIndexDistal)); + + // 중지 + allFingers[2].push_back(const_cast(&*body.leftMiddleProximal)); + allFingers[2].push_back(const_cast(&*body.leftMiddleMedial)); + allFingers[2].push_back(const_cast(&*body.leftMiddleDistal)); + + // 약지 + allFingers[3].push_back(const_cast(&*body.leftRingProximal)); + allFingers[3].push_back(const_cast(&*body.leftRingMedial)); + allFingers[3].push_back(const_cast(&*body.leftRingDistal)); + + // 새끼 + allFingers[4].push_back(const_cast(&*body.leftLittleProximal)); + allFingers[4].push_back(const_cast(&*body.leftLittleMedial)); + allFingers[4].push_back(const_cast(&*body.leftLittleDistal)); + + } else { + // 오른손 손가락들 (5개 손가락) + allFingers.resize(5); + + // 엄지 + allFingers[0].push_back(const_cast(&*body.rightThumbProximal)); + allFingers[0].push_back(const_cast(&*body.rightThumbMedial)); + allFingers[0].push_back(const_cast(&*body.rightThumbDistal)); + + // 검지 + allFingers[1].push_back(const_cast(&*body.rightIndexProximal)); + allFingers[1].push_back(const_cast(&*body.rightIndexMedial)); + allFingers[1].push_back(const_cast(&*body.rightIndexDistal)); + + // 중지 + allFingers[2].push_back(const_cast(&*body.rightMiddleProximal)); + allFingers[2].push_back(const_cast(&*body.rightMiddleMedial)); + allFingers[2].push_back(const_cast(&*body.rightMiddleDistal)); + + // 약지 + allFingers[3].push_back(const_cast(&*body.rightRingProximal)); + allFingers[3].push_back(const_cast(&*body.rightRingMedial)); + allFingers[3].push_back(const_cast(&*body.rightRingDistal)); + + // 새끼 + allFingers[4].push_back(const_cast(&*body.rightLittleProximal)); + allFingers[4].push_back(const_cast(&*body.rightLittleMedial)); + allFingers[4].push_back(const_cast(&*body.rightLittleDistal)); + } + + 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_Fixed/ExampleGloveAdapterSingleton.h b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/ExampleGloveAdapterSingleton.h new file mode 100644 index 00000000..67029d97 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/ExampleGloveAdapterSingleton.h @@ -0,0 +1,348 @@ +////====================================================================================================== +//// Copyright 2023, NaturalPoint Inc. +////====================================================================================================== +/** + * ExampleGloveAdapterSingleton class is an adapter class provided to demonstrate how communication between plugin device and + * the glove SDK can be managed. A singleton instance of this class manages interaction between plugin device and the glove + * device SDK. The adapter instance also stores a keeps the latest data and device info map that stores the currently detected device serials + * and the latest frame data so that corresponding glove device can poll from it. Please note that this is provided only as an example, and + * there could be other ways to set this up. + */ + + +#pragma once +#include +#include +#include +#include +#include +#include + +// OptiTrack Peripheral Device API +#include "AnalogChannelDescriptor.h" +#include "GloveDataFormat.h" +#include "HardwareSimulator.h" + +// Rokoko Integration +#include "RokokoData.h" +#include "RokokoUDPReceiver.h" +#include "RokokoDataParser.h" +#include "RokokoDataConverter.h" + +namespace OptiTrackPluginDevices +{ + namespace ExampleDevice + { + class ExampleGloveAdapterSingleton; + + static ExampleGloveAdapterSingleton* s_Instance = nullptr; + static std::unique_ptr gGloveAdapter; + + + /** + * This is an example glove adapter (singleton) class provided to demonstrate how glove data can be populated. + * This adapter class is reponsible for all interaction with the device SDK, and populating the glove data. + * Glove data for multiple glove devices should get aggregated in here. + */ + class ExampleGloveAdapterSingleton + { + friend class ExampleGloveDevice; + + public: + ExampleGloveAdapterSingleton(AnalogSystem::IDeviceManager* pDeviceManager); + ~ExampleGloveAdapterSingleton(); + + // Singleton design pattern + ExampleGloveAdapterSingleton(const ExampleGloveAdapterSingleton&) = delete; // Should not be cloneable + ExampleGloveAdapterSingleton& operator=(const ExampleGloveAdapterSingleton& other) = delete; // Shounot be assignable + + + /** + * Should call the shutdown from the plugin SDK. In this case, shutsdown the simulator + */ + bool ClientShutdown(); + + /** + * Detection thread for connecting to the glove server. + */ + void DoDetectionThread(); + std::thread mDetectionThread; + unsigned int mConnectionAttemptCount = 0; + const unsigned int kMaxConnectionAttempt = 10; + + + /** + * Connect to the glove host. + */ + bool ConnectToHost(); + + /** + * Connect to Rokoko Studio via UDP. + */ + bool ConnectToRokoko(); + + /** + * Disconnect from Rokoko Studio. + */ + void DisconnectFromRokoko(); + + private: + /** + * Returns whether detector is connected to Glove Host. + */ + bool IsConnected(); + + /** + * Saves the latest data to the map + */ + void SetLatestData(const sGloveDeviceData& gloveFingerData); + + /** + * Pointer to device manager for additional operations + */ + void SetDeviceManager(AnalogSystem::IDeviceManager* pDeviceManager); + + /** + * Saves the latest glove data to the map + */ + void SetLatestDeviceInfo(const sGloveDeviceBaseInfo& deviceInfo); + + + /** + * Gets the latest data for device with given unique serial + */ + bool GetLatestData(const std::uint64_t mDeviceSerial, sGloveDeviceData& gloveFingerData); + + /** + * Creates new device by instantiating the factory and transferring it to Motive + */ + void CreateNewGloveDevice(sGloveDeviceBaseInfo& deviceInfo); + + /** + * Prints error into Motive. + */ + void NotifyConnectionFail(); + + bool bIsConnected = false; + bool bIsDetecting = true; + int mCurrentDeviceIndex = 0; + std::string mServerAddress = ""; + + + /** + * [Glove SDK Placeholder] + * Example data map for storing aggregated device data. + */ + uint16_t mDeviceCount = 0; + std::vector mDetectedDevices; + std::unordered_map mLatestGloveData; + std::unordered_map mLatestDeviceInfo; + + // T-포즈 캘리브레이션 데이터 (로컬 로테이션 계산을 위해 필요) + std::unordered_map> mTPoseReferences; // 장치별 T-포즈 기준값 + std::unordered_map mTPoseCalibrated; // 장치별 캘리브레이션 상태 + + /** + * Rokoko integration members + */ + std::unique_ptr mRokokoReceiver; + bool bIsRokokoConnected = false; + RokokoData::LiveFrame_v4 mLastRokokoFrame; + + /** + * [Legacy Glove SDK Placeholder - Commented Out] + * The following methods were sample callback functions for simulated hardware. + * Now replaced by Rokoko UDP communication. + */ + // bool bIsSimulating; + void StartSimulatedHardware(int deviceCount); // Legacy support + static void RegisterSDKCallbacks(); // Legacy support + static void OnDataCallback(std::vector& gloveFingerData); // Legacy support + static void OnDeviceInfoCallback(std::vector& newGloveInfo); // Legacy support + static sGloveDeviceBaseInfo ConvertDeviceInfoFormat(SimulatedPluginDevices::SimulatedDeviceInfo& glove); // Legacy support + static sGloveDeviceData ConvertDataFormat(const SimulatedPluginDevices::SimulatedGloveFrameData& glove); // Legacy support + + /** + * Rokoko UDP data callback function. + */ + void OnRokokoDataReceived(const std::vector& data, const std::string& senderIP); + + /** + * Process received Rokoko data and convert to OptiTrack format. + */ + void ProcessRokokoData(const std::vector& data); + + /** + * Update device information (battery, signal strength, etc.) + */ + void UpdateDeviceInfo(uint64_t deviceId); + + // Dynamic Device Detection and Management + void DetectAndCreateRokokoDevices(); + void DetectActorsFromRokokoData(const RokokoData::LiveFrame_v4& rokokoFrame); + void ProcessMultipleDeviceData(const RokokoData::LiveFrame_v4& rokokoFrame); + + /** + * Process hand data for a specific device + */ + bool ProcessHandData(const RokokoData::Body& body, uint64_t deviceId, eGloveHandSide handSide); + + /** + * Map left hand joints from Rokoko data + */ + void MapLeftHandJoints(const RokokoData::Body& body, std::vector& nodes); + + /** + * Map right hand joints from Rokoko data + */ + void MapRightHandJoints(const RokokoData::Body& body, std::vector& nodes); + + /** + * 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: + + /** + * [Legacy Glove SDK Simulator - Commented Out] + * Instance of simulator (now replaced by Rokoko integration) + */ + // SimulatedPluginDevices::HardwareSimulator* mGloveSimulator; + + /** + * [Glove SDK Placeholder] + * Example glove data mutex + */ + std::recursive_mutex* mGloveDataMutex; + + /** + * Pointer to device manager in Motive for reporting error messages. + */ + AnalogSystem::IDeviceManager* mDeviceManager; + }; + } + +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/ExampleGloveData.csv b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/ExampleGloveData.csv new file mode 100644 index 00000000..21e484c7 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/ExampleGloveData.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:816498d40aca6a7060d48313bd1946b20553894715095670b14192d4639bdce5 +size 549783 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/ExampleGloveDevice.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/ExampleGloveDevice.cpp new file mode 100644 index 00000000..dbb24ec1 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/ExampleGloveDevice.cpp @@ -0,0 +1,311 @@ +//====================================================================================================== +// Copyright 2022, NaturalPoint Inc. +//====================================================================================================== + +#pragma once +#include "ExampleGloveDevice.h" +#include "ExampleGloveAdapterSingleton.h" + +// OptiTrack Peripheral Device API +#include "AnalogChannelDescriptor.h" +#include "IDeviceManager.h" +using namespace AnalogSystem; +using namespace OptiTrackPluginDevices; +using namespace GloveDeviceProperties; +using namespace ExampleDevice; + + +/////////////////////////////////////////////////////////////////////////////// +// +// Device Helper: Initialization and Shutdown +// +void OptiTrackPluginDevices::ExampleDevice::ExampleGlove_EnumerateDeviceFactories(IDeviceManager* pDeviceManager, std::list>& dfs) +{ + // Start server detection + if (gGloveAdapter == nullptr) { + gGloveAdapter = std::make_unique(pDeviceManager); + } +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGlove_Shutdown() +{ + if (gGloveAdapter != nullptr) + { + gGloveAdapter->ClientShutdown(); + gGloveAdapter.reset(); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// Example Glove Device Factory +// +const char* OptiTrackPluginDevices::ExampleDevice::ExampleGloveDeviceFactory::Name() const +{ + return "ExampleGloveDevice"; +} + +std::unique_ptr OptiTrackPluginDevices::ExampleDevice::ExampleGloveDeviceFactory::Create() const +{ + ExampleGloveDevice* pDevice = new ExampleGloveDevice(mDeviceSerial, mDeviceInfo); + SetCommonGloveDeviceProperties(pDevice); + SetQuaternionDataChannels(pDevice); + + // Transfer ownership to host + std::unique_ptr ptrDevice(pDevice); + return ptrDevice; +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// Example Glove Device +// +OptiTrackPluginDevices::ExampleDevice::ExampleGloveDevice::ExampleGloveDevice(uint32_t serial, sGloveDeviceBaseInfo deviceInfo) +{ + mDeviceInfo = deviceInfo; + mDeviceSerial = deviceInfo.gloveId; + bIsEnabled = true; +} + + +bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveDevice::Configure() +{ + bool success = Deconfigure(); + if (!success) + return false; + + // update device's buffer allocation based on current channel and data type configuration + success = cPluginDevice::Configure(); + if (!success) + return false; + + bIsConfigured = success; + DeviceManager()->MessageToHost(this, "", MessageType_RequestRestart); + return success; +} + +bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveDevice::Deconfigure() +{ + bool success = cPluginDevice::Deconfigure(); + + return success; +} + +bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveDevice::StartCapture() +{ + bool success = cPluginDevice::StartCapture(); + + bIsCollecting = true; + mCollectionThread = CreateThread(nullptr, 0, CollectionThread, this, 0, nullptr); + if (mCollectionThread == nullptr) + { + return false; + bIsCollecting = false; + } + + return success; +} + +bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveDevice::StopCapture() +{ + bool success = cPluginDevice::StopCapture(); + + // REQUIRED: Stop collecting data. Terminate hardware device polling thread + bIsCollecting = false; + DWORD waitResult = WaitForSingleObject(mCollectionThread, 1000); + if (waitResult == WAIT_OBJECT_0) + { + CloseHandle(mCollectionThread); + mCollectionThread = NULL; + success = true; + } + else if (waitResult == WAIT_TIMEOUT) + { + BOOL result = TerminateThread(mCollectionThread, 0); + success = (result == TRUE); + } + else + { + success = false; + } + return success; +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveDevice::OnPropertyChanged(const char* propertyName) +{ + cPluginDevice::OnPropertyChanged(propertyName); + + if (strcmp(propertyName, kEnabledPropName) == 0) + { + // Update device enabled state + GetProperty(kEnabledPropName, bIsEnabled); + } + else if (strcmp(propertyName, kRatePropName) == 0) + { + // Update device capture rate + GetProperty(kRatePropName, mDeviceRateFPS); + mRequestedRateMS = (1.0f / mDeviceRateFPS) * 1000.0f; + } + else if (strcmp(propertyName, kDataStatePropName) == 0) + { + int deviceState = 0; + GetProperty(kDataStatePropName, deviceState); + if (deviceState != DeviceDataState::DeviceState_ReceivingData) + { + // if not receiving data, disable battery state and signal strength + mBatteryLevel = GloveDeviceProp_BatteryUninitialized; + SetProperty(GloveDeviceProp_Battery, mBatteryLevel); + mSignalStrength = GloveDeviceProp_SignalStrengthUnitialized; + SetProperty(GloveDeviceProp_SignalStrength, mSignalStrength); + } + } + else if (strcmp(propertyName, GloveDeviceProp_Solver) == 0) + { + // Route solver type to device user data for interpreting in pipeline + int solver = 0; + GetProperty(GloveDeviceProp_Solver, solver); + SetProperty(cPluginDeviceBase::kUserDataPropName, solver); + } +} + +unsigned long __stdcall OptiTrackPluginDevices::ExampleDevice::ExampleGloveDevice::CollectionThread(LPVOID Context) +{ + ExampleGloveDevice* pThis = static_cast(Context); + return pThis->DoCollectionThread(); +} + +unsigned long OptiTrackPluginDevices::ExampleDevice::ExampleGloveDevice::DoCollectionThread() +{ + /* Collection Thread - + Motive's 15 channel glove data channel format for finger node rotations: + 1 = Thumb MP 1 (w,x,y,z) + 2 = Thumb PIP 2 + 3 = Thumb DIP 3 + 4 = Index MP 1 + 5 = Index PIP 2 + 6 = Index DIP 3 + 7 = Middle MP 1 + 8 = Middle PIP 2 + 9 = Middle DIP 3 + 10 = Ring MP 1 + 11 = Ring PIP 2 + 12 = Ring DIP 3 + 13 = Pinky MP 1 + 14 = Pinky PIP 2 + 15 = Pinky DIP 3 + + Hand joint orientation respects right-handed coordinate system. + For left hand, +X is pointed towards the finger tip. + For right hand, +X is pointer towards the wrist or the body. + */ + + // timers used for frame and ui updates + std::chrono::high_resolution_clock::time_point frameTimerStart, frameTimerEnd; + std::chrono::high_resolution_clock::time_point uiTimerStart, uiTimerEnd; + std::chrono::milliseconds actualFrameDurationMS; + double adjustment = 0.0; + double durationDeltaMS = 0.0; + + + // Glove device parameters (rate / battery / signal) + GetProperty(kEnabledPropName, bIsEnabled); + GetProperty(GloveDeviceProp_Battery, mBatteryLevel); + GetProperty(GloveDeviceProp_SignalStrength, mSignalStrength); + GetProperty(kRatePropName, mDeviceRateFPS); + mRequestedRateMS = (1.0f / mDeviceRateFPS) * 1000.0f; + + // Initialize glove handedness + InitializeGloveProperty(); + + //glove channel data arrray to be copied + float gloveChannelData[kGloveAnalogChannelCount]; + + // Collection thread + uiTimerStart = std::chrono::high_resolution_clock::now(); + while (bIsCollecting) + { + frameTimerStart = std::chrono::high_resolution_clock::now(); + int deviceFrameID = this->FrameCounter(); + + // Skip disabled devices + bool isDeviceEnabled = false; + GetProperty(kEnabledPropName, isDeviceEnabled); + if (!isDeviceEnabled) continue; + + // Poll latest glove data from the adapter + sGloveDeviceData t_data; + bool isGloveDataAvailable = gGloveAdapter->GetLatestData(mDeviceInfo.gloveId, t_data); + + if (isGloveDataAvailable) + { + if (!bIsInitialized) { + InitializeGloveProperty(); + } + + // Update ui every 5 secs (too frequent can cause unnecessary slowdowns) + uiTimerEnd = std::chrono::high_resolution_clock::now(); + auto elapsedTime = std::chrono::duration_cast(uiTimerEnd - uiTimerStart); + if (elapsedTime.count() > 5) + { + UpdateGloveProperty(mDeviceInfo); + uiTimerStart = std::chrono::high_resolution_clock::now(); + } + + // Frame Begin: get frame from device buffer + AnalogFrame* pFrame = this->BeginFrameUpdate(deviceFrameID); + if (pFrame) + { + // fill in frame header + int flags = 0; + + // Iterate through each bone and populate channel data. + // Skip the first bone, hand base, as it's driven from rigid body transform instead. + for (int i = 0; i < 15; i++) + { + int nodeChannelIndex = i * 4; + gloveChannelData[nodeChannelIndex] = t_data.nodes.at(i).quat_w; //w + gloveChannelData[nodeChannelIndex + 1] = t_data.nodes.at(i).quat_x; //x + gloveChannelData[nodeChannelIndex + 2] = t_data.nodes.at(i).quat_y; //y + gloveChannelData[nodeChannelIndex + 3] = t_data.nodes.at(i).quat_z; //z + } + + pFrame->SetID(deviceFrameID); + pFrame->SetFlag(flags); + //pFrame->SetTimestamp((double)mLastGloveDataTimebstamp.time); + ::memcpy(pFrame->ChannelData(), &gloveChannelData[0], kGloveAnalogChannelCount * sizeof(float)); + EndFrameUpdate(); + this->SetFrameCounter(deviceFrameID + 1); + } + + + // End frame update. Sleep the thread for the remaining frame period. + std::this_thread::sleep_for(std::chrono::milliseconds((int)(mRequestedRateMS - adjustment))); + frameTimerEnd = std::chrono::high_resolution_clock::now(); + actualFrameDurationMS = std::chrono::duration_cast(frameTimerEnd - frameTimerStart); + durationDeltaMS = actualFrameDurationMS.count() - mRequestedRateMS; + + // estimating adjustment to prevent oscillation on sleep duration. + if (durationDeltaMS > 1.0) + adjustment = -1.0; + else if (durationDeltaMS > 2.0) + adjustment = -2.0; + else if (durationDeltaMS > 3.0) + adjustment = -3.0; + else if (durationDeltaMS < -3.0) + adjustment = -3.0; + else if (durationDeltaMS < -2.0) + adjustment = 2.0; + else if (durationDeltaMS < -1.0) + adjustment = 1.0; + else + adjustment = 0.0; + if (fabs(adjustment) > 1.0) + { + this->LogError(MessageType_StatusInfo, "[Example Device] Device timing resolution off by %3.1f ms (requested:%3.1f, actual:%3.1f)", durationDeltaMS, mRequestedRateMS, (double) actualFrameDurationMS.count()); + } + } + } + return 0; +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/ExampleGloveDevice.h b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/ExampleGloveDevice.h new file mode 100644 index 00000000..a7a6d3a7 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/ExampleGloveDevice.h @@ -0,0 +1,86 @@ +//====================================================================================================== +// Copyright 2023, NaturalPoint Inc. +//====================================================================================================== +/** + * This is an example glove device provided to demonstrate how a plugin device can be set up as a glove device + * in Motive. This class derives from GloveDeviceBase class which contains basic setups required by gloves. + */ + +#pragma once +#include +#include + +// OptiTrack Peripheral Device API +#include "dllcommon.h" +#include "GloveDeviceBase.h" +#include "GloveDataFormat.h" +#include "ExampleGloveAdapterSingleton.h" + +namespace OptiTrackPluginDevices +{ + namespace ExampleDevice{ + + class ExampleGloveDevice; + class ExampleGloveDeviceFactory; + + void ExampleGlove_EnumerateDeviceFactories(AnalogSystem::IDeviceManager* pDeviceManager, std::list>& dfs); + void ExampleGlove_Shutdown(); + + + /** + * For creating a device in Motive, a factory class must be created first. This example class inherits from the + * parent glove device factory where common glove functionalities and configurations are set. + */ + class ExampleGloveDeviceFactory : public OptiTrackPluginDevices::GloveDeviceFactoryBase + { + public: + ExampleGloveDeviceFactory(std::string deviceName, sGloveDeviceBaseInfo deviceInfo) : + GloveDeviceFactoryBase(deviceName.c_str(), deviceInfo.gloveId), mDeviceInfo(deviceInfo) + { + } + + /** + * Return device factory name + */ + virtual const char* Name() const override; + + /** + * The following method gets called by Motive. It creates and returns a new instance of device and transfers ownership to Motive. + */ + std::unique_ptr Create() const override; + + private: + sGloveDeviceBaseInfo mDeviceInfo; + }; + + + /** + * ExampleGloveDevice inherits from GloveDeviceBase and demonstrates how a glove plugin device can be created. + * Each device gets their own collection thread where they populate the channel data. This class is inherited from the parent + * GloveDeviceBase class where the basic glove setup is shown. + */ + class ExampleGloveDevice : public OptiTrackPluginDevices::GloveDeviceBase + { + public: + ExampleGloveDevice(uint32_t serial, sGloveDeviceBaseInfo deviceInfo); + virtual ~ExampleGloveDevice() = default; + + // IDevice implementation + virtual bool Configure(); + virtual bool Deconfigure(); + virtual bool StartCapture(); + virtual bool StopCapture(); + virtual void OnPropertyChanged(const char* propertyName); + + private: + + + /** + * Collection thread for populating glove analog data channels + */ + static unsigned long __stdcall CollectionThread(LPVOID Context); + unsigned long DoCollectionThread() override; + void* mCollectionThread = nullptr; + }; + } +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/FIXES_APPLIED.md b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/FIXES_APPLIED.md new file mode 100644 index 00000000..bf170c0a --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/FIXES_APPLIED.md @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8503243908fbe3016fa8d6fc07ef2ad791733e4a24473df6e44ed209a850e042 +size 6364 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/GloveDataFormat.h b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/GloveDataFormat.h new file mode 100644 index 00000000..193eb2a6 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/GloveDataFormat.h @@ -0,0 +1,84 @@ +//====================================================================================================== +// Copyright 2023, NaturalPoint Inc. +//====================================================================================================== +/** + * This includes common glove data formats referenced in Motive. + */ + + +#pragma once +#include +#include +#include +#include + +enum class eGloveHandSide : int +{ + Unknown = 0, + Left = 1, + Right = 2 +}; + + +struct sGloveDataTimestamp +{ + uint8_t hour = 0; + uint8_t minute = 0; + uint32_t seconds = 0; + uint32_t nanoseconds = 0; + double t = 0; + sGloveDataTimestamp(uint32_t s, uint32_t n) : seconds(s), nanoseconds(n) { t = double(seconds) + double(nanoseconds) * 0.000000001; } + sGloveDataTimestamp(double time) { t = time; } + sGloveDataTimestamp() : t(0.0) {} + + bool operator >(const sGloveDataTimestamp& x) + { + return this->t > x.t; + } + bool operator <(const sGloveDataTimestamp& x) + { + return this->t < x.t; + } + bool operator ==(const sGloveDataTimestamp& x) + { + return this->t == x.t; + } + + sGloveDataTimestamp operator -(const sGloveDataTimestamp& x) + { + return sGloveDataTimestamp(this->t - x.t); + } + + operator const double() { + return t; + } +}; + +struct sFingerNode { + int node_id; + float quat_x; + float quat_y; + float quat_z; + float quat_w; +}; + +struct sGloveDeviceData { + uint64_t gloveId = 0; + std::vector nodes; + sGloveDataTimestamp timestamp; +}; + +struct sGloveDeviceBaseInfo +{ + uint32_t gloveId = 0; // device serial id + int battery = 0 ; + int signalStrength= 0; + eGloveHandSide handSide = eGloveHandSide::Unknown; + std::string actorName = ""; // Actor 이름 (다중 장비 구분용) +}; + + + + + + diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/GloveDeviceBase.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/GloveDeviceBase.cpp new file mode 100644 index 00000000..9700f47f --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/GloveDeviceBase.cpp @@ -0,0 +1,258 @@ +//====================================================================================================== +// Copyright 2022, NaturalPoint Inc. +//====================================================================================================== +#include "dllcommon.h" +#include "GloveDeviceBase.h" + +// Optitrack Peripheral Device API +#include "AnalogChannelDescriptor.h" +#include "IDeviceManager.h" +#include "GloveDataFormat.h" +using namespace AnalogSystem; +using namespace OptiTrackPluginDevices; +using namespace GloveDeviceProperties; + +OptiTrackPluginDevices::GloveDeviceBase::GloveDeviceBase() +{ + this->SetCommonDeviceProperties(); + this->SetCommonGloveDeviceProperties(); +} + +void OptiTrackPluginDevices::GloveDeviceBase::SetCommonDeviceProperties() +{ + // Set appropriate default property values (and set advanced state) + ModifyProperty(cPluginDeviceBase::kModelPropName, true, true, false); + ModifyProperty(cPluginDeviceBase::kOrderPropName, true, false, true); + ModifyProperty(cPluginDeviceBase::kDisplayNamePropName, false, false, false); + + // Reveal glove related prop name + ModifyProperty(cPluginDeviceBase::kAssetPropName, false, false, false); // name of the paired skeleton asset + + // hide default properties that aren't relevant to glove device + ModifyProperty(cPluginDeviceBase::kSyncModePropName, true, true, true); + ModifyProperty(cPluginDeviceBase::kSyncStatusPropName, true, true, true); + ModifyProperty(cPluginDeviceBase::kUseExternalClockPropName, true, true, true); + ModifyProperty(cPluginDeviceBase::kMocapRateMultiplePropName, true, true, true); + ModifyProperty(cPluginDeviceBase::kScalePropName, true, true, true); + ModifyProperty(cPluginDeviceBase::kCalOffsetPropName, true, true, true); + ModifyProperty(cPluginDeviceBase::kCalSquareRotationPropName, true, true, true); + ModifyProperty(cPluginDeviceBase::kUserDataPropName, true, true, true); + ModifyProperty(cPluginDeviceBase::kZeroPropName, true, true, true); + ModifyProperty(cPluginDeviceBase::kGroupNamePropName, true, true, true); + + // hide advanced properties + ModifyProperty(cPluginDeviceBase::kConnectedPropName, true, false, true); + ModifyProperty(cPluginDeviceBase::kNamePropName, true, false, true); + ModifyProperty(cPluginDeviceBase::kChannelCountPropName, true, false, true); + ModifyProperty(cPluginDeviceBase::kAppRunModePropName, true, false, true); + ModifyProperty(cPluginDeviceBase::kMocapSyncFramePropName, true, false, true); + ModifyProperty(cPluginDeviceBase::kSyncFramePropName, true, false, true); + ModifyProperty(cPluginDeviceBase::kNeedDeviceSyncFramePropName, false, false, true); + ModifyProperty(cPluginDeviceBase::kNeedMocapSyncFramePropName, false, false, true); + ModifyProperty(cPluginDeviceBase::kUseDriftCorrectionPropName, false, false, true); + ModifyProperty(cPluginDeviceBase::kMasterSerialPropName, true, false, true); + ModifyProperty(cPluginDeviceBase::kDriftCorrectionPropName, true, false , true); + SetProperty(cPluginDeviceBase::kEnabledPropName, true); + SetProperty(cPluginDeviceBase::kConnectedPropName, true); + SetProperty(cPluginDeviceBase::kMocapRateMultiplePropName, 1); +} + +void OptiTrackPluginDevices::GloveDeviceBase::SetCommonGloveDeviceProperties() +{ + // Add glove related properties + AddProperty(GloveDeviceProperties::GloveDeviceProp_HandSide, GloveHandSide, kHandSideCount, 0, "Settings", false); + AddProperty(GloveDeviceProperties::GloveDeviceProp_Battery, GloveDeviceProp_BatteryUninitialized, "Settings", false); + AddProperty(GloveDeviceProperties::GloveDeviceProp_SignalStrength, GloveDeviceProp_SignalStrengthUnitialized, "Settings", false); + //AddProperty(GloveDeviceProperties::GloveDeviceProp_Reconnect, false, "Settings", false); + + // Modify property visibility + ModifyProperty(GloveDeviceProperties::GloveDeviceProp_SignalStrength, true, false, false, false); + ModifyProperty(GloveDeviceProperties::GloveDeviceProp_HandSide, true, false, false); + ModifyProperty(GloveDeviceProperties::GloveDeviceProp_Battery, true, false, false, false); +} + +/////////////////////////////////////////////////////////////////////////////// +// +// Setter: Glove Device +// +void OptiTrackPluginDevices::GloveDeviceBase::SetDeviceRate(int rate) +{ + mDeviceRateFPS = rate; + mRequestedRateMS = (1.0f / mDeviceRateFPS) * 1000.0f; + return; +} + +void OptiTrackPluginDevices::GloveDeviceBase::SetHandSide(eGloveHandSide side) +{ + mHandSide = side; + return; +} + +void OptiTrackPluginDevices::GloveDeviceBase::SetEnabled(bool enabled) +{ + bIsEnabled = enabled; +} + +void OptiTrackPluginDevices::GloveDeviceBase::SetCollecting(bool collecting) +{ + bIsCollecting = collecting; +} + +bool OptiTrackPluginDevices::GloveDeviceBase::SetDeviceSerial(std::string serial) +{ + mDeviceSerial = serial; + // Set device serial property + return false; +} + +bool OptiTrackPluginDevices::GloveDeviceBase::SetDeviceData(sGloveDeviceData data) +{ + mLastGloveData = data; + return true; +} + +bool OptiTrackPluginDevices::GloveDeviceBase::SetBatteryLevel(int level) +{ + if (SetProperty(GloveDeviceProperties::GloveDeviceProp_Battery, (int)level)) + { + mBatteryLevel = level; + return true; + } + return false; +} + +bool OptiTrackPluginDevices::GloveDeviceBase::SetSignalStrength(int signal) +{ + if (SetProperty(GloveDeviceProperties::GloveDeviceProp_SignalStrength, (int)signal)) + { + mSignalStrength = signal; + return true; + } + return false; +} + +void OptiTrackPluginDevices::GloveDeviceBase::InitializeGloveProperty() +{ + if (mDeviceInfo.handSide == eGloveHandSide::Left) + { + SetProperty(GloveDeviceProperties::GloveDeviceProp_HandSide, 1); + SetProperty(cPluginDeviceBase::kOrderPropName, 1); + bIsInitialized = true; + } + else if (mDeviceInfo.handSide == eGloveHandSide::Right) + { + SetProperty(GloveDeviceProperties::GloveDeviceProp_HandSide, 2); + SetProperty(cPluginDeviceBase::kOrderPropName, 2); + bIsInitialized = true; + } + return; +} + + +void OptiTrackPluginDevices::GloveDeviceBase::UpdateGloveProperty(const sGloveDeviceBaseInfo& deviceInfo) +{ + // update glove property when the device is receiving data. + int deviceState = 0; + GetProperty(kDataStatePropName, deviceState); + if (deviceState == DeviceDataState::DeviceState_ReceivingData) + { + if (mSignalStrength != 1) + { + mSignalStrength = deviceInfo.signalStrength; + double signal = fabs(mSignalStrength); + SetProperty(GloveDeviceProp_SignalStrength, (int)signal); + } + SetProperty(GloveDeviceProp_SignalStrength, -100); + + if (mBatteryLevel != deviceInfo.battery) + { + mBatteryLevel = (int)(deviceInfo.battery); //Battery level percentage. + SetProperty(GloveDeviceProp_Battery, (int)mBatteryLevel); + } + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// Glove Device Factory +// +void OptiTrackPluginDevices::GloveDeviceFactoryBase::SetCommonGloveDeviceProperties(GloveDeviceBase* pDevice) const +{ + // REQUIRED: Set device name/model/serial + pDevice->SetProperty(cPluginDeviceBase::kNamePropName, (char*)DeviceName()); + pDevice->SetProperty(cPluginDeviceBase::kDisplayNamePropName, (char*)DeviceName()); + pDevice->SetProperty(cPluginDeviceBase::kModelPropName, "Glove Model"); // model + char mDeviceSerial[MAX_PATH]; + sprintf_s(mDeviceSerial, "%s-serial", DeviceName()); + pDevice->SetProperty(cPluginDeviceBase::kSerialPropName, mDeviceSerial); // device serial (must be unique!) + pDevice->SetProperty(cPluginDeviceBase::kDeviceTypePropName, (long)DeviceType_Glove); // set device type as glove + pDevice->SetProperty(cPluginDeviceBase::kRatePropName, 120.0); // glove sampling rate + pDevice->SetProperty(cPluginDeviceBase::kUseDriftCorrectionPropName, true); // drift correction to fetch most recent data. + pDevice->SetProperty(cPluginDeviceBase::kOrderPropName, (int) eGloveHandSide::Unknown); // device order: (0 = uninitialized, 1=left, 2=right) +} + + +void OptiTrackPluginDevices::GloveDeviceFactoryBase::SetDeviceIndex(int t_index) +{ + mDeviceIndex = t_index; + return; +} + +void OptiTrackPluginDevices::GloveDeviceFactoryBase::SetQuaternionDataChannels(GloveDeviceBase* pDevice) const +{ + // Set glove data channels: + // 5 fingers * 3 joints per finger * 4 floats per quat (x/y/z/w) = 60 channels + int channelIndex; + int gloveAnalogChannelCount = 60; + char szChannelNames[MAX_PATH]; + + // add channels + for (int i = 0; i < gloveAnalogChannelCount; i++) + { + int finger = i / 12; // 12 channels per finger + switch (finger) + { + case 0: sprintf_s(szChannelNames, "T"); break; + case 1: sprintf_s(szChannelNames, "I"); break; + case 2: sprintf_s(szChannelNames, "M"); break; + case 3: sprintf_s(szChannelNames, "R"); break; + case 4: sprintf_s(szChannelNames, "P"); break; + } + + int joint = (i / 4) % 3; // 3 joints per finger + switch (joint) + { + case 0: sprintf_s(szChannelNames, "%s-MCP", szChannelNames); break; + case 1: sprintf_s(szChannelNames, "%s-PIP", szChannelNames); break; + case 2: sprintf_s(szChannelNames, "%s-DIP", szChannelNames); break; + } + + int axis = i % 4; // 4 floats per joint + switch (axis) + { + case 0: sprintf_s(szChannelNames, "%s W", szChannelNames); break; + case 1: sprintf_s(szChannelNames, "%s X", szChannelNames); break; + case 2: sprintf_s(szChannelNames, "%s Y", szChannelNames); break; + case 3: sprintf_s(szChannelNames, "%s Z", szChannelNames); break; + } + channelIndex = pDevice->AddChannelDescriptor(szChannelNames, ChannelType_Float); + + } + + // enable all channels by default + for (int i = 0; i <= channelIndex; i++) + { + // data channel enabled by default + pDevice->ChannelDescriptor(i)->SetProperty(ChannelProp_Enabled, true); + + // hide unused channel properties + pDevice->ChannelDescriptor(i)->ModifyProperty(ChannelProp_Units, true, true); + pDevice->ChannelDescriptor(i)->ModifyProperty(ChannelProp_Name, true, true); + pDevice->ChannelDescriptor(i)->ModifyProperty(ChannelProp_MinVoltage, true, true); + pDevice->ChannelDescriptor(i)->ModifyProperty(ChannelProp_MaxVoltage, true, true); + pDevice->ChannelDescriptor(i)->ModifyProperty(ChannelProp_TerminalName, true, true); + pDevice->ChannelDescriptor(i)->ModifyProperty(ChannelProp_TerminalType, true, true); + pDevice->ChannelDescriptor(i)->ModifyProperty(ChannelProp_MaxVoltage, true, true); + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/GloveDeviceBase.h b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/GloveDeviceBase.h new file mode 100644 index 00000000..cac502cc --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/GloveDeviceBase.h @@ -0,0 +1,180 @@ +//====================================================================================================== +// Copyright 2023, NaturalPoint Inc. +//====================================================================================================== +/** + * GloveDeviceBase/GloveDeviceFactory class extends the cPluginDeviceBase and configures data channels and device properties required + * by a glove device in Motive. The purpose of this class is to abstract out setups needed for creating glove device as demonstrated + * in the ExampleGloveDevice. When developing a glove plugin to animate fingers in Motive, the following class can be inherited if needed. + */ + +#include +#include +#include +#include + +// OptiTrack Peripheral Device API +#include "PluginDevice.h" +#include "PluginDeviceFactory.h" +#include "GloveDataFormat.h" + +namespace OptiTrackPluginDevices +{ + class GloveDeviceBase; + class GloveDeviceFactoryBase; + + /** + * Common glove device properties used in Motive. + */ + namespace GloveDeviceProperties + { + static const int kHandSideCount = 3; + static const char* GloveHandSide[kHandSideCount] = + { + "Uninitialized", + "Left", + "Right" + }; + // Glove type needs to be set at the device level (0 = uninitialized, 1 = left, 2 = right) + static const char* GloveDeviceProp_HandSide = "Hand Side"; + static const char* GloveDeviceProp_Battery = "Battery"; + static const char* GloveDeviceProp_SignalStrength = "Signal Strength"; + static const char* GloveDeviceProp_ServerAddress = "Server Address"; + static const char* GloveDeviceProp_Reconnect = "Reconnect"; + static const char* GloveDeviceProp_Solver = "Glove Solver"; + static const int GloveDeviceProp_BatteryUninitialized = -1; + static const int GloveDeviceProp_SignalStrengthUnitialized = -1; + static const int kGloveAnalogChannelCount = 60; + } + + /** + * GloveDeviceFactory class demonstrates how plugin device factory can be set up for creating a glove device in Motive. + * To create a device in Motive, an instance of PLuginDeviceFactory must be created per each device, and then, its ownership must be + * transferred to Motive by using Create method. The GloveDeviceFactory inherits from PLuginDeviceFactory and demonstrates common + * glove properties and data channels can be configured; which is demonstrated in this base class. + */ + class GloveDeviceFactoryBase : public AnalogSystem::PluginDeviceFactory + { + public: + GloveDeviceFactoryBase(std::string deviceName, uint32_t serial) : + AnalogSystem::PluginDeviceFactory(deviceName.c_str()), mDeviceSerial(serial) {} + + uint32_t mDeviceSerial = -1; + int mDeviceIndex = 0; + + protected: + void SetDeviceIndex(int t_index); + + /** + * Sets up quaternion data channels for delivering local rotation of the finger nodes. + * Motive skeleton's hand consists of total 15 finger nodes, 3 per each finger. + * These data channels will get populated by the collection thread running on each device. + * Quaternion values are expected, resulting in total 60 float channels (15 finger nodes * 4 quat floats / node). + * Local rotation data is expected, and both hand data expects right-handed coordinate system with +x axis + * pointing towards the finger tip for left hand and towards the wrist/body for right hand. + */ + void SetQuaternionDataChannels(GloveDeviceBase* pDevice) const; + + /** + * Set Common Glove device properties + */ + void SetCommonGloveDeviceProperties(GloveDeviceBase* pDevice) const; + }; + + /** + * cGloveDeviceBase class is an example class which glove devices could inherit from. + * This class includes basic setups and configurations for glove devices in Motive. + */ + class GloveDeviceBase : public AnalogSystem::cPluginDevice + { + friend class GloveDeviceFactoryBase; + + public: + GloveDeviceBase(); + ~GloveDeviceBase() = default; + + void SetCommonDeviceProperties(); + void SetCommonGloveDeviceProperties(); + + protected: + // Device status + bool bIsEnabled = false; + bool bIsCollecting = false; + bool bIsInitialized = false; + bool bIsConfigured = false; + + // Device info + sGloveDeviceBaseInfo mGloveInfo; + std::string mDeviceSerial = ""; + eGloveHandSide mHandSide = eGloveHandSide::Unknown; + int mBatteryLevel = 0; + int mSignalStrength = 0; + double mDeviceRateFPS = 0; + double mRequestedRateMS = 0.0; + + // Glove data + sGloveDeviceData mLastGloveData; + + // Collection thread must be created on the device class. Defined in ExampleGloveDevice class. + virtual unsigned long DoCollectionThread() = 0; + + /** + * Configure device rate + */ + void SetDeviceRate(int rate); + + /** + * Configure hand side + */ + void SetHandSide(eGloveHandSide side); + + /** + * Enable or disable device + */ + void SetEnabled(bool enabled); + + /** + * Set collection status + */ + void SetCollecting(bool collecting); + + /** + * Set device serial string each device must have unique serial + */ + bool SetDeviceSerial(std::string serial); + + /** + * Set device data + */ + bool SetDeviceData(sGloveDeviceData data); + + /** + * Set device battery level (1-100) + */ + bool SetBatteryLevel(int level); + + /** + * Sets the signal stregth (1-128) + */ + bool SetSignalStrength(int signal); + + + bool IsEnabled() { return bIsEnabled; }; + bool IsCollecting() { return bIsCollecting; }; + int GetSignalStrength() { return mSignalStrength; } + int GetBatteryLevel() { return mBatteryLevel; } + double GetDeviceRate() { return mDeviceRateFPS; } + sGloveDeviceData GetLastestData() { return mLastGloveData; }; + + /** + * Initialize the properties for the glove device + */ + void InitializeGloveProperty(); + + /** + * Updates the device properties. Gets called periodically within collection thread. + * Mainly updates the battery level and signal strength. + */ + void UpdateGloveProperty(const sGloveDeviceBaseInfo& deviceInfo); + sGloveDeviceBaseInfo mDeviceInfo; + }; +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/HardwareSimulator.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/HardwareSimulator.cpp new file mode 100644 index 00000000..d6add29c --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/HardwareSimulator.cpp @@ -0,0 +1,185 @@ +//====================================================================================================== +// Copyright 2023, NaturalPoint Inc. +//====================================================================================================== +#include "HardwareSimulator.h" +#include +#include +#include +#include + +using namespace std; + +SimulatedPluginDevices::HardwareSimulator::HardwareSimulator() +{} + +SimulatedPluginDevices::HardwareSimulator::~HardwareSimulator() +{ + bIsRunning = false; + if (mUpdateThread.joinable()) { + mUpdateThread.join(); + } +} + +void SimulatedPluginDevices::HardwareSimulator::StartData() +{ + bIsRunning = true; + mUpdateThread = std::thread(&HardwareSimulator::ReadDataFromCSV, this); +} + +void SimulatedPluginDevices::HardwareSimulator::Shutdown() +{ + bIsRunning = false; + if (mUpdateThread.joinable()) + { + mUpdateThread.join(); + } +} + +void SimulatedPluginDevices::HardwareSimulator::RegisterDeviceInfoCallback(std::function&)> device_info_callback) +{ + mOnDeviceInfoUpdate = device_info_callback; +} + +void SimulatedPluginDevices::HardwareSimulator::NotifyDataCallback() +{ + if (mOnFrameDataUpdate) + { + mOnFrameDataUpdate(mSimulatedFrameDataSet); + } +} + +void SimulatedPluginDevices::HardwareSimulator::NotifyInfoCallback() +{ + if (mOnDeviceInfoUpdate) + { + mOnDeviceInfoUpdate(mNewDeviceInfo); + mNewDeviceInfo.clear(); + } +} + +void SimulatedPluginDevices::HardwareSimulator::AddSimulatedGlove(int deviceId, int nodeCount, int handedness) +{ + SimulatedDeviceInfo device(deviceId, nodeCount, handedness); + mSimulatedDeviceInfoSet.push_back(device); + mNewDeviceInfo.push_back(device); + mSimulatedFrameDataSet.push_back(SimulatedGloveFrameData(device.mDeviceSerial, device.mNodeCount)); + NotifyInfoCallback(); +} + +void SimulatedPluginDevices::HardwareSimulator::RegisterFrameDataCallback(std::function&)> data_callback) +{ + mOnFrameDataUpdate = data_callback; +} + + +/** + * [Example] Simply read each frame data from the provided csv file and update the data callback. + */ +bool SimulatedPluginDevices::HardwareSimulator::ReadDataFromCSV() +{ + double mRequestedRateMS = (1.0f / mDataSampleRate) * 1000.0f; + std::string filename = GetExePath() + "\\devices\\ExampleGloveData.csv"; + + // Read the provided example CSV file (60 channels) + std::ifstream fin(filename); + if (!fin.is_open()) + { + // Failed to open the device + return false; + } + + bool isFirstLine = true; + std::vector field_names; + + // loop through lines of data + std::string lineRead; + while (bIsRunning) + { + std::vector frame_data; + std::vector jointData; + + if (fin.eof()) + { + //loop back to begining + fin.clear(); + fin.seekg(0, std::ios::beg); + isFirstLine = true; + } + + std::getline(fin, lineRead); + std::stringstream lineStream(lineRead); + + // Read comma-separated values + std::string val; + std::getline(lineStream, val, ','); // skip first column + while (getline(lineStream, val, ',')) + { + if (isFirstLine) + { + field_names.push_back(val); + } + else + { + try + { + float val_f = std::stof(val); + jointData.push_back(val_f); + + } + catch (const std::exception* e) + { + // error converting the value. Terminate + return false; + } + + if (jointData.size() == 4) + { + SimulatedFingerData finger; + // next finger joint + finger.quat_w = jointData.at(0); + finger.quat_x = jointData.at(1); + finger.quat_y = jointData.at(2); + finger.quat_z = jointData.at(3); + frame_data.push_back(finger); + jointData.clear(); + } + } + } + + if (isFirstLine) + isFirstLine = false; + else { + if (frame_data.size() != 0) + { + UpdateAllDevicesWithData(frame_data); + } + } + + // End of a line sleep + std::this_thread::sleep_for(std::chrono::milliseconds((int)mRequestedRateMS)); + } + return true; +} + +void SimulatedPluginDevices::HardwareSimulator::UpdateAllDevicesWithData(std::vector& data) +{ + mDataLock.lock(); + // Loop through list of data instances in the frame data vector and update all instances + for (auto& gloveDevice : mSimulatedFrameDataSet) + { + // Set all finger data + gloveDevice.gloveFingerData = data; + } + NotifyDataCallback(); + mDataLock.unlock(); +} + +std::string SimulatedPluginDevices::HardwareSimulator::GetExePath() +{ + std::string path; + char buffer[MAX_PATH]; + GetModuleFileNameA(NULL, buffer, MAX_PATH); + string::size_type pos = string(buffer).find_last_of("\\/"); + path = string(buffer).substr(0, pos)/*+"\\system.exe"*/; + return path; +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/HardwareSimulator.h b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/HardwareSimulator.h new file mode 100644 index 00000000..6a1deada --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/HardwareSimulator.h @@ -0,0 +1,111 @@ +////====================================================================================================== +//// Copyright 2023, NaturalPoint Inc. +////====================================================================================================== +/* +* SimulatedHardware class is used for demonstrating third-party glove SDK DLL to simulate a third-party hardware. +* For the purpose of the example glove device, the finger tracking data is read from the csv file. +*/ +#pragma once +#define DATA_SAMPLERATE 120 + +#include +#include +#include +#include +#include +#include + +namespace SimulatedPluginDevices { + + class HardwareSimulator; + + struct SimulatedFingerData + { + float quat_x = 0; + float quat_y = 0; + float quat_z = 0; + float quat_w = 0; + float pos_x = 0; + float pos_y = 0; + float pos_z = 0; + + SimulatedFingerData() {}; + SimulatedFingerData(float x, float y, float z, float w) : + quat_x(x), quat_y(y), quat_z(z), quat_w(w) {} + }; + + struct SimulatedGloveFrameData { + int mDeviceSerial = 0; + int mChannelCount = 0; + int kChannelPerNode = 4; + int mNodeCount = 0; + + std::vector gloveFingerData; // for glove data + + SimulatedGloveFrameData() {} + SimulatedGloveFrameData(int deviceSerial, int nodeCount) : + mDeviceSerial(deviceSerial), + mNodeCount(nodeCount) + { + mChannelCount = mNodeCount * kChannelPerNode; + gloveFingerData.resize(mNodeCount); + } + }; + + struct SimulatedDeviceInfo { + int mDeviceSerial = 0; // device serial id + int mBattery = 100; + int mSignalStrength = 100; + int mHandSide = 0; + int mNodeCount = 0; + int mChannelCount = 0; + int kChannelPerNode = 4; + + SimulatedDeviceInfo() {} + SimulatedDeviceInfo(int deviceSerial, int channelCount): + mDeviceSerial(deviceSerial), mChannelCount(channelCount) + {} + SimulatedDeviceInfo(int deviceSerial, int nodeCount, int handSide) : + mDeviceSerial(deviceSerial), mHandSide(handSide), mNodeCount(nodeCount) + { + mChannelCount = nodeCount * kChannelPerNode; + } + }; + + /// + /// Simple simulator for outputting sine wave channel data. + /// + class HardwareSimulator { + public: + HardwareSimulator(); + ~HardwareSimulator(); + + void AddSimulatedGlove(int deviceId, int nodeCount, int handedness); + void RegisterFrameDataCallback(std::function&)> data_callback); + void RegisterDeviceInfoCallback(std::function&)> device_info_callback); + void StartData(); + void Shutdown(); + + private: + bool bIsRunning = false; + std::thread mUpdateThread; + + void NotifyDataCallback(); + void NotifyInfoCallback(); + bool ReadDataFromCSV(); + static std::string GetExePath(); + + void UpdateAllDevicesWithData(std::vector& data); + + std::vector mSimulatedFrameDataSet; + std::vector mSimulatedDeviceInfoSet; + std::vector mNewDeviceInfo; + std::function&)> mOnFrameDataUpdate; + std::function&)> mOnDeviceInfoUpdate; + + const double mDataSampleRate = DATA_SAMPLERATE; + + protected: + std::recursive_mutex mDataLock; + }; +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/LZ4Wrapper.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/LZ4Wrapper.cpp new file mode 100644 index 00000000..302fe07c --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/LZ4Wrapper.cpp @@ -0,0 +1,161 @@ +//====================================================================================================== +// Copyright 2025, Rokoko Glove OptiTrack Integration +//====================================================================================================== + +#include "LZ4Wrapper.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 +{ + std::vector LZ4Wrapper::Decompress(const uint8_t* compressedData, int compressedSize) + { + try { + // 입력 데이터 검증 + if (!compressedData || compressedSize <= 0) { + return {}; + } + + // Unity LZ4 DLL 로드 + if (!LoadUnityLZ4()) { + return {}; + } + + // Unity 방식 정확히 구현 + // 1. Unity_LZ4_uncompressSize로 압축 해제된 크기 구하기 + int uncompressedSize = pUnity_LZ4_uncompressSize( + reinterpret_cast(compressedData), + compressedSize + ); + + if (uncompressedSize <= 0) { + return {}; + } + + // 크기 유효성 검사 + if (uncompressedSize > MAX_DECOMPRESSED_SIZE) { + return {}; + } + + // 2. Unity_LZ4_decompress로 압축 해제 + std::vector decompressed(uncompressedSize); + int result = pUnity_LZ4_decompress( + reinterpret_cast(compressedData), + compressedSize, + reinterpret_cast(decompressed.data()), + uncompressedSize + ); + + // Unity에서는 result != 0이면 실패 + if (result != 0) { + return {}; + } + + return decompressed; + + } catch (...) { + // 모든 예외 상황에서 안전하게 빈 벡터 반환 + return {}; + } + } + + bool LZ4Wrapper::IsValidLZ4Data(const uint8_t* data, int size) + { + if (!data || size < 4) { + return false; + } + + // 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 { + // Unity LZ4 DLL 로드 + if (!LoadUnityLZ4()) { + return -1; + } + + // Unity 방식으로 압축 해제된 크기 구하기 + int decompressedSize = pUnity_LZ4_uncompressSize( + reinterpret_cast(compressedData), + compressedSize + ); + return decompressedSize; + + } catch (...) { + return -1; + } + } + + bool LZ4Wrapper::ValidateDecompressedSize(int compressedSize, int decompressedSize) + { + // 압축 해제된 크기가 합리적인 범위인지 확인 + if (decompressedSize <= 0 || decompressedSize > MAX_DECOMPRESSED_SIZE) { + return false; + } + + // 압축률이 합리적인지 확인 (일반적으로 1:1 ~ 1:10) + if (decompressedSize < compressedSize || decompressedSize > compressedSize * 10) { + return false; + } + + return true; + } +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/LZ4Wrapper.h b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/LZ4Wrapper.h new file mode 100644 index 00000000..120230f3 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/LZ4Wrapper.h @@ -0,0 +1,62 @@ +//====================================================================================================== +// Copyright 2025, Rokoko Glove OptiTrack Integration +//====================================================================================================== +/** + * LZ4Wrapper class provides LZ4 decompression functionality for Rokoko glove data. + * This wrapper handles safe decompression and validation of compressed data. + */ + +#pragma once +#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 + { + public: + /** + * Decompresses LZ4 compressed data + * @param compressedData Pointer to compressed data + * @param compressedSize Size of compressed data + * @return Decompressed data as vector, empty if decompression fails + */ + static std::vector Decompress(const uint8_t* compressedData, int compressedSize); + + /** + * Validates if the data appears to be valid LZ4 compressed data + * @param data Pointer to data to validate + * @param size Size of data + * @return true if data appears to be valid LZ4 data + */ + static bool IsValidLZ4Data(const uint8_t* data, int size); + + /** + * Gets the decompressed size from LZ4 header + * @param compressedData Pointer to compressed data + * @param compressedSize Size of compressed data + * @return Decompressed size, -1 if invalid + */ + static int GetDecompressedSize(const uint8_t* compressedData, int compressedSize); + + private: + /** + * Validates decompressed size is reasonable + * @param compressedSize Size of compressed data + * @param decompressedSize Size of decompressed data + * @return true if sizes are reasonable + */ + static bool ValidateDecompressedSize(int compressedSize, int decompressedSize); + + /** + * Maximum reasonable decompressed size (10MB) + */ + static const int MAX_DECOMPRESSED_SIZE = 10 * 1024 * 1024; + }; +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoData.h b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoData.h new file mode 100644 index 00000000..1d904f46 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoData.h @@ -0,0 +1,148 @@ +//====================================================================================================== +// Copyright 2025, Rokoko Glove OptiTrack Integration +//====================================================================================================== +/** + * RokokoData.h defines the data structures for Rokoko Studio LiveFrame_v4 format. + * These structures match the JSON format used by Rokoko Unity scripts. + */ + +#pragma once +#include +#include +#include +#include + +namespace RokokoData +{ + // Vector3 structure for position data + struct Vector3Frame + { + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + }; + + // Vector4 structure for quaternion rotation data + struct Vector4Frame + { + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + float w = 1.0f; + }; + + // Actor joint frame containing position and rotation + struct ActorJointFrame + { + Vector3Frame position; + Vector4Frame rotation; + }; + + // Body structure containing full skeleton (Unity BodyFrame와 동일) + // ✅ OPTIMIZATION: Using std::optional instead of shared_ptr for stack allocation (zero heap allocations) + struct Body + { + // 전신 스켈레톤 데이터 (Unity BodyFrame과 동일) + std::optional hip; + std::optional spine; + std::optional chest; + std::optional neck; + std::optional head; + + std::optional leftShoulder; + std::optional leftUpperArm; + std::optional leftLowerArm; + std::optional leftHand; + + std::optional rightShoulder; + std::optional rightUpperArm; + std::optional rightLowerArm; + std::optional rightHand; + + // 다리 데이터 (필요시) + std::optional leftUpLeg; + std::optional leftLeg; + std::optional leftFoot; + std::optional leftToe; + std::optional leftToeEnd; + + std::optional rightUpLeg; + std::optional rightLeg; + std::optional rightFoot; + std::optional rightToe; + std::optional rightToeEnd; + + // Left hand finger joints + std::optional leftThumbProximal; + std::optional leftThumbMedial; + std::optional leftThumbDistal; + std::optional leftThumbTip; + + std::optional leftIndexProximal; + std::optional leftIndexMedial; + std::optional leftIndexDistal; + std::optional leftIndexTip; + + std::optional leftMiddleProximal; + std::optional leftMiddleMedial; + std::optional leftMiddleDistal; + std::optional leftMiddleTip; + + std::optional leftRingProximal; + std::optional leftRingMedial; + std::optional leftRingDistal; + std::optional leftRingTip; + + std::optional leftLittleProximal; + std::optional leftLittleMedial; + std::optional leftLittleDistal; + std::optional leftLittleTip; + + // Right hand finger joints (similar structure) + std::optional rightThumbProximal; + std::optional rightThumbMedial; + std::optional rightThumbDistal; + std::optional rightThumbTip; + + std::optional rightIndexProximal; + std::optional rightIndexMedial; + std::optional rightIndexDistal; + std::optional rightIndexTip; + + std::optional rightMiddleProximal; + std::optional rightMiddleMedial; + std::optional rightMiddleDistal; + std::optional rightMiddleTip; + + std::optional rightRingProximal; + std::optional rightRingMedial; + std::optional rightRingDistal; + std::optional rightRingTip; + + std::optional rightLittleProximal; + std::optional rightLittleMedial; + std::optional rightLittleDistal; + std::optional rightLittleTip; + }; + + // Actor data structure + struct ActorData + { + std::string id; + std::string name; + Body body; + }; + + // Scene structure containing actors + struct SceneFrame + { + std::vector actors; + }; + + // Main LiveFrame_v4 structure + struct LiveFrame_v4 + { + double timestamp = 0.0; + SceneFrame scene; + }; +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoDataConverter.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoDataConverter.cpp new file mode 100644 index 00000000..42a29055 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoDataConverter.cpp @@ -0,0 +1,214 @@ +//====================================================================================================== +// Copyright 2025, Rokoko Glove OptiTrack Integration +//====================================================================================================== + +#include "RokokoDataConverter.h" +#include "RokokoData.h" +#include +#include +#include + +namespace RokokoIntegration +{ + // 정적 상수는 헤더에서 inline으로 정의됨 + + sGloveDeviceData RokokoDataConverter::ConvertRokokoToOptiTrack(const RokokoData::LiveFrame_v4& rokokoFrame) + { + sGloveDeviceData optiTrackData; + + try { + // 기본 데이터 설정 + optiTrackData.gloveId = 1; // 기본 장갑 ID + optiTrackData.timestamp = 0.0; // 타임스탬프는 나중에 설정 + + // 15개 OptiTrack 관절 노드 초기화 + optiTrackData.nodes.resize(TOTAL_OPTITRACK_JOINTS); + + // 검증: actors가 존재하는지 확인 + if (rokokoFrame.scene.actors.empty()) { + optiTrackData.nodes.clear(); + return optiTrackData; + } + + const auto& actor = rokokoFrame.scene.actors[0]; + + // ✅ OPTIMIZATION: Use has_value() for optional instead of implicit bool conversion + // 손가락별 데이터 매핑 (왼손) + // 엄지손가락 (Thumb) + if (actor.body.leftThumbMedial.has_value()) ConvertJoint(*actor.body.leftThumbMedial, optiTrackData.nodes[0]); // MP + if (actor.body.leftThumbDistal.has_value()) ConvertJoint(*actor.body.leftThumbDistal, optiTrackData.nodes[1]); // PIP + if (actor.body.leftThumbTip.has_value()) ConvertJoint(*actor.body.leftThumbTip, optiTrackData.nodes[2]); // DIP + + // 검지손가락 (Index) + if (actor.body.leftIndexMedial.has_value()) ConvertJoint(*actor.body.leftIndexMedial, optiTrackData.nodes[3]); // MP + if (actor.body.leftIndexDistal.has_value()) ConvertJoint(*actor.body.leftIndexDistal, optiTrackData.nodes[4]); // PIP + if (actor.body.leftIndexTip.has_value()) ConvertJoint(*actor.body.leftIndexTip, optiTrackData.nodes[5]); // DIP + + // 중지손가락 (Middle) + if (actor.body.leftMiddleMedial.has_value()) ConvertJoint(*actor.body.leftMiddleMedial, optiTrackData.nodes[6]); // MP + if (actor.body.leftMiddleDistal.has_value()) ConvertJoint(*actor.body.leftMiddleDistal, optiTrackData.nodes[7]); // PIP + if (actor.body.leftMiddleTip.has_value()) ConvertJoint(*actor.body.leftMiddleTip, optiTrackData.nodes[8]); // DIP + + // 약지손가락 (Ring) + if (actor.body.leftRingMedial.has_value()) ConvertJoint(*actor.body.leftRingMedial, optiTrackData.nodes[9]); // MP + if (actor.body.leftRingDistal.has_value()) ConvertJoint(*actor.body.leftRingDistal, optiTrackData.nodes[10]); // PIP + if (actor.body.leftRingTip.has_value()) ConvertJoint(*actor.body.leftRingTip, optiTrackData.nodes[11]); // DIP + + // 새끼손가락 (Little) + if (actor.body.leftLittleMedial.has_value()) ConvertJoint(*actor.body.leftLittleMedial, optiTrackData.nodes[12]); // MP + if (actor.body.leftLittleDistal.has_value()) ConvertJoint(*actor.body.leftLittleDistal, optiTrackData.nodes[13]); // PIP + if (actor.body.leftLittleTip.has_value()) ConvertJoint(*actor.body.leftLittleTip, optiTrackData.nodes[14]); // DIP + + // 노드 ID 설정 + for (int i = 0; i < TOTAL_OPTITRACK_JOINTS; i++) { + optiTrackData.nodes[i].node_id = i; + } + + } catch (...) { + // 에러 발생 시 빈 데이터 반환 + optiTrackData.nodes.clear(); + } + + return optiTrackData; + } + + bool RokokoDataConverter::ConvertJoint(const RokokoData::ActorJointFrame& rokokoJoint, sFingerNode& optiTrackNode) + { + try { + // 쿼터니언 변환 + ConvertQuaternion(rokokoJoint.rotation, + optiTrackNode.quat_w, + optiTrackNode.quat_x, + optiTrackNode.quat_y, + optiTrackNode.quat_z); + + // 쿼터니언 정규화 + NormalizeQuaternion(optiTrackNode.quat_w, + optiTrackNode.quat_x, + optiTrackNode.quat_y, + optiTrackNode.quat_z); + + // 쿼터니언 유효성 검사 + if (!ValidateQuaternion(optiTrackNode.quat_w, + optiTrackNode.quat_x, + optiTrackNode.quat_y, + optiTrackNode.quat_z)) { + return false; + } + + return true; + + } catch (...) { + return false; + } + } + + bool RokokoDataConverter::ValidateOptiTrackData(const sGloveDeviceData& gloveData) + { + // 기본 검증 + if (gloveData.nodes.size() != TOTAL_OPTITRACK_JOINTS) { + return false; + } + + // 각 노드 검증 + for (const auto& node : gloveData.nodes) { + if (!ValidateQuaternion(node.quat_w, node.quat_x, node.quat_y, node.quat_z)) { + return false; + } + } + + return true; + } + + std::string RokokoDataConverter::GetMappingInfo() + { + std::ostringstream oss; + oss << "Rokoko to OptiTrack Finger Joint Mapping:\n"; + oss << "Total Rokoko joints: " << TOTAL_ROKOKO_JOINTS << " (4 per finger)\n"; + oss << "Total OptiTrack joints: " << TOTAL_OPTITRACK_JOINTS << " (3 per finger)\n"; + oss << "Mapping removes proximal joints and keeps:\n"; + oss << " - Medial (MP): Metacarpophalangeal\n"; + oss << " - Distal (PIP): Proximal Interphalangeal\n"; + oss << " - Tip (DIP): Distal Interphalangeal\n"; + + return oss.str(); + } + + void RokokoDataConverter::MapFingerJoints(const std::vector& rokokoFingers, + std::vector& optiTrackNodes) + { + if (rokokoFingers.size() != TOTAL_ROKOKO_JOINTS || optiTrackNodes.size() != TOTAL_OPTITRACK_JOINTS) { + return; + } + + // 매핑 테이블을 사용하여 관절 변환 + for (int i = 0; i < TOTAL_OPTITRACK_JOINTS; i++) { + int rokokoIndex = JOINT_MAPPING[i]; + if (rokokoIndex >= 0 && rokokoIndex < TOTAL_ROKOKO_JOINTS) { + ConvertJoint(rokokoFingers[rokokoIndex], optiTrackNodes[i]); + } + } + } + + void RokokoDataConverter::ConvertQuaternion(const RokokoData::Vector4Frame& rokokoQuat, + float& quat_w, float& quat_x, float& quat_y, float& quat_z) + { + // Rokoko 쿼터니언을 OptiTrack 형식으로 복사 + quat_w = rokokoQuat.w; + quat_x = rokokoQuat.x; + quat_y = rokokoQuat.y; + quat_z = rokokoQuat.z; + } + + void RokokoDataConverter::NormalizeQuaternion(float& w, float& x, float& y, float& z) + { + float length = std::sqrt(w * w + x * x + y * y + z * z); + + if (length > 0.0f) { + float invLength = 1.0f / length; + w *= invLength; + x *= invLength; + y *= invLength; + z *= invLength; + } else { + // 유효하지 않은 쿼터니언인 경우 기본값 설정 + w = 1.0f; + x = 0.0f; + y = 0.0f; + z = 0.0f; + } + } + + bool RokokoDataConverter::ValidateQuaternion(float w, float x, float y, float z) + { + // NaN 체크 + if (std::isnan(w) || std::isnan(x) || std::isnan(y) || std::isnan(z)) { + return false; + } + + // 무한대 체크 + if (std::isinf(w) || std::isinf(x) || std::isinf(y) || std::isinf(z)) { + return false; + } + + // 쿼터니언 길이 체크 (정규화된 경우 1.0에 가까워야 함) + float length = std::sqrt(w * w + x * x + y * y + z * z); + if (std::abs(length - 1.0f) > 0.1f) { + return false; + } + + return true; + } + + void RokokoDataConverter::ApplyHandCoordinateSystem(bool isLeftHand, float& x, float& y, float& z) + { + // 좌우손 좌표계 변환 + if (isLeftHand) { + // 왼손: +X축이 손가락 끝 방향 + // 변환 없음 (기본값) + } else { + // 오른손: +X축이 손목/몸 방향 + x = -x; // X축 반전 + } + } +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoDataConverter.h b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoDataConverter.h new file mode 100644 index 00000000..96072590 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoDataConverter.h @@ -0,0 +1,116 @@ +//====================================================================================================== +// Copyright 2025, Rokoko Glove OptiTrack Integration +//====================================================================================================== +/** + * RokokoDataConverter class provides data conversion functionality from Rokoko format to OptiTrack format. + * This converter handles the mapping of 20 Rokoko finger joints to 15 OptiTrack finger joints. + */ + +#pragma once +#include +#include +#include "GloveDataFormat.h" + +// Forward declarations +namespace RokokoData +{ + struct Vector3Frame; + struct Vector4Frame; + struct ActorJointFrame; + struct ActorData; + struct LiveFrame_v4; +} + +namespace RokokoIntegration +{ + class RokokoDataConverter + { + public: + /** + * Converts Rokoko LiveFrame_v4 data to OptiTrack glove data format + * @param rokokoFrame Input Rokoko frame data + * @return Converted OptiTrack glove data + */ + static sGloveDeviceData ConvertRokokoToOptiTrack(const RokokoData::LiveFrame_v4& rokokoFrame); + + /** + * Converts Rokoko finger joint data to OptiTrack finger node format + * @param rokokoJoint Input Rokoko joint data + * @param optiTrackNode Output OptiTrack node data + * @return true if conversion successful + */ + static bool ConvertJoint(const RokokoData::ActorJointFrame& rokokoJoint, sFingerNode& optiTrackNode); + + /** + * Validates converted OptiTrack data + * @param gloveData Data to validate + * @return true if data is valid + */ + static bool ValidateOptiTrackData(const sGloveDeviceData& gloveData); + + /** + * Gets the mapping information for debugging + * @return String containing mapping details + */ + static std::string GetMappingInfo(); + + private: + /** + * Maps Rokoko finger joints to OptiTrack format + * Maps 20 Rokoko joints (4 per finger) to 15 OptiTrack joints (3 per finger) + * Removes proximal joints and keeps medial, distal, tip + */ + static void MapFingerJoints(const std::vector& rokokoFingers, + std::vector& optiTrackNodes); + + /** + * Converts quaternion from Rokoko format to OptiTrack format + * @param rokokoQuat Input Rokoko quaternion + * @param optiTrackQuat Output OptiTrack quaternion + */ + static void ConvertQuaternion(const RokokoData::Vector4Frame& rokokoQuat, + float& quat_w, float& quat_x, float& quat_y, float& quat_z); + + /** + * Normalizes quaternion values + * @param w, x, y, z Quaternion components (modified in place) + */ + static void NormalizeQuaternion(float& w, float& x, float& y, float& z); + + /** + * Validates quaternion values + * @param w, x, y, z Quaternion components + * @return true if quaternion is valid + */ + static bool ValidateQuaternion(float w, float x, float y, float z); + + /** + * Applies coordinate system conversion for left/right hands + * @param isLeftHand true if left hand, false if right hand + * @param x, y, z Position coordinates (modified in place) + */ + static void ApplyHandCoordinateSystem(bool isLeftHand, float& x, float& y, float& z); + + // Finger mapping constants + static inline const int ROKOKO_JOINTS_PER_FINGER = 4; // Proximal, Medial, Distal, Tip + static inline const int OPTITRACK_JOINTS_PER_FINGER = 3; // MP, PIP, DIP + static inline const int TOTAL_FINGERS = 5; // Thumb, Index, Middle, Ring, Little + static inline const int TOTAL_ROKOKO_JOINTS = TOTAL_FINGERS * ROKOKO_JOINTS_PER_FINGER; // 20 + static inline const int TOTAL_OPTITRACK_JOINTS = TOTAL_FINGERS * OPTITRACK_JOINTS_PER_FINGER; // 15 + + // Joint mapping table: Rokoko index -> OptiTrack index + // Removes proximal joints (index 0, 4, 8, 12, 16) + static inline const int JOINT_MAPPING[15] = { + // Thumb: Medial(1), Distal(2), Tip(3) -> MP(0), PIP(1), DIP(2) + 1, 2, 3, + // Index: Medial(5), Distal(6), Tip(7) -> MP(3), PIP(4), DIP(5) + 5, 6, 7, + // Middle: Medial(9), Distal(10), Tip(11) -> MP(6), PIP(7), DIP(8) + 9, 10, 11, + // Ring: Medial(13), Distal(14), Tip(15) -> MP(9), PIP(10), DIP(11) + 13, 14, 15, + // Little: Medial(17), Distal(18), Tip(19) -> MP(12), PIP(13), DIP(14) + 17, 18, 19 + }; + }; +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoDataParser.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoDataParser.cpp new file mode 100644 index 00000000..0a738260 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoDataParser.cpp @@ -0,0 +1,339 @@ +//====================================================================================================== +// Copyright 2025, Rokoko Glove OptiTrack Integration +//====================================================================================================== + +#include "RokokoDataParser.h" +#include "RokokoData.h" +#include +#include +#include +#include + +namespace RokokoIntegration +{ + bool RokokoDataParser::ParseLiveFrame(const std::string& jsonString, RokokoData::LiveFrame_v4& frame) + { + try { + // nlohmann/json을 사용하여 JSON 파싱 + nlohmann::json j = nlohmann::json::parse(jsonString); + + // Unity JsonLiveSerializerV3.cs와 동일한 구조로 파싱 + + // 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 (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::optional& joint) + { + if (bodyJson.contains(jointName)) { + auto& jointJson = bodyJson[jointName]; + + // ✅ OPTIMIZATION: Direct assignment to optional (stack allocation, no heap) + RokokoData::ActorJointFrame tempJoint; + + // Position 파싱 + if (jointJson.contains("position")) { + auto& pos = jointJson["position"]; + tempJoint.position.x = pos.value("x", 0.0f); + tempJoint.position.y = pos.value("y", 0.0f); + tempJoint.position.z = pos.value("z", 0.0f); + } + + // Rotation 파싱 + if (jointJson.contains("rotation")) { + auto& rot = jointJson["rotation"]; + tempJoint.rotation.x = rot.value("x", 0.0f); + tempJoint.rotation.y = rot.value("y", 0.0f); + tempJoint.rotation.z = rot.value("z", 0.0f); + tempJoint.rotation.w = rot.value("w", 1.0f); + + // 쿼터니언 정규화 + NormalizeQuaternion(tempJoint.rotation.x, tempJoint.rotation.y, tempJoint.rotation.z, tempJoint.rotation.w); + } + + // Assign to optional + joint = tempJoint; + } + } + + bool RokokoDataParser::ValidateFrameData(const RokokoData::LiveFrame_v4& frame) + { + try { + // 기본 검증 + if (frame.scene.actors.empty()) { + return false; + } + + const auto& actor = frame.scene.actors[0]; + + // 손가락 데이터 검증 (왼손 엄지손가락만 체크) + // ✅ OPTIMIZATION: Use has_value() for optional instead of nullptr check + if (!actor.body.leftThumbMedial.has_value() || !actor.body.leftThumbDistal.has_value() || !actor.body.leftThumbTip.has_value()) { + return false; + } + + return true; + + } catch (...) { + return false; + } + } + + bool RokokoDataParser::ExtractFingerData(const RokokoData::LiveFrame_v4& frame, + std::vector& leftHandFingers, + std::vector& rightHandFingers) + { + try { + leftHandFingers.clear(); + rightHandFingers.clear(); + + if (frame.scene.actors.empty()) { + return false; + } + + const auto& actor = frame.scene.actors[0]; + + // ✅ OPTIMIZATION: Use has_value() for optional instead of implicit bool conversion + // 왼손 데이터 추출 (20개 관절) + if (actor.body.leftThumbProximal.has_value()) leftHandFingers.push_back(*actor.body.leftThumbProximal); + if (actor.body.leftThumbMedial.has_value()) leftHandFingers.push_back(*actor.body.leftThumbMedial); + if (actor.body.leftThumbDistal.has_value()) leftHandFingers.push_back(*actor.body.leftThumbDistal); + if (actor.body.leftThumbTip.has_value()) leftHandFingers.push_back(*actor.body.leftThumbTip); + + if (actor.body.leftIndexProximal.has_value()) leftHandFingers.push_back(*actor.body.leftIndexProximal); + if (actor.body.leftIndexMedial.has_value()) leftHandFingers.push_back(*actor.body.leftIndexMedial); + if (actor.body.leftIndexDistal.has_value()) leftHandFingers.push_back(*actor.body.leftIndexDistal); + if (actor.body.leftIndexTip.has_value()) leftHandFingers.push_back(*actor.body.leftIndexTip); + + if (actor.body.leftMiddleProximal.has_value()) leftHandFingers.push_back(*actor.body.leftMiddleProximal); + if (actor.body.leftMiddleMedial.has_value()) leftHandFingers.push_back(*actor.body.leftMiddleMedial); + if (actor.body.leftMiddleDistal.has_value()) leftHandFingers.push_back(*actor.body.leftMiddleDistal); + if (actor.body.leftMiddleTip.has_value()) leftHandFingers.push_back(*actor.body.leftMiddleTip); + + if (actor.body.leftRingProximal.has_value()) leftHandFingers.push_back(*actor.body.leftRingProximal); + if (actor.body.leftRingMedial.has_value()) leftHandFingers.push_back(*actor.body.leftRingMedial); + if (actor.body.leftRingDistal.has_value()) leftHandFingers.push_back(*actor.body.leftRingDistal); + if (actor.body.leftRingTip.has_value()) leftHandFingers.push_back(*actor.body.leftRingTip); + + if (actor.body.leftLittleProximal.has_value()) leftHandFingers.push_back(*actor.body.leftLittleProximal); + if (actor.body.leftLittleMedial.has_value()) leftHandFingers.push_back(*actor.body.leftLittleMedial); + if (actor.body.leftLittleDistal.has_value()) leftHandFingers.push_back(*actor.body.leftLittleDistal); + if (actor.body.leftLittleTip.has_value()) leftHandFingers.push_back(*actor.body.leftLittleTip); + + // 오른손 데이터 추출 (20개 관절) + if (actor.body.rightThumbProximal.has_value()) rightHandFingers.push_back(*actor.body.rightThumbProximal); + if (actor.body.rightThumbMedial.has_value()) rightHandFingers.push_back(*actor.body.rightThumbMedial); + if (actor.body.rightThumbDistal.has_value()) rightHandFingers.push_back(*actor.body.rightThumbDistal); + if (actor.body.rightThumbTip.has_value()) rightHandFingers.push_back(*actor.body.rightThumbTip); + + if (actor.body.rightIndexProximal.has_value()) rightHandFingers.push_back(*actor.body.rightIndexProximal); + if (actor.body.rightIndexMedial.has_value()) rightHandFingers.push_back(*actor.body.rightIndexMedial); + if (actor.body.rightIndexDistal.has_value()) rightHandFingers.push_back(*actor.body.rightIndexDistal); + if (actor.body.rightIndexTip.has_value()) rightHandFingers.push_back(*actor.body.rightIndexTip); + + if (actor.body.rightMiddleProximal.has_value()) rightHandFingers.push_back(*actor.body.rightMiddleProximal); + if (actor.body.rightMiddleMedial.has_value()) rightHandFingers.push_back(*actor.body.rightMiddleMedial); + if (actor.body.rightMiddleDistal.has_value()) rightHandFingers.push_back(*actor.body.rightMiddleDistal); + if (actor.body.rightMiddleTip.has_value()) rightHandFingers.push_back(*actor.body.rightMiddleTip); + + if (actor.body.rightRingProximal.has_value()) rightHandFingers.push_back(*actor.body.rightRingProximal); + if (actor.body.rightRingMedial.has_value()) rightHandFingers.push_back(*actor.body.rightRingMedial); + if (actor.body.rightRingDistal.has_value()) rightHandFingers.push_back(*actor.body.rightRingDistal); + if (actor.body.rightRingTip.has_value()) rightHandFingers.push_back(*actor.body.rightRingTip); + + if (actor.body.rightLittleProximal.has_value()) rightHandFingers.push_back(*actor.body.rightLittleProximal); + if (actor.body.rightLittleMedial.has_value()) rightHandFingers.push_back(*actor.body.rightLittleMedial); + if (actor.body.rightLittleDistal.has_value()) rightHandFingers.push_back(*actor.body.rightLittleDistal); + if (actor.body.rightLittleTip.has_value()) rightHandFingers.push_back(*actor.body.rightLittleTip); + + return true; + + } catch (...) { + return false; + } + } + + bool RokokoDataParser::ParseActorData(const void* actorJson, RokokoData::ActorData& actor) + { + // 현재는 기본 구현만 제공 + // 실제 JSON 파싱 라이브러리 사용 시 구현 + return true; + } + + bool RokokoDataParser::ParseFingerData(const void* fingerJson, RokokoData::ActorJointFrame& finger) + { + // 현재는 기본 구현만 제공 + // 실제 JSON 파싱 라이브러리 사용 시 구현 + return true; + } + + bool RokokoDataParser::ValidateQuaternion(float x, float y, float z, float w) + { + // NaN 체크 + if (std::isnan(x) || std::isnan(y) || std::isnan(z) || std::isnan(w)) { + return false; + } + + // 무한대 체크 + if (std::isinf(x) || std::isinf(y) || std::isinf(z) || std::isinf(w)) { + return false; + } + + // 쿼터니언 길이 체크 (정규화된 경우 1.0에 가까워야 함) + float length = std::sqrt(x * x + y * y + z * z + w * w); + if (length < 0.1f || length > 10.0f) { + return false; + } + + return true; + } + + 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.0001f) { + x /= length; + y /= length; + z /= length; + w /= length; + } else { + // 기본값 설정 + x = 0.0f; + y = 0.0f; + z = 0.0f; + w = 1.0f; + } + } +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoDataParser.h b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoDataParser.h new file mode 100644 index 00000000..423d9c11 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoDataParser.h @@ -0,0 +1,104 @@ +//====================================================================================================== +// Copyright 2025, Rokoko Glove OptiTrack Integration +//====================================================================================================== +/** + * RokokoDataParser class provides JSON parsing functionality for Rokoko glove data. + * This parser handles the LiveFrame_v4 JSON format from Rokoko Studio. + */ + +#pragma once +#include +#include +#include +#include +#include + +// Forward declarations for JSON data structures +namespace RokokoData +{ + struct Vector3Frame; + struct Vector4Frame; + struct ActorJointFrame; + struct ActorData; + struct LiveFrame_v4; + struct Body; +} + +namespace RokokoIntegration +{ + class RokokoDataParser + { + public: + /** + * Parses LiveFrame_v4 JSON data + * @param jsonString JSON string to parse + * @param frame Output frame data structure + * @return true if parsing successful + */ + static bool ParseLiveFrame(const std::string& jsonString, RokokoData::LiveFrame_v4& frame); + + /** + * Validates parsed frame data + * @param frame Frame data to validate + * @return true if frame data is valid + */ + static bool ValidateFrameData(const RokokoData::LiveFrame_v4& frame); + + /** + * Extracts finger joint data from frame + * @param frame Input frame data + * @param leftHandFingers Output left hand finger data + * @param rightHandFingers Output right hand finger data + * @return true if extraction successful + */ + static bool ExtractFingerData(const RokokoData::LiveFrame_v4& frame, + std::vector& leftHandFingers, + 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 (std::optional for stack allocation) + */ + static void ParseFingerJoint(const nlohmann::json& bodyJson, const std::string& jointName, + std::optional& joint); + + /** + * Parses actor data from JSON + * @param actorJson JSON object containing actor data + * @param actor Output actor data structure + * @return true if parsing successful + */ + static bool ParseActorData(const void* actorJson, RokokoData::ActorData& actor); + + /** + * Parses finger joint data from JSON + * @param fingerJson JSON object containing finger data + * @param finger Output finger data structure + * @return true if parsing successful + */ + static bool ParseFingerData(const void* fingerJson, RokokoData::ActorJointFrame& finger); + + /** + * Validates quaternion values + * @param x, y, z, w Quaternion components + * @return true if quaternion is valid + */ + static bool ValidateQuaternion(float x, float y, float z, float w); + + /** + * Normalizes quaternion values + * @param x, y, z, w Quaternion components (modified in place) + */ + static void NormalizeQuaternion(float& x, float& y, float& z, float& w); + }; +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoGloveDevice_Fixed.vcxproj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoGloveDevice_Fixed.vcxproj new file mode 100644 index 00000000..2838c65e --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoGloveDevice_Fixed.vcxproj @@ -0,0 +1,151 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE} + Win32Proj + RokokoGloveDevice_Fixed + 8.1 + RokokoGloveDevice_Fixed + + + + DynamicLibrary + true + v142 + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + C:\Program Files\OptiTrack\Motive\Motive.exe + WindowsLocalDebugger + + + false + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + C:\Program Files\OptiTrack\Motive\Motive.exe + WindowsLocalDebugger + + + + + + Level3 + Disabled + stdcpp17 + WIN32;_DEBUG;_WINDOWS;_USRDLL;ANALOGSYSTEM_IMPORTS;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;%(PreprocessorDefinitions) + false + ..\include;..\external;..\external\nlohmann;%(AdditionalIncludeDirectories) + /utf-8 %(AdditionalOptions) + + + Windows + true + NotSet + PeripheralImport.lib;ws2_32.lib;%(AdditionalDependencies) + ..\lib;%(AdditionalLibraryDirectories) + + + false + + + + + Level3 + MaxSpeed + true + true + stdcpp17 + WIN32;NDEBUG;_WINDOWS;_USRDLL;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;ANALOGSYSTEM_IMPORTS;%(PreprocessorDefinitions) + ..\include;..\external;..\external\nlohmann;%(AdditionalIncludeDirectories) + /utf-8 %(AdditionalOptions) + + + Windows + true + true + true + PeripheralImport.lib;ws2_32.lib;%(AdditionalDependencies) + ..\lib;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + + + + + + false + + + false + + + + + + + + + + + + + + + + + + Document + + + + + + \ No newline at end of file diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoGloveDevice_Fixed.vcxproj.filters b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoGloveDevice_Fixed.vcxproj.filters new file mode 100644 index 00000000..974cb17f --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoGloveDevice_Fixed.vcxproj.filters @@ -0,0 +1,90 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + + + + \ No newline at end of file diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoUDPReceiver.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoUDPReceiver.cpp new file mode 100644 index 00000000..ca607350 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoUDPReceiver.cpp @@ -0,0 +1,307 @@ +//====================================================================================================== +// Copyright 2025, Rokoko Glove OptiTrack Integration - FIXED VERSION +//====================================================================================================== +/** + * FIXES APPLIED: + * 1. ✅ WSACleanup duplicate calls - Added reference counting + * 2. ✅ CPU 100% usage - Added select() with timeout instead of busy-wait + * 3. ✅ Thread safety - Using lock_guard consistently + */ + +#include "RokokoUDPReceiver.h" +#include +#include +#include + +namespace RokokoIntegration +{ + // Static members for WSA reference counting + std::atomic RokokoUDPReceiver::s_wsaRefCount{0}; + std::mutex RokokoUDPReceiver::s_wsaMutex; + + RokokoUDPReceiver::RokokoUDPReceiver() + : mSocket(INVALID_SOCKET) + , mIsRunning(false) + , mIsListening(false) + , mPacketsReceived(0) + , mBytesReceived(0) + , mLastPacketTime(0.0) + , mPort(14043) + , mBufferSize(65000) + , mWsaInitialized(false) + { + } + + RokokoUDPReceiver::~RokokoUDPReceiver() + { + StopListening(); + CloseSocket(); + } + + // FIX 1: WSA Reference Counting + bool RokokoUDPReceiver::InitializeWSA() + { + std::lock_guard lock(s_wsaMutex); + + if (s_wsaRefCount == 0) { + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + return false; + } + } + + s_wsaRefCount++; + mWsaInitialized = true; + return true; + } + + void RokokoUDPReceiver::CleanupWSA() + { + if (!mWsaInitialized) { + return; + } + + std::lock_guard lock(s_wsaMutex); + + s_wsaRefCount--; + if (s_wsaRefCount == 0) { + WSACleanup(); + } + + mWsaInitialized = false; + } + + bool RokokoUDPReceiver::Initialize(int port) + { + try { + mPort = port; + + // FIX: Use reference-counted WSA initialization + if (!InitializeWSA()) { + SetError("WSAStartup failed"); + return false; + } + + // UDP 소켓 생성 + mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (mSocket == INVALID_SOCKET) { + SetError("Failed to create UDP socket"); + CleanupWSA(); + return false; + } + + // 소켓 옵션 설정 + int sendBufferSize = mBufferSize; + if (setsockopt(mSocket, SOL_SOCKET, SO_SNDBUF, + (char*)&sendBufferSize, sizeof(sendBufferSize)) == SOCKET_ERROR) { + SetError("Failed to set send buffer size"); + CloseSocket(); + return false; + } + + // 주소 구조체 설정 + struct sockaddr_in serverAddr; + serverAddr.sin_family = AF_INET; + serverAddr.sin_port = htons(mPort); + serverAddr.sin_addr.s_addr = INADDR_ANY; + + // 소켓 바인딩 + if (bind(mSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { + std::ostringstream oss; + oss << "Failed to bind to port " << mPort << ". Port may be in use."; + SetError(oss.str()); + CloseSocket(); + return false; + } + + // 논블로킹 모드는 select()와 함께 사용 + u_long mode = 1; + if (ioctlsocket(mSocket, FIONBIO, &mode) == SOCKET_ERROR) { + SetError("Failed to set non-blocking mode"); + CloseSocket(); + return false; + } + + return true; + + } catch (...) { + SetError("Exception during UDP initialization"); + return false; + } + } + + bool RokokoUDPReceiver::StartListening() + { + if (mSocket == INVALID_SOCKET) { + SetError("UDP socket not initialized"); + return false; + } + + if (mIsListening) { + SetError("Already listening"); + return false; + } + + try { + mIsRunning = true; + mIsListening = true; + + // 수신 스레드 시작 + mReceiveThread = std::thread(&RokokoUDPReceiver::ReceiveThread, this); + + return true; + + } catch (...) { + SetError("Exception starting receive thread"); + mIsRunning = false; + mIsListening = false; + return false; + } + } + + void RokokoUDPReceiver::StopListening() + { + mIsRunning = false; + mIsListening = false; + + if (mReceiveThread.joinable()) { + mReceiveThread.join(); + } + } + + void RokokoUDPReceiver::SetDataCallback(std::function&, const std::string&)> callback) + { + mDataCallback = callback; + } + + bool RokokoUDPReceiver::IsListening() const + { + return mIsListening; + } + + bool RokokoUDPReceiver::IsConnected() const + { + return mIsListening && mPacketsReceived > 0; + } + + std::string RokokoUDPReceiver::GetLastError() const + { + std::lock_guard lock(mErrorMutex); + return mLastError; + } + + void RokokoUDPReceiver::GetStatistics(uint64_t& packetsReceived, uint64_t& bytesReceived, double& lastPacketTime) const + { + std::lock_guard lock(mStatsMutex); + packetsReceived = mPacketsReceived; + bytesReceived = mBytesReceived; + lastPacketTime = mLastPacketTime; + } + + // FIX 2: Use select() to prevent CPU 100% usage + void RokokoUDPReceiver::ReceiveThread() + { + std::vector buffer(mBufferSize); + + while (mIsRunning) { + try { + // ✅ FIX: Use select() to wait for data efficiently + fd_set readSet; + FD_ZERO(&readSet); + FD_SET(mSocket, &readSet); + + // Timeout: 100ms (10 checks per second) + timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 100000; // 100ms + + int selectResult = select(0, &readSet, nullptr, nullptr, &timeout); + + if (selectResult > 0 && FD_ISSET(mSocket, &readSet)) { + // Data available - receive it + struct sockaddr_in senderAddr; + int senderAddrSize = sizeof(senderAddr); + + int bytesReceived = recvfrom(mSocket, + reinterpret_cast(buffer.data()), + mBufferSize, + 0, + (struct sockaddr*)&senderAddr, + &senderAddrSize); + + if (bytesReceived > 0) { + // 발신자 IP 주소 추출 + char senderIP[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &senderAddr.sin_addr, senderIP, INET_ADDRSTRLEN); + std::string senderIPStr(senderIP); + + // 데이터 처리 + ProcessIncomingData(buffer.data(), bytesReceived, senderIPStr); + + // 통계 업데이트 + UpdateStatistics(bytesReceived); + } + } else if (selectResult == SOCKET_ERROR) { + int error = WSAGetLastError(); + std::ostringstream oss; + oss << "select() error: " << error; + SetError(oss.str()); + break; + } + // selectResult == 0 means timeout - just continue loop (no busy-wait!) + + } catch (...) { + SetError("Exception in receive thread"); + break; + } + } + + mIsListening = false; + } + + void RokokoUDPReceiver::ProcessIncomingData(const uint8_t* data, int size, const std::string& senderIP) + { + if (mDataCallback && data && size > 0) { + try { + // 데이터를 벡터로 복사 + std::vector dataCopy(data, data + size); + + // 콜백 호출 + mDataCallback(dataCopy, senderIP); + + } catch (...) { + SetError("Exception in data callback"); + } + } + } + + void RokokoUDPReceiver::SetError(const std::string& error) + { + std::lock_guard lock(mErrorMutex); + mLastError = error; + + // 에러 로깅 (OptiTrack에서 확인 가능) + std::cerr << "[RokokoUDPReceiver] Error: " << error << std::endl; + } + + void RokokoUDPReceiver::UpdateStatistics(int packetSize) + { + std::lock_guard lock(mStatsMutex); + mPacketsReceived++; + mBytesReceived += packetSize; + mLastPacketTime = std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch() + ).count() / 1000.0; + } + + void RokokoUDPReceiver::CloseSocket() + { + if (mSocket != INVALID_SOCKET) { + closesocket(mSocket); + mSocket = INVALID_SOCKET; + } + + // FIX: Use reference-counted cleanup + CleanupWSA(); + } +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoUDPReceiver.h b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoUDPReceiver.h new file mode 100644 index 00000000..331b152d --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/RokokoUDPReceiver.h @@ -0,0 +1,154 @@ +//====================================================================================================== +// Copyright 2025, Rokoko Glove OptiTrack Integration +//====================================================================================================== +/** + * RokokoUDPReceiver class provides UDP communication functionality for receiving Rokoko glove data. + * This receiver handles UDP socket communication and data reception from Rokoko Studio. + */ + +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace RokokoIntegration +{ + class RokokoUDPReceiver + { + public: + /** + * Constructor + */ + RokokoUDPReceiver(); + + /** + * Destructor + */ + ~RokokoUDPReceiver(); + + /** + * Initializes UDP receiver + * @param port UDP port to listen on (default: 14043) + * @return true if initialization successful + */ + bool Initialize(int port = 14043); + + /** + * Starts listening for UDP data + * @return true if started successfully + */ + bool StartListening(); + + /** + * Stops listening for UDP data + */ + void StopListening(); + + /** + * Sets callback function for received data + * @param callback Function to call when data is received + */ + void SetDataCallback(std::function&, const std::string&)> callback); + + /** + * Checks if receiver is currently listening + * @return true if listening + */ + bool IsListening() const; + + /** + * Gets the current connection status + * @return true if connected and receiving data + */ + bool IsConnected() const; + + /** + * Gets the last error message + * @return Error message string + */ + std::string GetLastError() const; + + /** + * Gets connection statistics + * @param packetsReceived Number of packets received + * @param bytesReceived Total bytes received + * @param lastPacketTime Timestamp of last packet + */ + void GetStatistics(uint64_t& packetsReceived, uint64_t& bytesReceived, double& lastPacketTime) const; + + private: + // UDP socket + SOCKET mSocket; + + // Thread management + std::thread mReceiveThread; + std::atomic mIsRunning; + std::atomic mIsListening; + + // Data callback + std::function&, const std::string&)> mDataCallback; + + // Statistics + mutable std::mutex mStatsMutex; + uint64_t mPacketsReceived; + uint64_t mBytesReceived; + double mLastPacketTime; + + // Error handling + mutable std::mutex mErrorMutex; + std::string mLastError; + + // Configuration + int mPort; + int mBufferSize; + + /** + * Main receive thread function + */ + void ReceiveThread(); + + /** + * Processes incoming UDP data + * @param data Received data + * @param size Size of received data + * @param senderIP IP address of sender + */ + void ProcessIncomingData(const uint8_t* data, int size, const std::string& senderIP); + + /** + * Sets error message + * @param error Error message + */ + void SetError(const std::string& error); + + /** + * Updates statistics + * @param packetSize Size of received packet + */ + void UpdateStatistics(int packetSize); + + /** + * Closes UDP socket + */ + void CloseSocket(); + + // WSA reference counting (FIX: Prevent duplicate WSACleanup calls) + static std::atomic s_wsaRefCount; + static std::mutex s_wsaMutex; + bool mWsaInitialized; + + /** + * Initialize WSA with reference counting + */ + bool InitializeWSA(); + + /** + * Cleanup WSA with reference counting + */ + void CleanupWSA(); + }; +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/dllcommon.h b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/dllcommon.h new file mode 100644 index 00000000..71f9b68b --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/dllcommon.h @@ -0,0 +1,12 @@ +//====================================================================================================== +// Copyright 2016, NaturalPoint Inc. +//====================================================================================================== + +// windows +#include +#define _CRTDBG_MAP_ALLOC +#include +#include +#define WIN32_LEAN_AND_MEAN +#include + diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/dllmain.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/dllmain.cpp new file mode 100644 index 00000000..31ce0fe6 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/dllmain.cpp @@ -0,0 +1,103 @@ +//====================================================================================================== +// Copyright 2016, NaturalPoint Inc. +//====================================================================================================== +// +// dllmain.cpp : Defines the entry point for the DLL application. +// + +#include "dllcommon.h" + +// stl +#include +#include +using namespace std; + +// OptiTrack Peripheral Device API +#include "IDeviceManager.h" +using namespace AnalogSystem; + +// local devices +#include "ExampleGloveDevice.h" +using namespace OptiTrackPluginDevices; + +int hostVersionMajor, hostVersionMinor, hostVersionRevision; + +#ifdef OPTITRACKPERIPHERALEXAMPLE_EXPORTS +#define OPTITRACKPERIPHERALEXAMPLE_API __declspec(dllexport) +#else +#define OPTITRACKPERIPHERALEXAMPLE_API __declspec(dllimport) +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +using namespace OptiTrackPluginDevices; +using namespace ExampleDevice; + + +/** + * Defines the version information of the plugin DLL within Motive. + */ +OPTITRACKPERIPHERALEXAMPLE_API void DLLVersion(int hostMajor, int hostMinor, int hostRevision, int& dllMajor, int& dllMinor, int& dllRevision) +{ + hostVersionMajor = hostMajor; + hostVersionMajor = hostMinor; + hostVersionMajor = hostRevision; + + // report this peripheral device's version to host + dllMajor = 1; + dllMinor = 0; + dllRevision = 0; +} + +/** + * Motive calls the following method on application start up. This register device factories with Motive + * so that the device can be instantiated when it's ready (1 factory per device) + */ +OPTITRACKPERIPHERALEXAMPLE_API int DLLEnumerateDeviceFactories(IDeviceManager* pDeviceManager) +{ + + list> availDFs; + + ExampleDevice::ExampleGlove_EnumerateDeviceFactories(pDeviceManager, availDFs); + for (list>::iterator iter = availDFs.begin(); iter != availDFs.end(); iter++) { + // transfers ownership of device factory to host + pDeviceManager->AddDevice(std::move(*iter)); + } + + return (int)availDFs.size(); +} + +/** + * The following method gets called on application shutdown. Proper shutdown should happen here; +* including termination of the process of the DLL and memory unload. + */ +OPTITRACKPERIPHERALEXAMPLE_API int PluginDLLUnload(IDeviceManager* pDeviceManager) +{ + // OPTIONAL: perform device DLL shutdown here + ExampleGlove_Shutdown(); + return 0; +} + +#ifdef __cplusplus +} +#endif + + +BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + break; + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/lz4.c b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/lz4.c new file mode 100644 index 00000000..654bfdf3 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/lz4.c @@ -0,0 +1,2722 @@ +/* + LZ4 - Fast LZ compression algorithm + Copyright (C) 2011-2020, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ + +/*-************************************ +* Tuning parameters +**************************************/ +/* + * LZ4_HEAPMODE : + * Select how default compression functions will allocate memory for their hash table, + * in memory stack (0:default, fastest), or in memory heap (1:requires malloc()). + */ +#ifndef LZ4_HEAPMODE +# define LZ4_HEAPMODE 0 +#endif + +/* + * LZ4_ACCELERATION_DEFAULT : + * Select "acceleration" for LZ4_compress_fast() when parameter value <= 0 + */ +#define LZ4_ACCELERATION_DEFAULT 1 +/* + * LZ4_ACCELERATION_MAX : + * Any "acceleration" value higher than this threshold + * get treated as LZ4_ACCELERATION_MAX instead (fix #876) + */ +#define LZ4_ACCELERATION_MAX 65537 + + +/*-************************************ +* CPU Feature Detection +**************************************/ +/* LZ4_FORCE_MEMORY_ACCESS + * By default, access to unaligned memory is controlled by `memcpy()`, which is safe and portable. + * Unfortunately, on some target/compiler combinations, the generated assembly is sub-optimal. + * The below switch allow to select different access method for improved performance. + * Method 0 (default) : use `memcpy()`. Safe and portable. + * Method 1 : `__packed` statement. It depends on compiler extension (ie, not portable). + * This method is safe if your compiler supports it, and *generally* as fast or faster than `memcpy`. + * Method 2 : direct access. This method is portable but violate C standard. + * It can generate buggy code on targets which assembly generation depends on alignment. + * But in some circumstances, it's the only known way to get the most performance (ie GCC + ARMv6) + * See https://fastcompression.blogspot.fr/2015/08/accessing-unaligned-memory.html for details. + * Prefer these methods in priority order (0 > 1 > 2) + */ +#ifndef LZ4_FORCE_MEMORY_ACCESS /* can be defined externally */ +# if defined(__GNUC__) && \ + ( defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) \ + || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) || defined(__ARM_ARCH_6T2__) ) +# define LZ4_FORCE_MEMORY_ACCESS 2 +# elif (defined(__INTEL_COMPILER) && !defined(_WIN32)) || defined(__GNUC__) +# define LZ4_FORCE_MEMORY_ACCESS 1 +# endif +#endif + +/* + * LZ4_FORCE_SW_BITCOUNT + * Define this parameter if your target system or compiler does not support hardware bit count + */ +#if defined(_MSC_VER) && defined(_WIN32_WCE) /* Visual Studio for WinCE doesn't support Hardware bit count */ +# undef LZ4_FORCE_SW_BITCOUNT /* avoid double def */ +# define LZ4_FORCE_SW_BITCOUNT +#endif + + + +/*-************************************ +* Dependency +**************************************/ +/* + * LZ4_SRC_INCLUDED: + * Amalgamation flag, whether lz4.c is included + */ +#ifndef LZ4_SRC_INCLUDED +# define LZ4_SRC_INCLUDED 1 +#endif + +#ifndef LZ4_STATIC_LINKING_ONLY +#define LZ4_STATIC_LINKING_ONLY +#endif + +#ifndef LZ4_DISABLE_DEPRECATE_WARNINGS +#define LZ4_DISABLE_DEPRECATE_WARNINGS /* due to LZ4_decompress_safe_withPrefix64k */ +#endif + +#define LZ4_STATIC_LINKING_ONLY /* LZ4_DISTANCE_MAX */ +#include "lz4.h" +/* see also "memory routines" below */ + + +/*-************************************ +* Compiler Options +**************************************/ +#if defined(_MSC_VER) && (_MSC_VER >= 1400) /* Visual Studio 2005+ */ +# include /* only present in VS2005+ */ +# pragma warning(disable : 4127) /* disable: C4127: conditional expression is constant */ +# pragma warning(disable : 6237) /* disable: C6237: conditional expression is always 0 */ +#endif /* _MSC_VER */ + +#ifndef LZ4_FORCE_INLINE +# ifdef _MSC_VER /* Visual Studio */ +# define LZ4_FORCE_INLINE static __forceinline +# else +# if defined (__cplusplus) || defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L /* C99 */ +# ifdef __GNUC__ +# define LZ4_FORCE_INLINE static inline __attribute__((always_inline)) +# else +# define LZ4_FORCE_INLINE static inline +# endif +# else +# define LZ4_FORCE_INLINE static +# endif /* __STDC_VERSION__ */ +# endif /* _MSC_VER */ +#endif /* LZ4_FORCE_INLINE */ + +/* LZ4_FORCE_O2 and LZ4_FORCE_INLINE + * gcc on ppc64le generates an unrolled SIMDized loop for LZ4_wildCopy8, + * together with a simple 8-byte copy loop as a fall-back path. + * However, this optimization hurts the decompression speed by >30%, + * because the execution does not go to the optimized loop + * for typical compressible data, and all of the preamble checks + * before going to the fall-back path become useless overhead. + * This optimization happens only with the -O3 flag, and -O2 generates + * a simple 8-byte copy loop. + * With gcc on ppc64le, all of the LZ4_decompress_* and LZ4_wildCopy8 + * functions are annotated with __attribute__((optimize("O2"))), + * and also LZ4_wildCopy8 is forcibly inlined, so that the O2 attribute + * of LZ4_wildCopy8 does not affect the compression speed. + */ +#if defined(__PPC64__) && defined(__LITTLE_ENDIAN__) && defined(__GNUC__) && !defined(__clang__) +# define LZ4_FORCE_O2 __attribute__((optimize("O2"))) +# undef LZ4_FORCE_INLINE +# define LZ4_FORCE_INLINE static __inline __attribute__((optimize("O2"),always_inline)) +#else +# define LZ4_FORCE_O2 +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 800)) || defined(__clang__) +# define expect(expr,value) (__builtin_expect ((expr),(value)) ) +#else +# define expect(expr,value) (expr) +#endif + +#ifndef likely +#define likely(expr) expect((expr) != 0, 1) +#endif +#ifndef unlikely +#define unlikely(expr) expect((expr) != 0, 0) +#endif + +/* Should the alignment test prove unreliable, for some reason, + * it can be disabled by setting LZ4_ALIGN_TEST to 0 */ +#ifndef LZ4_ALIGN_TEST /* can be externally provided */ +# define LZ4_ALIGN_TEST 1 +#endif + + +/*-************************************ +* Memory routines +**************************************/ + +/*! LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION : + * Disable relatively high-level LZ4/HC functions that use dynamic memory + * allocation functions (malloc(), calloc(), free()). + * + * Note that this is a compile-time switch. And since it disables + * public/stable LZ4 v1 API functions, we don't recommend using this + * symbol to generate a library for distribution. + * + * The following public functions are removed when this symbol is defined. + * - lz4 : LZ4_createStream, LZ4_freeStream, + * LZ4_createStreamDecode, LZ4_freeStreamDecode, LZ4_create (deprecated) + * - lz4hc : LZ4_createStreamHC, LZ4_freeStreamHC, + * LZ4_createHC (deprecated), LZ4_freeHC (deprecated) + * - lz4frame, lz4file : All LZ4F_* functions + */ +#if defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +# define ALLOC(s) lz4_error_memory_allocation_is_disabled +# define ALLOC_AND_ZERO(s) lz4_error_memory_allocation_is_disabled +# define FREEMEM(p) lz4_error_memory_allocation_is_disabled +#elif defined(LZ4_USER_MEMORY_FUNCTIONS) +/* memory management functions can be customized by user project. + * Below functions must exist somewhere in the Project + * and be available at link time */ +void* LZ4_malloc(size_t s); +void* LZ4_calloc(size_t n, size_t s); +void LZ4_free(void* p); +# define ALLOC(s) LZ4_malloc(s) +# define ALLOC_AND_ZERO(s) LZ4_calloc(1,s) +# define FREEMEM(p) LZ4_free(p) +#else +# include /* malloc, calloc, free */ +# define ALLOC(s) malloc(s) +# define ALLOC_AND_ZERO(s) calloc(1,s) +# define FREEMEM(p) free(p) +#endif + +#if ! LZ4_FREESTANDING +# include /* memset, memcpy */ +#endif +#if !defined(LZ4_memset) +# define LZ4_memset(p,v,s) memset((p),(v),(s)) +#endif +#define MEM_INIT(p,v,s) LZ4_memset((p),(v),(s)) + + +/*-************************************ +* Common Constants +**************************************/ +#define MINMATCH 4 + +#define WILDCOPYLENGTH 8 +#define LASTLITERALS 5 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MFLIMIT 12 /* see ../doc/lz4_Block_format.md#parsing-restrictions */ +#define MATCH_SAFEGUARD_DISTANCE ((2*WILDCOPYLENGTH) - MINMATCH) /* ensure it's possible to write 2 x wildcopyLength without overflowing output buffer */ +#define FASTLOOP_SAFE_DISTANCE 64 +static const int LZ4_minLength = (MFLIMIT+1); + +#define KB *(1 <<10) +#define MB *(1 <<20) +#define GB *(1U<<30) + +#define LZ4_DISTANCE_ABSOLUTE_MAX 65535 +#if (LZ4_DISTANCE_MAX > LZ4_DISTANCE_ABSOLUTE_MAX) /* max supported by LZ4 format */ +# error "LZ4_DISTANCE_MAX is too big : must be <= 65535" +#endif + +#define ML_BITS 4 +#define ML_MASK ((1U<=1) +# include +#else +# ifndef assert +# define assert(condition) ((void)0) +# endif +#endif + +#define LZ4_STATIC_ASSERT(c) { enum { LZ4_static_assert = 1/(int)(!!(c)) }; } /* use after variable declarations */ + +#if defined(LZ4_DEBUG) && (LZ4_DEBUG>=2) +# include + static int g_debuglog_enable = 1; +# define DEBUGLOG(l, ...) { \ + if ((g_debuglog_enable) && (l<=LZ4_DEBUG)) { \ + fprintf(stderr, __FILE__ ": "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, " \n"); \ + } } +#else +# define DEBUGLOG(l, ...) {} /* disabled */ +#endif + +static int LZ4_isAligned(const void* ptr, size_t alignment) +{ + return ((size_t)ptr & (alignment -1)) == 0; +} + + +/*-************************************ +* Types +**************************************/ +#include +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include + typedef uint8_t BYTE; + typedef uint16_t U16; + typedef uint32_t U32; + typedef int32_t S32; + typedef uint64_t U64; + typedef uintptr_t uptrval; +#else +# if UINT_MAX != 4294967295UL +# error "LZ4 code (when not C++ or C99) assumes that sizeof(int) == 4" +# endif + typedef unsigned char BYTE; + typedef unsigned short U16; + typedef unsigned int U32; + typedef signed int S32; + typedef unsigned long long U64; + typedef size_t uptrval; /* generally true, except OpenVMS-64 */ +#endif + +#if defined(__x86_64__) + typedef U64 reg_t; /* 64-bits in x32 mode */ +#else + typedef size_t reg_t; /* 32-bits in x32 mode */ +#endif + +typedef enum { + notLimited = 0, + limitedOutput = 1, + fillOutput = 2 +} limitedOutput_directive; + + +/*-************************************ +* Reading and writing into memory +**************************************/ + +/** + * LZ4 relies on memcpy with a constant size being inlined. In freestanding + * environments, the compiler can't assume the implementation of memcpy() is + * standard compliant, so it can't apply its specialized memcpy() inlining + * logic. When possible, use __builtin_memcpy() to tell the compiler to analyze + * memcpy() as if it were standard compliant, so it can inline it in freestanding + * environments. This is needed when decompressing the Linux Kernel, for example. + */ +#if !defined(LZ4_memcpy) +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4_memcpy(dst, src, size) __builtin_memcpy(dst, src, size) +# else +# define LZ4_memcpy(dst, src, size) memcpy(dst, src, size) +# endif +#endif + +#if !defined(LZ4_memmove) +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4_memmove __builtin_memmove +# else +# define LZ4_memmove memmove +# endif +#endif + +static unsigned LZ4_isLittleEndian(void) +{ + const union { U32 u; BYTE c[4]; } one = { 1 }; /* don't use static : performance detrimental */ + return one.c[0]; +} + + +#if defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==2) +/* lie to the compiler about data alignment; use with caution */ + +static U16 LZ4_read16(const void* memPtr) { return *(const U16*) memPtr; } +static U32 LZ4_read32(const void* memPtr) { return *(const U32*) memPtr; } +static reg_t LZ4_read_ARCH(const void* memPtr) { return *(const reg_t*) memPtr; } + +static void LZ4_write16(void* memPtr, U16 value) { *(U16*)memPtr = value; } +static void LZ4_write32(void* memPtr, U32 value) { *(U32*)memPtr = value; } + +#elif defined(LZ4_FORCE_MEMORY_ACCESS) && (LZ4_FORCE_MEMORY_ACCESS==1) + +/* __pack instructions are safer, but compiler specific, hence potentially problematic for some compilers */ +/* currently only defined for gcc and icc */ +typedef union { U16 u16; U32 u32; reg_t uArch; } __attribute__((packed)) LZ4_unalign; + +static U16 LZ4_read16(const void* ptr) { return ((const LZ4_unalign*)ptr)->u16; } +static U32 LZ4_read32(const void* ptr) { return ((const LZ4_unalign*)ptr)->u32; } +static reg_t LZ4_read_ARCH(const void* ptr) { return ((const LZ4_unalign*)ptr)->uArch; } + +static void LZ4_write16(void* memPtr, U16 value) { ((LZ4_unalign*)memPtr)->u16 = value; } +static void LZ4_write32(void* memPtr, U32 value) { ((LZ4_unalign*)memPtr)->u32 = value; } + +#else /* safe and portable access using memcpy() */ + +static U16 LZ4_read16(const void* memPtr) +{ + U16 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +static U32 LZ4_read32(const void* memPtr) +{ + U32 val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +static reg_t LZ4_read_ARCH(const void* memPtr) +{ + reg_t val; LZ4_memcpy(&val, memPtr, sizeof(val)); return val; +} + +static void LZ4_write16(void* memPtr, U16 value) +{ + LZ4_memcpy(memPtr, &value, sizeof(value)); +} + +static void LZ4_write32(void* memPtr, U32 value) +{ + LZ4_memcpy(memPtr, &value, sizeof(value)); +} + +#endif /* LZ4_FORCE_MEMORY_ACCESS */ + + +static U16 LZ4_readLE16(const void* memPtr) +{ + if (LZ4_isLittleEndian()) { + return LZ4_read16(memPtr); + } else { + const BYTE* p = (const BYTE*)memPtr; + return (U16)((U16)p[0] + (p[1]<<8)); + } +} + +static void LZ4_writeLE16(void* memPtr, U16 value) +{ + if (LZ4_isLittleEndian()) { + LZ4_write16(memPtr, value); + } else { + BYTE* p = (BYTE*)memPtr; + p[0] = (BYTE) value; + p[1] = (BYTE)(value>>8); + } +} + +/* customized variant of memcpy, which can overwrite up to 8 bytes beyond dstEnd */ +LZ4_FORCE_INLINE +void LZ4_wildCopy8(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { LZ4_memcpy(d,s,8); d+=8; s+=8; } while (d= 16. */ +LZ4_FORCE_INLINE void +LZ4_wildCopy32(void* dstPtr, const void* srcPtr, void* dstEnd) +{ + BYTE* d = (BYTE*)dstPtr; + const BYTE* s = (const BYTE*)srcPtr; + BYTE* const e = (BYTE*)dstEnd; + + do { LZ4_memcpy(d,s,16); LZ4_memcpy(d+16,s+16,16); d+=32; s+=32; } while (d= dstPtr + MINMATCH + * - there is at least 8 bytes available to write after dstEnd */ +LZ4_FORCE_INLINE void +LZ4_memcpy_using_offset(BYTE* dstPtr, const BYTE* srcPtr, BYTE* dstEnd, const size_t offset) +{ + BYTE v[8]; + + assert(dstEnd >= dstPtr + MINMATCH); + + switch(offset) { + case 1: + MEM_INIT(v, *srcPtr, 8); + break; + case 2: + LZ4_memcpy(v, srcPtr, 2); + LZ4_memcpy(&v[2], srcPtr, 2); +#if defined(_MSC_VER) && (_MSC_VER <= 1933) /* MSVC 2022 ver 17.3 or earlier */ +# pragma warning(push) +# pragma warning(disable : 6385) /* warning C6385: Reading invalid data from 'v'. */ +#endif + LZ4_memcpy(&v[4], v, 4); +#if defined(_MSC_VER) && (_MSC_VER <= 1933) /* MSVC 2022 ver 17.3 or earlier */ +# pragma warning(pop) +#endif + break; + case 4: + LZ4_memcpy(v, srcPtr, 4); + LZ4_memcpy(&v[4], srcPtr, 4); + break; + default: + LZ4_memcpy_using_offset_base(dstPtr, srcPtr, dstEnd, offset); + return; + } + + LZ4_memcpy(dstPtr, v, 8); + dstPtr += 8; + while (dstPtr < dstEnd) { + LZ4_memcpy(dstPtr, v, 8); + dstPtr += 8; + } +} +#endif + + +/*-************************************ +* Common functions +**************************************/ +static unsigned LZ4_NbCommonBytes (reg_t val) +{ + assert(val != 0); + if (LZ4_isLittleEndian()) { + if (sizeof(val) == 8) { +# if defined(_MSC_VER) && (_MSC_VER >= 1800) && (defined(_M_AMD64) && !defined(_M_ARM64EC)) && !defined(LZ4_FORCE_SW_BITCOUNT) +/*-************************************************************************************************* +* ARM64EC is a Microsoft-designed ARM64 ABI compatible with AMD64 applications on ARM64 Windows 11. +* The ARM64EC ABI does not support AVX/AVX2/AVX512 instructions, nor their relevant intrinsics +* including _tzcnt_u64. Therefore, we need to neuter the _tzcnt_u64 code path for ARM64EC. +****************************************************************************************************/ +# if defined(__clang__) && (__clang_major__ < 10) + /* Avoid undefined clang-cl intrinsics issue. + * See https://github.com/lz4/lz4/pull/1017 for details. */ + return (unsigned)__builtin_ia32_tzcnt_u64(val) >> 3; +# else + /* x64 CPUS without BMI support interpret `TZCNT` as `REP BSF` */ + return (unsigned)_tzcnt_u64(val) >> 3; +# endif +# elif defined(_MSC_VER) && defined(_WIN64) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r = 0; + _BitScanForward64(&r, (U64)val); + return (unsigned)r >> 3; +# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_ctzll((U64)val) >> 3; +# else + const U64 m = 0x0101010101010101ULL; + val ^= val - 1; + return (unsigned)(((U64)((val & (m - 1)) * m)) >> 56); +# endif + } else /* 32 bits */ { +# if defined(_MSC_VER) && (_MSC_VER >= 1400) && !defined(LZ4_FORCE_SW_BITCOUNT) + unsigned long r; + _BitScanForward(&r, (U32)val); + return (unsigned)r >> 3; +# elif (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_ctz((U32)val) >> 3; +# else + const U32 m = 0x01010101; + return (unsigned)((((val - 1) ^ val) & (m - 1)) * m) >> 24; +# endif + } + } else /* Big Endian CPU */ { + if (sizeof(val)==8) { +# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(__TINYC__) && !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_clzll((U64)val) >> 3; +# else +#if 1 + /* this method is probably faster, + * but adds a 128 bytes lookup table */ + static const unsigned char ctz7_tab[128] = { + 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + }; + U64 const mask = 0x0101010101010101ULL; + U64 const t = (((val >> 8) - mask) | val) & mask; + return ctz7_tab[(t * 0x0080402010080402ULL) >> 57]; +#else + /* this method doesn't consume memory space like the previous one, + * but it contains several branches, + * that may end up slowing execution */ + static const U32 by32 = sizeof(val)*4; /* 32 on 64 bits (goal), 16 on 32 bits. + Just to avoid some static analyzer complaining about shift by 32 on 32-bits target. + Note that this code path is never triggered in 32-bits mode. */ + unsigned r; + if (!(val>>by32)) { r=4; } else { r=0; val>>=by32; } + if (!(val>>16)) { r+=2; val>>=8; } else { val>>=24; } + r += (!val); + return r; +#endif +# endif + } else /* 32 bits */ { +# if (defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 3) || \ + ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 4))))) && \ + !defined(LZ4_FORCE_SW_BITCOUNT) + return (unsigned)__builtin_clz((U32)val) >> 3; +# else + val >>= 8; + val = ((((val + 0x00FFFF00) | 0x00FFFFFF) + val) | + (val + 0x00FF0000)) >> 24; + return (unsigned)val ^ 3; +# endif + } + } +} + + +#define STEPSIZE sizeof(reg_t) +LZ4_FORCE_INLINE +unsigned LZ4_count(const BYTE* pIn, const BYTE* pMatch, const BYTE* pInLimit) +{ + const BYTE* const pStart = pIn; + + if (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { + pIn+=STEPSIZE; pMatch+=STEPSIZE; + } else { + return LZ4_NbCommonBytes(diff); + } } + + while (likely(pIn < pInLimit-(STEPSIZE-1))) { + reg_t const diff = LZ4_read_ARCH(pMatch) ^ LZ4_read_ARCH(pIn); + if (!diff) { pIn+=STEPSIZE; pMatch+=STEPSIZE; continue; } + pIn += LZ4_NbCommonBytes(diff); + return (unsigned)(pIn - pStart); + } + + if ((STEPSIZE==8) && (pIn<(pInLimit-3)) && (LZ4_read32(pMatch) == LZ4_read32(pIn))) { pIn+=4; pMatch+=4; } + if ((pIn<(pInLimit-1)) && (LZ4_read16(pMatch) == LZ4_read16(pIn))) { pIn+=2; pMatch+=2; } + if ((pIn compression run slower on incompressible data */ + + +/*-************************************ +* Local Structures and types +**************************************/ +typedef enum { clearedTable = 0, byPtr, byU32, byU16 } tableType_t; + +/** + * This enum distinguishes several different modes of accessing previous + * content in the stream. + * + * - noDict : There is no preceding content. + * - withPrefix64k : Table entries up to ctx->dictSize before the current blob + * blob being compressed are valid and refer to the preceding + * content (of length ctx->dictSize), which is available + * contiguously preceding in memory the content currently + * being compressed. + * - usingExtDict : Like withPrefix64k, but the preceding content is somewhere + * else in memory, starting at ctx->dictionary with length + * ctx->dictSize. + * - usingDictCtx : Everything concerning the preceding content is + * in a separate context, pointed to by ctx->dictCtx. + * ctx->dictionary, ctx->dictSize, and table entries + * in the current context that refer to positions + * preceding the beginning of the current compression are + * ignored. Instead, ctx->dictCtx->dictionary and ctx->dictCtx + * ->dictSize describe the location and size of the preceding + * content, and matches are found by looking in the ctx + * ->dictCtx->hashTable. + */ +typedef enum { noDict = 0, withPrefix64k, usingExtDict, usingDictCtx } dict_directive; +typedef enum { noDictIssue = 0, dictSmall } dictIssue_directive; + + +/*-************************************ +* Local Utils +**************************************/ +int LZ4_versionNumber (void) { return LZ4_VERSION_NUMBER; } +const char* LZ4_versionString(void) { return LZ4_VERSION_STRING; } +int LZ4_compressBound(int isize) { return LZ4_COMPRESSBOUND(isize); } +int LZ4_sizeofState(void) { return sizeof(LZ4_stream_t); } + + +/*-**************************************** +* Internal Definitions, used only in Tests +*******************************************/ +#if defined (__cplusplus) +extern "C" { +#endif + +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize); + +int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, + int compressedSize, int maxOutputSize, + const void* dictStart, size_t dictSize); +int LZ4_decompress_safe_partial_forceExtDict(const char* source, char* dest, + int compressedSize, int targetOutputSize, int dstCapacity, + const void* dictStart, size_t dictSize); +#if defined (__cplusplus) +} +#endif + +/*-****************************** +* Compression functions +********************************/ +LZ4_FORCE_INLINE U32 LZ4_hash4(U32 sequence, tableType_t const tableType) +{ + if (tableType == byU16) + return ((sequence * 2654435761U) >> ((MINMATCH*8)-(LZ4_HASHLOG+1))); + else + return ((sequence * 2654435761U) >> ((MINMATCH*8)-LZ4_HASHLOG)); +} + +LZ4_FORCE_INLINE U32 LZ4_hash5(U64 sequence, tableType_t const tableType) +{ + const U32 hashLog = (tableType == byU16) ? LZ4_HASHLOG+1 : LZ4_HASHLOG; + if (LZ4_isLittleEndian()) { + const U64 prime5bytes = 889523592379ULL; + return (U32)(((sequence << 24) * prime5bytes) >> (64 - hashLog)); + } else { + const U64 prime8bytes = 11400714785074694791ULL; + return (U32)(((sequence >> 24) * prime8bytes) >> (64 - hashLog)); + } +} + +LZ4_FORCE_INLINE U32 LZ4_hashPosition(const void* const p, tableType_t const tableType) +{ + if ((sizeof(reg_t)==8) && (tableType != byU16)) return LZ4_hash5(LZ4_read_ARCH(p), tableType); + return LZ4_hash4(LZ4_read32(p), tableType); +} + +LZ4_FORCE_INLINE void LZ4_clearHash(U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: { /* illegal! */ assert(0); return; } + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = NULL; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = 0; return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = 0; return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putIndexOnHash(U32 idx, U32 h, void* tableBase, tableType_t const tableType) +{ + switch (tableType) + { + default: /* fallthrough */ + case clearedTable: /* fallthrough */ + case byPtr: { /* illegal! */ assert(0); return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = idx; return; } + case byU16: { U16* hashTable = (U16*) tableBase; assert(idx < 65536); hashTable[h] = (U16)idx; return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putPositionOnHash(const BYTE* p, U32 h, + void* tableBase, tableType_t const tableType, + const BYTE* srcBase) +{ + switch (tableType) + { + case clearedTable: { /* illegal! */ assert(0); return; } + case byPtr: { const BYTE** hashTable = (const BYTE**)tableBase; hashTable[h] = p; return; } + case byU32: { U32* hashTable = (U32*) tableBase; hashTable[h] = (U32)(p-srcBase); return; } + case byU16: { U16* hashTable = (U16*) tableBase; hashTable[h] = (U16)(p-srcBase); return; } + } +} + +LZ4_FORCE_INLINE void LZ4_putPosition(const BYTE* p, void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + LZ4_putPositionOnHash(p, h, tableBase, tableType, srcBase); +} + +/* LZ4_getIndexOnHash() : + * Index of match position registered in hash table. + * hash position must be calculated by using base+index, or dictBase+index. + * Assumption 1 : only valid if tableType == byU32 or byU16. + * Assumption 2 : h is presumed valid (within limits of hash table) + */ +LZ4_FORCE_INLINE U32 LZ4_getIndexOnHash(U32 h, const void* tableBase, tableType_t tableType) +{ + LZ4_STATIC_ASSERT(LZ4_MEMORY_USAGE > 2); + if (tableType == byU32) { + const U32* const hashTable = (const U32*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-2))); + return hashTable[h]; + } + if (tableType == byU16) { + const U16* const hashTable = (const U16*) tableBase; + assert(h < (1U << (LZ4_MEMORY_USAGE-1))); + return hashTable[h]; + } + assert(0); return 0; /* forbidden case */ +} + +static const BYTE* LZ4_getPositionOnHash(U32 h, const void* tableBase, tableType_t tableType, const BYTE* srcBase) +{ + if (tableType == byPtr) { const BYTE* const* hashTable = (const BYTE* const*) tableBase; return hashTable[h]; } + if (tableType == byU32) { const U32* const hashTable = (const U32*) tableBase; return hashTable[h] + srcBase; } + { const U16* const hashTable = (const U16*) tableBase; return hashTable[h] + srcBase; } /* default, to ensure a return */ +} + +LZ4_FORCE_INLINE const BYTE* +LZ4_getPosition(const BYTE* p, + const void* tableBase, tableType_t tableType, + const BYTE* srcBase) +{ + U32 const h = LZ4_hashPosition(p, tableType); + return LZ4_getPositionOnHash(h, tableBase, tableType, srcBase); +} + +LZ4_FORCE_INLINE void +LZ4_prepareTable(LZ4_stream_t_internal* const cctx, + const int inputSize, + const tableType_t tableType) { + /* If the table hasn't been used, it's guaranteed to be zeroed out, and is + * therefore safe to use no matter what mode we're in. Otherwise, we figure + * out if it's safe to leave as is or whether it needs to be reset. + */ + if ((tableType_t)cctx->tableType != clearedTable) { + assert(inputSize >= 0); + if ((tableType_t)cctx->tableType != tableType + || ((tableType == byU16) && cctx->currentOffset + (unsigned)inputSize >= 0xFFFFU) + || ((tableType == byU32) && cctx->currentOffset > 1 GB) + || tableType == byPtr + || inputSize >= 4 KB) + { + DEBUGLOG(4, "LZ4_prepareTable: Resetting table in %p", cctx); + MEM_INIT(cctx->hashTable, 0, LZ4_HASHTABLESIZE); + cctx->currentOffset = 0; + cctx->tableType = (U32)clearedTable; + } else { + DEBUGLOG(4, "LZ4_prepareTable: Re-use hash table (no reset)"); + } + } + + /* Adding a gap, so all previous entries are > LZ4_DISTANCE_MAX back, + * is faster than compressing without a gap. + * However, compressing with currentOffset == 0 is faster still, + * so we preserve that case. + */ + if (cctx->currentOffset != 0 && tableType == byU32) { + DEBUGLOG(5, "LZ4_prepareTable: adding 64KB to currentOffset"); + cctx->currentOffset += 64 KB; + } + + /* Finally, clear history */ + cctx->dictCtx = NULL; + cctx->dictionary = NULL; + cctx->dictSize = 0; +} + +/** LZ4_compress_generic() : + * inlined, to ensure branches are decided at compilation time. + * Presumed already validated at this stage: + * - source != NULL + * - inputSize > 0 + */ +LZ4_FORCE_INLINE int LZ4_compress_generic_validated( + LZ4_stream_t_internal* const cctx, + const char* const source, + char* const dest, + const int inputSize, + int* inputConsumed, /* only written when outputDirective == fillOutput */ + const int maxOutputSize, + const limitedOutput_directive outputDirective, + const tableType_t tableType, + const dict_directive dictDirective, + const dictIssue_directive dictIssue, + const int acceleration) +{ + int result; + const BYTE* ip = (const BYTE*) source; + + U32 const startIndex = cctx->currentOffset; + const BYTE* base = (const BYTE*) source - startIndex; + const BYTE* lowLimit; + + const LZ4_stream_t_internal* dictCtx = (const LZ4_stream_t_internal*) cctx->dictCtx; + const BYTE* const dictionary = + dictDirective == usingDictCtx ? dictCtx->dictionary : cctx->dictionary; + const U32 dictSize = + dictDirective == usingDictCtx ? dictCtx->dictSize : cctx->dictSize; + const U32 dictDelta = (dictDirective == usingDictCtx) ? startIndex - dictCtx->currentOffset : 0; /* make indexes in dictCtx comparable with index in current context */ + + int const maybe_extMem = (dictDirective == usingExtDict) || (dictDirective == usingDictCtx); + U32 const prefixIdxLimit = startIndex - dictSize; /* used when dictDirective == dictSmall */ + const BYTE* const dictEnd = dictionary ? dictionary + dictSize : dictionary; + const BYTE* anchor = (const BYTE*) source; + const BYTE* const iend = ip + inputSize; + const BYTE* const mflimitPlusOne = iend - MFLIMIT + 1; + const BYTE* const matchlimit = iend - LASTLITERALS; + + /* the dictCtx currentOffset is indexed on the start of the dictionary, + * while a dictionary in the current context precedes the currentOffset */ + const BYTE* dictBase = (dictionary == NULL) ? NULL : + (dictDirective == usingDictCtx) ? + dictionary + dictSize - dictCtx->currentOffset : + dictionary + dictSize - startIndex; + + BYTE* op = (BYTE*) dest; + BYTE* const olimit = op + maxOutputSize; + + U32 offset = 0; + U32 forwardH; + + DEBUGLOG(5, "LZ4_compress_generic_validated: srcSize=%i, tableType=%u", inputSize, tableType); + assert(ip != NULL); + /* If init conditions are not met, we don't have to mark stream + * as having dirty context, since no action was taken yet */ + if (outputDirective == fillOutput && maxOutputSize < 1) { return 0; } /* Impossible to store anything */ + if ((tableType == byU16) && (inputSize>=LZ4_64Klimit)) { return 0; } /* Size too large (not within 64K limit) */ + if (tableType==byPtr) assert(dictDirective==noDict); /* only supported use case with byPtr */ + assert(acceleration >= 1); + + lowLimit = (const BYTE*)source - (dictDirective == withPrefix64k ? dictSize : 0); + + /* Update context state */ + if (dictDirective == usingDictCtx) { + /* Subsequent linked blocks can't use the dictionary. */ + /* Instead, they use the block we just compressed. */ + cctx->dictCtx = NULL; + cctx->dictSize = (U32)inputSize; + } else { + cctx->dictSize += (U32)inputSize; + } + cctx->currentOffset += (U32)inputSize; + cctx->tableType = (U32)tableType; + + if (inputSizehashTable, tableType, base); + ip++; forwardH = LZ4_hashPosition(ip, tableType); + + /* Main Loop */ + for ( ; ; ) { + const BYTE* match; + BYTE* token; + const BYTE* filledIp; + + /* Find a match */ + if (tableType == byPtr) { + const BYTE* forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + match = LZ4_getPositionOnHash(h, cctx->hashTable, tableType, base); + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putPositionOnHash(ip, h, cctx->hashTable, tableType, base); + + } while ( (match+LZ4_DISTANCE_MAX < ip) + || (LZ4_read32(match) != LZ4_read32(ip)) ); + + } else { /* byU32, byU16 */ + + const BYTE* forwardIp = ip; + int step = 1; + int searchMatchNb = acceleration << LZ4_skipTrigger; + do { + U32 const h = forwardH; + U32 const current = (U32)(forwardIp - base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex <= current); + assert(forwardIp - base < (ptrdiff_t)(2 GB - 1)); + ip = forwardIp; + forwardIp += step; + step = (searchMatchNb++ >> LZ4_skipTrigger); + + if (unlikely(forwardIp > mflimitPlusOne)) goto _last_literals; + assert(ip < mflimitPlusOne); + + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + assert(tableType == byU32); + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + matchIndex += dictDelta; /* make dictCtx index comparable with current context */ + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else if (dictDirective == usingExtDict) { + if (matchIndex < startIndex) { + DEBUGLOG(7, "extDict candidate: matchIndex=%5u < startIndex=%5u", matchIndex, startIndex); + assert(startIndex - matchIndex >= MINMATCH); + assert(dictBase); + match = dictBase + matchIndex; + lowLimit = dictionary; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; + } + } else { /* single continuous memory segment */ + match = base + matchIndex; + } + forwardH = LZ4_hashPosition(forwardIp, tableType); + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + + DEBUGLOG(7, "candidate at pos=%u (offset=%u \n", matchIndex, current - matchIndex); + if ((dictIssue == dictSmall) && (matchIndex < prefixIdxLimit)) { continue; } /* match outside of valid area */ + assert(matchIndex < current); + if ( ((tableType != byU16) || (LZ4_DISTANCE_MAX < LZ4_DISTANCE_ABSOLUTE_MAX)) + && (matchIndex+LZ4_DISTANCE_MAX < current)) { + continue; + } /* too far */ + assert((current - matchIndex) <= LZ4_DISTANCE_MAX); /* match now expected within distance */ + + if (LZ4_read32(match) == LZ4_read32(ip)) { + if (maybe_extMem) offset = current - matchIndex; + break; /* match found */ + } + + } while(1); + } + + /* Catch up */ + filledIp = ip; + while (((ip>anchor) & (match > lowLimit)) && (unlikely(ip[-1]==match[-1]))) { ip--; match--; } + + /* Encode Literals */ + { unsigned const litLength = (unsigned)(ip - anchor); + token = op++; + if ((outputDirective == limitedOutput) && /* Check output buffer overflow */ + (unlikely(op + litLength + (2 + 1 + LASTLITERALS) + (litLength/255) > olimit)) ) { + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + if ((outputDirective == fillOutput) && + (unlikely(op + (litLength+240)/255 /* litlen */ + litLength /* literals */ + 2 /* offset */ + 1 /* token */ + MFLIMIT - MINMATCH /* min last literals so last match is <= end - MFLIMIT */ > olimit))) { + op--; + goto _last_literals; + } + if (litLength >= RUN_MASK) { + int len = (int)(litLength - RUN_MASK); + *token = (RUN_MASK<= 255 ; len-=255) *op++ = 255; + *op++ = (BYTE)len; + } + else *token = (BYTE)(litLength< olimit)) { + /* the match was too close to the end, rewind and go to last literals */ + op = token; + goto _last_literals; + } + + /* Encode Offset */ + if (maybe_extMem) { /* static test */ + DEBUGLOG(6, " with offset=%u (ext if > %i)", offset, (int)(ip - (const BYTE*)source)); + assert(offset <= LZ4_DISTANCE_MAX && offset > 0); + LZ4_writeLE16(op, (U16)offset); op+=2; + } else { + DEBUGLOG(6, " with offset=%u (same segment)", (U32)(ip - match)); + assert(ip-match <= LZ4_DISTANCE_MAX); + LZ4_writeLE16(op, (U16)(ip - match)); op+=2; + } + + /* Encode MatchLength */ + { unsigned matchCode; + + if ( (dictDirective==usingExtDict || dictDirective==usingDictCtx) + && (lowLimit==dictionary) /* match within extDict */ ) { + const BYTE* limit = ip + (dictEnd-match); + assert(dictEnd > match); + if (limit > matchlimit) limit = matchlimit; + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, limit); + ip += (size_t)matchCode + MINMATCH; + if (ip==limit) { + unsigned const more = LZ4_count(limit, (const BYTE*)source, matchlimit); + matchCode += more; + ip += more; + } + DEBUGLOG(6, " with matchLength=%u starting in extDict", matchCode+MINMATCH); + } else { + matchCode = LZ4_count(ip+MINMATCH, match+MINMATCH, matchlimit); + ip += (size_t)matchCode + MINMATCH; + DEBUGLOG(6, " with matchLength=%u", matchCode+MINMATCH); + } + + if ((outputDirective) && /* Check output buffer overflow */ + (unlikely(op + (1 + LASTLITERALS) + (matchCode+240)/255 > olimit)) ) { + if (outputDirective == fillOutput) { + /* Match description too long : reduce it */ + U32 newMatchCode = 15 /* in token */ - 1 /* to avoid needing a zero byte */ + ((U32)(olimit - op) - 1 - LASTLITERALS) * 255; + ip -= matchCode - newMatchCode; + assert(newMatchCode < matchCode); + matchCode = newMatchCode; + if (unlikely(ip <= filledIp)) { + /* We have already filled up to filledIp so if ip ends up less than filledIp + * we have positions in the hash table beyond the current position. This is + * a problem if we reuse the hash table. So we have to remove these positions + * from the hash table. + */ + const BYTE* ptr; + DEBUGLOG(5, "Clearing %u positions", (U32)(filledIp - ip)); + for (ptr = ip; ptr <= filledIp; ++ptr) { + U32 const h = LZ4_hashPosition(ptr, tableType); + LZ4_clearHash(h, cctx->hashTable, tableType); + } + } + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } + if (matchCode >= ML_MASK) { + *token += ML_MASK; + matchCode -= ML_MASK; + LZ4_write32(op, 0xFFFFFFFF); + while (matchCode >= 4*255) { + op+=4; + LZ4_write32(op, 0xFFFFFFFF); + matchCode -= 4*255; + } + op += matchCode / 255; + *op++ = (BYTE)(matchCode % 255); + } else + *token += (BYTE)(matchCode); + } + /* Ensure we have enough space for the last literals. */ + assert(!(outputDirective == fillOutput && op + 1 + LASTLITERALS > olimit)); + + anchor = ip; + + /* Test end of chunk */ + if (ip >= mflimitPlusOne) break; + + /* Fill table */ + LZ4_putPosition(ip-2, cctx->hashTable, tableType, base); + + /* Test next position */ + if (tableType == byPtr) { + + match = LZ4_getPosition(ip, cctx->hashTable, tableType, base); + LZ4_putPosition(ip, cctx->hashTable, tableType, base); + if ( (match+LZ4_DISTANCE_MAX >= ip) + && (LZ4_read32(match) == LZ4_read32(ip)) ) + { token=op++; *token=0; goto _next_match; } + + } else { /* byU32, byU16 */ + + U32 const h = LZ4_hashPosition(ip, tableType); + U32 const current = (U32)(ip-base); + U32 matchIndex = LZ4_getIndexOnHash(h, cctx->hashTable, tableType); + assert(matchIndex < current); + if (dictDirective == usingDictCtx) { + if (matchIndex < startIndex) { + /* there was no match, try the dictionary */ + matchIndex = LZ4_getIndexOnHash(h, dictCtx->hashTable, byU32); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + matchIndex += dictDelta; + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else if (dictDirective==usingExtDict) { + if (matchIndex < startIndex) { + assert(dictBase); + match = dictBase + matchIndex; + lowLimit = dictionary; /* required for match length counter */ + } else { + match = base + matchIndex; + lowLimit = (const BYTE*)source; /* required for match length counter */ + } + } else { /* single memory segment */ + match = base + matchIndex; + } + LZ4_putIndexOnHash(current, h, cctx->hashTable, tableType); + assert(matchIndex < current); + if ( ((dictIssue==dictSmall) ? (matchIndex >= prefixIdxLimit) : 1) + && (((tableType==byU16) && (LZ4_DISTANCE_MAX == LZ4_DISTANCE_ABSOLUTE_MAX)) ? 1 : (matchIndex+LZ4_DISTANCE_MAX >= current)) + && (LZ4_read32(match) == LZ4_read32(ip)) ) { + token=op++; + *token=0; + if (maybe_extMem) offset = current - matchIndex; + DEBUGLOG(6, "seq.start:%i, literals=%u, match.start:%i", + (int)(anchor-(const BYTE*)source), 0, (int)(ip-(const BYTE*)source)); + goto _next_match; + } + } + + /* Prepare next loop */ + forwardH = LZ4_hashPosition(++ip, tableType); + + } + +_last_literals: + /* Encode Last Literals */ + { size_t lastRun = (size_t)(iend - anchor); + if ( (outputDirective) && /* Check output buffer overflow */ + (op + lastRun + 1 + ((lastRun+255-RUN_MASK)/255) > olimit)) { + if (outputDirective == fillOutput) { + /* adapt lastRun to fill 'dst' */ + assert(olimit >= op); + lastRun = (size_t)(olimit-op) - 1/*token*/; + lastRun -= (lastRun + 256 - RUN_MASK) / 256; /*additional length tokens*/ + } else { + assert(outputDirective == limitedOutput); + return 0; /* cannot compress within `dst` budget. Stored indexes in hash table are nonetheless fine */ + } + } + DEBUGLOG(6, "Final literal run : %i literals", (int)lastRun); + if (lastRun >= RUN_MASK) { + size_t accumulator = lastRun - RUN_MASK; + *op++ = RUN_MASK << ML_BITS; + for(; accumulator >= 255 ; accumulator-=255) *op++ = 255; + *op++ = (BYTE) accumulator; + } else { + *op++ = (BYTE)(lastRun< 0); + DEBUGLOG(5, "LZ4_compress_generic: compressed %i bytes into %i bytes", inputSize, result); + return result; +} + +/** LZ4_compress_generic() : + * inlined, to ensure branches are decided at compilation time; + * takes care of src == (NULL, 0) + * and forward the rest to LZ4_compress_generic_validated */ +LZ4_FORCE_INLINE int LZ4_compress_generic( + LZ4_stream_t_internal* const cctx, + const char* const src, + char* const dst, + const int srcSize, + int *inputConsumed, /* only written when outputDirective == fillOutput */ + const int dstCapacity, + const limitedOutput_directive outputDirective, + const tableType_t tableType, + const dict_directive dictDirective, + const dictIssue_directive dictIssue, + const int acceleration) +{ + DEBUGLOG(5, "LZ4_compress_generic: srcSize=%i, dstCapacity=%i", + srcSize, dstCapacity); + + if ((U32)srcSize > (U32)LZ4_MAX_INPUT_SIZE) { return 0; } /* Unsupported srcSize, too large (or negative) */ + if (srcSize == 0) { /* src == NULL supported if srcSize == 0 */ + if (outputDirective != notLimited && dstCapacity <= 0) return 0; /* no output, can't write anything */ + DEBUGLOG(5, "Generating an empty block"); + assert(outputDirective == notLimited || dstCapacity >= 1); + assert(dst != NULL); + dst[0] = 0; + if (outputDirective == fillOutput) { + assert (inputConsumed != NULL); + *inputConsumed = 0; + } + return 1; + } + assert(src != NULL); + + return LZ4_compress_generic_validated(cctx, src, dst, srcSize, + inputConsumed, /* only written into if outputDirective == fillOutput */ + dstCapacity, outputDirective, + tableType, dictDirective, dictIssue, acceleration); +} + + +int LZ4_compress_fast_extState(void* state, const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + LZ4_stream_t_internal* const ctx = & LZ4_initStream(state, sizeof(LZ4_stream_t)) -> internal_donotuse; + assert(ctx != NULL); + if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; + if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; + if (maxOutputSize >= LZ4_compressBound(inputSize)) { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (inputSize < LZ4_64Klimit) { + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, byU16, noDict, noDictIssue, acceleration); + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)source > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(ctx, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + +/** + * LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. It is only safe + * to call if the state buffer is known to be correctly initialized already + * (see comment in lz4.h on LZ4_resetStream_fast() for a definition of + * "correctly initialized"). + */ +int LZ4_compress_fast_extState_fastReset(void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration) +{ + LZ4_stream_t_internal* ctx = &((LZ4_stream_t*)state)->internal_donotuse; + if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; + if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; + + if (dstCapacity >= LZ4_compressBound(srcSize)) { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, 0, notLimited, tableType, noDict, noDictIssue, acceleration); + } + } else { + if (srcSize < LZ4_64Klimit) { + const tableType_t tableType = byU16; + LZ4_prepareTable(ctx, srcSize, tableType); + if (ctx->currentOffset) { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, dictSmall, acceleration); + } else { + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } else { + const tableType_t tableType = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + LZ4_prepareTable(ctx, srcSize, tableType); + return LZ4_compress_generic(ctx, src, dst, srcSize, NULL, dstCapacity, limitedOutput, tableType, noDict, noDictIssue, acceleration); + } + } +} + + +int LZ4_compress_fast(const char* source, char* dest, int inputSize, int maxOutputSize, int acceleration) +{ + int result; +#if (LZ4_HEAPMODE) + LZ4_stream_t* ctxPtr = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctxPtr == NULL) return 0; +#else + LZ4_stream_t ctx; + LZ4_stream_t* const ctxPtr = &ctx; +#endif + result = LZ4_compress_fast_extState(ctxPtr, source, dest, inputSize, maxOutputSize, acceleration); + +#if (LZ4_HEAPMODE) + FREEMEM(ctxPtr); +#endif + return result; +} + + +int LZ4_compress_default(const char* src, char* dst, int srcSize, int maxOutputSize) +{ + return LZ4_compress_fast(src, dst, srcSize, maxOutputSize, 1); +} + + +/* Note!: This function leaves the stream in an unclean/broken state! + * It is not safe to subsequently use the same state with a _fastReset() or + * _continue() call without resetting it. */ +static int LZ4_compress_destSize_extState (LZ4_stream_t* state, const char* src, char* dst, int* srcSizePtr, int targetDstSize) +{ + void* const s = LZ4_initStream(state, sizeof (*state)); + assert(s != NULL); (void)s; + + if (targetDstSize >= LZ4_compressBound(*srcSizePtr)) { /* compression success is guaranteed */ + return LZ4_compress_fast_extState(state, src, dst, *srcSizePtr, targetDstSize, 1); + } else { + if (*srcSizePtr < LZ4_64Klimit) { + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, byU16, noDict, noDictIssue, 1); + } else { + tableType_t const addrMode = ((sizeof(void*)==4) && ((uptrval)src > LZ4_DISTANCE_MAX)) ? byPtr : byU32; + return LZ4_compress_generic(&state->internal_donotuse, src, dst, *srcSizePtr, srcSizePtr, targetDstSize, fillOutput, addrMode, noDict, noDictIssue, 1); + } } +} + + +int LZ4_compress_destSize(const char* src, char* dst, int* srcSizePtr, int targetDstSize) +{ +#if (LZ4_HEAPMODE) + LZ4_stream_t* ctx = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); /* malloc-calloc always properly aligned */ + if (ctx == NULL) return 0; +#else + LZ4_stream_t ctxBody; + LZ4_stream_t* ctx = &ctxBody; +#endif + + int result = LZ4_compress_destSize_extState(ctx, src, dst, srcSizePtr, targetDstSize); + +#if (LZ4_HEAPMODE) + FREEMEM(ctx); +#endif + return result; +} + + + +/*-****************************** +* Streaming functions +********************************/ + +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +LZ4_stream_t* LZ4_createStream(void) +{ + LZ4_stream_t* const lz4s = (LZ4_stream_t*)ALLOC(sizeof(LZ4_stream_t)); + LZ4_STATIC_ASSERT(sizeof(LZ4_stream_t) >= sizeof(LZ4_stream_t_internal)); + DEBUGLOG(4, "LZ4_createStream %p", lz4s); + if (lz4s == NULL) return NULL; + LZ4_initStream(lz4s, sizeof(*lz4s)); + return lz4s; +} +#endif + +static size_t LZ4_stream_t_alignment(void) +{ +#if LZ4_ALIGN_TEST + typedef struct { char c; LZ4_stream_t t; } t_a; + return sizeof(t_a) - sizeof(LZ4_stream_t); +#else + return 1; /* effectively disabled */ +#endif +} + +LZ4_stream_t* LZ4_initStream (void* buffer, size_t size) +{ + DEBUGLOG(5, "LZ4_initStream"); + if (buffer == NULL) { return NULL; } + if (size < sizeof(LZ4_stream_t)) { return NULL; } + if (!LZ4_isAligned(buffer, LZ4_stream_t_alignment())) return NULL; + MEM_INIT(buffer, 0, sizeof(LZ4_stream_t_internal)); + return (LZ4_stream_t*)buffer; +} + +/* resetStream is now deprecated, + * prefer initStream() which is more general */ +void LZ4_resetStream (LZ4_stream_t* LZ4_stream) +{ + DEBUGLOG(5, "LZ4_resetStream (ctx:%p)", LZ4_stream); + MEM_INIT(LZ4_stream, 0, sizeof(LZ4_stream_t_internal)); +} + +void LZ4_resetStream_fast(LZ4_stream_t* ctx) { + LZ4_prepareTable(&(ctx->internal_donotuse), 0, byU32); +} + +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +int LZ4_freeStream (LZ4_stream_t* LZ4_stream) +{ + if (!LZ4_stream) return 0; /* support free on NULL */ + DEBUGLOG(5, "LZ4_freeStream %p", LZ4_stream); + FREEMEM(LZ4_stream); + return (0); +} +#endif + + +#define HASH_UNIT sizeof(reg_t) +int LZ4_loadDict (LZ4_stream_t* LZ4_dict, const char* dictionary, int dictSize) +{ + LZ4_stream_t_internal* dict = &LZ4_dict->internal_donotuse; + const tableType_t tableType = byU32; + const BYTE* p = (const BYTE*)dictionary; + const BYTE* const dictEnd = p + dictSize; + const BYTE* base; + + DEBUGLOG(4, "LZ4_loadDict (%i bytes from %p into %p)", dictSize, dictionary, LZ4_dict); + + /* It's necessary to reset the context, + * and not just continue it with prepareTable() + * to avoid any risk of generating overflowing matchIndex + * when compressing using this dictionary */ + LZ4_resetStream(LZ4_dict); + + /* We always increment the offset by 64 KB, since, if the dict is longer, + * we truncate it to the last 64k, and if it's shorter, we still want to + * advance by a whole window length so we can provide the guarantee that + * there are only valid offsets in the window, which allows an optimization + * in LZ4_compress_fast_continue() where it uses noDictIssue even when the + * dictionary isn't a full 64k. */ + dict->currentOffset += 64 KB; + + if (dictSize < (int)HASH_UNIT) { + return 0; + } + + if ((dictEnd - p) > 64 KB) p = dictEnd - 64 KB; + base = dictEnd - dict->currentOffset; + dict->dictionary = p; + dict->dictSize = (U32)(dictEnd - p); + dict->tableType = (U32)tableType; + + while (p <= dictEnd-HASH_UNIT) { + LZ4_putPosition(p, dict->hashTable, tableType, base); + p+=3; + } + + return (int)dict->dictSize; +} + +void LZ4_attach_dictionary(LZ4_stream_t* workingStream, const LZ4_stream_t* dictionaryStream) +{ + const LZ4_stream_t_internal* dictCtx = (dictionaryStream == NULL) ? NULL : + &(dictionaryStream->internal_donotuse); + + DEBUGLOG(4, "LZ4_attach_dictionary (%p, %p, size %u)", + workingStream, dictionaryStream, + dictCtx != NULL ? dictCtx->dictSize : 0); + + if (dictCtx != NULL) { + /* If the current offset is zero, we will never look in the + * external dictionary context, since there is no value a table + * entry can take that indicate a miss. In that case, we need + * to bump the offset to something non-zero. + */ + if (workingStream->internal_donotuse.currentOffset == 0) { + workingStream->internal_donotuse.currentOffset = 64 KB; + } + + /* Don't actually attach an empty dictionary. + */ + if (dictCtx->dictSize == 0) { + dictCtx = NULL; + } + } + workingStream->internal_donotuse.dictCtx = dictCtx; +} + + +static void LZ4_renormDictT(LZ4_stream_t_internal* LZ4_dict, int nextSize) +{ + assert(nextSize >= 0); + if (LZ4_dict->currentOffset + (unsigned)nextSize > 0x80000000) { /* potential ptrdiff_t overflow (32-bits mode) */ + /* rescale hash table */ + U32 const delta = LZ4_dict->currentOffset - 64 KB; + const BYTE* dictEnd = LZ4_dict->dictionary + LZ4_dict->dictSize; + int i; + DEBUGLOG(4, "LZ4_renormDictT"); + for (i=0; ihashTable[i] < delta) LZ4_dict->hashTable[i]=0; + else LZ4_dict->hashTable[i] -= delta; + } + LZ4_dict->currentOffset = 64 KB; + if (LZ4_dict->dictSize > 64 KB) LZ4_dict->dictSize = 64 KB; + LZ4_dict->dictionary = dictEnd - LZ4_dict->dictSize; + } +} + + +int LZ4_compress_fast_continue (LZ4_stream_t* LZ4_stream, + const char* source, char* dest, + int inputSize, int maxOutputSize, + int acceleration) +{ + const tableType_t tableType = byU32; + LZ4_stream_t_internal* const streamPtr = &LZ4_stream->internal_donotuse; + const char* dictEnd = streamPtr->dictSize ? (const char*)streamPtr->dictionary + streamPtr->dictSize : NULL; + + DEBUGLOG(5, "LZ4_compress_fast_continue (inputSize=%i, dictSize=%u)", inputSize, streamPtr->dictSize); + + LZ4_renormDictT(streamPtr, inputSize); /* fix index overflow */ + if (acceleration < 1) acceleration = LZ4_ACCELERATION_DEFAULT; + if (acceleration > LZ4_ACCELERATION_MAX) acceleration = LZ4_ACCELERATION_MAX; + + /* invalidate tiny dictionaries */ + if ( (streamPtr->dictSize < 4) /* tiny dictionary : not enough for a hash */ + && (dictEnd != source) /* prefix mode */ + && (inputSize > 0) /* tolerance : don't lose history, in case next invocation would use prefix mode */ + && (streamPtr->dictCtx == NULL) /* usingDictCtx */ + ) { + DEBUGLOG(5, "LZ4_compress_fast_continue: dictSize(%u) at addr:%p is too small", streamPtr->dictSize, streamPtr->dictionary); + /* remove dictionary existence from history, to employ faster prefix mode */ + streamPtr->dictSize = 0; + streamPtr->dictionary = (const BYTE*)source; + dictEnd = source; + } + + /* Check overlapping input/dictionary space */ + { const char* const sourceEnd = source + inputSize; + if ((sourceEnd > (const char*)streamPtr->dictionary) && (sourceEnd < dictEnd)) { + streamPtr->dictSize = (U32)(dictEnd - sourceEnd); + if (streamPtr->dictSize > 64 KB) streamPtr->dictSize = 64 KB; + if (streamPtr->dictSize < 4) streamPtr->dictSize = 0; + streamPtr->dictionary = (const BYTE*)dictEnd - streamPtr->dictSize; + } + } + + /* prefix mode : source data follows dictionary */ + if (dictEnd == source) { + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) + return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, dictSmall, acceleration); + else + return LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, withPrefix64k, noDictIssue, acceleration); + } + + /* external dictionary mode */ + { int result; + if (streamPtr->dictCtx) { + /* We depend here on the fact that dictCtx'es (produced by + * LZ4_loadDict) guarantee that their tables contain no references + * to offsets between dictCtx->currentOffset - 64 KB and + * dictCtx->currentOffset - dictCtx->dictSize. This makes it safe + * to use noDictIssue even when the dict isn't a full 64 KB. + */ + if (inputSize > 4 KB) { + /* For compressing large blobs, it is faster to pay the setup + * cost to copy the dictionary's tables into the active context, + * so that the compression loop is only looking into one table. + */ + LZ4_memcpy(streamPtr, streamPtr->dictCtx, sizeof(*streamPtr)); + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingDictCtx, noDictIssue, acceleration); + } + } else { /* small data <= 4 KB */ + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, dictSmall, acceleration); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, inputSize, NULL, maxOutputSize, limitedOutput, tableType, usingExtDict, noDictIssue, acceleration); + } + } + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)inputSize; + return result; + } +} + + +/* Hidden debug function, to force-test external dictionary mode */ +int LZ4_compress_forceExtDict (LZ4_stream_t* LZ4_dict, const char* source, char* dest, int srcSize) +{ + LZ4_stream_t_internal* streamPtr = &LZ4_dict->internal_donotuse; + int result; + + LZ4_renormDictT(streamPtr, srcSize); + + if ((streamPtr->dictSize < 64 KB) && (streamPtr->dictSize < streamPtr->currentOffset)) { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, dictSmall, 1); + } else { + result = LZ4_compress_generic(streamPtr, source, dest, srcSize, NULL, 0, notLimited, byU32, usingExtDict, noDictIssue, 1); + } + + streamPtr->dictionary = (const BYTE*)source; + streamPtr->dictSize = (U32)srcSize; + + return result; +} + + +/*! LZ4_saveDict() : + * If previously compressed data block is not guaranteed to remain available at its memory location, + * save it into a safer place (char* safeBuffer). + * Note : no need to call LZ4_loadDict() afterwards, dictionary is immediately usable, + * one can therefore call LZ4_compress_fast_continue() right after. + * @return : saved dictionary size in bytes (necessarily <= dictSize), or 0 if error. + */ +int LZ4_saveDict (LZ4_stream_t* LZ4_dict, char* safeBuffer, int dictSize) +{ + LZ4_stream_t_internal* const dict = &LZ4_dict->internal_donotuse; + + DEBUGLOG(5, "LZ4_saveDict : dictSize=%i, safeBuffer=%p", dictSize, safeBuffer); + + if ((U32)dictSize > 64 KB) { dictSize = 64 KB; } /* useless to define a dictionary > 64 KB */ + if ((U32)dictSize > dict->dictSize) { dictSize = (int)dict->dictSize; } + + if (safeBuffer == NULL) assert(dictSize == 0); + if (dictSize > 0) { + const BYTE* const previousDictEnd = dict->dictionary + dict->dictSize; + assert(dict->dictionary); + LZ4_memmove(safeBuffer, previousDictEnd - dictSize, (size_t)dictSize); + } + + dict->dictionary = (const BYTE*)safeBuffer; + dict->dictSize = (U32)dictSize; + + return dictSize; +} + + + +/*-******************************* + * Decompression functions + ********************************/ + +typedef enum { decode_full_block = 0, partial_decode = 1 } earlyEnd_directive; + +#undef MIN +#define MIN(a,b) ( (a) < (b) ? (a) : (b) ) + + +/* variant for decompress_unsafe() + * does not know end of input + * presumes input is well formed + * note : will consume at least one byte */ +size_t read_long_length_no_check(const BYTE** pp) +{ + size_t b, l = 0; + do { b = **pp; (*pp)++; l += b; } while (b==255); + DEBUGLOG(6, "read_long_length_no_check: +length=%zu using %zu input bytes", l, l/255 + 1) + return l; +} + +/* core decoder variant for LZ4_decompress_fast*() + * for legacy support only : these entry points are deprecated. + * - Presumes input is correctly formed (no defense vs malformed inputs) + * - Does not know input size (presume input buffer is "large enough") + * - Decompress a full block (only) + * @return : nb of bytes read from input. + * Note : this variant is not optimized for speed, just for maintenance. + * the goal is to remove support of decompress_fast*() variants by v2.0 +**/ +LZ4_FORCE_INLINE int +LZ4_decompress_unsafe_generic( + const BYTE* const istart, + BYTE* const ostart, + int decompressedSize, + + size_t prefixSize, + const BYTE* const dictStart, /* only if dict==usingExtDict */ + const size_t dictSize /* note: =0 if dictStart==NULL */ + ) +{ + const BYTE* ip = istart; + BYTE* op = (BYTE*)ostart; + BYTE* const oend = ostart + decompressedSize; + const BYTE* const prefixStart = ostart - prefixSize; + + DEBUGLOG(5, "LZ4_decompress_unsafe_generic"); + if (dictStart == NULL) assert(dictSize == 0); + + while (1) { + /* start new sequence */ + unsigned token = *ip++; + + /* literals */ + { size_t ll = token >> ML_BITS; + if (ll==15) { + /* long literal length */ + ll += read_long_length_no_check(&ip); + } + if ((size_t)(oend-op) < ll) return -1; /* output buffer overflow */ + LZ4_memmove(op, ip, ll); /* support in-place decompression */ + op += ll; + ip += ll; + if ((size_t)(oend-op) < MFLIMIT) { + if (op==oend) break; /* end of block */ + DEBUGLOG(5, "invalid: literals end at distance %zi from end of block", oend-op); + /* incorrect end of block : + * last match must start at least MFLIMIT==12 bytes before end of output block */ + return -1; + } } + + /* match */ + { size_t ml = token & 15; + size_t const offset = LZ4_readLE16(ip); + ip+=2; + + if (ml==15) { + /* long literal length */ + ml += read_long_length_no_check(&ip); + } + ml += MINMATCH; + + if ((size_t)(oend-op) < ml) return -1; /* output buffer overflow */ + + { const BYTE* match = op - offset; + + /* out of range */ + if (offset > (size_t)(op - prefixStart) + dictSize) { + DEBUGLOG(6, "offset out of range"); + return -1; + } + + /* check special case : extDict */ + if (offset > (size_t)(op - prefixStart)) { + /* extDict scenario */ + const BYTE* const dictEnd = dictStart + dictSize; + const BYTE* extMatch = dictEnd - (offset - (size_t)(op-prefixStart)); + size_t const extml = (size_t)(dictEnd - extMatch); + if (extml > ml) { + /* match entirely within extDict */ + LZ4_memmove(op, extMatch, ml); + op += ml; + ml = 0; + } else { + /* match split between extDict & prefix */ + LZ4_memmove(op, extMatch, extml); + op += extml; + ml -= extml; + } + match = prefixStart; + } + + /* match copy - slow variant, supporting overlap copy */ + { size_t u; + for (u=0; u= ipmax before start of loop. Returns initial_error if so. + * @error (output) - error code. Must be set to 0 before call. +**/ +typedef size_t Rvl_t; +static const Rvl_t rvl_error = (Rvl_t)(-1); +LZ4_FORCE_INLINE Rvl_t +read_variable_length(const BYTE** ip, const BYTE* ilimit, + int initial_check) +{ + Rvl_t s, length = 0; + assert(ip != NULL); + assert(*ip != NULL); + assert(ilimit != NULL); + if (initial_check && unlikely((*ip) >= ilimit)) { /* read limit reached */ + return rvl_error; + } + do { + s = **ip; + (*ip)++; + length += s; + if (unlikely((*ip) > ilimit)) { /* read limit reached */ + return rvl_error; + } + /* accumulator overflow detection (32-bit mode only) */ + if ((sizeof(length)<8) && unlikely(length > ((Rvl_t)(-1)/2)) ) { + return rvl_error; + } + } while (s==255); + + return length; +} + +/*! LZ4_decompress_generic() : + * This generic decompression function covers all use cases. + * It shall be instantiated several times, using different sets of directives. + * Note that it is important for performance that this function really get inlined, + * in order to remove useless branches during compilation optimization. + */ +LZ4_FORCE_INLINE int +LZ4_decompress_generic( + const char* const src, + char* const dst, + int srcSize, + int outputSize, /* If endOnInput==endOnInputSize, this value is `dstCapacity` */ + + earlyEnd_directive partialDecoding, /* full, partial */ + dict_directive dict, /* noDict, withPrefix64k, usingExtDict */ + const BYTE* const lowPrefix, /* always <= dst, == dst when no prefix */ + const BYTE* const dictStart, /* only if dict==usingExtDict */ + const size_t dictSize /* note : = 0 if noDict */ + ) +{ + if ((src == NULL) || (outputSize < 0)) { return -1; } + + { const BYTE* ip = (const BYTE*) src; + const BYTE* const iend = ip + srcSize; + + BYTE* op = (BYTE*) dst; + BYTE* const oend = op + outputSize; + BYTE* cpy; + + const BYTE* const dictEnd = (dictStart == NULL) ? NULL : dictStart + dictSize; + + const int checkOffset = (dictSize < (int)(64 KB)); + + + /* Set up the "end" pointers for the shortcut. */ + const BYTE* const shortiend = iend - 14 /*maxLL*/ - 2 /*offset*/; + const BYTE* const shortoend = oend - 14 /*maxLL*/ - 18 /*maxML*/; + + const BYTE* match; + size_t offset; + unsigned token; + size_t length; + + + DEBUGLOG(5, "LZ4_decompress_generic (srcSize:%i, dstSize:%i)", srcSize, outputSize); + + /* Special cases */ + assert(lowPrefix <= op); + if (unlikely(outputSize==0)) { + /* Empty output buffer */ + if (partialDecoding) return 0; + return ((srcSize==1) && (*ip==0)) ? 0 : -1; + } + if (unlikely(srcSize==0)) { return -1; } + + /* LZ4_FAST_DEC_LOOP: + * designed for modern OoO performance cpus, + * where copying reliably 32-bytes is preferable to an unpredictable branch. + * note : fast loop may show a regression for some client arm chips. */ +#if LZ4_FAST_DEC_LOOP + if ((oend - op) < FASTLOOP_SAFE_DISTANCE) { + DEBUGLOG(6, "skip fast decode loop"); + goto safe_decode; + } + + /* Fast loop : decode sequences as long as output < oend-FASTLOOP_SAFE_DISTANCE */ + while (1) { + /* Main fastloop assertion: We can always wildcopy FASTLOOP_SAFE_DISTANCE */ + assert(oend - op >= FASTLOOP_SAFE_DISTANCE); + assert(ip < iend); + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + /* decode literal length */ + if (length == RUN_MASK) { + size_t const addl = read_variable_length(&ip, iend-RUN_MASK, 1); + if (addl == rvl_error) { goto _output_error; } + length += addl; + if (unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */ + if (unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ + + /* copy literals */ + cpy = op+length; + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if ((cpy>oend-32) || (ip+length>iend-32)) { goto safe_literal_copy; } + LZ4_wildCopy32(op, ip, cpy); + ip += length; op = cpy; + } else { + cpy = op+length; + DEBUGLOG(7, "copy %u bytes in a 16-bytes stripe", (unsigned)length); + /* We don't need to check oend, since we check it once for each loop below */ + if (ip > iend-(16 + 1/*max lit + offset + nextToken*/)) { goto safe_literal_copy; } + /* Literals can only be <= 14, but hope compilers optimize better when copy by a register size */ + LZ4_memcpy(op, ip, 16); + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + assert(match <= op); /* overflow check */ + + /* get matchlength */ + length = token & ML_MASK; + + if (length == ML_MASK) { + size_t const addl = read_variable_length(&ip, iend - LASTLITERALS + 1, 0); + if (addl == rvl_error) { goto _output_error; } + length += addl; + length += MINMATCH; + if (unlikely((uptrval)(op)+length<(uptrval)op)) { goto _output_error; } /* overflow detection */ + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */ + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } + } else { + length += MINMATCH; + if (op + length >= oend - FASTLOOP_SAFE_DISTANCE) { + goto safe_match_copy; + } + + /* Fastpath check: skip LZ4_wildCopy32 when true */ + if ((dict == withPrefix64k) || (match >= lowPrefix)) { + if (offset >= 8) { + assert(match >= lowPrefix); + assert(match <= op); + assert(op + 18 <= oend); + + LZ4_memcpy(op, match, 8); + LZ4_memcpy(op+8, match+8, 8); + LZ4_memcpy(op+16, match+16, 2); + op += length; + continue; + } } } + + if (checkOffset && (unlikely(match + dictSize < lowPrefix))) { goto _output_error; } /* Error : offset outside buffers */ + /* match starting within external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + assert(dictEnd != NULL); + if (unlikely(op+length > oend-LASTLITERALS)) { + if (partialDecoding) { + DEBUGLOG(7, "partialDecoding: dictionary match, close to dstEnd"); + length = MIN(length, (size_t)(oend-op)); + } else { + goto _output_error; /* end-of-block condition violated */ + } } + + if (length <= (size_t)(lowPrefix-match)) { + /* match fits entirely within external dictionary : just copy */ + LZ4_memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + LZ4_memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) { *op++ = *copyFrom++; } + } else { + LZ4_memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + + /* copy match within block */ + cpy = op + length; + + assert((op <= oend) && (oend-op >= 32)); + if (unlikely(offset<16)) { + LZ4_memcpy_using_offset(op, match, cpy, offset); + } else { + LZ4_wildCopy32(op, match, cpy); + } + + op = cpy; /* wildcopy correction */ + } + safe_decode: +#endif + + /* Main Loop : decode remaining sequences where output < FASTLOOP_SAFE_DISTANCE */ + while (1) { + assert(ip < iend); + token = *ip++; + length = token >> ML_BITS; /* literal length */ + + /* A two-stage shortcut for the most common case: + * 1) If the literal length is 0..14, and there is enough space, + * enter the shortcut and copy 16 bytes on behalf of the literals + * (in the fast mode, only 8 bytes can be safely copied this way). + * 2) Further if the match length is 4..18, copy 18 bytes in a similar + * manner; but we ensure that there's enough space in the output for + * those 18 bytes earlier, upon entering the shortcut (in other words, + * there is a combined check for both stages). + */ + if ( (length != RUN_MASK) + /* strictly "less than" on input, to re-enter the loop with at least one byte */ + && likely((ip < shortiend) & (op <= shortoend)) ) { + /* Copy the literals */ + LZ4_memcpy(op, ip, 16); + op += length; ip += length; + + /* The second stage: prepare for match copying, decode full info. + * If it doesn't work out, the info won't be wasted. */ + length = token & ML_MASK; /* match length */ + offset = LZ4_readLE16(ip); ip += 2; + match = op - offset; + assert(match <= op); /* check overflow */ + + /* Do not deal with overlapping matches. */ + if ( (length != ML_MASK) + && (offset >= 8) + && (dict==withPrefix64k || match >= lowPrefix) ) { + /* Copy the match. */ + LZ4_memcpy(op + 0, match + 0, 8); + LZ4_memcpy(op + 8, match + 8, 8); + LZ4_memcpy(op +16, match +16, 2); + op += length + MINMATCH; + /* Both stages worked, load the next token. */ + continue; + } + + /* The second stage didn't work out, but the info is ready. + * Propel it right to the point of match copying. */ + goto _copy_match; + } + + /* decode literal length */ + if (length == RUN_MASK) { + size_t const addl = read_variable_length(&ip, iend-RUN_MASK, 1); + if (addl == rvl_error) { goto _output_error; } + length += addl; + if (unlikely((uptrval)(op)+length<(uptrval)(op))) { goto _output_error; } /* overflow detection */ + if (unlikely((uptrval)(ip)+length<(uptrval)(ip))) { goto _output_error; } /* overflow detection */ + } + + /* copy literals */ + cpy = op+length; +#if LZ4_FAST_DEC_LOOP + safe_literal_copy: +#endif + LZ4_STATIC_ASSERT(MFLIMIT >= WILDCOPYLENGTH); + if ((cpy>oend-MFLIMIT) || (ip+length>iend-(2+1+LASTLITERALS))) { + /* We've either hit the input parsing restriction or the output parsing restriction. + * In the normal scenario, decoding a full block, it must be the last sequence, + * otherwise it's an error (invalid input or dimensions). + * In partialDecoding scenario, it's necessary to ensure there is no buffer overflow. + */ + if (partialDecoding) { + /* Since we are partial decoding we may be in this block because of the output parsing + * restriction, which is not valid since the output buffer is allowed to be undersized. + */ + DEBUGLOG(7, "partialDecoding: copying literals, close to input or output end") + DEBUGLOG(7, "partialDecoding: literal length = %u", (unsigned)length); + DEBUGLOG(7, "partialDecoding: remaining space in dstBuffer : %i", (int)(oend - op)); + DEBUGLOG(7, "partialDecoding: remaining space in srcBuffer : %i", (int)(iend - ip)); + /* Finishing in the middle of a literals segment, + * due to lack of input. + */ + if (ip+length > iend) { + length = (size_t)(iend-ip); + cpy = op + length; + } + /* Finishing in the middle of a literals segment, + * due to lack of output space. + */ + if (cpy > oend) { + cpy = oend; + assert(op<=oend); + length = (size_t)(oend-op); + } + } else { + /* We must be on the last sequence (or invalid) because of the parsing limitations + * so check that we exactly consume the input and don't overrun the output buffer. + */ + if ((ip+length != iend) || (cpy > oend)) { + DEBUGLOG(6, "should have been last run of literals") + DEBUGLOG(6, "ip(%p) + length(%i) = %p != iend (%p)", ip, (int)length, ip+length, iend); + DEBUGLOG(6, "or cpy(%p) > oend(%p)", cpy, oend); + goto _output_error; + } + } + LZ4_memmove(op, ip, length); /* supports overlapping memory regions, for in-place decompression scenarios */ + ip += length; + op += length; + /* Necessarily EOF when !partialDecoding. + * When partialDecoding, it is EOF if we've either + * filled the output buffer or + * can't proceed with reading an offset for following match. + */ + if (!partialDecoding || (cpy == oend) || (ip >= (iend-2))) { + break; + } + } else { + LZ4_wildCopy8(op, ip, cpy); /* can overwrite up to 8 bytes beyond cpy */ + ip += length; op = cpy; + } + + /* get offset */ + offset = LZ4_readLE16(ip); ip+=2; + match = op - offset; + + /* get matchlength */ + length = token & ML_MASK; + + _copy_match: + if (length == ML_MASK) { + size_t const addl = read_variable_length(&ip, iend - LASTLITERALS + 1, 0); + if (addl == rvl_error) { goto _output_error; } + length += addl; + if (unlikely((uptrval)(op)+length<(uptrval)op)) goto _output_error; /* overflow detection */ + } + length += MINMATCH; + +#if LZ4_FAST_DEC_LOOP + safe_match_copy: +#endif + if ((checkOffset) && (unlikely(match + dictSize < lowPrefix))) goto _output_error; /* Error : offset outside buffers */ + /* match starting within external dictionary */ + if ((dict==usingExtDict) && (match < lowPrefix)) { + assert(dictEnd != NULL); + if (unlikely(op+length > oend-LASTLITERALS)) { + if (partialDecoding) length = MIN(length, (size_t)(oend-op)); + else goto _output_error; /* doesn't respect parsing restriction */ + } + + if (length <= (size_t)(lowPrefix-match)) { + /* match fits entirely within external dictionary : just copy */ + LZ4_memmove(op, dictEnd - (lowPrefix-match), length); + op += length; + } else { + /* match stretches into both external dictionary and current block */ + size_t const copySize = (size_t)(lowPrefix - match); + size_t const restSize = length - copySize; + LZ4_memcpy(op, dictEnd - copySize, copySize); + op += copySize; + if (restSize > (size_t)(op - lowPrefix)) { /* overlap copy */ + BYTE* const endOfMatch = op + restSize; + const BYTE* copyFrom = lowPrefix; + while (op < endOfMatch) *op++ = *copyFrom++; + } else { + LZ4_memcpy(op, lowPrefix, restSize); + op += restSize; + } } + continue; + } + assert(match >= lowPrefix); + + /* copy match within block */ + cpy = op + length; + + /* partialDecoding : may end anywhere within the block */ + assert(op<=oend); + if (partialDecoding && (cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { + size_t const mlen = MIN(length, (size_t)(oend-op)); + const BYTE* const matchEnd = match + mlen; + BYTE* const copyEnd = op + mlen; + if (matchEnd > op) { /* overlap copy */ + while (op < copyEnd) { *op++ = *match++; } + } else { + LZ4_memcpy(op, match, mlen); + } + op = copyEnd; + if (op == oend) { break; } + continue; + } + + if (unlikely(offset<8)) { + LZ4_write32(op, 0); /* silence msan warning when offset==0 */ + op[0] = match[0]; + op[1] = match[1]; + op[2] = match[2]; + op[3] = match[3]; + match += inc32table[offset]; + LZ4_memcpy(op+4, match, 4); + match -= dec64table[offset]; + } else { + LZ4_memcpy(op, match, 8); + match += 8; + } + op += 8; + + if (unlikely(cpy > oend-MATCH_SAFEGUARD_DISTANCE)) { + BYTE* const oCopyLimit = oend - (WILDCOPYLENGTH-1); + if (cpy > oend-LASTLITERALS) { goto _output_error; } /* Error : last LASTLITERALS bytes must be literals (uncompressed) */ + if (op < oCopyLimit) { + LZ4_wildCopy8(op, match, oCopyLimit); + match += oCopyLimit - op; + op = oCopyLimit; + } + while (op < cpy) { *op++ = *match++; } + } else { + LZ4_memcpy(op, match, 8); + if (length > 16) { LZ4_wildCopy8(op+8, match+8, cpy); } + } + op = cpy; /* wildcopy correction */ + } + + /* end of decoding */ + DEBUGLOG(5, "decoded %i bytes", (int) (((char*)op)-dst)); + return (int) (((char*)op)-dst); /* Nb of output bytes decoded */ + + /* Overflow error detected */ + _output_error: + return (int) (-(((const char*)ip)-src))-1; + } +} + + +/*===== Instantiate the API decoding functions. =====*/ + +LZ4_FORCE_O2 +int LZ4_decompress_safe(const char* source, char* dest, int compressedSize, int maxDecompressedSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxDecompressedSize, + decode_full_block, noDict, + (BYTE*)dest, NULL, 0); +} + +LZ4_FORCE_O2 +int LZ4_decompress_safe_partial(const char* src, char* dst, int compressedSize, int targetOutputSize, int dstCapacity) +{ + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(src, dst, compressedSize, dstCapacity, + partial_decode, + noDict, (BYTE*)dst, NULL, 0); +} + +LZ4_FORCE_O2 +int LZ4_decompress_fast(const char* source, char* dest, int originalSize) +{ + DEBUGLOG(5, "LZ4_decompress_fast"); + return LZ4_decompress_unsafe_generic( + (const BYTE*)source, (BYTE*)dest, originalSize, + 0, NULL, 0); +} + +/*===== Instantiate a few more decoding cases, used more than once. =====*/ + +LZ4_FORCE_O2 /* Exported, an obsolete API function. */ +int LZ4_decompress_safe_withPrefix64k(const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + decode_full_block, withPrefix64k, + (BYTE*)dest - 64 KB, NULL, 0); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_safe_partial_withPrefix64k(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity) +{ + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity, + partial_decode, withPrefix64k, + (BYTE*)dest - 64 KB, NULL, 0); +} + +/* Another obsolete API function, paired with the previous one. */ +int LZ4_decompress_fast_withPrefix64k(const char* source, char* dest, int originalSize) +{ + return LZ4_decompress_unsafe_generic( + (const BYTE*)source, (BYTE*)dest, originalSize, + 64 KB, NULL, 0); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_safe_withSmallPrefix(const char* source, char* dest, int compressedSize, int maxOutputSize, + size_t prefixSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + decode_full_block, noDict, + (BYTE*)dest-prefixSize, NULL, 0); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_safe_partial_withSmallPrefix(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity, + size_t prefixSize) +{ + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity, + partial_decode, noDict, + (BYTE*)dest-prefixSize, NULL, 0); +} + +LZ4_FORCE_O2 +int LZ4_decompress_safe_forceExtDict(const char* source, char* dest, + int compressedSize, int maxOutputSize, + const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + decode_full_block, usingExtDict, + (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + +LZ4_FORCE_O2 +int LZ4_decompress_safe_partial_forceExtDict(const char* source, char* dest, + int compressedSize, int targetOutputSize, int dstCapacity, + const void* dictStart, size_t dictSize) +{ + dstCapacity = MIN(targetOutputSize, dstCapacity); + return LZ4_decompress_generic(source, dest, compressedSize, dstCapacity, + partial_decode, usingExtDict, + (BYTE*)dest, (const BYTE*)dictStart, dictSize); +} + +LZ4_FORCE_O2 +static int LZ4_decompress_fast_extDict(const char* source, char* dest, int originalSize, + const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_unsafe_generic( + (const BYTE*)source, (BYTE*)dest, originalSize, + 0, (const BYTE*)dictStart, dictSize); +} + +/* The "double dictionary" mode, for use with e.g. ring buffers: the first part + * of the dictionary is passed as prefix, and the second via dictStart + dictSize. + * These routines are used only once, in LZ4_decompress_*_continue(). + */ +LZ4_FORCE_INLINE +int LZ4_decompress_safe_doubleDict(const char* source, char* dest, int compressedSize, int maxOutputSize, + size_t prefixSize, const void* dictStart, size_t dictSize) +{ + return LZ4_decompress_generic(source, dest, compressedSize, maxOutputSize, + decode_full_block, usingExtDict, + (BYTE*)dest-prefixSize, (const BYTE*)dictStart, dictSize); +} + +/*===== streaming decompression functions =====*/ + +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +LZ4_streamDecode_t* LZ4_createStreamDecode(void) +{ + LZ4_STATIC_ASSERT(sizeof(LZ4_streamDecode_t) >= sizeof(LZ4_streamDecode_t_internal)); + return (LZ4_streamDecode_t*) ALLOC_AND_ZERO(sizeof(LZ4_streamDecode_t)); +} + +int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream) +{ + if (LZ4_stream == NULL) { return 0; } /* support free on NULL */ + FREEMEM(LZ4_stream); + return 0; +} +#endif + +/*! LZ4_setStreamDecode() : + * Use this function to instruct where to find the dictionary. + * This function is not necessary if previous data is still available where it was decoded. + * Loading a size of 0 is allowed (same effect as no dictionary). + * @return : 1 if OK, 0 if error + */ +int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + lz4sd->prefixSize = (size_t)dictSize; + if (dictSize) { + assert(dictionary != NULL); + lz4sd->prefixEnd = (const BYTE*) dictionary + dictSize; + } else { + lz4sd->prefixEnd = (const BYTE*) dictionary; + } + lz4sd->externalDict = NULL; + lz4sd->extDictSize = 0; + return 1; +} + +/*! LZ4_decoderRingBufferSize() : + * when setting a ring buffer for streaming decompression (optional scenario), + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * Note : in a ring buffer scenario, + * blocks are presumed decompressed next to each other. + * When not enough space remains for next block (remainingSize < maxBlockSize), + * decoding resumes from beginning of ring buffer. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +int LZ4_decoderRingBufferSize(int maxBlockSize) +{ + if (maxBlockSize < 0) return 0; + if (maxBlockSize > LZ4_MAX_INPUT_SIZE) return 0; + if (maxBlockSize < 16) maxBlockSize = 16; + return LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize); +} + +/* +*_continue() : + These decoding functions allow decompression of multiple blocks in "streaming" mode. + Previously decoded blocks must still be available at the memory position where they were decoded. + If it's not possible, save the relevant part of decoded data into a safe buffer, + and indicate where it stands using LZ4_setStreamDecode() +*/ +LZ4_FORCE_O2 +int LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* source, char* dest, int compressedSize, int maxOutputSize) +{ + LZ4_streamDecode_t_internal* lz4sd = &LZ4_streamDecode->internal_donotuse; + int result; + + if (lz4sd->prefixSize == 0) { + /* The first call, no dictionary yet. */ + assert(lz4sd->extDictSize == 0); + result = LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } else if (lz4sd->prefixEnd == (BYTE*)dest) { + /* They're rolling the current segment. */ + if (lz4sd->prefixSize >= 64 KB - 1) + result = LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); + else if (lz4sd->extDictSize == 0) + result = LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, + lz4sd->prefixSize); + else + result = LZ4_decompress_safe_doubleDict(source, dest, compressedSize, maxOutputSize, + lz4sd->prefixSize, lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += (size_t)result; + lz4sd->prefixEnd += result; + } else { + /* The buffer wraps around, or they're switching to another buffer. */ + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, + lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)result; + lz4sd->prefixEnd = (BYTE*)dest + result; + } + + return result; +} + +LZ4_FORCE_O2 int +LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, + const char* source, char* dest, int originalSize) +{ + LZ4_streamDecode_t_internal* const lz4sd = + (assert(LZ4_streamDecode!=NULL), &LZ4_streamDecode->internal_donotuse); + int result; + + DEBUGLOG(5, "LZ4_decompress_fast_continue (toDecodeSize=%i)", originalSize); + assert(originalSize >= 0); + + if (lz4sd->prefixSize == 0) { + DEBUGLOG(5, "first invocation : no prefix nor extDict"); + assert(lz4sd->extDictSize == 0); + result = LZ4_decompress_fast(source, dest, originalSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } else if (lz4sd->prefixEnd == (BYTE*)dest) { + DEBUGLOG(5, "continue using existing prefix"); + result = LZ4_decompress_unsafe_generic( + (const BYTE*)source, (BYTE*)dest, originalSize, + lz4sd->prefixSize, + lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize += (size_t)originalSize; + lz4sd->prefixEnd += originalSize; + } else { + DEBUGLOG(5, "prefix becomes extDict"); + lz4sd->extDictSize = lz4sd->prefixSize; + lz4sd->externalDict = lz4sd->prefixEnd - lz4sd->extDictSize; + result = LZ4_decompress_fast_extDict(source, dest, originalSize, + lz4sd->externalDict, lz4sd->extDictSize); + if (result <= 0) return result; + lz4sd->prefixSize = (size_t)originalSize; + lz4sd->prefixEnd = (BYTE*)dest + originalSize; + } + + return result; +} + + +/* +Advanced decoding functions : +*_usingDict() : + These decoding functions work the same as "_continue" ones, + the dictionary must be explicitly provided within parameters +*/ + +int LZ4_decompress_safe_usingDict(const char* source, char* dest, int compressedSize, int maxOutputSize, const char* dictStart, int dictSize) +{ + if (dictSize==0) + return LZ4_decompress_safe(source, dest, compressedSize, maxOutputSize); + if (dictStart+dictSize == dest) { + if (dictSize >= 64 KB - 1) { + return LZ4_decompress_safe_withPrefix64k(source, dest, compressedSize, maxOutputSize); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_withSmallPrefix(source, dest, compressedSize, maxOutputSize, (size_t)dictSize); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_forceExtDict(source, dest, compressedSize, maxOutputSize, dictStart, (size_t)dictSize); +} + +int LZ4_decompress_safe_partial_usingDict(const char* source, char* dest, int compressedSize, int targetOutputSize, int dstCapacity, const char* dictStart, int dictSize) +{ + if (dictSize==0) + return LZ4_decompress_safe_partial(source, dest, compressedSize, targetOutputSize, dstCapacity); + if (dictStart+dictSize == dest) { + if (dictSize >= 64 KB - 1) { + return LZ4_decompress_safe_partial_withPrefix64k(source, dest, compressedSize, targetOutputSize, dstCapacity); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_partial_withSmallPrefix(source, dest, compressedSize, targetOutputSize, dstCapacity, (size_t)dictSize); + } + assert(dictSize >= 0); + return LZ4_decompress_safe_partial_forceExtDict(source, dest, compressedSize, targetOutputSize, dstCapacity, dictStart, (size_t)dictSize); +} + +int LZ4_decompress_fast_usingDict(const char* source, char* dest, int originalSize, const char* dictStart, int dictSize) +{ + if (dictSize==0 || dictStart+dictSize == dest) + return LZ4_decompress_unsafe_generic( + (const BYTE*)source, (BYTE*)dest, originalSize, + (size_t)dictSize, NULL, 0); + assert(dictSize >= 0); + return LZ4_decompress_fast_extDict(source, dest, originalSize, dictStart, (size_t)dictSize); +} + + +/*=************************************************* +* Obsolete Functions +***************************************************/ +/* obsolete compression functions */ +int LZ4_compress_limitedOutput(const char* source, char* dest, int inputSize, int maxOutputSize) +{ + return LZ4_compress_default(source, dest, inputSize, maxOutputSize); +} +int LZ4_compress(const char* src, char* dest, int srcSize) +{ + return LZ4_compress_default(src, dest, srcSize, LZ4_compressBound(srcSize)); +} +int LZ4_compress_limitedOutput_withState (void* state, const char* src, char* dst, int srcSize, int dstSize) +{ + return LZ4_compress_fast_extState(state, src, dst, srcSize, dstSize, 1); +} +int LZ4_compress_withState (void* state, const char* src, char* dst, int srcSize) +{ + return LZ4_compress_fast_extState(state, src, dst, srcSize, LZ4_compressBound(srcSize), 1); +} +int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_stream, const char* src, char* dst, int srcSize, int dstCapacity) +{ + return LZ4_compress_fast_continue(LZ4_stream, src, dst, srcSize, dstCapacity, 1); +} +int LZ4_compress_continue (LZ4_stream_t* LZ4_stream, const char* source, char* dest, int inputSize) +{ + return LZ4_compress_fast_continue(LZ4_stream, source, dest, inputSize, LZ4_compressBound(inputSize), 1); +} + +/* +These decompression functions are deprecated and should no longer be used. +They are only provided here for compatibility with older user programs. +- LZ4_uncompress is totally equivalent to LZ4_decompress_fast +- LZ4_uncompress_unknownOutputSize is totally equivalent to LZ4_decompress_safe +*/ +int LZ4_uncompress (const char* source, char* dest, int outputSize) +{ + return LZ4_decompress_fast(source, dest, outputSize); +} +int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize) +{ + return LZ4_decompress_safe(source, dest, isize, maxOutputSize); +} + +/* Obsolete Streaming functions */ + +int LZ4_sizeofStreamState(void) { return sizeof(LZ4_stream_t); } + +int LZ4_resetStreamState(void* state, char* inputBuffer) +{ + (void)inputBuffer; + LZ4_resetStream((LZ4_stream_t*)state); + return 0; +} + +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +void* LZ4_create (char* inputBuffer) +{ + (void)inputBuffer; + return LZ4_createStream(); +} +#endif + +char* LZ4_slideInputBuffer (void* state) +{ + /* avoid const char * -> char * conversion warning */ + return (char *)(uptrval)((LZ4_stream_t*)state)->internal_donotuse.dictionary; +} + +#endif /* LZ4_COMMONDEFS_ONLY */ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/lz4.h b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/lz4.h new file mode 100644 index 00000000..491c6087 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/lz4.h @@ -0,0 +1,842 @@ +/* + * LZ4 - Fast LZ compression algorithm + * Header File + * Copyright (C) 2011-2020, Yann Collet. + + BSD 2-Clause License (http://www.opensource.org/licenses/bsd-license.php) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + You can contact the author at : + - LZ4 homepage : http://www.lz4.org + - LZ4 source repository : https://github.com/lz4/lz4 +*/ +#if defined (__cplusplus) +extern "C" { +#endif + +#ifndef LZ4_H_2983827168210 +#define LZ4_H_2983827168210 + +/* --- Dependency --- */ +#include /* size_t */ + + +/** + Introduction + + LZ4 is lossless compression algorithm, providing compression speed >500 MB/s per core, + scalable with multi-cores CPU. It features an extremely fast decoder, with speed in + multiple GB/s per core, typically reaching RAM speed limits on multi-core systems. + + The LZ4 compression library provides in-memory compression and decompression functions. + It gives full buffer control to user. + Compression can be done in: + - a single step (described as Simple Functions) + - a single step, reusing a context (described in Advanced Functions) + - unbounded multiple steps (described as Streaming compression) + + lz4.h generates and decodes LZ4-compressed blocks (doc/lz4_Block_format.md). + Decompressing such a compressed block requires additional metadata. + Exact metadata depends on exact decompression function. + For the typical case of LZ4_decompress_safe(), + metadata includes block's compressed size, and maximum bound of decompressed size. + Each application is free to encode and pass such metadata in whichever way it wants. + + lz4.h only handle blocks, it can not generate Frames. + + Blocks are different from Frames (doc/lz4_Frame_format.md). + Frames bundle both blocks and metadata in a specified manner. + Embedding metadata is required for compressed data to be self-contained and portable. + Frame format is delivered through a companion API, declared in lz4frame.h. + The `lz4` CLI can only manage frames. +*/ + +/*^*************************************************************** +* Export parameters +*****************************************************************/ +/* +* LZ4_DLL_EXPORT : +* Enable exporting of functions when building a Windows DLL +* LZ4LIB_VISIBILITY : +* Control library symbols visibility. +*/ +#ifndef LZ4LIB_VISIBILITY +# if defined(__GNUC__) && (__GNUC__ >= 4) +# define LZ4LIB_VISIBILITY __attribute__ ((visibility ("default"))) +# else +# define LZ4LIB_VISIBILITY +# endif +#endif +#if defined(LZ4_DLL_EXPORT) && (LZ4_DLL_EXPORT==1) +# define LZ4LIB_API __declspec(dllexport) LZ4LIB_VISIBILITY +#elif defined(LZ4_DLL_IMPORT) && (LZ4_DLL_IMPORT==1) +# define LZ4LIB_API __declspec(dllimport) LZ4LIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ +#else +# define LZ4LIB_API LZ4LIB_VISIBILITY +#endif + +/*! LZ4_FREESTANDING : + * When this macro is set to 1, it enables "freestanding mode" that is + * suitable for typical freestanding environment which doesn't support + * standard C library. + * + * - LZ4_FREESTANDING is a compile-time switch. + * - It requires the following macros to be defined: + * LZ4_memcpy, LZ4_memmove, LZ4_memset. + * - It only enables LZ4/HC functions which don't use heap. + * All LZ4F_* functions are not supported. + * - See tests/freestanding.c to check its basic setup. + */ +#if defined(LZ4_FREESTANDING) && (LZ4_FREESTANDING == 1) +# define LZ4_HEAPMODE 0 +# define LZ4HC_HEAPMODE 0 +# define LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION 1 +# if !defined(LZ4_memcpy) +# error "LZ4_FREESTANDING requires macro 'LZ4_memcpy'." +# endif +# if !defined(LZ4_memset) +# error "LZ4_FREESTANDING requires macro 'LZ4_memset'." +# endif +# if !defined(LZ4_memmove) +# error "LZ4_FREESTANDING requires macro 'LZ4_memmove'." +# endif +#elif ! defined(LZ4_FREESTANDING) +# define LZ4_FREESTANDING 0 +#endif + + +/*------ Version ------*/ +#define LZ4_VERSION_MAJOR 1 /* for breaking interface changes */ +#define LZ4_VERSION_MINOR 9 /* for new (non-breaking) interface capabilities */ +#define LZ4_VERSION_RELEASE 4 /* for tweaks, bug-fixes, or development */ + +#define LZ4_VERSION_NUMBER (LZ4_VERSION_MAJOR *100*100 + LZ4_VERSION_MINOR *100 + LZ4_VERSION_RELEASE) + +#define LZ4_LIB_VERSION LZ4_VERSION_MAJOR.LZ4_VERSION_MINOR.LZ4_VERSION_RELEASE +#define LZ4_QUOTE(str) #str +#define LZ4_EXPAND_AND_QUOTE(str) LZ4_QUOTE(str) +#define LZ4_VERSION_STRING LZ4_EXPAND_AND_QUOTE(LZ4_LIB_VERSION) /* requires v1.7.3+ */ + +LZ4LIB_API int LZ4_versionNumber (void); /**< library version number; useful to check dll version; requires v1.3.0+ */ +LZ4LIB_API const char* LZ4_versionString (void); /**< library version string; useful to check dll version; requires v1.7.5+ */ + + +/*-************************************ +* Tuning parameter +**************************************/ +#define LZ4_MEMORY_USAGE_MIN 10 +#define LZ4_MEMORY_USAGE_DEFAULT 14 +#define LZ4_MEMORY_USAGE_MAX 20 + +/*! + * LZ4_MEMORY_USAGE : + * Memory usage formula : N->2^N Bytes (examples : 10 -> 1KB; 12 -> 4KB ; 16 -> 64KB; 20 -> 1MB; ) + * Increasing memory usage improves compression ratio, at the cost of speed. + * Reduced memory usage may improve speed at the cost of ratio, thanks to better cache locality. + * Default value is 14, for 16KB, which nicely fits into Intel x86 L1 cache + */ +#ifndef LZ4_MEMORY_USAGE +# define LZ4_MEMORY_USAGE LZ4_MEMORY_USAGE_DEFAULT +#endif + +#if (LZ4_MEMORY_USAGE < LZ4_MEMORY_USAGE_MIN) +# error "LZ4_MEMORY_USAGE is too small !" +#endif + +#if (LZ4_MEMORY_USAGE > LZ4_MEMORY_USAGE_MAX) +# error "LZ4_MEMORY_USAGE is too large !" +#endif + +/*-************************************ +* Simple Functions +**************************************/ +/*! LZ4_compress_default() : + * Compresses 'srcSize' bytes from buffer 'src' + * into already allocated 'dst' buffer of size 'dstCapacity'. + * Compression is guaranteed to succeed if 'dstCapacity' >= LZ4_compressBound(srcSize). + * It also runs faster, so it's a recommended setting. + * If the function cannot compress 'src' into a more limited 'dst' budget, + * compression stops *immediately*, and the function result is zero. + * In which case, 'dst' content is undefined (invalid). + * srcSize : max supported value is LZ4_MAX_INPUT_SIZE. + * dstCapacity : size of buffer 'dst' (which must be already allocated) + * @return : the number of bytes written into buffer 'dst' (necessarily <= dstCapacity) + * or 0 if compression fails + * Note : This function is protected against buffer overflow scenarios (never writes outside 'dst' buffer, nor read outside 'source' buffer). + */ +LZ4LIB_API int LZ4_compress_default(const char* src, char* dst, int srcSize, int dstCapacity); + +/*! LZ4_decompress_safe() : + * compressedSize : is the exact complete size of the compressed block. + * dstCapacity : is the size of destination buffer (which must be already allocated), presumed an upper bound of decompressed size. + * @return : the number of bytes decompressed into destination buffer (necessarily <= dstCapacity) + * If destination buffer is not large enough, decoding will stop and output an error code (negative value). + * If the source stream is detected malformed, the function will stop decoding and return a negative result. + * Note 1 : This function is protected against malicious data packets : + * it will never writes outside 'dst' buffer, nor read outside 'source' buffer, + * even if the compressed block is maliciously modified to order the decoder to do these actions. + * In such case, the decoder stops immediately, and considers the compressed block malformed. + * Note 2 : compressedSize and dstCapacity must be provided to the function, the compressed block does not contain them. + * The implementation is free to send / store / derive this information in whichever way is most beneficial. + * If there is a need for a different format which bundles together both compressed data and its metadata, consider looking at lz4frame.h instead. + */ +LZ4LIB_API int LZ4_decompress_safe (const char* src, char* dst, int compressedSize, int dstCapacity); + + +/*-************************************ +* Advanced Functions +**************************************/ +#define LZ4_MAX_INPUT_SIZE 0x7E000000 /* 2 113 929 216 bytes */ +#define LZ4_COMPRESSBOUND(isize) ((unsigned)(isize) > (unsigned)LZ4_MAX_INPUT_SIZE ? 0 : (isize) + ((isize)/255) + 16) + +/*! LZ4_compressBound() : + Provides the maximum size that LZ4 compression may output in a "worst case" scenario (input data not compressible) + This function is primarily useful for memory allocation purposes (destination buffer size). + Macro LZ4_COMPRESSBOUND() is also provided for compilation-time evaluation (stack memory allocation for example). + Note that LZ4_compress_default() compresses faster when dstCapacity is >= LZ4_compressBound(srcSize) + inputSize : max supported value is LZ4_MAX_INPUT_SIZE + return : maximum output size in a "worst case" scenario + or 0, if input size is incorrect (too large or negative) +*/ +LZ4LIB_API int LZ4_compressBound(int inputSize); + +/*! LZ4_compress_fast() : + Same as LZ4_compress_default(), but allows selection of "acceleration" factor. + The larger the acceleration value, the faster the algorithm, but also the lesser the compression. + It's a trade-off. It can be fine tuned, with each successive value providing roughly +~3% to speed. + An acceleration value of "1" is the same as regular LZ4_compress_default() + Values <= 0 will be replaced by LZ4_ACCELERATION_DEFAULT (currently == 1, see lz4.c). + Values > LZ4_ACCELERATION_MAX will be replaced by LZ4_ACCELERATION_MAX (currently == 65537, see lz4.c). +*/ +LZ4LIB_API int LZ4_compress_fast (const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! LZ4_compress_fast_extState() : + * Same as LZ4_compress_fast(), using an externally allocated memory space for its state. + * Use LZ4_sizeofState() to know how much memory must be allocated, + * and allocate it on 8-bytes boundaries (using `malloc()` typically). + * Then, provide this buffer as `void* state` to compression function. + */ +LZ4LIB_API int LZ4_sizeofState(void); +LZ4LIB_API int LZ4_compress_fast_extState (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + + +/*! LZ4_compress_destSize() : + * Reverse the logic : compresses as much data as possible from 'src' buffer + * into already allocated buffer 'dst', of size >= 'targetDestSize'. + * This function either compresses the entire 'src' content into 'dst' if it's large enough, + * or fill 'dst' buffer completely with as much data as possible from 'src'. + * note: acceleration parameter is fixed to "default". + * + * *srcSizePtr : will be modified to indicate how many bytes where read from 'src' to fill 'dst'. + * New value is necessarily <= input value. + * @return : Nb bytes written into 'dst' (necessarily <= targetDestSize) + * or 0 if compression fails. + * + * Note : from v1.8.2 to v1.9.1, this function had a bug (fixed un v1.9.2+): + * the produced compressed content could, in specific circumstances, + * require to be decompressed into a destination buffer larger + * by at least 1 byte than the content to decompress. + * If an application uses `LZ4_compress_destSize()`, + * it's highly recommended to update liblz4 to v1.9.2 or better. + * If this can't be done or ensured, + * the receiving decompression function should provide + * a dstCapacity which is > decompressedSize, by at least 1 byte. + * See https://github.com/lz4/lz4/issues/859 for details + */ +LZ4LIB_API int LZ4_compress_destSize (const char* src, char* dst, int* srcSizePtr, int targetDstSize); + + +/*! LZ4_decompress_safe_partial() : + * Decompress an LZ4 compressed block, of size 'srcSize' at position 'src', + * into destination buffer 'dst' of size 'dstCapacity'. + * Up to 'targetOutputSize' bytes will be decoded. + * The function stops decoding on reaching this objective. + * This can be useful to boost performance + * whenever only the beginning of a block is required. + * + * @return : the number of bytes decoded in `dst` (necessarily <= targetOutputSize) + * If source stream is detected malformed, function returns a negative result. + * + * Note 1 : @return can be < targetOutputSize, if compressed block contains less data. + * + * Note 2 : targetOutputSize must be <= dstCapacity + * + * Note 3 : this function effectively stops decoding on reaching targetOutputSize, + * so dstCapacity is kind of redundant. + * This is because in older versions of this function, + * decoding operation would still write complete sequences. + * Therefore, there was no guarantee that it would stop writing at exactly targetOutputSize, + * it could write more bytes, though only up to dstCapacity. + * Some "margin" used to be required for this operation to work properly. + * Thankfully, this is no longer necessary. + * The function nonetheless keeps the same signature, in an effort to preserve API compatibility. + * + * Note 4 : If srcSize is the exact size of the block, + * then targetOutputSize can be any value, + * including larger than the block's decompressed size. + * The function will, at most, generate block's decompressed size. + * + * Note 5 : If srcSize is _larger_ than block's compressed size, + * then targetOutputSize **MUST** be <= block's decompressed size. + * Otherwise, *silent corruption will occur*. + */ +LZ4LIB_API int LZ4_decompress_safe_partial (const char* src, char* dst, int srcSize, int targetOutputSize, int dstCapacity); + + +/*-********************************************* +* Streaming Compression Functions +***********************************************/ +typedef union LZ4_stream_u LZ4_stream_t; /* incomplete type (defined later) */ + +/** + Note about RC_INVOKED + + - RC_INVOKED is predefined symbol of rc.exe (the resource compiler which is part of MSVC/Visual Studio). + https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros + + - Since rc.exe is a legacy compiler, it truncates long symbol (> 30 chars) + and reports warning "RC4011: identifier truncated". + + - To eliminate the warning, we surround long preprocessor symbol with + "#if !defined(RC_INVOKED) ... #endif" block that means + "skip this block when rc.exe is trying to read it". +*/ +#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */ +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +LZ4LIB_API LZ4_stream_t* LZ4_createStream(void); +LZ4LIB_API int LZ4_freeStream (LZ4_stream_t* streamPtr); +#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */ +#endif + +/*! LZ4_resetStream_fast() : v1.9.0+ + * Use this to prepare an LZ4_stream_t for a new chain of dependent blocks + * (e.g., LZ4_compress_fast_continue()). + * + * An LZ4_stream_t must be initialized once before usage. + * This is automatically done when created by LZ4_createStream(). + * However, should the LZ4_stream_t be simply declared on stack (for example), + * it's necessary to initialize it first, using LZ4_initStream(). + * + * After init, start any new stream with LZ4_resetStream_fast(). + * A same LZ4_stream_t can be re-used multiple times consecutively + * and compress multiple streams, + * provided that it starts each new stream with LZ4_resetStream_fast(). + * + * LZ4_resetStream_fast() is much faster than LZ4_initStream(), + * but is not compatible with memory regions containing garbage data. + * + * Note: it's only useful to call LZ4_resetStream_fast() + * in the context of streaming compression. + * The *extState* functions perform their own resets. + * Invoking LZ4_resetStream_fast() before is redundant, and even counterproductive. + */ +LZ4LIB_API void LZ4_resetStream_fast (LZ4_stream_t* streamPtr); + +/*! LZ4_loadDict() : + * Use this function to reference a static dictionary into LZ4_stream_t. + * The dictionary must remain available during compression. + * LZ4_loadDict() triggers a reset, so any previous data will be forgotten. + * The same dictionary will have to be loaded on decompression side for successful decoding. + * Dictionary are useful for better compression of small data (KB range). + * While LZ4 accept any input as dictionary, + * results are generally better when using Zstandard's Dictionary Builder. + * Loading a size of 0 is allowed, and is the same as reset. + * @return : loaded dictionary size, in bytes (necessarily <= 64 KB) + */ +LZ4LIB_API int LZ4_loadDict (LZ4_stream_t* streamPtr, const char* dictionary, int dictSize); + +/*! LZ4_compress_fast_continue() : + * Compress 'src' content using data from previously compressed blocks, for better compression ratio. + * 'dst' buffer must be already allocated. + * If dstCapacity >= LZ4_compressBound(srcSize), compression is guaranteed to succeed, and runs faster. + * + * @return : size of compressed block + * or 0 if there is an error (typically, cannot fit into 'dst'). + * + * Note 1 : Each invocation to LZ4_compress_fast_continue() generates a new block. + * Each block has precise boundaries. + * Each block must be decompressed separately, calling LZ4_decompress_*() with relevant metadata. + * It's not possible to append blocks together and expect a single invocation of LZ4_decompress_*() to decompress them together. + * + * Note 2 : The previous 64KB of source data is __assumed__ to remain present, unmodified, at same address in memory ! + * + * Note 3 : When input is structured as a double-buffer, each buffer can have any size, including < 64 KB. + * Make sure that buffers are separated, by at least one byte. + * This construction ensures that each block only depends on previous block. + * + * Note 4 : If input buffer is a ring-buffer, it can have any size, including < 64 KB. + * + * Note 5 : After an error, the stream status is undefined (invalid), it can only be reset or freed. + */ +LZ4LIB_API int LZ4_compress_fast_continue (LZ4_stream_t* streamPtr, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_saveDict() : + * If last 64KB data cannot be guaranteed to remain available at its current memory location, + * save it into a safer place (char* safeBuffer). + * This is schematically equivalent to a memcpy() followed by LZ4_loadDict(), + * but is much faster, because LZ4_saveDict() doesn't need to rebuild tables. + * @return : saved dictionary size in bytes (necessarily <= maxDictSize), or 0 if error. + */ +LZ4LIB_API int LZ4_saveDict (LZ4_stream_t* streamPtr, char* safeBuffer, int maxDictSize); + + +/*-********************************************** +* Streaming Decompression Functions +* Bufferless synchronous API +************************************************/ +typedef union LZ4_streamDecode_u LZ4_streamDecode_t; /* tracking context */ + +/*! LZ4_createStreamDecode() and LZ4_freeStreamDecode() : + * creation / destruction of streaming decompression tracking context. + * A tracking context can be re-used multiple times. + */ +#if !defined(RC_INVOKED) /* https://docs.microsoft.com/en-us/windows/win32/menurc/predefined-macros */ +#if !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) +LZ4LIB_API LZ4_streamDecode_t* LZ4_createStreamDecode(void); +LZ4LIB_API int LZ4_freeStreamDecode (LZ4_streamDecode_t* LZ4_stream); +#endif /* !defined(LZ4_STATIC_LINKING_ONLY_DISABLE_MEMORY_ALLOCATION) */ +#endif + +/*! LZ4_setStreamDecode() : + * An LZ4_streamDecode_t context can be allocated once and re-used multiple times. + * Use this function to start decompression of a new stream of blocks. + * A dictionary can optionally be set. Use NULL or size 0 for a reset order. + * Dictionary is presumed stable : it must remain accessible and unmodified during next decompression. + * @return : 1 if OK, 0 if error + */ +LZ4LIB_API int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode, const char* dictionary, int dictSize); + +/*! LZ4_decoderRingBufferSize() : v1.8.2+ + * Note : in a ring buffer scenario (optional), + * blocks are presumed decompressed next to each other + * up to the moment there is not enough remaining space for next block (remainingSize < maxBlockSize), + * at which stage it resumes from beginning of ring buffer. + * When setting such a ring buffer for streaming decompression, + * provides the minimum size of this ring buffer + * to be compatible with any source respecting maxBlockSize condition. + * @return : minimum ring buffer size, + * or 0 if there is an error (invalid maxBlockSize). + */ +LZ4LIB_API int LZ4_decoderRingBufferSize(int maxBlockSize); +#define LZ4_DECODER_RING_BUFFER_SIZE(maxBlockSize) (65536 + 14 + (maxBlockSize)) /* for static allocation; maxBlockSize presumed valid */ + +/*! LZ4_decompress_*_continue() : + * These decoding functions allow decompression of consecutive blocks in "streaming" mode. + * A block is an unsplittable entity, it must be presented entirely to a decompression function. + * Decompression functions only accepts one block at a time. + * The last 64KB of previously decoded data *must* remain available and unmodified at the memory position where they were decoded. + * If less than 64KB of data has been decoded, all the data must be present. + * + * Special : if decompression side sets a ring buffer, it must respect one of the following conditions : + * - Decompression buffer size is _at least_ LZ4_decoderRingBufferSize(maxBlockSize). + * maxBlockSize is the maximum size of any single block. It can have any value > 16 bytes. + * In which case, encoding and decoding buffers do not need to be synchronized. + * Actually, data can be produced by any source compliant with LZ4 format specification, and respecting maxBlockSize. + * - Synchronized mode : + * Decompression buffer size is _exactly_ the same as compression buffer size, + * and follows exactly same update rule (block boundaries at same positions), + * and decoding function is provided with exact decompressed size of each block (exception for last block of the stream), + * _then_ decoding & encoding ring buffer can have any size, including small ones ( < 64 KB). + * - Decompression buffer is larger than encoding buffer, by a minimum of maxBlockSize more bytes. + * In which case, encoding and decoding buffers do not need to be synchronized, + * and encoding ring buffer can have any size, including small ones ( < 64 KB). + * + * Whenever these conditions are not possible, + * save the last 64KB of decoded data into a safe buffer where it can't be modified during decompression, + * then indicate where this data is saved using LZ4_setStreamDecode(), before decompressing next block. +*/ +LZ4LIB_API int +LZ4_decompress_safe_continue (LZ4_streamDecode_t* LZ4_streamDecode, + const char* src, char* dst, + int srcSize, int dstCapacity); + + +/*! LZ4_decompress_*_usingDict() : + * These decoding functions work the same as + * a combination of LZ4_setStreamDecode() followed by LZ4_decompress_*_continue() + * They are stand-alone, and don't need an LZ4_streamDecode_t structure. + * Dictionary is presumed stable : it must remain accessible and unmodified during decompression. + * Performance tip : Decompression speed can be substantially increased + * when dst == dictStart + dictSize. + */ +LZ4LIB_API int +LZ4_decompress_safe_usingDict(const char* src, char* dst, + int srcSize, int dstCapacity, + const char* dictStart, int dictSize); + +LZ4LIB_API int +LZ4_decompress_safe_partial_usingDict(const char* src, char* dst, + int compressedSize, + int targetOutputSize, int maxOutputSize, + const char* dictStart, int dictSize); + +#endif /* LZ4_H_2983827168210 */ + + +/*^************************************* + * !!!!!! STATIC LINKING ONLY !!!!!! + ***************************************/ + +/*-**************************************************************************** + * Experimental section + * + * Symbols declared in this section must be considered unstable. Their + * signatures or semantics may change, or they may be removed altogether in the + * future. They are therefore only safe to depend on when the caller is + * statically linked against the library. + * + * To protect against unsafe usage, not only are the declarations guarded, + * the definitions are hidden by default + * when building LZ4 as a shared/dynamic library. + * + * In order to access these declarations, + * define LZ4_STATIC_LINKING_ONLY in your application + * before including LZ4's headers. + * + * In order to make their implementations accessible dynamically, you must + * define LZ4_PUBLISH_STATIC_FUNCTIONS when building the LZ4 library. + ******************************************************************************/ + +#ifdef LZ4_STATIC_LINKING_ONLY + +#ifndef LZ4_STATIC_3504398509 +#define LZ4_STATIC_3504398509 + +#ifdef LZ4_PUBLISH_STATIC_FUNCTIONS +#define LZ4LIB_STATIC_API LZ4LIB_API +#else +#define LZ4LIB_STATIC_API +#endif + + +/*! LZ4_compress_fast_extState_fastReset() : + * A variant of LZ4_compress_fast_extState(). + * + * Using this variant avoids an expensive initialization step. + * It is only safe to call if the state buffer is known to be correctly initialized already + * (see above comment on LZ4_resetStream_fast() for a definition of "correctly initialized"). + * From a high level, the difference is that + * this function initializes the provided state with a call to something like LZ4_resetStream_fast() + * while LZ4_compress_fast_extState() starts with a call to LZ4_resetStream(). + */ +LZ4LIB_STATIC_API int LZ4_compress_fast_extState_fastReset (void* state, const char* src, char* dst, int srcSize, int dstCapacity, int acceleration); + +/*! LZ4_attach_dictionary() : + * This is an experimental API that allows + * efficient use of a static dictionary many times. + * + * Rather than re-loading the dictionary buffer into a working context before + * each compression, or copying a pre-loaded dictionary's LZ4_stream_t into a + * working LZ4_stream_t, this function introduces a no-copy setup mechanism, + * in which the working stream references the dictionary stream in-place. + * + * Several assumptions are made about the state of the dictionary stream. + * Currently, only streams which have been prepared by LZ4_loadDict() should + * be expected to work. + * + * Alternatively, the provided dictionaryStream may be NULL, + * in which case any existing dictionary stream is unset. + * + * If a dictionary is provided, it replaces any pre-existing stream history. + * The dictionary contents are the only history that can be referenced and + * logically immediately precede the data compressed in the first subsequent + * compression call. + * + * The dictionary will only remain attached to the working stream through the + * first compression call, at the end of which it is cleared. The dictionary + * stream (and source buffer) must remain in-place / accessible / unchanged + * through the completion of the first compression call on the stream. + */ +LZ4LIB_STATIC_API void +LZ4_attach_dictionary(LZ4_stream_t* workingStream, + const LZ4_stream_t* dictionaryStream); + + +/*! In-place compression and decompression + * + * It's possible to have input and output sharing the same buffer, + * for highly constrained memory environments. + * In both cases, it requires input to lay at the end of the buffer, + * and decompression to start at beginning of the buffer. + * Buffer size must feature some margin, hence be larger than final size. + * + * |<------------------------buffer--------------------------------->| + * |<-----------compressed data--------->| + * |<-----------decompressed size------------------>| + * |<----margin---->| + * + * This technique is more useful for decompression, + * since decompressed size is typically larger, + * and margin is short. + * + * In-place decompression will work inside any buffer + * which size is >= LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize). + * This presumes that decompressedSize > compressedSize. + * Otherwise, it means compression actually expanded data, + * and it would be more efficient to store such data with a flag indicating it's not compressed. + * This can happen when data is not compressible (already compressed, or encrypted). + * + * For in-place compression, margin is larger, as it must be able to cope with both + * history preservation, requiring input data to remain unmodified up to LZ4_DISTANCE_MAX, + * and data expansion, which can happen when input is not compressible. + * As a consequence, buffer size requirements are much higher, + * and memory savings offered by in-place compression are more limited. + * + * There are ways to limit this cost for compression : + * - Reduce history size, by modifying LZ4_DISTANCE_MAX. + * Note that it is a compile-time constant, so all compressions will apply this limit. + * Lower values will reduce compression ratio, except when input_size < LZ4_DISTANCE_MAX, + * so it's a reasonable trick when inputs are known to be small. + * - Require the compressor to deliver a "maximum compressed size". + * This is the `dstCapacity` parameter in `LZ4_compress*()`. + * When this size is < LZ4_COMPRESSBOUND(inputSize), then compression can fail, + * in which case, the return code will be 0 (zero). + * The caller must be ready for these cases to happen, + * and typically design a backup scheme to send data uncompressed. + * The combination of both techniques can significantly reduce + * the amount of margin required for in-place compression. + * + * In-place compression can work in any buffer + * which size is >= (maxCompressedSize) + * with maxCompressedSize == LZ4_COMPRESSBOUND(srcSize) for guaranteed compression success. + * LZ4_COMPRESS_INPLACE_BUFFER_SIZE() depends on both maxCompressedSize and LZ4_DISTANCE_MAX, + * so it's possible to reduce memory requirements by playing with them. + */ + +#define LZ4_DECOMPRESS_INPLACE_MARGIN(compressedSize) (((compressedSize) >> 8) + 32) +#define LZ4_DECOMPRESS_INPLACE_BUFFER_SIZE(decompressedSize) ((decompressedSize) + LZ4_DECOMPRESS_INPLACE_MARGIN(decompressedSize)) /**< note: presumes that compressedSize < decompressedSize. note2: margin is overestimated a bit, since it could use compressedSize instead */ + +#ifndef LZ4_DISTANCE_MAX /* history window size; can be user-defined at compile time */ +# define LZ4_DISTANCE_MAX 65535 /* set to maximum value by default */ +#endif + +#define LZ4_COMPRESS_INPLACE_MARGIN (LZ4_DISTANCE_MAX + 32) /* LZ4_DISTANCE_MAX can be safely replaced by srcSize when it's smaller */ +#define LZ4_COMPRESS_INPLACE_BUFFER_SIZE(maxCompressedSize) ((maxCompressedSize) + LZ4_COMPRESS_INPLACE_MARGIN) /**< maxCompressedSize is generally LZ4_COMPRESSBOUND(inputSize), but can be set to any lower value, with the risk that compression can fail (return code 0(zero)) */ + +#endif /* LZ4_STATIC_3504398509 */ +#endif /* LZ4_STATIC_LINKING_ONLY */ + + + +#ifndef LZ4_H_98237428734687 +#define LZ4_H_98237428734687 + +/*-************************************************************ + * Private Definitions + ************************************************************** + * Do not use these definitions directly. + * They are only exposed to allow static allocation of `LZ4_stream_t` and `LZ4_streamDecode_t`. + * Accessing members will expose user code to API and/or ABI break in future versions of the library. + **************************************************************/ +#define LZ4_HASHLOG (LZ4_MEMORY_USAGE-2) +#define LZ4_HASHTABLESIZE (1 << LZ4_MEMORY_USAGE) +#define LZ4_HASH_SIZE_U32 (1 << LZ4_HASHLOG) /* required as macro for static allocation */ + +#if defined(__cplusplus) || (defined (__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) /* C99 */) +# include + typedef int8_t LZ4_i8; + typedef uint8_t LZ4_byte; + typedef uint16_t LZ4_u16; + typedef uint32_t LZ4_u32; +#else + typedef signed char LZ4_i8; + typedef unsigned char LZ4_byte; + typedef unsigned short LZ4_u16; + typedef unsigned int LZ4_u32; +#endif + +/*! LZ4_stream_t : + * Never ever use below internal definitions directly ! + * These definitions are not API/ABI safe, and may change in future versions. + * If you need static allocation, declare or allocate an LZ4_stream_t object. +**/ + +typedef struct LZ4_stream_t_internal LZ4_stream_t_internal; +struct LZ4_stream_t_internal { + LZ4_u32 hashTable[LZ4_HASH_SIZE_U32]; + const LZ4_byte* dictionary; + const LZ4_stream_t_internal* dictCtx; + LZ4_u32 currentOffset; + LZ4_u32 tableType; + LZ4_u32 dictSize; + /* Implicit padding to ensure structure is aligned */ +}; + +#define LZ4_STREAM_MINSIZE ((1UL << LZ4_MEMORY_USAGE) + 32) /* static size, for inter-version compatibility */ +union LZ4_stream_u { + char minStateSize[LZ4_STREAM_MINSIZE]; + LZ4_stream_t_internal internal_donotuse; +}; /* previously typedef'd to LZ4_stream_t */ + + +/*! LZ4_initStream() : v1.9.0+ + * An LZ4_stream_t structure must be initialized at least once. + * This is automatically done when invoking LZ4_createStream(), + * but it's not when the structure is simply declared on stack (for example). + * + * Use LZ4_initStream() to properly initialize a newly declared LZ4_stream_t. + * It can also initialize any arbitrary buffer of sufficient size, + * and will @return a pointer of proper type upon initialization. + * + * Note : initialization fails if size and alignment conditions are not respected. + * In which case, the function will @return NULL. + * Note2: An LZ4_stream_t structure guarantees correct alignment and size. + * Note3: Before v1.9.0, use LZ4_resetStream() instead +**/ +LZ4LIB_API LZ4_stream_t* LZ4_initStream (void* buffer, size_t size); + + +/*! LZ4_streamDecode_t : + * Never ever use below internal definitions directly ! + * These definitions are not API/ABI safe, and may change in future versions. + * If you need static allocation, declare or allocate an LZ4_streamDecode_t object. +**/ +typedef struct { + const LZ4_byte* externalDict; + const LZ4_byte* prefixEnd; + size_t extDictSize; + size_t prefixSize; +} LZ4_streamDecode_t_internal; + +#define LZ4_STREAMDECODE_MINSIZE 32 +union LZ4_streamDecode_u { + char minStateSize[LZ4_STREAMDECODE_MINSIZE]; + LZ4_streamDecode_t_internal internal_donotuse; +} ; /* previously typedef'd to LZ4_streamDecode_t */ + + + +/*-************************************ +* Obsolete Functions +**************************************/ + +/*! Deprecation warnings + * + * Deprecated functions make the compiler generate a warning when invoked. + * This is meant to invite users to update their source code. + * Should deprecation warnings be a problem, it is generally possible to disable them, + * typically with -Wno-deprecated-declarations for gcc + * or _CRT_SECURE_NO_WARNINGS in Visual. + * + * Another method is to define LZ4_DISABLE_DEPRECATE_WARNINGS + * before including the header file. + */ +#ifdef LZ4_DISABLE_DEPRECATE_WARNINGS +# define LZ4_DEPRECATED(message) /* disable deprecation warnings */ +#else +# if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ +# define LZ4_DEPRECATED(message) [[deprecated(message)]] +# elif defined(_MSC_VER) +# define LZ4_DEPRECATED(message) __declspec(deprecated(message)) +# elif defined(__clang__) || (defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 45)) +# define LZ4_DEPRECATED(message) __attribute__((deprecated(message))) +# elif defined(__GNUC__) && (__GNUC__ * 10 + __GNUC_MINOR__ >= 31) +# define LZ4_DEPRECATED(message) __attribute__((deprecated)) +# else +# pragma message("WARNING: LZ4_DEPRECATED needs custom implementation for this compiler") +# define LZ4_DEPRECATED(message) /* disabled */ +# endif +#endif /* LZ4_DISABLE_DEPRECATE_WARNINGS */ + +/*! Obsolete compression functions (since v1.7.3) */ +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress (const char* src, char* dest, int srcSize); +LZ4_DEPRECATED("use LZ4_compress_default() instead") LZ4LIB_API int LZ4_compress_limitedOutput (const char* src, char* dest, int srcSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_withState (void* state, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_extState() instead") LZ4LIB_API int LZ4_compress_limitedOutput_withState (void* state, const char* source, char* dest, int inputSize, int maxOutputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize); +LZ4_DEPRECATED("use LZ4_compress_fast_continue() instead") LZ4LIB_API int LZ4_compress_limitedOutput_continue (LZ4_stream_t* LZ4_streamPtr, const char* source, char* dest, int inputSize, int maxOutputSize); + +/*! Obsolete decompression functions (since v1.8.0) */ +LZ4_DEPRECATED("use LZ4_decompress_fast() instead") LZ4LIB_API int LZ4_uncompress (const char* source, char* dest, int outputSize); +LZ4_DEPRECATED("use LZ4_decompress_safe() instead") LZ4LIB_API int LZ4_uncompress_unknownOutputSize (const char* source, char* dest, int isize, int maxOutputSize); + +/* Obsolete streaming functions (since v1.7.0) + * degraded functionality; do not use! + * + * In order to perform streaming compression, these functions depended on data + * that is no longer tracked in the state. They have been preserved as well as + * possible: using them will still produce a correct output. However, they don't + * actually retain any history between compression calls. The compression ratio + * achieved will therefore be no better than compressing each chunk + * independently. + */ +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API void* LZ4_create (char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_createStream() instead") LZ4LIB_API int LZ4_sizeofStreamState(void); +LZ4_DEPRECATED("Use LZ4_resetStream() instead") LZ4LIB_API int LZ4_resetStreamState(void* state, char* inputBuffer); +LZ4_DEPRECATED("Use LZ4_saveDict() instead") LZ4LIB_API char* LZ4_slideInputBuffer (void* state); + +/*! Obsolete streaming decoding functions (since v1.7.0) */ +LZ4_DEPRECATED("use LZ4_decompress_safe_usingDict() instead") LZ4LIB_API int LZ4_decompress_safe_withPrefix64k (const char* src, char* dst, int compressedSize, int maxDstSize); +LZ4_DEPRECATED("use LZ4_decompress_fast_usingDict() instead") LZ4LIB_API int LZ4_decompress_fast_withPrefix64k (const char* src, char* dst, int originalSize); + +/*! Obsolete LZ4_decompress_fast variants (since v1.9.0) : + * These functions used to be faster than LZ4_decompress_safe(), + * but this is no longer the case. They are now slower. + * This is because LZ4_decompress_fast() doesn't know the input size, + * and therefore must progress more cautiously into the input buffer to not read beyond the end of block. + * On top of that `LZ4_decompress_fast()` is not protected vs malformed or malicious inputs, making it a security liability. + * As a consequence, LZ4_decompress_fast() is strongly discouraged, and deprecated. + * + * The last remaining LZ4_decompress_fast() specificity is that + * it can decompress a block without knowing its compressed size. + * Such functionality can be achieved in a more secure manner + * by employing LZ4_decompress_safe_partial(). + * + * Parameters: + * originalSize : is the uncompressed size to regenerate. + * `dst` must be already allocated, its size must be >= 'originalSize' bytes. + * @return : number of bytes read from source buffer (== compressed size). + * The function expects to finish at block's end exactly. + * If the source stream is detected malformed, the function stops decoding and returns a negative result. + * note : LZ4_decompress_fast*() requires originalSize. Thanks to this information, it never writes past the output buffer. + * However, since it doesn't know its 'src' size, it may read an unknown amount of input, past input buffer bounds. + * Also, since match offsets are not validated, match reads from 'src' may underflow too. + * These issues never happen if input (compressed) data is correct. + * But they may happen if input data is invalid (error or intentional tampering). + * As a consequence, use these functions in trusted environments with trusted data **only**. + */ +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe() instead") +LZ4LIB_API int LZ4_decompress_fast (const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_continue() instead") +LZ4LIB_API int LZ4_decompress_fast_continue (LZ4_streamDecode_t* LZ4_streamDecode, const char* src, char* dst, int originalSize); +LZ4_DEPRECATED("This function is deprecated and unsafe. Consider using LZ4_decompress_safe_usingDict() instead") +LZ4LIB_API int LZ4_decompress_fast_usingDict (const char* src, char* dst, int originalSize, const char* dictStart, int dictSize); + +/*! LZ4_resetStream() : + * An LZ4_stream_t structure must be initialized at least once. + * This is done with LZ4_initStream(), or LZ4_resetStream(). + * Consider switching to LZ4_initStream(), + * invoking LZ4_resetStream() will trigger deprecation warnings in the future. + */ +LZ4LIB_API void LZ4_resetStream (LZ4_stream_t* streamPtr); + + +#endif /* LZ4_H_98237428734687 */ + + +#if defined (__cplusplus) +} +#endif diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/lz4.zip b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/lz4.zip new file mode 100644 index 00000000..6ed72c9a --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/lz4.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37e63d56fb9cbe2e430c7f737a404cd4b98637b05e1467459d5c8fe1a4364cc3 +size 462886 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/readme.txt b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/readme.txt new file mode 100644 index 00000000..5161b3d0 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/readme.txt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:359874042b2232244d3f73037a76b79b1b57f52d0b5f5892ab23b40305f2a48c +size 1734 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/ExampleGloveAdapterSingleton.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/ExampleGloveAdapterSingleton.obj new file mode 100644 index 00000000..39eb3f5f --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/ExampleGloveAdapterSingleton.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e00b1239d1a04ca0fff91ed3a4865b8813dc4a9eeb09902760c0c8bf3c29827 +size 3526217 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/ExampleGloveDevice.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/ExampleGloveDevice.obj new file mode 100644 index 00000000..81b943bb --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/ExampleGloveDevice.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c290dc80b555e2ef8fe86a6c62745cad0f72b7aecbb4986839e3e12b33d4a9c +size 627880 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/GloveDeviceBase.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/GloveDeviceBase.obj new file mode 100644 index 00000000..d501294e --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/GloveDeviceBase.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09210a0a98f96cfd282e39028c031c029f44473a1fc50ca4b27b23efcab670d0 +size 371495 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/LZ4Wrapper.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/LZ4Wrapper.obj new file mode 100644 index 00000000..89e61640 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/LZ4Wrapper.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c0ec6d23ca419873636cc3bd0dd5dfd2efcda0e3ae40f7717a645af0b1f52e1 +size 188070 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoDataConverter.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoDataConverter.obj new file mode 100644 index 00000000..7ba906df --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoDataConverter.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b1ceb38dc7a55b4452ae52bc745ae8dc3832c28fd056ee3ef78af16b12250248 +size 409270 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoDataParser.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoDataParser.obj new file mode 100644 index 00000000..b25cf083 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoDataParser.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba5fe2378eb9493c6df8348f7d9ec755d3920386fce1bfe92c333879217d296b +size 5864194 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CL.command.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CL.command.1.tlog new file mode 100644 index 00000000..8f384092 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CL.command.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CL.read.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CL.read.1.tlog new file mode 100644 index 00000000..3c957845 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CL.read.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CL.write.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CL.write.1.tlog new file mode 100644 index 00000000..ebcef3ae Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CL.write.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.command.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.command.1.tlog new file mode 100644 index 00000000..8500ffda --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.command.1.tlog @@ -0,0 +1,2 @@ +^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_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.read.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.read.1.tlog new file mode 100644 index 00000000..91d479f4 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.read.1.tlog @@ -0,0 +1 @@ +^C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.write.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.write.1.tlog new file mode 100644 index 00000000..78e57e7f --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.write.1.tlog @@ -0,0 +1,2 @@ +^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_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.lastbuildstate b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.lastbuildstate new file mode 100644 index 00000000..27ddd87a --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.lastbuildstate @@ -0,0 +1,2 @@ +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_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.write.1u.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.write.1u.tlog new file mode 100644 index 00000000..46ea407b Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.write.1u.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/link.command.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/link.command.1.tlog new file mode 100644 index 00000000..f6dbb298 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/link.command.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/link.read.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/link.read.1.tlog new file mode 100644 index 00000000..46e9fee1 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/link.read.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/link.write.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/link.write.1.tlog new file mode 100644 index 00000000..6af6286f Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.513E58BC.tlog/link.write.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CL.command.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CL.command.1.tlog new file mode 100644 index 00000000..86d1a4e9 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CL.command.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CL.read.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CL.read.1.tlog new file mode 100644 index 00000000..e46cdddf Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CL.read.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CL.write.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CL.write.1.tlog new file mode 100644 index 00000000..9ebcf458 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CL.write.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CopyFile.command.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CopyFile.command.1.tlog new file mode 100644 index 00000000..bedd38b5 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CopyFile.command.1.tlog @@ -0,0 +1,2 @@ +^C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\ROKOKOGLOVEDEVICE_FIXED\EXAMPLEGLOVEDATA.CSV +C:\Users\user\Documents\Streamingle_URP\Optitrack Rokoko Glove\x64\Debug\ExampleGloveData.csv diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CopyFile.read.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CopyFile.read.1.tlog new file mode 100644 index 00000000..d500e0c9 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CopyFile.read.1.tlog @@ -0,0 +1 @@ +^C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\ROKOKOGLOVEDEVICE_FIXED\EXAMPLEGLOVEDATA.CSV diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CopyFile.write.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CopyFile.write.1.tlog new file mode 100644 index 00000000..a422c589 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/CopyFile.write.1.tlog @@ -0,0 +1,2 @@ +^C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\ROKOKOGLOVEDEVICE_FIXED\EXAMPLEGLOVEDATA.CSV +C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\X64\DEBUG\EXAMPLEGLOVEDATA.CSV diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/RokokoGloveDevice_Fixed.lastbuildstate b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/RokokoGloveDevice_Fixed.lastbuildstate new file mode 100644 index 00000000..27ddd87a --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/RokokoGloveDevice_Fixed.lastbuildstate @@ -0,0 +1,2 @@ +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_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/RokokoGloveDevice_Fixed.write.1u.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/RokokoGloveDevice_Fixed.write.1u.tlog new file mode 100644 index 00000000..993ca4d0 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/RokokoGloveDevice_Fixed.write.1u.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/link.command.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/link.command.1.tlog new file mode 100644 index 00000000..8a68a716 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/link.command.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/link.read.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/link.read.1.tlog new file mode 100644 index 00000000..1fa84606 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/link.read.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/link.write.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/link.write.1.tlog new file mode 100644 index 00000000..c3bb5dea Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGl.AAAAAAAA.tlog/link.write.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGloveDevice.dll.recipe b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGloveDevice.dll.recipe new file mode 100644 index 00000000..68ea365b --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGloveDevice.dll.recipe @@ -0,0 +1,11 @@ + + + + + C:\Users\user\Documents\Streamingle_URP\Optitrack Rokoko Glove\x64\Debug\RokokoGloveDevice.dll + + + + + + \ No newline at end of file diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGloveDevice.ilk b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGloveDevice.ilk new file mode 100644 index 00000000..2d98dbde Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGloveDevice.ilk differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGloveDevice.vcxproj.FileListAbsolute.txt b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGloveDevice.vcxproj.FileListAbsolute.txt new file mode 100644 index 00000000..e69de29b diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGloveDevice_Fixed.dll.recipe b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGloveDevice_Fixed.dll.recipe new file mode 100644 index 00000000..8ea86dad --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGloveDevice_Fixed.dll.recipe @@ -0,0 +1,11 @@ + + + + + C:\Users\user\Documents\Streamingle_URP\Optitrack Rokoko Glove\x64\Debug\RokokoGloveDevice_Fixed.dll + + + + + + \ No newline at end of file diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGloveDevice_Fixed.ilk b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGloveDevice_Fixed.ilk new file mode 100644 index 00000000..73b364af Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoGloveDevice_Fixed.ilk differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoUDPReceiver.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoUDPReceiver.obj new file mode 100644 index 00000000..59d65a2b --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/RokokoUDPReceiver.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3793871ed2062b02be43b00a7c61cf0c639af27d5d7dc6eb1a20f4d16f65298 +size 617547 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/dllmain.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/dllmain.obj new file mode 100644 index 00000000..825f2208 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/dllmain.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbdb66278d4bab5b4bd5cbc50e4d6031ce02548c0981ef7098168f08c8070004 +size 435430 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/vc142.idb b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/vc142.idb new file mode 100644 index 00000000..10cdc3cb Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Debug/vc142.idb differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/ExampleGloveAdapterSingleton.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/ExampleGloveAdapterSingleton.obj new file mode 100644 index 00000000..19ce0461 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/ExampleGloveAdapterSingleton.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c64b45ad1121f5c51d1057a328443dca17165f4dcd0ba17c7bd76e750e73300 +size 4466185 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/ExampleGloveDevice.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/ExampleGloveDevice.obj new file mode 100644 index 00000000..4cf09fc1 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/ExampleGloveDevice.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ee6c686532959052d6520e9a60fdd30cf0def001a54552df18d27f509f31eb86 +size 3539857 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/GloveDeviceBase.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/GloveDeviceBase.obj new file mode 100644 index 00000000..78d96ff8 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/GloveDeviceBase.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef00dab2aeb876e77fd485a4a698a07a95bc1d301816f53580ecbc553a5ebeae +size 728430 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/LZ4Wrapper.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/LZ4Wrapper.obj new file mode 100644 index 00000000..71ddba86 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/LZ4Wrapper.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78064cbc94a492ffbd88dd3887aea4c2005afd1dfb807b5dde7cd84413d24e8d +size 408538 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoDataConverter.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoDataConverter.obj new file mode 100644 index 00000000..74c3495f --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoDataConverter.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c4fcc703040c10f22ae4054bc8ea006945b70e768a6e541e7232598b907d607 +size 1222171 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoDataParser.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoDataParser.obj new file mode 100644 index 00000000..acc5918e --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoDataParser.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad0350ccf87e2b81942fd83352d2824b588d5b95e79f31e08afbbbe292d8ada4 +size 3584145 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CL.command.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CL.command.1.tlog new file mode 100644 index 00000000..6fad39f2 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CL.command.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CL.read.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CL.read.1.tlog new file mode 100644 index 00000000..cec42f6d Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CL.read.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CL.write.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CL.write.1.tlog new file mode 100644 index 00000000..9e978202 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CL.write.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CopyFile.command.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CopyFile.command.1.tlog new file mode 100644 index 00000000..8d6fa2dd --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CopyFile.command.1.tlog @@ -0,0 +1,2 @@ +^C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\ROKOKOGLOVEDEVICE_FIXED\EXAMPLEGLOVEDATA.CSV +C:\Users\user\Documents\Streamingle_URP\Optitrack Rokoko Glove\x64\Release\ExampleGloveData.csv diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CopyFile.read.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CopyFile.read.1.tlog new file mode 100644 index 00000000..d500e0c9 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CopyFile.read.1.tlog @@ -0,0 +1 @@ +^C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\ROKOKOGLOVEDEVICE_FIXED\EXAMPLEGLOVEDATA.CSV diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CopyFile.write.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CopyFile.write.1.tlog new file mode 100644 index 00000000..1b95c7ee --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/CopyFile.write.1.tlog @@ -0,0 +1,2 @@ +^C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\ROKOKOGLOVEDEVICE_FIXED\EXAMPLEGLOVEDATA.CSV +C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\X64\RELEASE\EXAMPLEGLOVEDATA.CSV diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/RokokoGloveDevice_Fixed.lastbuildstate b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/RokokoGloveDevice_Fixed.lastbuildstate new file mode 100644 index 00000000..64a25916 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/RokokoGloveDevice_Fixed.lastbuildstate @@ -0,0 +1,2 @@ +PlatformToolSet=v142:VCToolArchitecture=Native32Bit:VCToolsVersion=14.29.30133:TargetPlatformVersion=8.1: +Release|x64|C:\Users\user\Documents\Streamingle_URP\Optitrack Rokoko Glove\| diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/RokokoGloveDevice_Fixed.write.1u.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/RokokoGloveDevice_Fixed.write.1u.tlog new file mode 100644 index 00000000..4e7df3f1 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/RokokoGloveDevice_Fixed.write.1u.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/link.command.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/link.command.1.tlog new file mode 100644 index 00000000..f875b26b Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/link.command.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/link.read.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/link.read.1.tlog new file mode 100644 index 00000000..6905241a Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/link.read.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/link.write.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/link.write.1.tlog new file mode 100644 index 00000000..c00638f5 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGl.AAAAAAAA.tlog/link.write.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGloveDevice_Fixed.dll.recipe b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGloveDevice_Fixed.dll.recipe new file mode 100644 index 00000000..dd50e382 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGloveDevice_Fixed.dll.recipe @@ -0,0 +1,11 @@ + + + + + C:\Users\user\Documents\Streamingle_URP\Optitrack Rokoko Glove\x64\Release\RokokoGloveDevice_Fixed.dll + + + + + + \ No newline at end of file diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGloveDevice_Fixed.iobj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGloveDevice_Fixed.iobj new file mode 100644 index 00000000..48949e99 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGloveDevice_Fixed.iobj differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGloveDevice_Fixed.ipdb b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGloveDevice_Fixed.ipdb new file mode 100644 index 00000000..0d82929d Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoGloveDevice_Fixed.ipdb differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoUDPReceiver.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoUDPReceiver.obj new file mode 100644 index 00000000..19330cc3 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/RokokoUDPReceiver.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28826eae994bb276f235c5f91af8fbaf7e79bf5a5e2c3851fa8cb954a63de59b +size 1381047 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/dllmain.obj b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/dllmain.obj new file mode 100644 index 00000000..8b70d98d --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice_Fixed/x64/Release/dllmain.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d03f921e16148d8cff270a4a2c51bfad2c83a5ab3bbe00e64c565dfb74d9def1 +size 3495471 diff --git a/Optitrack Rokoko Glove/x64/Debug/RokokoGloveDevice_Fixed.exp b/Optitrack Rokoko Glove/x64/Debug/RokokoGloveDevice_Fixed.exp new file mode 100644 index 00000000..c9d6e9ef Binary files /dev/null and b/Optitrack Rokoko Glove/x64/Debug/RokokoGloveDevice_Fixed.exp differ diff --git a/Optitrack Rokoko Glove/x64/Debug/RokokoGloveDevice_Fixed.lib b/Optitrack Rokoko Glove/x64/Debug/RokokoGloveDevice_Fixed.lib new file mode 100644 index 00000000..8226e038 Binary files /dev/null and b/Optitrack Rokoko Glove/x64/Debug/RokokoGloveDevice_Fixed.lib differ diff --git a/Optitrack Rokoko Glove/x64/Debug/RokokoGlovePlugin_v2.exp b/Optitrack Rokoko Glove/x64/Debug/RokokoGlovePlugin_v2.exp new file mode 100644 index 00000000..f3251df0 Binary files /dev/null and b/Optitrack Rokoko Glove/x64/Debug/RokokoGlovePlugin_v2.exp differ diff --git a/Optitrack Rokoko Glove/x64/Debug/RokokoGlovePlugin_v2.lib b/Optitrack Rokoko Glove/x64/Debug/RokokoGlovePlugin_v2.lib new file mode 100644 index 00000000..be498a5a Binary files /dev/null and b/Optitrack Rokoko Glove/x64/Debug/RokokoGlovePlugin_v2.lib differ diff --git a/Optitrack Rokoko Glove/x64/Release/ExampleGloveData.csv b/Optitrack Rokoko Glove/x64/Release/ExampleGloveData.csv new file mode 100644 index 00000000..21e484c7 --- /dev/null +++ b/Optitrack Rokoko Glove/x64/Release/ExampleGloveData.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:816498d40aca6a7060d48313bd1946b20553894715095670b14192d4639bdce5 +size 549783 diff --git a/Optitrack Rokoko Glove/x64/Release/RokokoGloveDevice_Fixed.dll b/Optitrack Rokoko Glove/x64/Release/RokokoGloveDevice_Fixed.dll new file mode 100644 index 00000000..3946a77b --- /dev/null +++ b/Optitrack Rokoko Glove/x64/Release/RokokoGloveDevice_Fixed.dll @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f08d52cb62ea198d7528b672194e5c481c1c40dd0f3d7f5e07fa59743f866d5 +size 199168 diff --git a/Optitrack Rokoko Glove/x64/Release/RokokoGloveDevice_Fixed.exp b/Optitrack Rokoko Glove/x64/Release/RokokoGloveDevice_Fixed.exp new file mode 100644 index 00000000..47840c0e Binary files /dev/null and b/Optitrack Rokoko Glove/x64/Release/RokokoGloveDevice_Fixed.exp differ diff --git a/Optitrack Rokoko Glove/x64/Release/RokokoGloveDevice_Fixed.lib b/Optitrack Rokoko Glove/x64/Release/RokokoGloveDevice_Fixed.lib new file mode 100644 index 00000000..11123d3f Binary files /dev/null and b/Optitrack Rokoko Glove/x64/Release/RokokoGloveDevice_Fixed.lib differ