diff --git a/Optitrack Rokoko Glove/.vscode/settings.json b/Optitrack Rokoko Glove/.vscode/settings.json new file mode 100644 index 000000000..ff6eed53a --- /dev/null +++ b/Optitrack Rokoko Glove/.vscode/settings.json @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:924dfafac2c37db14913aa28c56ab40c45563e41347c8aeff076178d65cc06fd +size 1917 diff --git a/Optitrack Rokoko Glove/GloveDeviceExample/ExampleGloveAdapterSingleton.cpp b/Optitrack Rokoko Glove/GloveDeviceExample/ExampleGloveAdapterSingleton.cpp new file mode 100644 index 000000000..388f97bfd --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/ExampleGloveAdapterSingleton.cpp @@ -0,0 +1,282 @@ +//====================================================================================================== +// Copyright 2023, NaturalPoint Inc. +//====================================================================================================== + +#include +#include +#include + +// Peripheral Import +#include "ExampleGloveAdapterSingleton.h" +#include "IDeviceManager.h" +#include "ExampleGloveDevice.h" +using namespace AnalogSystem; + +// Simulated Device +#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; +} + + +bool OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ClientShutdown() +{ + bIsConnected = false; + bIsDetecting = false; + + if (mGloveSimulator != nullptr) + { + mGloveSimulator->Shutdown(); + } + return false; +} + +/////////////////////////////////////////////////////////////////////////////// +// +// 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(20000)); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// SDK Helper Functions +// +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; + + + StartSimulatedHardware(1); + RegisterSDKCallbacks(); + + return true; +} + + +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; + + bool res = false; + if (s_Instance->mGloveDataMutex->try_lock()) { + // 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; + } + s_Instance->mGloveDataMutex->unlock(); + } + return res; +} + + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::CreateNewGloveDevice(sGloveDeviceBaseInfo& deviceInfo) +{ + uint64_t gloveId = deviceInfo.gloveId; + std::string deviceName = "ExampleGlove_" + 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++; + + return; +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::NotifyConnectionFail() +{ + char* szServerAddress = new char[MAX_PATH]; + char szMessage[MAX_PATH]; + if (s_Instance->mDeviceManager->GetProperty("Glove Server Address", &szServerAddress)) { + if (!(strlen(szServerAddress) == 0)) + { + sprintf_s(szMessage, "[ExampleGlove] Glove host not found on %s", szServerAddress); + s_Instance->mDeviceManager->MessageToHost(szMessage, MessageType_StatusInfo); + } + else + { + sprintf_s(szMessage, "[ExampleGlove] Glove host not found on localhost"); + s_Instance->mDeviceManager->MessageToHost(szMessage, MessageType_StatusInfo); + } + } + delete[] szServerAddress; +} + +/////////////////////////////////////////////////////////////////////////////// +// +// [Glove SDK Placeholder] SDK callbacks +// +/* +* Methods in this section starts the device SDK and registers the callbacks. +*/ +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); // Create left glove + s_Instance->mGloveSimulator->AddSimulatedGlove(2, 15, 2); // Create right glove +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::OnDataCallback(std::vector& gloveAllFingerData) +{ + 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(); +} + +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); + } +} + +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; +} + +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; +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/GloveDeviceExample/ExampleGloveAdapterSingleton.h b/Optitrack Rokoko Glove/GloveDeviceExample/ExampleGloveAdapterSingleton.h new file mode 100644 index 000000000..5ebbfda19 --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/ExampleGloveAdapterSingleton.h @@ -0,0 +1,159 @@ +////====================================================================================================== +//// 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" + +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(); + + 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; + + /** + * [Glove SDK Placeholder] + * The following methods are sample callback functions as a demonstration of how this adapter could + * Communicate with plugin SDK. It receives aggregated frame data through the data callback. + */ + bool bIsSimulating; + void StartSimulatedHardware(int deviceCount); + static void RegisterSDKCallbacks(); + static void OnDataCallback(std::vector& gloveFingerData); + static void OnDeviceInfoCallback(std::vector& newGloveInfo); + static sGloveDeviceBaseInfo ConvertDeviceInfoFormat(SimulatedPluginDevices::SimulatedDeviceInfo& glove); + static sGloveDeviceData ConvertDataFormat(const SimulatedPluginDevices::SimulatedGloveFrameData& glove); + + protected: + + /** + * [Glove SDK Simulator] + * Instance of simulator + */ + 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/GloveDeviceExample/ExampleGloveData.csv b/Optitrack Rokoko Glove/GloveDeviceExample/ExampleGloveData.csv new file mode 100644 index 000000000..21e484c78 --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/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/GloveDeviceExample/ExampleGloveDevice.cpp b/Optitrack Rokoko Glove/GloveDeviceExample/ExampleGloveDevice.cpp new file mode 100644 index 000000000..dbb24ec1c --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/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/GloveDeviceExample/ExampleGloveDevice.h b/Optitrack Rokoko Glove/GloveDeviceExample/ExampleGloveDevice.h new file mode 100644 index 000000000..a7a6d3a7e --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/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/GloveDeviceExample/GloveDataFormat.h b/Optitrack Rokoko Glove/GloveDeviceExample/GloveDataFormat.h new file mode 100644 index 000000000..d434bca84 --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/GloveDataFormat.h @@ -0,0 +1,83 @@ +//====================================================================================================== +// 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; +}; + + + + + + diff --git a/Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceBase.cpp b/Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceBase.cpp new file mode 100644 index 000000000..9700f47fd --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/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/GloveDeviceExample/GloveDeviceBase.h b/Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceBase.h new file mode 100644 index 000000000..cac502cc7 --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/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/GloveDeviceExample/GloveDeviceExample.vcxproj b/Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceExample.vcxproj new file mode 100644 index 000000000..1547b996d --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceExample.vcxproj @@ -0,0 +1,135 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {DCD02920-D0DA-490D-86F2-3C2C9256A6B7} + Win32Proj + OptiTrackPeripheralExample + 8.1 + GloveDeviceExample + + + + 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 + + + + if not exist "C:/Program Files/OptiTrack/Motive/devices" mkdir "C:/Program Files/OptiTrack/Motive/devices" +copy "$(OutDir)GloveDeviceExample.dll" "C:/Program Files/OptiTrack/Motive/devices" +copy "$(OutDir)ExampleGloveData.csv" "C:/Program Files/OptiTrack/Motive/devices" + + + + + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;ANALOGSYSTEM_IMPORTS;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;%(PreprocessorDefinitions) + false + ..\..\include;%(AdditionalIncludeDirectories) + + + Windows + true + NotSet + PeripheralImport.lib;%(AdditionalDependencies) + ..\..\lib;%(AdditionalLibraryDirectories) + + + false + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;ANALOGSYSTEM_IMPORTS;%(PreprocessorDefinitions) + ..\..\include;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + PeripheralImport.lib;%(AdditionalDependencies) + ..\..\lib;%(AdditionalLibraryDirectories) + + + + + + + + + + + + + false + + + false + + + + + + + + + + + + + + Document + + + + + + \ No newline at end of file diff --git a/Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceExample.vcxproj.filters b/Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceExample.vcxproj.filters new file mode 100644 index 000000000..477cafb30 --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceExample.vcxproj.filters @@ -0,0 +1,60 @@ + + + + + {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 + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + + + + \ No newline at end of file diff --git a/Optitrack Rokoko Glove/GloveDeviceExample/HardwareSimulator.cpp b/Optitrack Rokoko Glove/GloveDeviceExample/HardwareSimulator.cpp new file mode 100644 index 000000000..d6add29c2 --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/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/GloveDeviceExample/HardwareSimulator.h b/Optitrack Rokoko Glove/GloveDeviceExample/HardwareSimulator.h new file mode 100644 index 000000000..6a1deada8 --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/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/GloveDeviceExample/dllcommon.h b/Optitrack Rokoko Glove/GloveDeviceExample/dllcommon.h new file mode 100644 index 000000000..71f9b68bd --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/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/GloveDeviceExample/dllmain.cpp b/Optitrack Rokoko Glove/GloveDeviceExample/dllmain.cpp new file mode 100644 index 000000000..31ce0fe66 --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/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/GloveDeviceExample/readme.txt b/Optitrack Rokoko Glove/GloveDeviceExample/readme.txt new file mode 100644 index 000000000..5161b3d09 --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/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/GloveDeviceExample/x64/Debug/GloveDeviceExample.dll.recipe b/Optitrack Rokoko Glove/GloveDeviceExample/x64/Debug/GloveDeviceExample.dll.recipe new file mode 100644 index 000000000..3bafc959e --- /dev/null +++ b/Optitrack Rokoko Glove/GloveDeviceExample/x64/Debug/GloveDeviceExample.dll.recipe @@ -0,0 +1,11 @@ + + + + + C:\Program Files\OptiTrack\Motive\PeripheralAPI\example\x64\Debug\GloveDeviceExample.dll + + + + + + \ No newline at end of file diff --git a/Optitrack Rokoko Glove/GloveDeviceExample/x64/Debug/GloveDeviceExample.vcxproj.FileListAbsolute.txt b/Optitrack Rokoko Glove/GloveDeviceExample/x64/Debug/GloveDeviceExample.vcxproj.FileListAbsolute.txt new file mode 100644 index 000000000..e69de29bb diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core.meta new file mode 100644 index 000000000..332934b71 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d7417c9a13b2bd14ab1fea1d7ffa8e0a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/ARKitBlendshapes.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/ARKitBlendshapes.cs new file mode 100644 index 000000000..2777229ff --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/ARKitBlendshapes.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Rokoko.Core +{ + [System.Serializable] + public enum BlendShapes + { + eyeBlinkLeft = 0, + eyeLookDownLeft = 1, + eyeLookInLeft = 2, + eyeLookOutLeft = 3, + eyeLookUpLeft = 4, + eyeSquintLeft = 5, + eyeWideLeft = 6, + eyeBlinkRight = 7, + eyeLookDownRight = 8, + eyeLookInRight = 9, + eyeLookOutRight = 10, + eyeLookUpRight = 11, + eyeSquintRight = 12, + eyeWideRight = 13, + jawForward = 14, + jawLeft = 15, + jawRight = 16, + jawOpen = 17, + mouthClose = 18, + mouthFunnel = 19, + mouthPucker = 20, + mouthLeft = 21, + mouthRight = 22, + mouthSmileLeft = 23, + mouthSmileRight = 24, + mouthFrownLeft = 25, + mouthFrownRight = 26, + mouthDimpleLeft = 27, + mouthDimpleRight = 28, + mouthStretchLeft = 29, + mouthStretchRight = 30, + mouthRollLower = 31, + mouthRollUpper = 32, + mouthShrugLower = 33, + mouthShrugUpper = 34, + mouthPressLeft = 35, + mouthPressRight = 36, + mouthLowerDownLeft = 37, + mouthLowerDownRight = 38, + mouthUpperUpLeft = 39, + mouthUpperUpRight = 40, + browDownLeft = 41, + browDownRight = 42, + browInnerUp = 43, + browOuterUpLeft = 44, + browOuterUpRight = 45, + cheekPuff = 46, + cheekSquintLeft = 47, + cheekSquintRight = 48, + noseSneerLeft = 49, + noseSneerRight = 50, + tongueOut = 51, + size = 52 + } +} diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/ARKitBlendshapes.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/ARKitBlendshapes.cs.meta new file mode 100644 index 000000000..9f3c5da8b --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/ARKitBlendshapes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9f43a84310e40244c970e95e1d934b1a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI.meta new file mode 100644 index 000000000..ef7e6e92d --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 721c3fac4cc3fbe4188ec6ec27cb4708 +folderAsset: yes +timeCreated: 1564996317 +licenseType: Pro +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/CalibrateRequestData.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/CalibrateRequestData.cs new file mode 100644 index 000000000..139e05cd5 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/CalibrateRequestData.cs @@ -0,0 +1,63 @@ +using UnityEngine; + +namespace Rokoko.CommandAPI +{ + public enum BalancedNewtonPose + { + // tpose + TPose = 0, + + // straight-arms-down + StraightArmsDown = 1, + + // straight-arms-forward + StraightArmsForward = 2, + } + + public class CalibrateRequestData + { + [SerializeField] private string device_id; // the live input device hubName that the command should target + + /// + /// Count down in seconds before calibration is executed. -1 will use default setting. + /// + [SerializeField] private int countdown_delay = -1; // countdown in seconds + + /// + /// Skip Smartsuit Pro calibration. + /// + [SerializeField] private bool skip_suit = false; // should we skip suit from a processing (calibration) + + /// + /// Skip Smartgloves calibration. + /// + [SerializeField] private bool skip_gloves = false; // should we skip gloves from a processing (calibration) + + /// + /// + /// + [SerializeField] private bool use_custom_pose = false; + + // useCustomPose is not Set, pose will be changed calibration + [SerializeField] private BalancedNewtonPose pose = BalancedNewtonPose.StraightArmsDown; + + // public members + + public string DeviceId { get => device_id; set => device_id = value; } + public int CountDownDelay { get => countdown_delay; set => countdown_delay = value; } + public bool SkipSuit { get => skip_suit; set => skip_suit = value; } + public bool SkipGloves { get => skip_gloves; set => skip_gloves = value; } + public bool UseCustomPose { get => use_custom_pose; set => use_custom_pose = value; } + public BalancedNewtonPose Pose { get => pose; set => pose = value; } + + public override string ToString() + { + return DeviceId; + } + + public string ToJson() + { + return JsonUtility.ToJson(this); + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/CalibrateRequestData.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/CalibrateRequestData.cs.meta new file mode 100644 index 000000000..7a6a6c69a --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/CalibrateRequestData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8982b58c0932ebd4eb488b92e43b178f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/InfoRequestData.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/InfoRequestData.cs new file mode 100644 index 000000000..a972ec8d0 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/InfoRequestData.cs @@ -0,0 +1,30 @@ +using UnityEngine; + +namespace Rokoko.CommandAPI +{ + public class InfoRequestData + { + [SerializeField] private bool devices_info = true; // return a list of scene input devices names + [SerializeField] private bool clips_info = true; // return a list of scene clip names + [SerializeField] private bool actors_info = false; // return a list of scene actor names + [SerializeField] private bool characters_info = false; // return a list of scene character names + + // public members + + public bool DoDevicesInfo { get => devices_info; set => devices_info = value; } + public bool DoClipsInfo { get => clips_info; set => clips_info = value; } + public bool DoActorsInfo { get => actors_info; set => actors_info = value; } + public bool DoCharactersInfo { get => characters_info; set => characters_info = value; } + + + public override string ToString() + { + return $"{DoDevicesInfo}, {DoClipsInfo}, {DoActorsInfo}, {DoCharactersInfo}"; + } + + public string ToJson() + { + return JsonUtility.ToJson(this); + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/InfoRequestData.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/InfoRequestData.cs.meta new file mode 100644 index 000000000..0e8c004c4 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/InfoRequestData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9029de61abd14c54194939b0c16fc45f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/LivestreamRequestData.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/LivestreamRequestData.cs new file mode 100644 index 000000000..5834985f5 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/LivestreamRequestData.cs @@ -0,0 +1,23 @@ +using UnityEngine; + +namespace Rokoko.CommandAPI +{ + public class LivestreamRequestData + { + [SerializeField] private bool enabled = true; // control a state of a custom live stream target + + // public members + + public bool Enabled { get => enabled; set => enabled = value; } + + public override string ToString() + { + return $"{Enabled}"; + } + + public string ToJson() + { + return JsonUtility.ToJson(this); + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/LivestreamRequestData.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/LivestreamRequestData.cs.meta new file mode 100644 index 000000000..eb42acbc9 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/LivestreamRequestData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6f300f4cf7bd8a44bb2bb5453228193e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/PlaybackRequestData.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/PlaybackRequestData.cs new file mode 100644 index 000000000..be5fc9657 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/PlaybackRequestData.cs @@ -0,0 +1,43 @@ +using UnityEngine; +using System; + +namespace Rokoko.CommandAPI +{ + [Flags] + public enum CommandAPIPlaybackChange : int + { + None = 0, + IsPlaying = 1, + CurrentTime = 2, + GoToFirstFrame = 4, + GoToLastFrame = 8, + PlaybackSpeed = 16, + }; + + + public class PlaybackRequestData + { + [SerializeField] private bool is_playing = false; // defines the timeline play / pause state + [SerializeField] private double current_time = 0.0; // defines a current time on a timeline + [SerializeField] private float playback_speed = 1.0f;// defines a playback speed multiplier + [SerializeField] private CommandAPIPlaybackChange change_flag; + + // public members + + public bool IsPlaying { get => is_playing; set => is_playing = value; } + public double CurrentTime { get => current_time; set => current_time = value; } + public float PlaybackSpeed { get => playback_speed; set => playback_speed = value; } + public CommandAPIPlaybackChange ChangeFlag { get => change_flag; set => change_flag = value; } + + + public override string ToString() + { + return $"{IsPlaying}, {CurrentTime}, {PlaybackSpeed}, {ChangeFlag}"; + } + + public string ToJson() + { + return JsonUtility.ToJson(this); + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/PlaybackRequestData.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/PlaybackRequestData.cs.meta new file mode 100644 index 000000000..3dd7fac09 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/PlaybackRequestData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 617231f45130160448cfef6a1670c3ff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/RecordingRequestData.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/RecordingRequestData.cs new file mode 100644 index 000000000..a2cc051db --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/RecordingRequestData.cs @@ -0,0 +1,30 @@ +using UnityEngine; + +namespace Rokoko.CommandAPI +{ + public class RecordingRequestData + { + [SerializeField] private string filename = ""; // actor clip name or filename for recording + [SerializeField] private string time = "00:00:00:00"; // recording start/stop time in SMPTE format + [SerializeField] private float frame_rate = 30.0f; + [SerializeField] private bool back_to_live = false; + + // public members + + public string Filename { get => filename; set => filename = value; } + public string Time { get => time; set => time = value; } + public float FrameRate { get => frame_rate; set => frame_rate = value; } + public bool BackToLive { get => back_to_live; set => back_to_live = value; } + + + public override string ToString() + { + return $"{Filename}, {Time}, {FrameRate}, {BackToLive}"; + } + + public string ToJson() + { + return JsonUtility.ToJson(this); + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/RecordingRequestData.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/RecordingRequestData.cs.meta new file mode 100644 index 000000000..b82176957 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/RecordingRequestData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d8f171d10619acd4193a50877e3a6856 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/RequestData.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/RequestData.cs new file mode 100644 index 000000000..dc95ce515 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/RequestData.cs @@ -0,0 +1,21 @@ +using UnityEngine; + +namespace Rokoko.CommandAPI +{ + public class RequestData + { + public string smartsuit = ""; + public float countdown_delay = 4; + public string filename = ""; + + public override string ToString() + { + return smartsuit + "," + countdown_delay + ", " + filename; + } + + public string ToJson() + { + return JsonUtility.ToJson(this); + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/RequestData.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/RequestData.cs.meta new file mode 100644 index 000000000..a43720bfc --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/RequestData.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d5ec1713e30442a19275b49b0572d01b +timeCreated: 1552057825 \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/ResetActorRequestData.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/ResetActorRequestData.cs new file mode 100644 index 000000000..b137ad2d6 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/ResetActorRequestData.cs @@ -0,0 +1,21 @@ +using UnityEngine; + +namespace Rokoko.CommandAPI +{ + public class ResetActorRequestData + { + [SerializeField] private string device_id = ""; + + public string DeviceId { get => device_id; set => device_id = value; } + + public override string ToString() + { + return DeviceId; + } + + public string ToJson() + { + return JsonUtility.ToJson(this); + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/ResetActorRequestData.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/ResetActorRequestData.cs.meta new file mode 100644 index 000000000..bac819f7f --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/ResetActorRequestData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8aa37890ebb422840868b7ece59e5570 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/StudioCommandAPI.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/StudioCommandAPI.cs new file mode 100644 index 000000000..70948e7e4 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/StudioCommandAPI.cs @@ -0,0 +1,188 @@ +using UnityEngine; +using UnityEngine.UI; + +namespace Rokoko.CommandAPI +{ + public class StudioCommandAPI : StudioCommandAPIBase + { + [Header("Show CommandAPI response (Optional)")] + [SerializeField] private Text responseText = null; + + [Header("The IP address of Studio. Leave default for same machine")] + public string ipAddress = "127.0.0.1"; + + [Header("Common Parameters")] + [Tooltip("The actor name or hardware device id in the scene")] + [SerializeField] public string deviceId; + + [Header("Calibration Parameters")] + [Tooltip("The calibration countdown delay")] + [SerializeField] public int countDownDelay = 3; + + [Tooltip("The calibration skip suit")] + [SerializeField] public bool calibrationSkipSuit = false; + + [Tooltip("The calibration skip gloves")] + [SerializeField] public bool calibrationSkipGloves = false; + + [Header("Recording Parameters")] + [Tooltip("Recording Clip Name")] + [SerializeField] public string clipName = "NewClip"; + + [Tooltip("Recording Start / Stop Time (SMPTE)")] + [SerializeField] public string recordingTime = "00:00:00:00"; + + [Tooltip("Return To Live Mode When Recording Is Done")] + [SerializeField] public bool backToLive = false; + + [Header("Tracker Parameters")] + [SerializeField] public string BoneAttached = ""; + [SerializeField] public float TrackerTimeout = 2f; + [SerializeField] public bool IsQueryOnly = false; + + [SerializeField] public Transform TrackerTransform; + + [Header("Playback Parameters")] + [SerializeField] public bool IsPlaying = true; + + [Header("Livestream Parameters")] + [SerializeField] public bool IsStreaming = true; + + [Header("Info Parameters")] + [SerializeField] public bool RequestDevicesInfo = true; + [SerializeField] public bool RequestClipsInfo = true; + [SerializeField] public bool RequestActorsInfo = true; + [SerializeField] public bool RequestCharactersInfo = true; + + protected override string IP => ipAddress; + protected override RequestData GetRequestData() => new RequestData(); + protected override ResetActorRequestData GetResetActorRequestData() + { + var data = new ResetActorRequestData() {DeviceId = deviceId}; + if (debug) + { + Debug.Log(data.ToJson()); + } + return data; + } + + protected override CalibrateRequestData GetCalibrateRequestData() + { + var data = new CalibrateRequestData() + { + DeviceId = deviceId, + CountDownDelay = countDownDelay, + SkipSuit = calibrationSkipSuit, + SkipGloves = calibrationSkipGloves + }; + if (debug) + { + Debug.Log(data.ToJson()); + } + return data; + } + + protected override RecordingRequestData GetRecordingRequestData() + { + var data = new RecordingRequestData() + { + Filename = clipName, + Time = recordingTime, + BackToLive = backToLive + }; + if (debug) + { + Debug.Log(data.ToJson()); + } + return data; + } + + protected override TrackerRequestData GetTrackerRequestData() + { + var data = new TrackerRequestData() + { + DeviceId = deviceId, + BoneAttached = BoneAttached, + Position = TrackerTransform?.position ?? Vector3.zero, + Rotation = TrackerTransform?.rotation ?? Quaternion.identity, + Timeout = TrackerTimeout, + IsQueryOnly = IsQueryOnly + }; + if (debug) + { + Debug.Log(data.ToJson()); + } + return data; + } + + protected override PlaybackRequestData GetPlaybackRequestData() + { + var data = new PlaybackRequestData() + { + IsPlaying = IsPlaying, + CurrentTime = 0.0, + PlaybackSpeed = 1.0f, + ChangeFlag = CommandAPIPlaybackChange.IsPlaying + }; + if (debug) + { + Debug.Log(data.ToJson()); + } + return data; + } + + protected override LivestreamRequestData GetLivestreamRequestData() + { + var data = new LivestreamRequestData() + { + Enabled = IsStreaming + }; + if (debug) + { + Debug.Log(data.ToJson()); + } + return data; + } + + protected override InfoRequestData GetInfoRequestData() + { + var data = new InfoRequestData() + { + DoDevicesInfo = RequestDevicesInfo, + DoClipsInfo = RequestClipsInfo, + DoActorsInfo = RequestActorsInfo, + DoCharactersInfo = RequestCharactersInfo + }; + if (debug) + { + Debug.Log(data.ToJson()); + } + return data; + } + + private void Start() + { + SetStatusText(""); + } + + protected override void OnCommmandResponse(ResponseMessage response) + { + base.OnCommmandResponse(response); + SetStatusText(response.description); + } + + protected override void OnCommmandError(string error) + { + base.OnCommmandError(error); + SetStatusText($"{error}\nPlease make sure Rokoko Studio is running and Command API is enabled (Menu->Settings->Command API->Enabled).\nCheck also the receiving port and API key in both Rokoko Studio and Unity plugin."); + } + + private void SetStatusText(string text) + { + if (responseText == null) return; + responseText.transform.parent.gameObject.SetActive(!string.IsNullOrEmpty(text)); + responseText.text = text; + LayoutRebuilder.ForceRebuildLayoutImmediate(responseText.transform.parent as RectTransform); + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/StudioCommandAPI.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/StudioCommandAPI.cs.meta new file mode 100644 index 000000000..1c1fd3396 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/StudioCommandAPI.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f38fe44d6bfb49dbabd93aea7da287ed +timeCreated: 1565078235 \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/StudioCommandAPIBase.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/StudioCommandAPIBase.cs new file mode 100644 index 000000000..5c2947e00 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/StudioCommandAPIBase.cs @@ -0,0 +1,151 @@ +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.Networking; + +namespace Rokoko.CommandAPI +{ + /// + /// This component provides access to Studio's Command API. + /// + public abstract class StudioCommandAPIBase : MonoBehaviour + { + [Tooltip("The api key as defined in Studio. Settings->Command API->API key")] + public string apiKey; + + [Tooltip("The port number as defined in Studio. Settings->Command API->Listen port")] + public int port; + + [Header("Log extra info")] + public bool debug; + + protected abstract string IP { get; } + + public bool IsTrackerRequestInProgress = false; + + // legacy only commands + [ContextMenu("Unicast")] + public async void Unicast() => + await SendRequest("unicast", GetRequestData().ToJson()); + + [ContextMenu("Broadcast")] + public async void Broadcast() => + await SendRequest("broadcast", GetRequestData().ToJson()); + + [ContextMenu("Restart Smartsuit")] + public async void RestartSmartsuit() + => await SendRequest("Restart", GetRequestData().ToJson()); + + // commands that are compatible with Studio and Studio Legacy + + [ContextMenu("Start Recording")] + public async void StartRecording() => + await SendRequest("recording/start", GetRecordingRequestData().ToJson()); + + [ContextMenu("Stop Recording")] + public async void StopRecording() => + await SendRequest("recording/stop", GetRecordingRequestData().ToJson()); + + [ContextMenu("Calibrate all")] + public async void CalibrateAll() => + await SendRequest("calibrate", GetCalibrateRequestData().ToJson()); + + [ContextMenu("Reset Actor")] + public async void ResetActor() + => await SendRequest("resetactor", GetResetActorRequestData().ToJson()); + + // commands which are not presented in Studio Legacy + + [ContextMenu("Tracker")] + public async void Tracker() => + await SendRequest("tracker", GetTrackerRequestData().ToJson()); + + [ContextMenu("Info")] + public async void Info() => + await SendRequest("info", GetInfoRequestData().ToJson()); + + [ContextMenu("Playback")] + public async void PlaybackPlay() => + await SendRequest("playback", GetPlaybackRequestData().ToJson()); + + [ContextMenu("Livestream")] + public async void ToggleLiveStream() => + await SendRequest("livestream", GetLivestreamRequestData().ToJson()); + + + private Task SendRequest(string endpoint, string json) + { + var tcs = new TaskCompletionSource(); + StartCoroutine(SendRequestEnum(endpoint, json, tcs)); + return tcs.Task; + } + + protected abstract RequestData GetRequestData(); + protected abstract CalibrateRequestData GetCalibrateRequestData(); + protected abstract ResetActorRequestData GetResetActorRequestData(); + protected abstract RecordingRequestData GetRecordingRequestData(); + protected abstract TrackerRequestData GetTrackerRequestData(); + protected abstract PlaybackRequestData GetPlaybackRequestData(); + protected abstract LivestreamRequestData GetLivestreamRequestData(); + protected abstract InfoRequestData GetInfoRequestData(); + + private IEnumerator SendRequestEnum(string endpoint, string json, TaskCompletionSource task) + { + IsTrackerRequestInProgress = true; + + string url = $"http://{IP}:{port}/v1/{apiKey}/{endpoint}"; + if (debug) + { + Debug.Log("Sending request: " + url, this.transform); + Debug.Log("Sending data: " + json, this.transform); + } + + // convert json string to byte + byte[] formData = System.Text.Encoding.UTF8.GetBytes(json); + UnityWebRequest request = new UnityWebRequest(url, UnityWebRequest.kHttpVerbPOST); + UploadHandlerRaw uploadHandler = new UploadHandlerRaw(formData); + request.uploadHandler = uploadHandler; + request.downloadHandler = new DownloadHandlerBuffer(); + request.SetRequestHeader("Content-Type", "application/json"); + yield return request.SendWebRequest(); + + string body = request.downloadHandler.text; + if (request.result != UnityWebRequest.Result.ConnectionError) + { + if (debug) + Debug.Log($"Response: {request.responseCode}: {body}", this.transform); + OnCommmandResponse(JsonUtility.FromJson(body)); + } + else + { + if (debug) + Debug.LogWarning($"There was an error sending request: {request.error}\n{body}", this.transform); + OnCommmandError(request.error); + } + task.SetResult(body); + request.Dispose(); + uploadHandler.Dispose(); + IsTrackerRequestInProgress = false; + } + + protected virtual void OnCommmandResponse(ResponseMessage response) + { + + } + + protected virtual void OnCommmandError(string error) + { + + } + } + + [System.Serializable] + public class ResponseMessage + { + public string description; + public string response_code; + public long startTime; + public dynamic[] parameters; + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/StudioCommandAPIBase.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/StudioCommandAPIBase.cs.meta new file mode 100644 index 000000000..8b06017f9 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/StudioCommandAPIBase.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 23be0de9580d445683a1b786461b328d +timeCreated: 1565078385 \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/TrackerRequestData.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/TrackerRequestData.cs new file mode 100644 index 000000000..4411f6c82 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/TrackerRequestData.cs @@ -0,0 +1,75 @@ +using UnityEngine; + +namespace Rokoko.CommandAPI +{ + public class TrackerRequestData + { + // the struct is used to serialize attributes the same way as System.Numberics do + [System.Serializable] + private struct TrackerVector3 + { + [SerializeField] public float X; + [SerializeField] public float Y; + [SerializeField] public float Z; + + public TrackerVector3(float x, float y, float z) + { + X = x; + Y = y; + Z = z; + } + } + [System.Serializable] + private struct TrackerQuaternion + { + [SerializeField] public float X; + [SerializeField] public float Y; + [SerializeField] public float Z; + [SerializeField] public float W; + [SerializeField] public bool IsIdentity; + + public TrackerQuaternion(float x, float y, float z, float w) + { + X = x; + Y = y; + Z = z; + W = w; + IsIdentity = false; + } + } + + // tracker attributes + + [SerializeField] private string device_id = ""; + [SerializeField] private string bone_attached = ""; + [SerializeField] private TrackerVector3 position; + [SerializeField] private TrackerQuaternion rotation; + [SerializeField] private float timeout = 2f; + [SerializeField] private bool is_query_only = false; + + + // public members + + public string DeviceId { get => device_id; set => device_id = value; } + public string BoneAttached { get => bone_attached; set => bone_attached = value; } + public Vector3 Position { + get => new Vector3(position.X, position.Y, position.Z); + set => position = new TrackerVector3(value.x, value.y, value.z); } + public Quaternion Rotation { + get => new Quaternion(rotation.X, rotation.Y, rotation.Z, rotation.W); + set => rotation = new TrackerQuaternion(value.x, value.y, value.z, value.w); } + public float Timeout { get => timeout; set => timeout = value; } + public bool IsQueryOnly { get => is_query_only; set => is_query_only = value; } + + + public override string ToString() + { + return $"{DeviceId}, {BoneAttached}, {Position}, {Rotation}, {Timeout}"; + } + + public string ToJson() + { + return JsonUtility.ToJson(this); + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/TrackerRequestData.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/TrackerRequestData.cs.meta new file mode 100644 index 000000000..0da218d94 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/CommandAPI/TrackerRequestData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd7aa49a68dc87441bc55e7e5d2db0d8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/JsonLiveSerializerV3.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/JsonLiveSerializerV3.cs new file mode 100644 index 000000000..f6be07d90 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/JsonLiveSerializerV3.cs @@ -0,0 +1,266 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Rokoko.Core +{ + #region Data Structs + + [System.Serializable] + public class LiveFrame_v4 + { + // Header + public float version; + // Target fps + public float fps; + public SceneFrame scene; + } + + [System.Serializable] + public struct SceneFrame + { + public float timestamp; + public ActorFrame[] actors; + public CharacterFrame[] characters; + public PropFrame[] props; + } + + [System.Serializable] + public struct ActorFrame + { + public string name; + public int[] color; + public Meta meta; + public Dimensions dimensions; + + public BodyFrame body; + public FaceFrame face; + + [System.Serializable] + public struct Meta + { + public bool hasGloves; + public bool hasLeftGlove; + public bool hasRightGlove; + public bool hasBody; + public bool hasFace; + } + + [System.Serializable] + public struct Dimensions + { + public float totalHeight; + public float hipHeight; + } + } + + [System.Serializable] + public class CharacterFrame + { + public string name; + public CharacterJointFrame[] joints; + public BlendshapesFrame blendshapes; + } + + [System.Serializable] + public class BlendshapesFrame + { + public string[] names; + public float[] values; + } + + [System.Serializable] + public class PropFrame + { + public string name; + public int[] color; + public int type; + public Vector3Frame position; + public Vector4Frame rotation; + } + + + [System.Serializable] + public class BodyFrame + { + public ActorJointFrame hip; + public ActorJointFrame spine; + public ActorJointFrame chest; + public ActorJointFrame neck; + public ActorJointFrame head; + public ActorJointFrame leftShoulder; + public ActorJointFrame leftUpperArm; + public ActorJointFrame leftLowerArm; + public ActorJointFrame leftHand; + public ActorJointFrame rightShoulder; + public ActorJointFrame rightUpperArm; + public ActorJointFrame rightLowerArm; + public ActorJointFrame rightHand; + + public ActorJointFrame leftUpLeg; + public ActorJointFrame leftLeg; + public ActorJointFrame leftFoot; + public ActorJointFrame leftToe; + public ActorJointFrame leftToeEnd; + + public ActorJointFrame rightUpLeg; + public ActorJointFrame rightLeg; + public ActorJointFrame rightFoot; + public ActorJointFrame rightToe; + public ActorJointFrame rightToeEnd; + + + public ActorJointFrame leftThumbProximal; + public ActorJointFrame leftThumbMedial; + public ActorJointFrame leftThumbDistal; + public ActorJointFrame leftThumbTip; + + public ActorJointFrame leftIndexProximal; + public ActorJointFrame leftIndexMedial; + public ActorJointFrame leftIndexDistal; + public ActorJointFrame leftIndexTip; + + public ActorJointFrame leftMiddleProximal; + public ActorJointFrame leftMiddleMedial; + public ActorJointFrame leftMiddleDistal; + public ActorJointFrame leftMiddleTip; + + public ActorJointFrame leftRingProximal; + public ActorJointFrame leftRingMedial; + public ActorJointFrame leftRingDistal; + public ActorJointFrame leftRingTip; + + public ActorJointFrame leftLittleProximal; + public ActorJointFrame leftLittleMedial; + public ActorJointFrame leftLittleDistal; + public ActorJointFrame leftLittleTip; + + + public ActorJointFrame rightThumbProximal; + public ActorJointFrame rightThumbMedial; + public ActorJointFrame rightThumbDistal; + public ActorJointFrame rightThumbTip; + + public ActorJointFrame rightIndexProximal; + public ActorJointFrame rightIndexMedial; + public ActorJointFrame rightIndexDistal; + public ActorJointFrame rightIndexTip; + + public ActorJointFrame rightMiddleProximal; + public ActorJointFrame rightMiddleMedial; + public ActorJointFrame rightMiddleDistal; + public ActorJointFrame rightMiddleTip; + public ActorJointFrame rightRingProximal; + public ActorJointFrame rightRingMedial; + public ActorJointFrame rightRingDistal; + public ActorJointFrame rightRingTip; + + public ActorJointFrame rightLittleProximal; + public ActorJointFrame rightLittleMedial; + public ActorJointFrame rightLittleDistal; + public ActorJointFrame rightLittleTip; + } + + [System.Serializable] + public class FaceFrame + { + public string faceId; + public float eyeBlinkLeft; + public float eyeLookDownLeft; + public float eyeLookInLeft; + public float eyeLookOutLeft; + public float eyeLookUpLeft; + public float eyeSquintLeft; + public float eyeWideLeft; + public float eyeBlinkRight; + public float eyeLookDownRight; + public float eyeLookInRight; + public float eyeLookOutRight; + public float eyeLookUpRight; + public float eyeSquintRight; + public float eyeWideRight; + public float jawForward; + public float jawLeft; + public float jawRight; + public float jawOpen; + public float mouthClose; + public float mouthFunnel; + public float mouthPucker; + public float mouthLeft; + public float mouthRight; + public float mouthSmileLeft; + public float mouthSmileRight; + public float mouthFrownLeft; + public float mouthFrownRight; + public float mouthDimpleLeft; + public float mouthDimpleRight; + public float mouthStretchLeft; + public float mouthStretchRight; + public float mouthRollLower; + public float mouthRollUpper; + public float mouthShrugLower; + public float mouthShrugUpper; + public float mouthPressLeft; + public float mouthPressRight; + public float mouthLowerDownLeft; + public float mouthLowerDownRight; + public float mouthUpperUpLeft; + public float mouthUpperUpRight; + public float browDownLeft; + public float browDownRight; + public float browInnerUp; + public float browOuterUpLeft; + public float browOuterUpRight; + public float cheekPuff; + public float cheekSquintLeft; + public float cheekSquintRight; + public float noseSneerLeft; + public float noseSneerRight; + public float tongueOut; + } + + [System.Serializable] + public struct Vector3Frame + { + public float x; + public float y; + public float z; + + public override string ToString() => $"VF3: {x}, {y}, {z} :"; + } + + [System.Serializable] + public struct Vector4Frame + { + public float x; + public float y; + public float z; + public float w; + + public override string ToString() => $"VF4: {x}, {y}, {z}, {w} :"; + } + + [System.Serializable] + public struct ActorJointFrame + { + public Vector3Frame position; + public Vector4Frame rotation; + } + + [System.Serializable] + public struct CharacterJointFrame + { + public string name; + public int parent; //!< parent index in a chracter frame joints array (-1 for the root) + public Vector3Frame position; + public Vector4Frame rotation; + } + + #endregion + + public enum PropType + { + NORMAL = 0, + CAMERA = 1, + STICK = 2 + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/JsonLiveSerializerV3.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/JsonLiveSerializerV3.cs.meta new file mode 100644 index 000000000..4a8e56783 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/JsonLiveSerializerV3.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 61943ed9a14442b4aa24885f7e8a735e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/LZ4Wrapper.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/LZ4Wrapper.cs new file mode 100644 index 000000000..e2ea8e752 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/LZ4Wrapper.cs @@ -0,0 +1,79 @@ +using System; +using System.Runtime.InteropServices; + +namespace Rokoko.Core +{ + public class LZ4Wrapper + { + public static class LZ4_API + { +#if (UNITY_IOS || UNITY_WEBGL) && !UNITY_EDITOR + private const string LUADLL = "__Internal"; +#else + private const string LUADLL = "lz4"; +#endif + + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int Unity_LZ4_compress(IntPtr src, int srcSize, IntPtr dst, int dstCapacity, int compressionLevel); + + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int Unity_LZ4_compressSize(int srcSize, int compressionLevel); + + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int Unity_LZ4_uncompressSize(IntPtr srcBuffer, int srcSize); + + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int Unity_LZ4_decompress(IntPtr src, int srcSize, IntPtr dst, int dstCapacity); + } + + public static byte[] Compress(byte[] input, int compressionLevel = 3) + { + byte[] result = null; + + if (input != null && input.Length > 0) + { + int maxSize = LZ4_API.Unity_LZ4_compressSize(input.Length, compressionLevel); + if (maxSize > 0) + { + var buffer = new byte[maxSize]; + var srcHandle = GCHandle.Alloc(input, GCHandleType.Pinned); + var dstHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); + var actualSize = LZ4_API.Unity_LZ4_compress(srcHandle.AddrOfPinnedObject(), input.Length, dstHandle.AddrOfPinnedObject(), maxSize, compressionLevel); + + if (actualSize > 0) + { + result = new byte[actualSize]; + Array.Copy(buffer, result, actualSize); + } + + srcHandle.Free(); + dstHandle.Free(); + } + } + + return result; + } + + public static byte[] Decompress(byte[] input) + { + byte[] result = null; + + if (input != null && input.Length > 0) + { + var srcHandle = GCHandle.Alloc(input, GCHandleType.Pinned); + var uncompressSize = LZ4_API.Unity_LZ4_uncompressSize(srcHandle.AddrOfPinnedObject(), input.Length); + result = new byte[uncompressSize]; + var dstHandle = GCHandle.Alloc(result, GCHandleType.Pinned); + if (LZ4_API.Unity_LZ4_decompress(srcHandle.AddrOfPinnedObject(), input.Length, dstHandle.AddrOfPinnedObject(), result.Length) != 0) + { + result = null; + } + + srcHandle.Free(); + dstHandle.Free(); + } + + return result; + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/LZ4Wrapper.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/LZ4Wrapper.cs.meta new file mode 100644 index 000000000..c366f3eab --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/LZ4Wrapper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8b437a01c2f9fde489b7a18b6bac93d7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/StudioReceiver.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/StudioReceiver.cs new file mode 100644 index 000000000..f0359119d --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/StudioReceiver.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using UnityEngine; + +namespace Rokoko.Core +{ + public class StudioReceiver : UDPReceiver + { + public event EventHandler onStudioDataReceived; + public bool useLZ4Compression = true; + public bool verbose = false; + + protected override void OnDataReceived(byte[] data, IPEndPoint endPoint) + { + LiveFrame_v4 liveFrame_V4 = null; + try + { + base.OnDataReceived(data, endPoint); + byte[] uncompressed; + + if (useLZ4Compression) + { + // Decompress LZ4 + uncompressed = LZ4Wrapper.Decompress(data); + if (uncompressed == null || uncompressed.Length == 0) + { + Debug.LogError("Incoming data are in bad format. Please ensure you are using JSON v3 as forward data format"); + return; + } + } + else + { + uncompressed = data; + } + + // Convert from Json + string text = System.Text.Encoding.UTF8.GetString(uncompressed); + if (verbose) + { + Debug.Log(text); + } + liveFrame_V4 = JsonUtility.FromJson(text); + + if (liveFrame_V4 == null) + { + Debug.LogError("Incoming data are in bad format. Please ensure you are using JSON v3 as forward data format"); + return; + } + + if (verbose) + { + int numberOfActors = (liveFrame_V4.scene.actors != null) ? liveFrame_V4.scene.actors.Length : 0; + int numberOfChars = (liveFrame_V4.scene.characters != null) ? liveFrame_V4.scene.characters.Length : 0; + + if (numberOfActors == 0 && numberOfChars == 0) + { + Debug.LogError("Incoming data has no actors and no characters in the stream"); + } + } + } + catch { } + + onStudioDataReceived?.Invoke(this, liveFrame_V4); + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/StudioReceiver.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/StudioReceiver.cs.meta new file mode 100644 index 000000000..250d15832 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/StudioReceiver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8cb1306ef28465d49b3d63bc97885931 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/UDPReceiver.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/UDPReceiver.cs new file mode 100644 index 000000000..ec8169f01 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/UDPReceiver.cs @@ -0,0 +1,109 @@ +using System.Net; +using System.Net.Sockets; +using System.Threading; +using UnityEngine; + +namespace Rokoko.Core +{ + public class UDPReceiver + { + public int sendPortNumber = 14043; + public int receivePortNumber = 14043; + public int bufferSize = 65000; + + private UdpClient client; + private Thread thread; + + public virtual void Initialize() + { + try + { + client = new UdpClient(receivePortNumber); + client.Client.SendBufferSize = bufferSize; + } + catch (SocketException) + { + Debug.LogError($"Seem like port:{receivePortNumber} is already in use. Is plugin running already in other application?"); + } + catch(System.Exception ex) + { + throw ex; + } + } + + public virtual void StartListening() + { + if (client == null) + { + Debug.LogError("UDPReceiver - Client isn't initialized."); + return; + } + + if (thread != null) + { + Debug.LogWarning("UDPReceiver - Cannot start listening. Thread is already listening"); + return; + } + + StartListeningThread(); + } + + public virtual void StopListening() + { + thread?.Abort(); + client?.Close(); + } + + public virtual void Dispose() + { + StopListening(); + client?.Dispose(); + client = null; + } + + public void Send(string ipAddress, byte[] data) + { + Send(ipAddress, data, sendPortNumber); + } + + public void Send(string ipAddress, byte[] data, int portNumber) + { + client?.Send(data, data.Length, new IPEndPoint(IPAddress.Parse(ipAddress), portNumber)); + } + + public void Send(IPEndPoint endPoint, byte[] data) + { + client?.Send(data, data.Length, endPoint); + } + + public bool IsListening() => thread != null; + + protected virtual void OnDataReceived(byte[] data, IPEndPoint endPoint) { } + + private void StartListeningThread() + { + thread = new Thread(ListenToUDP); + thread.IsBackground = true; + thread.Start(); + } + + private void ListenToUDP() + { + while (client != null) + { + try + { + IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, receivePortNumber); + byte[] data = client.Receive(ref endpoint); + OnDataReceived(data, endpoint); + } + catch (ThreadAbortException) { } + catch (SocketException) { } + catch (System.Exception ex) + { + Debug.Log(ex.Message); + } + } + } + } +} diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/UDPReceiver.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/UDPReceiver.cs.meta new file mode 100644 index 000000000..e79ad368e --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/UDPReceiver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9af01aaa3a63e7a4499c28b22dd1b20d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono.meta new file mode 100644 index 000000000..6801a5a69 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9fd28f925f7e38846913c7631653e97a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs.meta new file mode 100644 index 000000000..2fc218d10 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0b9e800521674004693e0a38816ed885 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Actor.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Actor.cs new file mode 100644 index 000000000..b4d0574f1 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Actor.cs @@ -0,0 +1,397 @@ +using Rokoko.Core; +using Rokoko.Helper; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Rokoko.Inputs +{ + public class Actor : MonoBehaviour + { + [System.Serializable] + public enum BoneMappingEnum + { + Animator, + Custom + } + + [System.Serializable] + public enum RotationSpace + { + Offset, + World, + Self + } + + [HideInInspector] public string profileName = "DemoProfile"; + + [HideInInspector] public BoneMappingEnum boneMapping; + [HideInInspector] public Animator animator; + [HideInInspector] public HumanBoneMapping customBoneMapping; + + [Header("Convert Space")] + [Tooltip("Convert Studio data to Unity position space")] + public Space positionSpace = Space.Self; + [Tooltip("Convert Studio data to Unity rotation space")] + public RotationSpace rotationSpace = RotationSpace.Offset; + + [Space(10)] + [Tooltip("Calculate Model's height comparing to Actor's and position the Hips accordingly.\nGreat tool to align with the floor")] + public bool adjustHipHeightBasedOnStudioActor = false; + + [HideInInspector] public Face face = null; + + [Header("Log extra info")] + public bool debug = false; + + [HideInInspector] + public HumanTPoseDictionary characterTPose = new HumanTPoseDictionary(); + + [HideInInspector] + public bool isValidTpose = false; + + protected Dictionary animatorHumanBones = new Dictionary(); + private Dictionary offsets = new Dictionary(); + + private float hipHeight = 0; + + #region Initialize + + protected virtual void Awake() + { + if(animator == null) + { + Debug.LogError($"Actor {this.name} isn't configured", this.transform); + return; + } + + if (!animator.isHuman) + { + Debug.LogError("Model is not marked as Humanoid. Please go in model inspector, under Rig tab and select AnimationType as Humanoid.", this.transform); + return; + } + + InitializeAnimatorHumanBones(); + InitializeBoneOffsets(); + + // Get the Hip height independent of parent transformations + hipHeight = GetBone(HumanBodyBones.Hips).parent.InverseTransformVector(GetBone(HumanBodyBones.Hips).localPosition).y; + hipHeight = Mathf.Abs(hipHeight); + + if (characterTPose.Count == 0) + Debug.LogError($"Character {this.name} is not set to TPose. Please ensure you assign a valid TPose in Editor before playing", this.transform); + } + + /// + /// Register Actor override in StudioManager. + /// + private void Start() + { + if (animator != null && !animator.isHuman) return; + + if (!string.IsNullOrEmpty(profileName)) + StudioManager.AddActorOverride(this); + } + + [ContextMenu("CalcualteTPose")] + public void CalculateTPose() + { + InitializeAnimatorHumanBones(); + InitializeCharacterTPose(); + + isValidTpose = IsValidTPose(); + } + + private void InitializeBonesIfNeeded() + { + if (boneMapping == BoneMappingEnum.Animator && animatorHumanBones.Count == 0) + InitializeAnimatorHumanBones(); + } + + /// + /// Store Character's T Pose. + /// + protected void InitializeCharacterTPose() + { + characterTPose.Clear(); + foreach (HumanBodyBones bone in RokokoHelper.HumanBodyBonesArray) + { + if (bone == HumanBodyBones.LastBone) break; + Transform boneTransform = GetBone(bone); + + if (boneTransform == null) continue; + + characterTPose.Add(bone, boneTransform.rotation); + } + } + + /// + /// Calculate Character's offset based on its T Pose and Newton's T Pose. + /// + protected void InitializeBoneOffsets() + { + // Calculate offsets based on Smartsuit T pose + offsets = CalculateRotationOffsets(); + } + + /// + /// Cache the bone transforms from Animator. + /// + protected void InitializeAnimatorHumanBones() + { + if (boneMapping != BoneMappingEnum.Animator) return; + if (animator == null || !animator.isHuman) return; + animatorHumanBones.Clear(); + + foreach (HumanBodyBones bone in RokokoHelper.HumanBodyBonesArray) + { + if (bone == HumanBodyBones.LastBone) break; + animatorHumanBones.Add(bone, animator.GetBoneTransform(bone)); + } + } + + #endregion + + #region Public Methods + + /// + /// Update Skeleton and Face data based on ActorFrame. + /// + public virtual void UpdateActor(ActorFrame actorFrame) + { + if (animator == null || !animator.isHuman) return; + + profileName = actorFrame.name; + + bool updateBody = actorFrame.meta.hasBody || actorFrame.meta.hasGloves; + + // Update skeleton from data + if (updateBody) + UpdateSkeleton(actorFrame); + + // Update face from data + if (actorFrame.meta.hasFace) + face?.UpdateFace(actorFrame.face); + } + + /// + /// Create Idle/Default Actor. + /// + public virtual void CreateIdle(string actorName) + { + this.profileName = actorName; + } + + public float GetActorHeight() + { + InitializeBonesIfNeeded(); + + Transform head = GetBone(HumanBodyBones.Head); + Transform foot = GetBone(HumanBodyBones.LeftFoot); + if (head == null || foot == null) + return 1.8f; + + // Add space for head mesh + return Vector3.Distance(head.position, foot.position) + 0.25f; + } + + #endregion + + #region Internal Logic + + private bool IsValidTPose() + { + InitializeBonesIfNeeded(); + + Transform rightHand = GetBone(HumanBodyBones.RightHand); + Transform leftHand = GetBone(HumanBodyBones.LeftHand); + + Transform spine = GetBone(HumanBodyBones.Spine); + Transform chest = GetBone(HumanBodyBones.Chest); + + if(rightHand == null || leftHand == null || spine == null || chest == null) + { + Debug.LogError("Cant validate actor height. Bone is missing", this.transform); + return false; + } + + Vector3 armsDirection = rightHand.position - leftHand.position; + armsDirection.Normalize(); + + Vector3 spineDirection = chest.position - spine.position; + spineDirection.Normalize(); + + return Vector3.Dot(armsDirection, Vector3.right) > 0.99f && + Vector3.Dot(spineDirection, Vector3.up) > 0.99f; + } + + /// + /// Get Transform from a given HumanBodyBones. + /// + private Transform GetBone(HumanBodyBones bone) + { + switch (boneMapping) + { + case BoneMappingEnum.Animator: + return animatorHumanBones[bone]; + case BoneMappingEnum.Custom: + return customBoneMapping.customBodyBones[(int)bone]; + } + + return null; + } + + /// + /// Update Humanoid Skeleton based on BodyData. + /// + protected void UpdateSkeleton(ActorFrame actorFrame) + { + foreach (HumanBodyBones bone in RokokoHelper.HumanBodyBonesArray) + { + if (bone == HumanBodyBones.LastBone) break; + ActorJointFrame? boneFrame = actorFrame.body.GetBoneFrame(bone); + if (boneFrame != null) + { + bool shouldUpdatePosition = bone == HumanBodyBones.Hips; + + Quaternion worldRotation = boneFrame.Value.rotation.ToQuaternion(); + Vector3 worldPosition = boneFrame.Value.position.ToVector3(); + + // Offset Hip bone + if (shouldUpdatePosition && adjustHipHeightBasedOnStudioActor) + worldPosition = new Vector3(worldPosition.x, worldPosition.y - (actorFrame.dimensions.hipHeight - hipHeight), worldPosition.z); + + UpdateBone(bone, worldPosition, worldRotation, shouldUpdatePosition, positionSpace, rotationSpace); + } + } + } + + /// + /// Update Human bone. + /// + protected void UpdateBone(HumanBodyBones bone, Vector3 worldPosition, Quaternion worldRotation, bool updatePosition, Space positionSpace, RotationSpace rotationSpace) + { + // Find Humanoid bone + Transform boneTransform = GetBone(bone); + + // Check if bone is valid + if (boneTransform == null) + { + if (debug) + Debug.LogWarning($"Couldn't find Transform for bone:{bone} in {boneMapping}Mapping component", this.transform); + return; + } + + // Update position + if (updatePosition) + { + if (positionSpace == Space.World || boneTransform.parent == null) + { + boneTransform.position = worldPosition; + } + else + { + boneTransform.position = boneTransform.parent.rotation * worldPosition + boneTransform.parent.position; + } + } + + // Update Rotation + if (rotationSpace == RotationSpace.World) + { + boneTransform.rotation = worldRotation; + } + else if (rotationSpace == RotationSpace.Self) + { + if (transform.parent != null) + boneTransform.rotation = this.transform.parent.rotation * worldRotation; + else + boneTransform.rotation = worldRotation; + } + else + { + boneTransform.rotation = GetBone(HumanBodyBones.Hips).parent.rotation * worldRotation * offsets[bone]; + } + } + + #endregion + + /// + /// Get the rotational difference between 2 humanoid T poses. + /// + private Dictionary CalculateRotationOffsets() + { + Dictionary offsets = new Dictionary(); + foreach (HumanBodyBones bone in RokokoHelper.HumanBodyBonesArray) + { + if (!characterTPose.Contains(bone)) continue; + Quaternion rotation = Quaternion.Inverse(SmartsuitTPose[bone]) * characterTPose[bone]; + + offsets.Add(bone, rotation); + } + return offsets; + } + + /// + /// Get Smartsuit T pose data + /// + private static Dictionary SmartsuitTPose = new Dictionary() { + {HumanBodyBones.Hips, new Quaternion(0.000f, 0.000f, 0.000f, 1.000f)}, + {HumanBodyBones.LeftUpperLeg, new Quaternion(0.000f, 0.707f, 0.000f, 0.707f)}, + {HumanBodyBones.RightUpperLeg, new Quaternion(0.000f, -0.707f, 0.000f, 0.707f)}, + {HumanBodyBones.LeftLowerLeg, new Quaternion(0.000f, 0.707f, 0.000f, 0.707f)}, + {HumanBodyBones.RightLowerLeg, new Quaternion(0.000f, -0.707f, 0.000f, 0.707f)}, + {HumanBodyBones.LeftFoot, new Quaternion(0.000f, 0.707f, -0.707f, 0.000f)}, + {HumanBodyBones.RightFoot, new Quaternion(0.000f, -0.707f, 0.707f, 0.000f)}, + {HumanBodyBones.Spine, new Quaternion(0.000f, 0.000f, 1.000f, 0.000f)}, + {HumanBodyBones.Chest, new Quaternion(0.000f, 0.000f, 1.000f, 0.000f)}, + {HumanBodyBones.Neck, new Quaternion(0.000f, 0.000f, 1.000f, 0.000f)}, + {HumanBodyBones.Head, new Quaternion(0.000f, 0.000f, 1.000f, 0.000f)}, + {HumanBodyBones.LeftShoulder, new Quaternion(0.000f, 0.000f, 0.707f, -0.707f)}, + {HumanBodyBones.RightShoulder, new Quaternion(0.000f, 0.000f, 0.707f, 0.707f)}, + {HumanBodyBones.LeftUpperArm, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)}, + {HumanBodyBones.RightUpperArm, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)}, + {HumanBodyBones.LeftLowerArm, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)}, + {HumanBodyBones.RightLowerArm, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)}, + {HumanBodyBones.LeftHand, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)}, + {HumanBodyBones.RightHand, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)}, + {HumanBodyBones.LeftToes, new Quaternion(0.000f, 0.707f, -0.707f, 0.000f)}, + {HumanBodyBones.RightToes, new Quaternion(0.000f, -0.707f, 0.707f, 0.000f)}, + {HumanBodyBones.LeftEye, new Quaternion(0.000f, 0.000f, 0.000f, 0.000f)}, + {HumanBodyBones.RightEye, new Quaternion(0.000f, 0.000f, 0.000f, 0.000f)}, + {HumanBodyBones.Jaw, new Quaternion(0.000f, 0.000f, 0.000f, 0.000f)}, + {HumanBodyBones.LeftThumbProximal, new Quaternion(-0.561f, -0.701f, 0.430f, -0.092f)}, + {HumanBodyBones.LeftThumbIntermediate, new Quaternion(-0.653f, -0.653f, 0.271f, -0.271f)}, + {HumanBodyBones.LeftThumbDistal, new Quaternion(-0.653f, -0.653f, 0.271f, -0.271f)}, + {HumanBodyBones.LeftIndexProximal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)}, + {HumanBodyBones.LeftIndexIntermediate, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)}, + {HumanBodyBones.LeftIndexDistal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)}, + {HumanBodyBones.LeftMiddleProximal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)}, + {HumanBodyBones.LeftMiddleIntermediate, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)}, + {HumanBodyBones.LeftMiddleDistal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)}, + {HumanBodyBones.LeftRingProximal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)}, + {HumanBodyBones.LeftRingIntermediate, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)}, + {HumanBodyBones.LeftRingDistal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)}, + {HumanBodyBones.LeftLittleProximal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)}, + {HumanBodyBones.LeftLittleIntermediate, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)}, + {HumanBodyBones.LeftLittleDistal, new Quaternion(-0.500f, -0.500f, 0.500f, -0.500f)}, + {HumanBodyBones.RightThumbProximal, new Quaternion(0.561f, -0.701f, 0.430f, 0.092f)}, + {HumanBodyBones.RightThumbIntermediate, new Quaternion(0.653f, -0.653f, 0.271f, 0.271f)}, + {HumanBodyBones.RightThumbDistal, new Quaternion(0.653f, -0.653f, 0.271f, 0.271f)}, + {HumanBodyBones.RightIndexProximal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)}, + {HumanBodyBones.RightIndexIntermediate, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)}, + {HumanBodyBones.RightIndexDistal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)}, + {HumanBodyBones.RightMiddleProximal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)}, + {HumanBodyBones.RightMiddleIntermediate, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)}, + {HumanBodyBones.RightMiddleDistal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)}, + {HumanBodyBones.RightRingProximal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)}, + {HumanBodyBones.RightRingIntermediate, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)}, + {HumanBodyBones.RightRingDistal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)}, + {HumanBodyBones.RightLittleProximal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)}, + {HumanBodyBones.RightLittleIntermediate, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)}, + {HumanBodyBones.RightLittleDistal, new Quaternion(0.500f, -0.500f, 0.500f, 0.500f)}, + {HumanBodyBones.UpperChest, new Quaternion(0.000f, 0.000f, 1.000f, 0.000f)} + }; + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Actor.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Actor.cs.meta new file mode 100644 index 000000000..dd97febbc --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Actor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0b3d830ef66b485459e15992199096b7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/ActorNewton.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/ActorNewton.cs new file mode 100644 index 000000000..5fc4bf471 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/ActorNewton.cs @@ -0,0 +1,91 @@ +using Rokoko.Core; +using Rokoko.Helper; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Rokoko.Inputs +{ + public class ActorNewton : Actor + { + private const int HEAD_TO_MATERIAL_INDEX = 5; + private const int JOINT_TO_MATERIAL_INDEX = 1; + + [Header("Newton materials")] + [SerializeField] protected Renderer meshRenderer = null; + [SerializeField] private Material bodyMaterial = null; + [SerializeField] private Material faceInvisibleMaterial = null; + public bool autoHideFaceWhenInactive = false; + + protected Material[] meshMaterials; + + #region Initialize + + protected override void Awake() + { + base.Awake(); + InitializeMaterials(); + } + + private void InitializeMaterials() + { + // Clone the material, so not to affect other objects + bodyMaterial = Material.Instantiate(bodyMaterial); + meshMaterials = new Material[meshRenderer.materials.Length]; + for (int i = 0; i < meshMaterials.Length; i++) + { + // Keep joint material as source + if (i == JOINT_TO_MATERIAL_INDEX) + meshMaterials[i] = meshRenderer.materials[i]; + else + meshMaterials[i] = bodyMaterial; + } + meshRenderer.materials = meshMaterials; + } + + #endregion + + #region Public Methods + + public override void CreateIdle(string actorName) + { + base.CreateIdle(actorName); + + if (autoHideFaceWhenInactive) + face?.gameObject.SetActive(false); + } + + public override void UpdateActor(ActorFrame actorFrame) + { + base.UpdateActor(actorFrame); + + bool updateBody = actorFrame.meta.hasBody || actorFrame.meta.hasGloves; + + // Enable/Disable body renderer + meshRenderer.enabled = updateBody; + + // Update material color and visibility + UpdateMaterialColors(actorFrame); + + // Enable/Disable face renderer + if (autoHideFaceWhenInactive) + face?.gameObject.SetActive(actorFrame.meta.hasFace); + } + + #endregion + + #region Internal Logic + + private void UpdateMaterialColors(ActorFrame actorFrame) + { + bodyMaterial.color = actorFrame.color.ToColor(); + meshMaterials[HEAD_TO_MATERIAL_INDEX] = (actorFrame.meta.hasFace) ? faceInvisibleMaterial : bodyMaterial; + meshRenderer.materials = meshMaterials; + + face?.SetColor(actorFrame.color.ToColor()); + } + + #endregion + + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/ActorNewton.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/ActorNewton.cs.meta new file mode 100644 index 000000000..501ead62f --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/ActorNewton.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7694c1eb6726a2d49a01558f46c8ef83 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/BlendShapesMapping.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/BlendShapesMapping.cs new file mode 100644 index 000000000..1aa9a7731 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/BlendShapesMapping.cs @@ -0,0 +1,13 @@ +using Rokoko.Core; +using Rokoko.Helper; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Rokoko.Inputs +{ + public class BlendShapesMapping : MonoBehaviour + { + public BlendshapesDictionary blendshapeNames = new BlendshapesDictionary(); + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/BlendShapesMapping.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/BlendShapesMapping.cs.meta new file mode 100644 index 000000000..0fb0b28ed --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/BlendShapesMapping.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07a3f22e5c105474e9725d5438558a99 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Character.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Character.cs new file mode 100644 index 000000000..111b83dad --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Character.cs @@ -0,0 +1,183 @@ +using Rokoko.Core; +using Rokoko.Helper; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Rokoko.Inputs +{ + public class Character : MonoBehaviour + { + [System.Serializable] + public enum RotationSpace + { + Offset, + World, + Self + } + + [HideInInspector] + public string profileName = ""; + + [HideInInspector] public Animator animator; + + [Header("Convert Space")] + [Tooltip("Convert Studio data to Unity position space")] + public Space positionSpace = Space.World; + [Tooltip("Convert Studio data to Unity rotation space")] + public RotationSpace rotationSpace = RotationSpace.World; + + [Tooltip("A rotation pre rotation applied")] + public Vector3 PreRotation = new Vector3(0.0f, 0.0f, 0.0f); + + [Tooltip("A rotation post rotation applied")] + public Vector3 PostRotation = new Vector3(0.0f, 0.0f, 0.0f); + + [Space(10)] + [Tooltip("Calculate Model's height comparing to Actor's and position the Hips accordingly.\nGreat tool to align with the floor")] + public bool adjustHipHeightBasedOnStudioActor = false; + + public string hipsName = "pelvis"; + + [HideInInspector] public Face face = null; + + [Header("Log extra info")] + public bool debug = false; + + + private Dictionary _skeletonJoints = new Dictionary(); + + #region Initialize + + protected virtual void Awake() + { + if(animator == null) + { + Debug.LogError($"Character {this.name} isn't configured", this.transform); + //return; + } + + InitializeSkeletonJoints(); + } + + private void InitializeSkeletonJoints() + { + _skeletonJoints.Clear(); + Transform[] transforms = GetComponentsInChildren(); + for (int i=0; i() != null) + continue; + + _skeletonJoints.Add(transforms[i].name, transforms[i]); + } + } + + /// + /// Register Actor override in StudioManager. + /// + private void Start() + { + //if (animator != null && !animator.isHuman) return; + + if (!string.IsNullOrEmpty(profileName)) + StudioManager.AddCharacterOverride(this); + } + + + #endregion + + #region Public Methods + + /// + /// Update Skeleton and Face data based on ActorFrame. + /// + public virtual void UpdateCharacter(CharacterFrame frame) + { + //if (animator == null || !animator.isHuman) return; + + profileName = frame.name; + + // Update skeleton from data + UpdateSkeleton(frame); + + if (frame.blendshapes != null && frame.blendshapes.names != null && frame.blendshapes.names.Length > 0) + { + face?.UpdateFace(frame.blendshapes.names, frame.blendshapes.values); + } + else if (debug) + { + Debug.LogError($"Character {this.name} face has no blendshapes"); + } + } + + /// + /// Create Idle/Default Actor. + /// + public virtual void CreateIdle(string actorName) + { + this.profileName = actorName; + } + + #endregion + + #region Internal Logic + + + /// + /// Update Humanoid Skeleton based on BodyData. + /// + protected void UpdateSkeleton(CharacterFrame frame) + { + for (int i=0; i + /// Update Human bone. + /// + protected void UpdateBone(Transform boneTransform, Vector3 worldPosition, Quaternion worldRotation, bool updatePosition, Space positionSpace, RotationSpace rotationSpace) + { + // Update position + if (updatePosition) + { + if (positionSpace == Space.World || boneTransform.parent == null) + { + boneTransform.position = worldPosition; + } + else + { + boneTransform.position = boneTransform.parent.rotation * worldPosition + boneTransform.parent.position; + } + } + + // Update Rotation + if (rotationSpace == RotationSpace.World) + { + boneTransform.rotation = Quaternion.Euler(PreRotation.x, PreRotation.y, PreRotation.z) * worldRotation * Quaternion.Euler(PostRotation.x, PostRotation.y, PostRotation.z); + } + else if (rotationSpace == RotationSpace.Self) + { + if (transform.parent != null) + boneTransform.rotation = this.transform.parent.rotation * worldRotation; + else + boneTransform.rotation = worldRotation; + } + } + + #endregion + + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Character.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Character.cs.meta new file mode 100644 index 000000000..9af6d2ae0 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Character.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 78192e8160352b7458fd2bc8ec3501ce +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Face.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Face.cs new file mode 100644 index 000000000..3667703c2 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Face.cs @@ -0,0 +1,111 @@ +using Rokoko.Core; +using Rokoko.Helper; +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Rokoko.Inputs +{ + public class Face : MonoBehaviour + { + [System.Serializable] + public enum FaceMappingEnum + { + ARKit, + Custom + } + + private const int HEAD_TO_MATERIAL_INDEX = 3; + + [HideInInspector] public FaceMappingEnum blendshapeMapping; + [HideInInspector] public BlendShapesMapping blendshapeCustomMap; + + [HideInInspector] public SkinnedMeshRenderer meshRenderer = null; + + [Header("Log extra info")] + public bool debug = false; + + private Dictionary blendshapeNamesToIndex = new Dictionary(); + + private void Start() + { + if (meshRenderer == null) + { + Debug.LogError("Unassigned SkinnedMeshRenderer for face", this.transform); + return; + } + + blendshapeNamesToIndex = meshRenderer.sharedMesh.GetAllBlendshapes(); + } + + public void UpdateFace(FaceFrame faceFrame) + { + if (meshRenderer == null) return; + + float[] blendshapeValues = faceFrame.GetValues(); + for (int i = 0; i < RokokoHelper.BlendshapesArray.Length; i++) + { + // Get blendshape name + string blendShapeName; + + // Set default blendshape name + if (blendshapeMapping == FaceMappingEnum.ARKit) + { + blendShapeName = RokokoHelper.BlendshapesArray[i].ToString(); + } + // Get custom blendshape name + else + { + blendShapeName = blendshapeCustomMap.blendshapeNames[RokokoHelper.BlendshapesArray[i]]; + } + + int blendshapeIndex = GetBlendshapeIndex(blendShapeName); + if (blendshapeIndex >= 0) + meshRenderer.SetBlendShapeWeight(blendshapeIndex, blendshapeValues[i]); + else + { + if (debug) + Debug.LogWarning($"Couldn't find blendshape name:{blendShapeName} in Mesh blendshapes (count:{meshRenderer.sharedMesh.blendShapeCount})", this.transform); + } + } + } + + public void UpdateFace(string[] names, float[] blendshapeValues) + { + if (meshRenderer == null) return; + + for (int i = 0; i < names.Length; ++i) + { + // Get blendshape name + string blendShapeName = names[i]; + + int blendshapeIndex = GetBlendshapeIndex(blendShapeName); + if (blendshapeIndex >= 0) + meshRenderer.SetBlendShapeWeight(blendshapeIndex, blendshapeValues[i]); + else + { + if (debug) + Debug.LogWarning($"Couldn't find blendshape name:{blendShapeName} in Mesh blendshapes (count:{meshRenderer.sharedMesh.blendShapeCount})", this.transform); + } + } + } + + private int GetBlendshapeIndex(string blendshape) + { + foreach (string blendshapeKey in blendshapeNamesToIndex.Keys) + { + if (blendshapeKey.Contains(blendshape.ToLower())) + return blendshapeNamesToIndex[blendshapeKey]; + } + return -1; + } + + public void SetColor(Color color) + { + if (meshRenderer == null) return; + + meshRenderer.materials[HEAD_TO_MATERIAL_INDEX].color = color; + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Face.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Face.cs.meta new file mode 100644 index 000000000..adab01c0a --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Face.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b6f1b7bab29ae9c4ab510759d0fbaa91 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/HumanBoneMapping.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/HumanBoneMapping.cs new file mode 100644 index 000000000..fff24d066 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/HumanBoneMapping.cs @@ -0,0 +1,11 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Rokoko.Inputs +{ + public class HumanBoneMapping : MonoBehaviour + { + public Transform[] customBodyBones = new Transform[0]; + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/HumanBoneMapping.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/HumanBoneMapping.cs.meta new file mode 100644 index 000000000..f2eba44dc --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/HumanBoneMapping.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4a162b99fb9dfe542b5844d7b3d7c9fe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Prop.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Prop.cs new file mode 100644 index 000000000..b13ed19c6 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Prop.cs @@ -0,0 +1,42 @@ +using Rokoko.Core; +using Rokoko.Helper; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Rokoko.Inputs +{ + public class Prop : MonoBehaviour + { + [HideInInspector] public string propName; + public Space positionSpace = Space.Self; + public Space rotationSpace = Space.Self; + + protected virtual void Start() + { + if (!string.IsNullOrEmpty(propName)) + StudioManager.AddPropOverride(this); + } + + public virtual void UpdateProp(PropFrame propFrame) + { + propName = propFrame.name; + + if (positionSpace == Space.World) + this.transform.position = propFrame.position.ToVector3(); + else + this.transform.localPosition = propFrame.position.ToVector3(); + + Quaternion worldRotation = propFrame.rotation.ToQuaternion(); + if (rotationSpace == Space.World) + this.transform.rotation = worldRotation; + else + { + if (transform.parent != null) + this.transform.rotation = Quaternion.Inverse(transform.parent.rotation) * worldRotation; + else + this.transform.rotation = worldRotation; + } + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Prop.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Prop.cs.meta new file mode 100644 index 000000000..e81e384f2 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Prop.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8274508f93c440949969e644e7b64e65 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/PropColor.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/PropColor.cs new file mode 100644 index 000000000..717ea4123 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/PropColor.cs @@ -0,0 +1,21 @@ +using Rokoko.Core; +using Rokoko.Helper; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Rokoko.Inputs +{ + public class PropColor : Prop + { + [HideInInspector] public MeshRenderer meshRenderer; + + public override void UpdateProp(PropFrame propFrame) + { + base.UpdateProp(propFrame); + + if (meshRenderer != null) + meshRenderer.material.color = propFrame.color.ToColor(); + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/PropColor.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/PropColor.cs.meta new file mode 100644 index 000000000..2f2e87847 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/PropColor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 93bc1b702e5f6fa4c9d905909f4741e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/PrefabInstancer.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/PrefabInstancer.cs new file mode 100644 index 000000000..72a3f5dd6 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/PrefabInstancer.cs @@ -0,0 +1,46 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Rokoko +{ + public class PrefabInstancer where P : MonoBehaviour + { + private PrefabPool

pool; + private Dictionary objects; + + public PrefabInstancer(P prefab, Transform container, int poolNumber = 0) + { + pool = new PrefabPool

(prefab, container, poolNumber); + objects = new Dictionary(); + } + + public P this[T key] + { + get + { + if (!objects.ContainsKey(key)) + objects.Add(key, pool.Dequeue()); + return objects[key]; + } + } + + public bool ContainsKey(T key) => objects.ContainsKey(key); + + public bool ContainsValue(P item) => objects.ContainsValue(item); + + public IEnumerable Keys => objects.Keys; + + public IEnumerable Values => objects.Values; + + public int Count => objects.Count; + + public void Remove(T key) + { + if (!ContainsKey(key)) return; + P item = objects[key]; + objects.Remove(key); + pool.Enqueue(item); + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/PrefabInstancer.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/PrefabInstancer.cs.meta new file mode 100644 index 000000000..3972edc8e --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/PrefabInstancer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ded564a0c7575434ebb9c2faf540099d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/PrefabPool.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/PrefabPool.cs new file mode 100644 index 000000000..4c3dcd89a --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/PrefabPool.cs @@ -0,0 +1,53 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Rokoko +{ + public class PrefabPool where T : MonoBehaviour + { + public int poolNumber = 3; + public T prefab; + public Transform container; + + private Queue pool = new Queue(); + + public PrefabPool(T prefab, Transform container, int poolNumber = 3) + { + this.prefab = prefab; + this.container = container; + this.poolNumber = poolNumber; + + for (int i = 0; i < poolNumber; i++) + { + Enqueue(InstantiatePrefab()); + } + } + + public T Dequeue() + { + if (pool.Count == 0) + Enqueue(InstantiatePrefab()); + T instance = pool.Dequeue(); + instance.gameObject.SetActive(true); + return instance; + } + + public void Enqueue(T instance) + { + pool.Enqueue(instance); + instance.gameObject.SetActive(false); + instance.name = prefab.name; + } + + private T InstantiatePrefab() + { + T instance = GameObject.Instantiate(prefab); + instance.transform.SetParent(container); + instance.transform.position = Vector3.zero; + instance.transform.rotation = Quaternion.identity; + instance.name = prefab.name; + return instance; + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/PrefabPool.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/PrefabPool.cs.meta new file mode 100644 index 000000000..dfd0a4202 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/PrefabPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 47b981a3ed34dc64b9ab5ae02663b220 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/RokokoHelper.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/RokokoHelper.cs new file mode 100644 index 000000000..c00e5ccc3 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/RokokoHelper.cs @@ -0,0 +1,344 @@ +using Rokoko.Core; +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Rokoko.Helper +{ + public static class RokokoHelper + { + public static Vector3 ToVector3(this Vector3Frame vec3) + { + return new Vector3(vec3.x, vec3.y, vec3.z); + } + + public static Quaternion ToQuaternion(this Vector4Frame vec4) + { + return new Quaternion(vec4.x, vec4.y, vec4.z, vec4.w); + } + + public static string ToLowerFirstChar(this string input) + { + string newString = input; + if (!String.IsNullOrEmpty(newString) && Char.IsUpper(newString[0])) + newString = Char.ToLower(newString[0]) + newString.Substring(1); + return newString; + } + + public static string ToUpperFirstChar(this string input) + { + string newString = input; + if (!String.IsNullOrEmpty(newString) && Char.IsLower(newString[0])) + newString = Char.ToUpper(newString[0]) + newString.Substring(1); + return newString; + } + + public static ActorJointFrame? GetBoneFrame(this BodyFrame frame, HumanBodyBones bone) + { + switch (bone) + { + case HumanBodyBones.Hips: + return frame.hip; + case HumanBodyBones.LeftUpperLeg: + return frame.leftUpLeg; + case HumanBodyBones.RightUpperLeg: + return frame.rightUpLeg; + case HumanBodyBones.LeftLowerLeg: + return frame.leftLeg; + case HumanBodyBones.RightLowerLeg: + return frame.rightLeg; + case HumanBodyBones.LeftFoot: + return frame.leftFoot; + case HumanBodyBones.RightFoot: + return frame.rightFoot; + case HumanBodyBones.Spine: + return frame.spine; + //case HumanBodyBones.Chest: + // return frame.chest; + case HumanBodyBones.Neck: + return frame.neck; + case HumanBodyBones.Head: + return frame.head; + case HumanBodyBones.LeftShoulder: + return frame.leftShoulder; + case HumanBodyBones.RightShoulder: + return frame.rightShoulder; + case HumanBodyBones.LeftUpperArm: + return frame.leftUpperArm; + case HumanBodyBones.RightUpperArm: + return frame.rightUpperArm; + case HumanBodyBones.LeftLowerArm: + return frame.leftLowerArm; + case HumanBodyBones.RightLowerArm: + return frame.rightLowerArm; + case HumanBodyBones.LeftHand: + return frame.leftHand; + case HumanBodyBones.RightHand: + return frame.rightHand; + case HumanBodyBones.LeftToes: + return frame.leftToe; + case HumanBodyBones.RightToes: + return frame.rightToe; + //case HumanBodyBones.LeftEye: + // return frame.leftEye; + //case HumanBodyBones.RightEye: + // return frame.rightEye; + //case HumanBodyBones.Jaw: + // return frame.jaw; + case HumanBodyBones.LeftThumbProximal: + return frame.leftThumbProximal; + case HumanBodyBones.LeftThumbIntermediate: + return frame.leftThumbMedial; + case HumanBodyBones.LeftThumbDistal: + return frame.leftThumbDistal; + case HumanBodyBones.LeftIndexProximal: + return frame.leftIndexProximal; + case HumanBodyBones.LeftIndexIntermediate: + return frame.leftIndexMedial; + case HumanBodyBones.LeftIndexDistal: + return frame.leftIndexDistal; + case HumanBodyBones.LeftMiddleProximal: + return frame.leftMiddleProximal; + case HumanBodyBones.LeftMiddleIntermediate: + return frame.leftMiddleMedial; + case HumanBodyBones.LeftMiddleDistal: + return frame.leftMiddleDistal; + case HumanBodyBones.LeftRingProximal: + return frame.leftRingProximal; + case HumanBodyBones.LeftRingIntermediate: + return frame.leftRingMedial; + case HumanBodyBones.LeftRingDistal: + return frame.leftRingDistal; + case HumanBodyBones.LeftLittleProximal: + return frame.leftLittleProximal; + case HumanBodyBones.LeftLittleIntermediate: + return frame.leftLittleMedial; + case HumanBodyBones.LeftLittleDistal: + return frame.leftLittleDistal; + case HumanBodyBones.RightThumbProximal: + return frame.rightThumbProximal; + case HumanBodyBones.RightThumbIntermediate: + return frame.rightThumbMedial; + case HumanBodyBones.RightThumbDistal: + return frame.rightThumbDistal; + case HumanBodyBones.RightIndexProximal: + return frame.rightIndexProximal; + case HumanBodyBones.RightIndexIntermediate: + return frame.rightIndexMedial; + case HumanBodyBones.RightIndexDistal: + return frame.rightIndexDistal; + case HumanBodyBones.RightMiddleProximal: + return frame.rightMiddleProximal; + case HumanBodyBones.RightMiddleIntermediate: + return frame.rightMiddleMedial; + case HumanBodyBones.RightMiddleDistal: + return frame.rightMiddleDistal; + case HumanBodyBones.RightRingProximal: + return frame.rightRingProximal; + case HumanBodyBones.RightRingIntermediate: + return frame.rightRingMedial; + case HumanBodyBones.RightRingDistal: + return frame.rightRingDistal; + case HumanBodyBones.RightLittleProximal: + return frame.rightLittleProximal; + case HumanBodyBones.RightLittleIntermediate: + return frame.rightLittleMedial; + case HumanBodyBones.RightLittleDistal: + return frame.rightLittleDistal; + case HumanBodyBones.UpperChest: + return frame.chest; + } + + return null; + } + + public static Color ToColor(this int[] color) + { + if (color == null || color.Length != 3) + return Color.white; + return new Color((float)color[0] / 255f, (float)color[1] / 255f, (float)color[2] / 255f); + } + + public static void DestroyChildren(this Transform transform) + { + List children = new List(); + foreach (Transform child in transform) + children.Add(child); + + foreach (Transform child in children) + GameObject.Destroy(child.gameObject); + } + + ///

+ /// Check if actor name is present in live data + /// + public static bool HasProfile(this LiveFrame_v4 frame, string profileName) + { + for (int i = 0; i < frame.scene.actors.Length; i++) + { + if (frame.scene.actors[i].name == profileName) + return true; + } + return false; + } + + /// + /// Check if character name is present in live data + /// + public static bool HasCharacter(this LiveFrame_v4 frame, string profileName) + { + for (int i = 0; i < frame.scene.characters.Length; i++) + { + if (frame.scene.characters[i].name == profileName) + return true; + } + return false; + } + + /// + /// Check if prop name is present in live data + /// + public static bool HasProp(this LiveFrame_v4 frame, string propName) + { + for (int i = 0; i < frame.scene.props.Length; i++) + { + if (frame.scene.props[i].name == propName) + return true; + } + return false; + } + + public static float[] GetValues(this FaceFrame faceFrame) + { + var values = new float[(int)BlendShapes.size]; + values[(int)BlendShapes.eyeBlinkLeft] = faceFrame.eyeBlinkLeft; + values[(int)BlendShapes.eyeLookDownLeft] = faceFrame.eyeLookDownLeft; + values[(int)BlendShapes.eyeLookInLeft] = faceFrame.eyeLookInLeft; + values[(int)BlendShapes.eyeLookOutLeft] = faceFrame.eyeLookOutLeft; + values[(int)BlendShapes.eyeLookUpLeft] = faceFrame.eyeLookUpLeft; + values[(int)BlendShapes.eyeSquintLeft] = faceFrame.eyeSquintLeft; + values[(int)BlendShapes.eyeWideLeft] = faceFrame.eyeWideLeft; + values[(int)BlendShapes.eyeBlinkRight] = faceFrame.eyeBlinkRight; + values[(int)BlendShapes.eyeLookDownRight] = faceFrame.eyeLookDownRight; + values[(int)BlendShapes.eyeLookInRight] = faceFrame.eyeLookInRight; + values[(int)BlendShapes.eyeLookOutRight] = faceFrame.eyeLookOutRight; + values[(int)BlendShapes.eyeLookUpRight] = faceFrame.eyeLookUpRight; + values[(int)BlendShapes.eyeSquintRight] = faceFrame.eyeSquintRight; + values[(int)BlendShapes.eyeWideRight] = faceFrame.eyeWideRight; + values[(int)BlendShapes.jawForward] = faceFrame.jawForward; + values[(int)BlendShapes.jawLeft] = faceFrame.jawLeft; + values[(int)BlendShapes.jawRight] = faceFrame.jawRight; + values[(int)BlendShapes.jawOpen] = faceFrame.jawOpen; + values[(int)BlendShapes.mouthClose] = faceFrame.mouthClose; + values[(int)BlendShapes.mouthFunnel] = faceFrame.mouthFunnel; + values[(int)BlendShapes.mouthPucker] = faceFrame.mouthPucker; + values[(int)BlendShapes.mouthLeft] = faceFrame.mouthLeft; + values[(int)BlendShapes.mouthRight] = faceFrame.mouthRight; + values[(int)BlendShapes.mouthSmileLeft] = faceFrame.mouthSmileLeft; + values[(int)BlendShapes.mouthSmileRight] = faceFrame.mouthSmileRight; + values[(int)BlendShapes.mouthFrownLeft] = faceFrame.mouthFrownLeft; + values[(int)BlendShapes.mouthFrownRight] = faceFrame.mouthFrownRight; + values[(int)BlendShapes.mouthDimpleLeft] = faceFrame.mouthDimpleLeft; + values[(int)BlendShapes.mouthDimpleRight] = faceFrame.mouthDimpleRight; + values[(int)BlendShapes.mouthStretchLeft] = faceFrame.mouthStretchLeft; + values[(int)BlendShapes.mouthStretchRight] = faceFrame.mouthStretchRight; + values[(int)BlendShapes.mouthRollLower] = faceFrame.mouthRollLower; + values[(int)BlendShapes.mouthRollUpper] = faceFrame.mouthRollUpper; + values[(int)BlendShapes.mouthShrugLower] = faceFrame.mouthShrugLower; + values[(int)BlendShapes.mouthShrugUpper] = faceFrame.mouthShrugUpper; + values[(int)BlendShapes.mouthPressLeft] = faceFrame.mouthPressLeft; + values[(int)BlendShapes.mouthPressRight] = faceFrame.mouthPressRight; + values[(int)BlendShapes.mouthLowerDownLeft] = faceFrame.mouthLowerDownLeft; + values[(int)BlendShapes.mouthLowerDownRight] = faceFrame.mouthLowerDownRight; + values[(int)BlendShapes.mouthUpperUpLeft] = faceFrame.mouthUpperUpLeft; + values[(int)BlendShapes.mouthUpperUpRight] = faceFrame.mouthUpperUpRight; + values[(int)BlendShapes.browDownLeft] = faceFrame.browDownLeft; + values[(int)BlendShapes.browDownRight] = faceFrame.browDownRight; + values[(int)BlendShapes.browInnerUp] = faceFrame.browInnerUp; + values[(int)BlendShapes.browOuterUpLeft] = faceFrame.browOuterUpLeft; + values[(int)BlendShapes.browOuterUpRight] = faceFrame.browOuterUpRight; + values[(int)BlendShapes.cheekPuff] = faceFrame.cheekPuff; + values[(int)BlendShapes.cheekSquintLeft] = faceFrame.cheekSquintLeft; + values[(int)BlendShapes.cheekSquintRight] = faceFrame.cheekSquintRight; + values[(int)BlendShapes.noseSneerLeft] = faceFrame.noseSneerLeft; + values[(int)BlendShapes.noseSneerRight] = faceFrame.noseSneerRight; + values[(int)BlendShapes.tongueOut] = faceFrame.tongueOut; + + return values; + } + + public static Dictionary GetAllBlendshapes(this Mesh mesh) + { + Dictionary blendshapeNamesToIndex = new Dictionary(); + for (int i = 0; i < mesh.blendShapeCount; i++) + { + blendshapeNamesToIndex.Add(mesh.GetBlendShapeName(i).ToLower(), i); + } + return blendshapeNamesToIndex; + } + + /// + /// Get all missing blendshapes comparing to ARKit 52 blendshapes + /// + public static List GetAllMissingBlendshapes(this Mesh mesh) + { + List missingBlendshapes = new List(); + List blendshapeNames = new List(mesh.GetAllBlendshapes().Keys); + for (int i = 0; i < BlendshapesArray.Length; i++) + { + string arkitName = BlendshapesArray[i].ToString(); + if (!blendshapeNames.Contains(arkitName.ToLower())) + missingBlendshapes.Add(arkitName); + } + + return missingBlendshapes; + } + + private static BlendShapes[] _BlendshapesArray = null; + public static BlendShapes[] BlendshapesArray + { + get + { + if (_BlendshapesArray == null) + { + _BlendshapesArray = new BlendShapes[(int)BlendShapes.size]; + for (int i = 0; i < _BlendshapesArray.Length; i++) + { + _BlendshapesArray[i] = (BlendShapes)i; + } + } + + return _BlendshapesArray; + } + } + + private static HumanBodyBones[] _HumanBodyBonesArray = null; + + public static HumanBodyBones[] HumanBodyBonesArray + { + get + { + if (_HumanBodyBonesArray == null) + { + _HumanBodyBonesArray = new HumanBodyBones[(int)HumanBodyBones.LastBone]; + for (int i = 0; i < _HumanBodyBonesArray.Length; i++) + _HumanBodyBonesArray[i] = (HumanBodyBones)i; + } + return _HumanBodyBonesArray; + } + } + + public static void Destroy(GameObject gameObject) + { + if (Application.isPlaying) + GameObject.Destroy(gameObject); + else + GameObject.DestroyImmediate(gameObject); + +#if UNITY_EDITOR + if (!Application.isPlaying) + UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(UnityEngine.SceneManagement.SceneManager.GetActiveScene()); +#endif + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/RokokoHelper.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/RokokoHelper.cs.meta new file mode 100644 index 000000000..a44fd391e --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/RokokoHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5fb415e868ac58149ae137d5bd2d006a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable.meta new file mode 100644 index 000000000..fe19255d9 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 363721fc8fef7dc40a9243ec4954d2af +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/BlendshapesDictionary.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/BlendshapesDictionary.cs new file mode 100644 index 000000000..7c3df5d7c --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/BlendshapesDictionary.cs @@ -0,0 +1,7 @@ +using Rokoko.Core; + +/// +/// Create a simple serialized version of a Dictionary in order to able to persist in Editor play mode. +/// +[System.Serializable] +public class BlendshapesDictionary : SerializableDictionary { } diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/BlendshapesDictionary.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/BlendshapesDictionary.cs.meta new file mode 100644 index 000000000..feb53ae5e --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/BlendshapesDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9681c86ca06b0de479fcf425d8560c3b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/HumanTPoseDictionary.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/HumanTPoseDictionary.cs new file mode 100644 index 000000000..992317c6b --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/HumanTPoseDictionary.cs @@ -0,0 +1,8 @@ +using System; +using UnityEngine; + +/// +/// Create a simple serialized version of a Dictionary in order to able to persist in Editor play mode. +/// +[System.Serializable] +public class HumanTPoseDictionary : SerializableDictionary { } diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/HumanTPoseDictionary.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/HumanTPoseDictionary.cs.meta new file mode 100644 index 000000000..8193f3bdb --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/HumanTPoseDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9a32ea12a419b4544a5ef5263af08a26 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/SerializableDictionary.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/SerializableDictionary.cs new file mode 100644 index 000000000..26804dbcc --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/SerializableDictionary.cs @@ -0,0 +1,63 @@ +using Rokoko.Core; +using System.Collections; +using System.Collections.Generic; + +/// +/// Create a simple serialized version of a Dictionary in order to able to persist in Editor play mode. +/// +[System.Serializable] +public abstract class SerializableDictionary +{ + public List keys = new List(); + public List values = new List(); + + public void Add(TKey key, TValue value) + { + if (keys.Contains(key)) + throw new System.Exception("Key already exists"); + keys.Add(key); + values.Add(value); + } + + public TValue this[TKey key] + { + get + { + if (!keys.Contains(key)) + throw new System.Exception("Key doesn't exists"); + return values[keys.IndexOf(key)]; + } + set + { + if (!keys.Contains(key)) + throw new System.Exception("Key doesn't exists"); + + int index = keys.IndexOf(key); + values[index] = value; + } + + } + + public KeyValuePair this[int index] + { + get + { + if (keys.Count < index) + throw new System.IndexOutOfRangeException(); + return new KeyValuePair(keys[index], values[index]); + } + } + + public bool Contains(TKey key) + { + return keys.Contains(key); + } + + public void Clear() + { + keys.Clear(); + values.Clear(); + } + + public int Count => keys.Count; +} diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/SerializableDictionary.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/SerializableDictionary.cs.meta new file mode 100644 index 000000000..47614b36f --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Serializable/SerializableDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 40cfb04b525907c409046edb971ff8f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/StudioManager.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/StudioManager.cs new file mode 100644 index 000000000..55a90ec64 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/StudioManager.cs @@ -0,0 +1,342 @@ +using Rokoko; +using Rokoko.Core; +using Rokoko.CommandAPI; +using Rokoko.Helper; +using Rokoko.Inputs; +using Rokoko.UI; +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Rokoko +{ + public class StudioManager : MonoBehaviour + { + private const string ACTOR_DEMO_IDLE_NAME = "ActorIdle"; + + private static StudioManager instance; + + [Header("Network")] + [Tooltip("ReceivePort must match Studio Live Stream port settings")] + public int receivePort = 14043; + + [Tooltip("Use LZ4 compression stream")] + public bool useLZ4Compression = true; + + [Tooltip("Log the stream frame information")] + public bool receiverVerbose = false; + + [Header("Default Inputs - Used when no overrides found (Optional)")] + [Tooltip("Actor Prefab to create actors when no overrides found")] + public Actor actorPrefab; + [Tooltip("Character Prefab to create characters when no overrides found")] + public Character characterPrefab; + [Tooltip("Prop Prefab to create props when no overrides found")] + public Prop propPrefab; + + [Header("UI (Optional)")] + public UIHierarchyManager uiManager; + + [Header("Command API (Optional)")] + public StudioCommandAPI CommandAPI; + public bool AutoSendTrackerCommands; + + [Header("Input Overrides - Automatically updated")] + public List actorOverrides = new List(); + public List characterOverrides = new List(); + public List propOverrides = new List(); + + [Header("Extra Behiavours")] + public bool autoGenerateInputsWhenNoOverridesFound = false; + public bool showDefaultActorWhenNoData = false; + + private StudioReceiver studioReceiver; + private PrefabInstancer actors; + private PrefabInstancer characters; + private PrefabInstancer props; + + private object actionsOnMainThread = new object(); + private List packetsToProcess = new List(); + + #region MonoBehaviour + + private void Awake() + { + if (instance == null) + instance = this; + } + + // Start is called before the first frame update + private IEnumerator Start() + { + studioReceiver = new StudioReceiver(); + studioReceiver.receivePortNumber = receivePort; + studioReceiver.useLZ4Compression = useLZ4Compression; + studioReceiver.verbose = receiverVerbose; + studioReceiver.Initialize(); + studioReceiver.StartListening(); + studioReceiver.onStudioDataReceived += StudioReceiver_onStudioDataReceived; + + if (actorPrefab != null) + actors = new PrefabInstancer(actorPrefab, this.transform); + if (characterPrefab != null) + characters = new PrefabInstancer(characterPrefab, this.transform); + if (propPrefab != null) + props = new PrefabInstancer(propPrefab, this.transform); + + yield return null; + + if (actorOverrides.Count == 0 && characterOverrides.Count == 0) + { + Debug.Log("No custom characters found. Will generate scene from default ones"); + } + } + + private void Update() + { + // Run all actions inside Unity's main thread + lock (actionsOnMainThread) + { + if (packetsToProcess.Count > 0) + { + ProcessLiveFrame(packetsToProcess[packetsToProcess.Count-1]); + packetsToProcess.Clear(); + } + } + } + + private void FixedUpdate() + { + if (AutoSendTrackerCommands && CommandAPI != null) + { + if (!CommandAPI.IsTrackerRequestInProgress) + { + CommandAPI.Tracker(); + } + } + } + + private void OnDestroy() + { + studioReceiver.Dispose(); + } + + #endregion + + private void StudioReceiver_onStudioDataReceived(object sender, LiveFrame_v4 e) + { + lock (actionsOnMainThread) + packetsToProcess.Add(e); + } + + /// + /// Main process logic of live data + /// + private void ProcessLiveFrame(LiveFrame_v4 frame) + { + int numberOfActors = frame?.scene.actors?.Length ?? 0; + int numberOfCharacters = frame?.scene.characters?.Length ?? 0; + + if (numberOfActors == 0 && numberOfCharacters == 0) + return; + + // Update each actor from live data + for (int i = 0; i < numberOfActors; i++) + { + ActorFrame actorFrame = frame.scene.actors[i]; + + List actorOverrides = GetActorOverride(actorFrame.name); + // Update custom actors if any + if (actorOverrides.Count > 0) + { + for (int a = 0; a < actorOverrides.Count; a++) + { + actorOverrides[a].UpdateActor(actorFrame); + } + } + // Update default actor + else if (autoGenerateInputsWhenNoOverridesFound && actors != null) + { + actors[actorFrame.name].UpdateActor(actorFrame); + } + } + + // Update each character from live data + for (int i = 0; i < numberOfCharacters; i++) + { + CharacterFrame charFrame = frame.scene.characters[i]; + + List characterOverrides = GetCharacterOverride(charFrame.name); + // Update custom characters if any + if (characterOverrides.Count > 0) + { + for (int a = 0; a < characterOverrides.Count; a++) + { + characterOverrides[a].UpdateCharacter(charFrame); + } + } + // Update default character + else if (autoGenerateInputsWhenNoOverridesFound && characters != null) + { + characters[charFrame.name].UpdateCharacter(charFrame); + } + } + + // Update each prop from live data + if (frame.scene.props != null) + { + for (int i = 0; i < frame.scene.props.Length; i++) + { + PropFrame propFrame = frame.scene.props[i]; + + List propOverrides = GetPropOverride(propFrame.name); + // Update custom props if any + if (propOverrides.Count > 0) + { + for (int a = 0; a < propOverrides.Count; a++) + { + propOverrides[a].UpdateProp(propFrame); + } + } + // Update default prop + else if (autoGenerateInputsWhenNoOverridesFound && props != null) + { + props[propFrame.name].UpdateProp(propFrame); + } + } + } + + // Remove all default Actors that doesn't exist in data + ClearUnusedDefaultInputs(frame); + + // Show default character + UpdateDefaultActorWhenIdle(); + + // Update Hierarchy UI + uiManager?.UpdateHierarchy(frame); + } + + /// + /// Show default T pose character when not playback data + /// + private void UpdateDefaultActorWhenIdle() + { + if (!showDefaultActorWhenNoData) return; + if (actors == null || props == null) // || characters == null) + return; + + // Create default actor + if (actors.Count == 0 && props.Count == 0) + { + actors[ACTOR_DEMO_IDLE_NAME].CreateIdle(ACTOR_DEMO_IDLE_NAME); + } + // No need to update + else if (actors.Count == 1 && actors.ContainsKey(ACTOR_DEMO_IDLE_NAME)) + { + + } + // Remove default actor when playback data available + else + { + actors.Remove(ACTOR_DEMO_IDLE_NAME); + } + } + + /// + /// Remove all default Actors that doesn't exist in data + /// + private void ClearUnusedDefaultInputs(LiveFrame_v4 frame) + { + if (actors != null) + { + foreach (Actor actor in new List((IEnumerable)actors.Values)) + { + // Don't remove idle demo + if (actor.profileName == ACTOR_DEMO_IDLE_NAME) continue; + + if (!frame.HasProfile(actor.profileName)) + actors.Remove(actor.profileName); + } + } + + if (characters != null) + { + foreach (Character character in new List((IEnumerable)characters.Values)) + { + // Don't remove idle demo + if (character.profileName == ACTOR_DEMO_IDLE_NAME) continue; + + if (!frame.HasCharacter(character.profileName)) + characters.Remove(character.profileName); + } + } + + if (props != null) + { + foreach (Prop prop in new List((IEnumerable)props.Values)) + { + if (!frame.HasProp(prop.propName)) + props.Remove(prop.propName); + } + } + } + + public List GetActorOverride(string profileName) + { + List overrides = new List(); + for (int i = 0; i < actorOverrides.Count; i++) + { + if (profileName.ToLower() == actorOverrides[i].profileName.ToLower()) + overrides.Add(actorOverrides[i]); + } + return overrides; + } + + public List GetCharacterOverride(string profileName) + { + List overrides = new List(); + for (int i = 0; i < characterOverrides.Count; i++) + { + if (characterOverrides[i] == null) + continue; + + if (profileName.ToLower() == characterOverrides[i].profileName.ToLower()) + overrides.Add(characterOverrides[i]); + } + return overrides; + } + + public List GetPropOverride(string profileName) + { + List overrides = new List(); + for (int i = 0; i < propOverrides.Count; i++) + { + if (profileName.ToLower() == propOverrides[i].propName.ToLower()) + overrides.Add(propOverrides[i]); + } + return overrides; + } + + public static void AddActorOverride(Actor actor) + { + if (instance == null) return; + if (instance.actorOverrides.Contains(actor)) return; + instance.actorOverrides.Add(actor); + } + + public static void AddCharacterOverride(Character character) + { + if (instance == null) return; + if (instance.characterOverrides.Contains(character)) return; + instance.characterOverrides.Add(character); + } + + public static void AddPropOverride(Prop prop) + { + if (instance == null) return; + if (instance.propOverrides.Contains(prop)) return; + instance.propOverrides.Add(prop); + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/StudioManager.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/StudioManager.cs.meta new file mode 100644 index 000000000..bc4ad1db4 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/StudioManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: de63a1dd9ec769e4080fd4da352fac11 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/TPoseGuideGameComponent.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/TPoseGuideGameComponent.cs new file mode 100644 index 000000000..13a999ac1 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/TPoseGuideGameComponent.cs @@ -0,0 +1,48 @@ +using Rokoko.Helper; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace Rokoko +{ + [ExecuteInEditMode] + public class TPoseGuideGameComponent : MonoBehaviour + { + public Transform followTarget; + public Vector3 followOffset = Vector3.zero; + + private void Awake() + { + SceneManager.activeSceneChanged += SceneManager_activeSceneChanged; + +#if !UNITY_EDITOR + RokokoHelper.Destroy(this.gameObject); +#endif + } + + private void Start() + { + + } + + private void SceneManager_activeSceneChanged(Scene arg0, Scene arg1) + { +#if UNITY_EDITOR + if (!Application.isPlaying) + { + RokokoHelper.Destroy(this.gameObject); + } +#endif + } + + // Update is called once per frame + void Update() + { + this.transform.rotation = Quaternion.LookRotation(Vector3.up * -1); + + if(followTarget != null) + { + this.transform.position = followTarget.transform.position + followOffset; + } + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/TPoseGuideGameComponent.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/TPoseGuideGameComponent.cs.meta new file mode 100644 index 000000000..0b97381ae --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/TPoseGuideGameComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4296b318fcf88b240a52b1fbe7be783e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI.meta new file mode 100644 index 000000000..108691aea --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7e6ff7aa69142344db48e66a446c3b21 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI/InputHierarchyRow.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI/InputHierarchyRow.cs new file mode 100644 index 000000000..44545377a --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI/InputHierarchyRow.cs @@ -0,0 +1,63 @@ +using Rokoko.Core; +using Rokoko.Helper; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI; + +namespace Rokoko.UI +{ + public class InputHierarchyRow : MonoBehaviour + { + public string profileName { get; private set; } + + [Header("Actor")] + [SerializeField] private GameObject actorPanel = null; + [SerializeField] private Image profileImage = null; + [SerializeField] private Text profileText = null; + [SerializeField] private Image faceImage = null; + [SerializeField] private Image suitImage = null; + [SerializeField] private Image leftGloveImage = null; + [SerializeField] private Image rightGloveImage = null; + [SerializeField] private Color inactiveColor = Color.gray; + + [Header("Prop")] + [SerializeField] private GameObject propPanel = null; + [SerializeField] private Image propImage = null; + [SerializeField] private Text propText = null; + + public void UpdateRow(ActorFrame actorFrame) + { + actorPanel.SetActive(true); + propPanel.SetActive(false); + + profileName = actorFrame.name; + profileImage.color = actorFrame.color.ToColor(); + profileText.text = actorFrame.name; + faceImage.color = actorFrame.meta.hasFace ? Color.white : inactiveColor; + suitImage.color = actorFrame.meta.hasBody ? Color.white : inactiveColor; + leftGloveImage.color = actorFrame.meta.hasLeftGlove ? Color.white : inactiveColor; + rightGloveImage.color = actorFrame.meta.hasRightGlove ? Color.white : inactiveColor; + } + + public void UpdateRow(CharacterFrame charFrame) + { + actorPanel.SetActive(false); + propPanel.SetActive(true); + + profileName = charFrame.name; + //propImage.color = propFrame.color.ToColor(); + propText.text = charFrame.name; + } + + public void UpdateRow(PropFrame propFrame) + { + actorPanel.SetActive(false); + propPanel.SetActive(true); + + profileName = propFrame.name; + propImage.color = propFrame.color.ToColor(); + propText.text = propFrame.name; + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI/InputHierarchyRow.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI/InputHierarchyRow.cs.meta new file mode 100644 index 000000000..cbbd46e6d --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI/InputHierarchyRow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1ad284b4954ca4e4e87c771f198afd80 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI/UIHierarchyManager.cs b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI/UIHierarchyManager.cs new file mode 100644 index 000000000..75c2d8dcb --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI/UIHierarchyManager.cs @@ -0,0 +1,88 @@ +using Rokoko.Core; +using Rokoko.Helper; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI; + +namespace Rokoko.UI +{ + public class UIHierarchyManager : MonoBehaviour + { + [SerializeField] private InputHierarchyRow rowPrefab = null; + [SerializeField] private Transform prefabContainer = null; + + private PrefabInstancer rows; + + // Start is called before the first frame update + void Start() + { + // Destroy children before PrefabInstancer creates the pool + prefabContainer.DestroyChildren(); + + rows = new PrefabInstancer(rowPrefab, prefabContainer, 3); + } + + public void UpdateHierarchy(LiveFrame_v4 dataFrame) + { + // Check if UI needs rebuild + bool forceLayoutUpdate = false; + + // Update each actor from live data + for (int i = 0; i < dataFrame.scene.actors.Length; i++) + { + ActorFrame actorFrame = dataFrame.scene.actors[i]; + string profileName = actorFrame.name; + + // If profile doesn't exist, mark for rebuild + if (forceLayoutUpdate == false && !rows.ContainsKey(profileName)) + forceLayoutUpdate = true; + + rows[profileName].UpdateRow(actorFrame); + } + + // Update each actor from live data + for (int i = 0; i < dataFrame.scene.characters.Length; i++) + { + CharacterFrame charFrame = dataFrame.scene.characters[i]; + string profileName = charFrame.name; + + // If profile doesn't exist, mark for rebuild + if (forceLayoutUpdate == false && !rows.ContainsKey(profileName)) + forceLayoutUpdate = true; + + rows[profileName].UpdateRow(charFrame); + } + + // Update each prop from live data + for (int i = 0; i < dataFrame.scene.props.Length; i++) + { + PropFrame propFrame = dataFrame.scene.props[i]; + string profileName = propFrame.name; + + // If profile doesn't exist, mark for rebuild + if (forceLayoutUpdate == false && !rows.ContainsKey(profileName)) + forceLayoutUpdate = true; + + rows[profileName].UpdateRow(propFrame); + } + + ClearUnusedInputRows(dataFrame); + + if (forceLayoutUpdate) + LayoutRebuilder.ForceRebuildLayoutImmediate(prefabContainer as RectTransform); + } + + /// + /// Remove all rows that no longer exists in live data + /// + private void ClearUnusedInputRows(LiveFrame_v4 frame) + { + foreach (InputHierarchyRow row in new List((IEnumerable)rows.Values)) + { + if (!frame.HasProfile(row.profileName) && !frame.HasProp(row.profileName) && !frame.HasCharacter(row.profileName)) + rows.Remove(row.profileName); + } + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI/UIHierarchyManager.cs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI/UIHierarchyManager.cs.meta new file mode 100644 index 000000000..584a968c5 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI/UIHierarchyManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0726fb4ef719b9d46a2afcc8c682dc54 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins.meta new file mode 100644 index 000000000..9070f333a --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bbb359d9a38c1004bbe87c4f14822488 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4.meta new file mode 100644 index 000000000..87e980937 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 26a3fe8611eb3a44aa1ae9081871b540 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android.meta new file mode 100644 index 000000000..a229d9a9a --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 82985f460b91d5d4ba2aa833b74fef35 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs.meta new file mode 100644 index 000000000..c24562f1f --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ea56c1cd6eae28e479bc6dd92c6898f0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/arm64-v8a.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/arm64-v8a.meta new file mode 100644 index 000000000..f003678e4 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/arm64-v8a.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5261e97e6db9c3c49bf370285d19c389 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/arm64-v8a/liblz4.so b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/arm64-v8a/liblz4.so new file mode 100644 index 000000000..09cf40ea1 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/arm64-v8a/liblz4.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7059713511df34b30a9d14697602eb9e43aed0c2218fb95468e0607fd786aff7 +size 185528 diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/arm64-v8a/liblz4.so.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/arm64-v8a/liblz4.so.meta new file mode 100644 index 000000000..3867593cc --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/arm64-v8a/liblz4.so.meta @@ -0,0 +1,27 @@ +fileFormatVersion: 2 +guid: 2034ecf0466b4e74f87f5c2418b9ca6a +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/armeabi-v7a.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/armeabi-v7a.meta new file mode 100644 index 000000000..61d32574c --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/armeabi-v7a.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ff5c59bfbd7cdc145932e75bd2c77813 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/armeabi-v7a/liblz4.so b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/armeabi-v7a/liblz4.so new file mode 100644 index 000000000..d019fa116 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/armeabi-v7a/liblz4.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8cf87aded6d6311fa92c5f004cd3333d66d8caf96b49cfa2f94d30e90c737b6 +size 184968 diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/armeabi-v7a/liblz4.so.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/armeabi-v7a/liblz4.so.meta new file mode 100644 index 000000000..6595b0d31 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/armeabi-v7a/liblz4.so.meta @@ -0,0 +1,27 @@ +fileFormatVersion: 2 +guid: 64661c496f7cc0a418cbe44f3c5df8d4 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/x86.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/x86.meta new file mode 100644 index 000000000..57c0d572f --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/x86.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fd93f2258a54c1341a561817c9d7221c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/x86/liblz4.so b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/x86/liblz4.so new file mode 100644 index 000000000..5e8d176c7 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/x86/liblz4.so @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:74b245ef940b45656ec7c53277680155a306b40ca63630ea31cf09397204c6fd +size 153592 diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/x86/liblz4.so.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/x86/liblz4.so.meta new file mode 100644 index 000000000..9fb020b24 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/x86/liblz4.so.meta @@ -0,0 +1,27 @@ +fileFormatVersion: 2 +guid: 334dff52187feb849ab4ea932a1b7b73 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/iOS.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/iOS.meta new file mode 100644 index 000000000..83801bf94 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/iOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dd770c574097d3e4ea6aeddfb5b6da48 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/iOS/liblz4.a b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/iOS/liblz4.a new file mode 100644 index 000000000..0f9892c9a --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/iOS/liblz4.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45b8c1919c3591414f58a1544d947718288836f061d2bcd8c465a9ef771ca2df +size 3762480 diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/iOS/liblz4.a.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/iOS/liblz4.a.meta new file mode 100644 index 000000000..c37cc48b1 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/iOS/liblz4.a.meta @@ -0,0 +1,27 @@ +fileFormatVersion: 2 +guid: 8cd1870f720ab8c4c92c3ca9afc19937 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/lz4.bundle.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/lz4.bundle.meta new file mode 100644 index 000000000..d5a8d7429 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/lz4.bundle.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: be0bf098585b39345bbd41d178618e0e +folderAsset: yes +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + DefaultValueInitialized: true + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/lz4.bundle/Contents/Info.plist b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/lz4.bundle/Contents/Info.plist new file mode 100644 index 000000000..d1d7f3ceb --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/lz4.bundle/Contents/Info.plist @@ -0,0 +1,46 @@ + + + + + BuildMachineOSBuild + 21D62 + CFBundleDevelopmentRegion + English + CFBundleExecutable + lz4 + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleSupportedPlatforms + + MacOSX + + CSResourcesFileMapped + + DTCompiler + com.apple.compilers.llvm.clang.1_0 + DTPlatformBuild + 13C100 + DTPlatformName + macosx + DTPlatformVersion + 12.1 + DTSDKBuild + 21C46 + DTSDKName + macosx12.1 + DTXcode + 1321 + DTXcodeBuild + 13C100 + LSMinimumSystemVersion + 12.1 + NSHumanReadableCopyright + + + diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/lz4.bundle/Contents/MacOS/lz4 b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/lz4.bundle/Contents/MacOS/lz4 new file mode 100644 index 000000000..ee9e530a5 Binary files /dev/null and b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/lz4.bundle/Contents/MacOS/lz4 differ diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/lz4.bundle/Contents/_CodeSignature/CodeResources b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/lz4.bundle/Contents/_CodeSignature/CodeResources new file mode 100644 index 000000000..d5d0fd744 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/lz4.bundle/Contents/_CodeSignature/CodeResources @@ -0,0 +1,115 @@ + + + + + files + + files2 + + rules + + ^Resources/ + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^version.plist$ + + + rules2 + + .*\.dSYM($|/) + + weight + 11 + + ^(.*/)?\.DS_Store$ + + omit + + weight + 2000 + + ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/ + + nested + + weight + 10 + + ^.* + + ^Info\.plist$ + + omit + + weight + 20 + + ^PkgInfo$ + + omit + + weight + 20 + + ^Resources/ + + weight + 20 + + ^Resources/.*\.lproj/ + + optional + + weight + 1000 + + ^Resources/.*\.lproj/locversion.plist$ + + omit + + weight + 1100 + + ^Resources/Base\.lproj/ + + weight + 1010 + + ^[^/]+$ + + nested + + weight + 10 + + ^embedded\.provisionprofile$ + + weight + 20 + + ^version\.plist$ + + weight + 20 + + + + diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/x86_64.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/x86_64.meta new file mode 100644 index 000000000..dbf0890b9 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/x86_64.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0b36efa71b5d9f444940eb00ad416ece +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/x86_64/lz4.dll b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/x86_64/lz4.dll new file mode 100644 index 000000000..eda7abe07 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/x86_64/lz4.dll @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7bb0b1b32a13c6b0e1ff9742c2e3aec8934d411c7dec141672e929189260b29 +size 1071104 diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/x86_64/lz4.dll.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/x86_64/lz4.dll.meta new file mode 100644 index 000000000..590e982d7 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/x86_64/lz4.dll.meta @@ -0,0 +1,27 @@ +fileFormatVersion: 2 +guid: e521e00f91993534799438ee73ee065a +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/RokokoAssembly.asmdef b/Optitrack Rokoko Glove/Rokoko Unity Scripts/RokokoAssembly.asmdef new file mode 100644 index 000000000..da2573d13 --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/RokokoAssembly.asmdef @@ -0,0 +1,3 @@ +{ + "name": "Rokoko" +} diff --git a/Optitrack Rokoko Glove/Rokoko Unity Scripts/RokokoAssembly.asmdef.meta b/Optitrack Rokoko Glove/Rokoko Unity Scripts/RokokoAssembly.asmdef.meta new file mode 100644 index 000000000..bd58ac20e --- /dev/null +++ b/Optitrack Rokoko Glove/Rokoko Unity Scripts/RokokoAssembly.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c0283c4bf59b9ad4aa5b4622e1c041a8 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveAdapterSingleton.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveAdapterSingleton.cpp new file mode 100644 index 000000000..4e2c28b29 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveAdapterSingleton.cpp @@ -0,0 +1,733 @@ +//====================================================================================================== +// Copyright 2023, NaturalPoint Inc. +// Modified 2025 for Rokoko Glove Integration +//====================================================================================================== + +#include +#include +#define WIN32_LEAN_AND_MEAN +#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(20000)); + } +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// 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; + + bool res = false; + if (s_Instance->mGloveDataMutex->try_lock()) { + // 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; + } + s_Instance->mGloveDataMutex->unlock(); + } + return res; +} + + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::CreateNewGloveDevice(sGloveDeviceBaseInfo& deviceInfo) +{ + uint64_t gloveId = deviceInfo.gloveId; + std::string deviceName = "RokokoGlove_" + std::to_string(deviceInfo.gloveId); // 이름을 RokokoGlove로 변경 + + // 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; + + try { + // 로코코 데이터 처리 + s_Instance->ProcessRokokoData(data); + + // 디버그 정보 출력 (OptiTrack에서 확인 가능) + if (s_Instance->mDeviceManager) { + std::string debugMsg = "[RokokoGlove] Received " + std::to_string(data.size()) + " bytes from " + senderIP; + s_Instance->mDeviceManager->MessageToHost(debugMsg.c_str(), MessageType_StatusInfo); + } + + } catch (...) { + if (s_Instance->mDeviceManager) { + s_Instance->mDeviceManager->MessageToHost("[RokokoGlove] Error processing received data", MessageType_StatusInfo); + } + } +} + +void OptiTrackPluginDevices::ExampleDevice::ExampleGloveAdapterSingleton::ProcessRokokoData(const std::vector& data) +{ + try { + // 1단계: LZ4 압축 해제 (필요한 경우) + std::vector decompressedData; + if (RokokoIntegration::LZ4Wrapper::IsValidLZ4Data(data.data(), static_cast(data.size()))) { + decompressedData = RokokoIntegration::LZ4Wrapper::Decompress(data.data(), static_cast(data.size())); + if (decompressedData.empty()) { + if (mDeviceManager) { + mDeviceManager->MessageToHost("[RokokoGlove] LZ4 decompression failed", MessageType_StatusInfo); + } + return; + } + } else { + // LZ4 압축이 아닌 경우 원본 데이터 사용 + decompressedData = data; + } + + // 2단계: JSON 문자열로 변환 + std::string jsonString(decompressedData.begin(), decompressedData.end()); + + // 3단계: JSON 파싱하여 Rokoko 데이터 구조로 변환 + RokokoData::LiveFrame_v4 rokokoFrame; + if (!RokokoIntegration::RokokoDataParser::ParseLiveFrame(jsonString, rokokoFrame)) { + if (mDeviceManager) { + mDeviceManager->MessageToHost("[RokokoGlove] JSON parsing failed", MessageType_StatusInfo); + } + return; + } + + // 4단계: 다중 장치 데이터 처리 (왼손, 오른손) + ProcessMultipleDeviceData(rokokoFrame); + + // 5단계: 최신 프레임 업데이트 + mLastRokokoFrame = rokokoFrame; + + // 성공 메시지 출력 + if (mDeviceManager) { + mDeviceManager->MessageToHost("[RokokoGlove] Successfully processed Rokoko data", MessageType_StatusInfo); + } + + } catch (const std::exception& e) { + if (mDeviceManager) { + std::string errorMsg = "[RokokoGlove] Exception: " + std::string(e.what()); + mDeviceManager->MessageToHost(errorMsg.c_str(), MessageType_StatusInfo); + } + } catch (...) { + if (mDeviceManager) { + mDeviceManager->MessageToHost("[RokokoGlove] Unknown exception occurred", MessageType_StatusInfo); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// +// 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(); + + // 왼손 장치 생성 + sGloveDeviceBaseInfo leftHandInfo; + leftHandInfo.gloveId = 1; + leftHandInfo.handSide = eGloveHandSide::Left; + leftHandInfo.battery = 100; + leftHandInfo.signalStrength = 100; + CreateNewGloveDevice(leftHandInfo); + SetLatestDeviceInfo(leftHandInfo); + mDetectedDevices.push_back(1); + + // 오른손 장치 생성 + sGloveDeviceBaseInfo rightHandInfo; + rightHandInfo.gloveId = 2; + rightHandInfo.handSide = eGloveHandSide::Right; + rightHandInfo.battery = 100; + rightHandInfo.signalStrength = 100; + 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); + } + } +} + +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; + } + + const auto& actor = rokokoFrame.scene.actors[0]; + + // 왼손 데이터 처리 + if (ProcessHandData(actor.body, 1, eGloveHandSide::Left)) { + UpdateDeviceInfo(1); + } + + // 오른손 데이터 처리 + if (ProcessHandData(actor.body, 2, eGloveHandSide::Right)) { + UpdateDeviceInfo(2); + } + + } 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; + } + + // 데이터 유효성 검사 + if (!RokokoIntegration::RokokoDataConverter::ValidateOptiTrackData(optiTrackData)) { + if (mDeviceManager) { + std::string errorMsg = "[RokokoGlove] Data validation failed for device " + std::to_string(deviceId); + mDeviceManager->MessageToHost(errorMsg.c_str(), MessageType_StatusInfo); + } + return false; + } + + // OptiTrack 데이터 저장 + mGloveDataMutex->lock(); + SetLatestData(optiTrackData); + mGloveDataMutex->unlock(); + + return true; + + } catch (...) { + if (mDeviceManager) { + std::string errorMsg = "[RokokoGlove] Error processing hand data for device " + std::to_string(deviceId); + mDeviceManager->MessageToHost(errorMsg.c_str(), MessageType_StatusInfo); + } + return false; + } +} + +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) +{ + // 쿼터니언 변환 + optiTrackNode.quat_w = rokokoJoint.rotation.w; + optiTrackNode.quat_x = rokokoJoint.rotation.x; + optiTrackNode.quat_y = rokokoJoint.rotation.y; + optiTrackNode.quat_z = rokokoJoint.rotation.z; +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveAdapterSingleton.h b/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveAdapterSingleton.h new file mode 100644 index 000000000..6f35ad9c0 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveAdapterSingleton.h @@ -0,0 +1,227 @@ +////====================================================================================================== +//// 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; + + /** + * 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); + + /** + * Detect and create Rokoko devices dynamically + */ + void DetectAndCreateRokokoDevices(); + + /** + * Process data for multiple devices (left/right hand) + */ + 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); + + 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/ExampleGloveData.csv b/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveData.csv new file mode 100644 index 000000000..21e484c78 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/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/ExampleGloveDevice.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveDevice.cpp new file mode 100644 index 000000000..dbb24ec1c --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/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/ExampleGloveDevice.h b/Optitrack Rokoko Glove/RokokoGloveDevice/ExampleGloveDevice.h new file mode 100644 index 000000000..a7a6d3a7e --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/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/GloveDataFormat.h b/Optitrack Rokoko Glove/RokokoGloveDevice/GloveDataFormat.h new file mode 100644 index 000000000..d434bca84 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/GloveDataFormat.h @@ -0,0 +1,83 @@ +//====================================================================================================== +// 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; +}; + + + + + + diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/GloveDeviceBase.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice/GloveDeviceBase.cpp new file mode 100644 index 000000000..9700f47fd --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/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/GloveDeviceBase.h b/Optitrack Rokoko Glove/RokokoGloveDevice/GloveDeviceBase.h new file mode 100644 index 000000000..cac502cc7 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/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/HardwareSimulator.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice/HardwareSimulator.cpp new file mode 100644 index 000000000..d6add29c2 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/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/HardwareSimulator.h b/Optitrack Rokoko Glove/RokokoGloveDevice/HardwareSimulator.h new file mode 100644 index 000000000..6a1deada8 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/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/LZ4Wrapper.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice/LZ4Wrapper.cpp new file mode 100644 index 000000000..8623e5e74 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/LZ4Wrapper.cpp @@ -0,0 +1,100 @@ +//====================================================================================================== +// Copyright 2025, Rokoko Glove OptiTrack Integration +//====================================================================================================== + +#include "LZ4Wrapper.h" +#include "lz4.h" +#include +#include + +namespace RokokoIntegration +{ + std::vector LZ4Wrapper::Decompress(const uint8_t* compressedData, int compressedSize) + { + try { + // 입력 데이터 검증 + if (!compressedData || compressedSize <= 0) { + return {}; + } + + // LZ4 데이터 유효성 검사 + if (!IsValidLZ4Data(compressedData, compressedSize)) { + return {}; + } + + // 압축 해제된 크기 추정 + int decompressedSize = GetDecompressedSize(compressedData, compressedSize); + if (decompressedSize <= 0) { + return {}; + } + + // 크기 유효성 검사 + if (!ValidateDecompressedSize(compressedSize, decompressedSize)) { + return {}; + } + + // 압축 해제 실행 + std::vector decompressed(decompressedSize); + int actualSize = LZ4_decompress_safe( + reinterpret_cast(compressedData), + reinterpret_cast(decompressed.data()), + compressedSize, + decompressedSize + ); + + // 압축 해제 결과 검증 + if (actualSize != decompressedSize) { + return {}; + } + + return decompressed; + + } catch (...) { + // 모든 예외 상황에서 안전하게 빈 벡터 반환 + return {}; + } + } + + bool LZ4Wrapper::IsValidLZ4Data(const uint8_t* data, int size) + { + if (!data || size < 4) { + return false; + } + + // LZ4 매직 넘버 확인 (간단한 검증) + // 실제로는 더 정교한 검증이 필요할 수 있음 + return true; + } + + int LZ4Wrapper::GetDecompressedSize(const uint8_t* compressedData, int compressedSize) + { + try { + // LZ4에서 압축 해제된 크기 추정 + int decompressedSize = LZ4_decompress_safe( + reinterpret_cast(compressedData), + nullptr, + compressedSize, + 0 + ); + 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/LZ4Wrapper.h b/Optitrack Rokoko Glove/RokokoGloveDevice/LZ4Wrapper.h new file mode 100644 index 000000000..50e6ac237 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/LZ4Wrapper.h @@ -0,0 +1,56 @@ +//====================================================================================================== +// 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 + +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/RokokoData.h b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoData.h new file mode 100644 index 000000000..b4abbf0ef --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoData.h @@ -0,0 +1,116 @@ +//====================================================================================================== +// 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 + +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 finger joints + struct Body + { + // Left hand finger joints + std::shared_ptr leftThumbProximal; + std::shared_ptr leftThumbMedial; + std::shared_ptr leftThumbDistal; + std::shared_ptr leftThumbTip; + + std::shared_ptr leftIndexProximal; + std::shared_ptr leftIndexMedial; + std::shared_ptr leftIndexDistal; + std::shared_ptr leftIndexTip; + + std::shared_ptr leftMiddleProximal; + std::shared_ptr leftMiddleMedial; + std::shared_ptr leftMiddleDistal; + std::shared_ptr leftMiddleTip; + + std::shared_ptr leftRingProximal; + std::shared_ptr leftRingMedial; + std::shared_ptr leftRingDistal; + std::shared_ptr leftRingTip; + + std::shared_ptr leftLittleProximal; + std::shared_ptr leftLittleMedial; + std::shared_ptr leftLittleDistal; + std::shared_ptr leftLittleTip; + + // Right hand finger joints (similar structure) + std::shared_ptr rightThumbProximal; + std::shared_ptr rightThumbMedial; + std::shared_ptr rightThumbDistal; + std::shared_ptr rightThumbTip; + + std::shared_ptr rightIndexProximal; + std::shared_ptr rightIndexMedial; + std::shared_ptr rightIndexDistal; + std::shared_ptr rightIndexTip; + + std::shared_ptr rightMiddleProximal; + std::shared_ptr rightMiddleMedial; + std::shared_ptr rightMiddleDistal; + std::shared_ptr rightMiddleTip; + + std::shared_ptr rightRingProximal; + std::shared_ptr rightRingMedial; + std::shared_ptr rightRingDistal; + std::shared_ptr rightRingTip; + + std::shared_ptr rightLittleProximal; + std::shared_ptr rightLittleMedial; + std::shared_ptr rightLittleDistal; + std::shared_ptr 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/RokokoDataConverter.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataConverter.cpp new file mode 100644 index 000000000..f0798fc66 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataConverter.cpp @@ -0,0 +1,213 @@ +//====================================================================================================== +// 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]; + + // 손가락별 데이터 매핑 (왼손) + // 엄지손가락 (Thumb) + if (actor.body.leftThumbMedial) ConvertJoint(*actor.body.leftThumbMedial, optiTrackData.nodes[0]); // MP + if (actor.body.leftThumbDistal) ConvertJoint(*actor.body.leftThumbDistal, optiTrackData.nodes[1]); // PIP + if (actor.body.leftThumbTip) ConvertJoint(*actor.body.leftThumbTip, optiTrackData.nodes[2]); // DIP + + // 검지손가락 (Index) + if (actor.body.leftIndexMedial) ConvertJoint(*actor.body.leftIndexMedial, optiTrackData.nodes[3]); // MP + if (actor.body.leftIndexDistal) ConvertJoint(*actor.body.leftIndexDistal, optiTrackData.nodes[4]); // PIP + if (actor.body.leftIndexTip) ConvertJoint(*actor.body.leftIndexTip, optiTrackData.nodes[5]); // DIP + + // 중지손가락 (Middle) + if (actor.body.leftMiddleMedial) ConvertJoint(*actor.body.leftMiddleMedial, optiTrackData.nodes[6]); // MP + if (actor.body.leftMiddleDistal) ConvertJoint(*actor.body.leftMiddleDistal, optiTrackData.nodes[7]); // PIP + if (actor.body.leftMiddleTip) ConvertJoint(*actor.body.leftMiddleTip, optiTrackData.nodes[8]); // DIP + + // 약지손가락 (Ring) + if (actor.body.leftRingMedial) ConvertJoint(*actor.body.leftRingMedial, optiTrackData.nodes[9]); // MP + if (actor.body.leftRingDistal) ConvertJoint(*actor.body.leftRingDistal, optiTrackData.nodes[10]); // PIP + if (actor.body.leftRingTip) ConvertJoint(*actor.body.leftRingTip, optiTrackData.nodes[11]); // DIP + + // 새끼손가락 (Little) + if (actor.body.leftLittleMedial) ConvertJoint(*actor.body.leftLittleMedial, optiTrackData.nodes[12]); // MP + if (actor.body.leftLittleDistal) ConvertJoint(*actor.body.leftLittleDistal, optiTrackData.nodes[13]); // PIP + if (actor.body.leftLittleTip) 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/RokokoDataConverter.h b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataConverter.h new file mode 100644 index 000000000..960725901 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/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/RokokoDataParser.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataParser.cpp new file mode 100644 index 000000000..54338487a --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataParser.cpp @@ -0,0 +1,225 @@ +//====================================================================================================== +// 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 { + // 간단한 JSON 파싱 구현 (실제로는 nlohmann/json 라이브러리 사용 권장) + // 현재는 기본 구조만 생성 + + // 기본 프레임 데이터 초기화 + frame.scene.actors.clear(); + frame.scene.actors.push_back(RokokoData::ActorData()); + + // 기본 손가락 데이터 구조 생성 + auto& actor = frame.scene.actors[0]; + + // 왼손 데이터 초기화 (모든 손가락) + // 엄지 + actor.body.leftThumbMedial = std::make_shared(); + actor.body.leftThumbDistal = std::make_shared(); + actor.body.leftThumbTip = std::make_shared(); + // 검지 + actor.body.leftIndexMedial = std::make_shared(); + actor.body.leftIndexDistal = std::make_shared(); + actor.body.leftIndexTip = std::make_shared(); + // 중지 + actor.body.leftMiddleMedial = std::make_shared(); + actor.body.leftMiddleDistal = std::make_shared(); + actor.body.leftMiddleTip = std::make_shared(); + // 약지 + actor.body.leftRingMedial = std::make_shared(); + actor.body.leftRingDistal = std::make_shared(); + actor.body.leftRingTip = std::make_shared(); + // 새끼 + actor.body.leftLittleMedial = std::make_shared(); + actor.body.leftLittleDistal = std::make_shared(); + actor.body.leftLittleTip = std::make_shared(); + + // 오른손 데이터 초기화 (모든 손가락) + // 엄지 + actor.body.rightThumbMedial = std::make_shared(); + actor.body.rightThumbDistal = std::make_shared(); + actor.body.rightThumbTip = std::make_shared(); + // 검지 + actor.body.rightIndexMedial = std::make_shared(); + actor.body.rightIndexDistal = std::make_shared(); + actor.body.rightIndexTip = std::make_shared(); + // 중지 + actor.body.rightMiddleMedial = std::make_shared(); + actor.body.rightMiddleDistal = std::make_shared(); + actor.body.rightMiddleTip = std::make_shared(); + // 약지 + actor.body.rightRingMedial = std::make_shared(); + actor.body.rightRingDistal = std::make_shared(); + actor.body.rightRingTip = std::make_shared(); + // 새끼 + actor.body.rightLittleMedial = std::make_shared(); + actor.body.rightLittleDistal = std::make_shared(); + actor.body.rightLittleTip = std::make_shared(); + + // 기본 쿼터니언 값 설정 (정규화된 단위 쿼터니언) + RokokoData::Vector4Frame defaultRotation = {1.0f, 0.0f, 0.0f, 0.0f}; + + // 왼손 기본값 설정 + actor.body.leftThumbMedial->rotation = defaultRotation; + actor.body.leftThumbDistal->rotation = defaultRotation; + actor.body.leftThumbTip->rotation = defaultRotation; + actor.body.leftIndexMedial->rotation = defaultRotation; + actor.body.leftIndexDistal->rotation = defaultRotation; + actor.body.leftIndexTip->rotation = defaultRotation; + actor.body.leftMiddleMedial->rotation = defaultRotation; + actor.body.leftMiddleDistal->rotation = defaultRotation; + actor.body.leftMiddleTip->rotation = defaultRotation; + actor.body.leftRingMedial->rotation = defaultRotation; + actor.body.leftRingDistal->rotation = defaultRotation; + actor.body.leftRingTip->rotation = defaultRotation; + actor.body.leftLittleMedial->rotation = defaultRotation; + actor.body.leftLittleDistal->rotation = defaultRotation; + actor.body.leftLittleTip->rotation = defaultRotation; + + // 오른손 기본값 설정 + actor.body.rightThumbMedial->rotation = defaultRotation; + actor.body.rightThumbDistal->rotation = defaultRotation; + actor.body.rightThumbTip->rotation = defaultRotation; + actor.body.rightIndexMedial->rotation = defaultRotation; + actor.body.rightIndexDistal->rotation = defaultRotation; + actor.body.rightIndexTip->rotation = defaultRotation; + actor.body.rightMiddleMedial->rotation = defaultRotation; + actor.body.rightMiddleDistal->rotation = defaultRotation; + actor.body.rightMiddleTip->rotation = defaultRotation; + actor.body.rightRingMedial->rotation = defaultRotation; + actor.body.rightRingDistal->rotation = defaultRotation; + actor.body.rightRingTip->rotation = defaultRotation; + actor.body.rightLittleMedial->rotation = defaultRotation; + actor.body.rightLittleDistal->rotation = defaultRotation; + actor.body.rightLittleTip->rotation = defaultRotation; + + return true; + + } catch (...) { + return false; + } + } + + bool RokokoDataParser::ValidateFrameData(const RokokoData::LiveFrame_v4& frame) + { + try { + // 기본 검증 + if (frame.scene.actors.empty()) { + return false; + } + + const auto& actor = frame.scene.actors[0]; + + // 손가락 데이터 검증 + if (!actor.body.leftThumbMedial || !actor.body.leftThumbDistal || !actor.body.leftThumbTip) { + 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]; + + // 왼손 데이터 추출 + if (actor.body.leftThumbMedial) { + leftHandFingers.push_back(*actor.body.leftThumbMedial); + } + if (actor.body.leftThumbDistal) { + leftHandFingers.push_back(*actor.body.leftThumbDistal); + } + if (actor.body.leftThumbTip) { + leftHandFingers.push_back(*actor.body.leftThumbTip); + } + + // 오른손 데이터는 현재 구현되지 않음 (기본값 사용) + // 실제 구현에서는 actor.body.rightThumb* 데이터 사용 + + 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 (std::abs(length - 1.0f) > 0.1f) { + 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.0f) { + float invLength = 1.0f / length; + x *= invLength; + y *= invLength; + z *= invLength; + w *= invLength; + } else { + // 유효하지 않은 쿼터니언인 경우 기본값 설정 + x = 0.0f; + y = 0.0f; + z = 0.0f; + w = 1.0f; + } + } +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataParser.h b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataParser.h new file mode 100644 index 000000000..7d5ef00cf --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoDataParser.h @@ -0,0 +1,85 @@ +//====================================================================================================== +// 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 + +// Forward declarations for JSON data structures +namespace RokokoData +{ + struct Vector3Frame; + struct Vector4Frame; + struct ActorJointFrame; + struct ActorData; + struct LiveFrame_v4; +} + +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 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/RokokoGloveDevice.vcxproj b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoGloveDevice.vcxproj new file mode 100644 index 000000000..e2c634afb --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoGloveDevice.vcxproj @@ -0,0 +1,149 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {513E58BC-64D4-4E29-8AF8-90F5D408C1ED} + Win32Proj + OptiTrackPeripheralExample + 8.1 + RokokoGloveDevice + + + + 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;%(AdditionalIncludeDirectories) + + + 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;%(AdditionalIncludeDirectories) + + + 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/RokokoGloveDevice.vcxproj.filters b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoGloveDevice.vcxproj.filters new file mode 100644 index 000000000..974cb17f7 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoGloveDevice.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/RokokoUDPReceiver.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoUDPReceiver.cpp new file mode 100644 index 000000000..f447e4046 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoUDPReceiver.cpp @@ -0,0 +1,254 @@ +//====================================================================================================== +// Copyright 2025, Rokoko Glove OptiTrack Integration +//====================================================================================================== + +#include "RokokoUDPReceiver.h" +#include +#include +#include + +namespace RokokoIntegration +{ + RokokoUDPReceiver::RokokoUDPReceiver() + : mSocket(INVALID_SOCKET) + , mIsRunning(false) + , mIsListening(false) + , mPacketsReceived(0) + , mBytesReceived(0) + , mLastPacketTime(0.0) + , mPort(14043) + , mBufferSize(65000) + { + } + + RokokoUDPReceiver::~RokokoUDPReceiver() + { + StopListening(); + CloseSocket(); + } + + bool RokokoUDPReceiver::Initialize(int port) + { + try { + mPort = port; + + // Winsock 초기화 + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + SetError("WSAStartup failed"); + return false; + } + + // UDP 소켓 생성 + mSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (mSocket == INVALID_SOCKET) { + SetError("Failed to create UDP socket"); + WSACleanup(); + 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; + } + + // 논블로킹 모드 설정 + 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; + } + + void RokokoUDPReceiver::ReceiveThread() + { + std::vector buffer(mBufferSize); + + while (mIsRunning) { + try { + struct sockaddr_in senderAddr; + int senderAddrSize = sizeof(senderAddr); + + // UDP 데이터 수신 + 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 (bytesReceived == SOCKET_ERROR) { + int error = WSAGetLastError(); + if (error != WSAEWOULDBLOCK) { + // 실제 에러인 경우 + std::ostringstream oss; + oss << "UDP receive error: " << error; + SetError(oss.str()); + break; + } + } + + // CPU 사용률 조절 + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + + } 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; + } + + WSACleanup(); + } +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoUDPReceiver.h b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoUDPReceiver.h new file mode 100644 index 000000000..892470a33 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/RokokoUDPReceiver.h @@ -0,0 +1,139 @@ +//====================================================================================================== +// 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(); + }; +} diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/dllcommon.h b/Optitrack Rokoko Glove/RokokoGloveDevice/dllcommon.h new file mode 100644 index 000000000..71f9b68bd --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/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/dllmain.cpp b/Optitrack Rokoko Glove/RokokoGloveDevice/dllmain.cpp new file mode 100644 index 000000000..31ce0fe66 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/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/lz4.c b/Optitrack Rokoko Glove/RokokoGloveDevice/lz4.c new file mode 100644 index 000000000..654bfdf32 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/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/lz4.h b/Optitrack Rokoko Glove/RokokoGloveDevice/lz4.h new file mode 100644 index 000000000..491c6087c --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/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/lz4.zip b/Optitrack Rokoko Glove/RokokoGloveDevice/lz4.zip new file mode 100644 index 000000000..6ed72c9ab --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/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/readme.txt b/Optitrack Rokoko Glove/RokokoGloveDevice/readme.txt new file mode 100644 index 000000000..5161b3d09 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/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/x64/Debug/ExampleGloveAdapterSingleton.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/ExampleGloveAdapterSingleton.obj new file mode 100644 index 000000000..eb625e2b7 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/ExampleGloveAdapterSingleton.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bcc5341ff6326f2e644dac6941ceae06ffe113f020d4979d420d1d13567565d +size 1900385 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/ExampleGloveDevice.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/ExampleGloveDevice.obj new file mode 100644 index 000000000..a64bd2a63 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/ExampleGloveDevice.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e828db2ae2584af3aa2382a807e639350239607b54e3e1317f14f121dbe23ac +size 411123 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/GloveDeviceBase.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/GloveDeviceBase.obj new file mode 100644 index 000000000..e758fcb3f --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/GloveDeviceBase.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba3436308df0b6cd5bd9da32aad30a949aee5ff01b804ead4f611c8afb07f7c0 +size 368143 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/LZ4Wrapper.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/LZ4Wrapper.obj new file mode 100644 index 000000000..764b25268 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/LZ4Wrapper.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7f57f67431c5e5c23f44a3a5c9785f204269c47e10b20c8853287abf491eeb27 +size 169548 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoDataConverter.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoDataConverter.obj new file mode 100644 index 000000000..22293387e --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoDataConverter.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ec59716322b316b1bca098666afe786abce44a88683bd8e49571e5385d72d16 +size 408468 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoDataParser.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoDataParser.obj new file mode 100644 index 000000000..17d345955 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoDataParser.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:156f16eafecd6b0b8bb5a9b47e3a58364ee96e47030faa04b96612c5b80c4c50 +size 379349 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.command.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.command.1.tlog new file mode 100644 index 000000000..1b64c85ae Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.command.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.read.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.read.1.tlog new file mode 100644 index 000000000..030b673af Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.read.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.write.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.write.1.tlog new file mode 100644 index 000000000..c36658540 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CL.write.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.command.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.command.1.tlog new file mode 100644 index 000000000..9be77f598 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.command.1.tlog @@ -0,0 +1,2 @@ +^C:\USERS\QSCFT\ONEDRIVE\문서\OPTITRACK\MOTIVE\PERIPHERALAPI\EXAMPLE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV +C:\Users\qscft\OneDrive\문서\OptiTrack\Motive\PeripheralAPI\example\x64\Debug\ExampleGloveData.csv diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.read.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.read.1.tlog new file mode 100644 index 000000000..120ee5e56 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.read.1.tlog @@ -0,0 +1 @@ +^C:\USERS\QSCFT\ONEDRIVE\문서\OPTITRACK\MOTIVE\PERIPHERALAPI\EXAMPLE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.write.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.write.1.tlog new file mode 100644 index 000000000..849c02ff0 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/CopyFile.write.1.tlog @@ -0,0 +1,2 @@ +^C:\USERS\QSCFT\ONEDRIVE\문서\OPTITRACK\MOTIVE\PERIPHERALAPI\EXAMPLE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV +C:\USERS\QSCFT\ONEDRIVE\문서\OPTITRACK\MOTIVE\PERIPHERALAPI\EXAMPLE\X64\DEBUG\EXAMPLEGLOVEDATA.CSV diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.lastbuildstate b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.lastbuildstate new file mode 100644 index 000000000..ca77c90af --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.lastbuildstate @@ -0,0 +1,2 @@ +PlatformToolSet=v142:VCToolArchitecture=Native64Bit:VCToolsVersion=14.29.30133:TargetPlatformVersion=8.1:VcpkgTriplet=x64-windows: +Debug|x64|C:\Users\qscft\OneDrive\문서\OptiTrack\Motive\PeripheralAPI\example\| diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.write.1u.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.write.1u.tlog new file mode 100644 index 000000000..4698b9336 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/RokokoGloveDevice.write.1u.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.command.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.command.1.tlog new file mode 100644 index 000000000..cc64a2f4f Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.command.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.read.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.read.1.tlog new file mode 100644 index 000000000..693ff8ac6 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.read.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.write.1.tlog b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.write.1.tlog new file mode 100644 index 000000000..47acec860 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGl.513E58BC.tlog/link.write.1.tlog differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.dll.recipe b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.dll.recipe new file mode 100644 index 000000000..339ec9848 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.dll.recipe @@ -0,0 +1,11 @@ + + + + + C:\Users\qscft\OneDrive\문서\OptiTrack\Motive\PeripheralAPI\example\x64\Debug\RokokoGloveDevice.dll + + + + + + \ No newline at end of file diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.ilk b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.ilk new file mode 100644 index 000000000..87b69ffa7 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.ilk differ diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.vcxproj.FileListAbsolute.txt b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoGloveDevice.vcxproj.FileListAbsolute.txt new file mode 100644 index 000000000..e69de29bb diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoUDPReceiver.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoUDPReceiver.obj new file mode 100644 index 000000000..39c1db9e8 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/RokokoUDPReceiver.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:80b256c2ede8a3d4285192069d2a25e2ae56d17900bef555e26c3aef751f8edf +size 674337 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/dllmain.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/dllmain.obj new file mode 100644 index 000000000..d78fb7c80 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/dllmain.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09ee07f42e48385a35cb9f33f6bcdcc760bada2d85e79f0311a055f61b161817 +size 278719 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/lz4.obj b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/lz4.obj new file mode 100644 index 000000000..4e918ddb2 --- /dev/null +++ b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/lz4.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:90c2abd5142ada866a1336c9189e7ec65e0d68dccf3e4f4d05c4b0d97ecc75ba +size 134890 diff --git a/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/vc142.idb b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/vc142.idb new file mode 100644 index 000000000..366c488b4 Binary files /dev/null and b/Optitrack Rokoko Glove/RokokoGloveDevice/x64/Debug/vc142.idb differ diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/ExampleDevice.cpp b/Optitrack Rokoko Glove/SimpleDeviceExample/ExampleDevice.cpp new file mode 100644 index 000000000..b6005e473 --- /dev/null +++ b/Optitrack Rokoko Glove/SimpleDeviceExample/ExampleDevice.cpp @@ -0,0 +1,337 @@ +//====================================================================================================== +// Copyright 2016, NaturalPoint Inc. +//====================================================================================================== + +#include "dllcommon.h" +#include "ExampleDevice.h" + +// stl +#include +#include +#include +using namespace std; + +// OptiTrack Peripheral Device API +#include "AnalogChannelDescriptor.h" +#include "IDeviceManager.h" +using namespace OptiTrackPluginDevices; + + +/////////////////////////////////////////////////////////////////////////////// +// +// Device Factory +// +const char* ExampleDeviceFactory::Name() const +{ + // REQUIRED: factory name + return "Example"; +} + +unique_ptr ExampleDeviceFactory::Create() const +{ + // Create device + ExampleDevice* pDevice = new ExampleDevice(); + + // REQUIRED: Set device name/model/serial + pDevice->SetProperty( AnalogSystem::cPluginDeviceBase::kNamePropName, (char*) DeviceName() ); // device name + pDevice->SetProperty( cPluginDeviceBase::kDisplayNamePropName, (char*) DeviceName() ); // device display name + pDevice->SetProperty( cPluginDeviceBase::kModelPropName, "ExampleModel" ); // device model + char szValue[MAX_PATH]; + sprintf_s( szValue, "%s-serial", DeviceName() ); + pDevice->SetProperty( cPluginDeviceBase::kSerialPropName, szValue ); // device serial (must be unique) + + // OPTIONAL: Add data channels + int channelIndex; + channelIndex = pDevice->AddChannelDescriptor( "ExampleChannel1", ChannelType_Float ); + channelIndex = pDevice->AddChannelDescriptor( "ExampleChannel2", ChannelType_Float ); + + // OPTIONAL: Set some property values + pDevice->SetProperty( cPluginDeviceBase::kRatePropName, 100.0 ); + pDevice->SetProperty(cPluginDeviceBase::kMocapRateMultiplePropName, 2.0); + pDevice->SetProperty( ExampleDeviceProp_Property1, "NewValue" ); + + 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_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); + } + + // Transfer ownership of the created device to the host, Motive. + unique_ptr ptrDevice( pDevice ); + return ptrDevice; +} + + +/////////////////////////////////////////////////////////////////////////////// +// +// Device +// +ExampleDevice::ExampleDevice() : + mCollecting( false ), + mCollectionThread( nullptr ) +{ + SetDeviceProperties(); +} + +ExampleDevice::~ExampleDevice() +{ +} + +void ExampleDevice::SetDeviceProperties() +{ + // OPTIONAL: Add custom properties + AddProperty( ExampleDeviceProp_Property1, 33.0, "Settings" ); + ModifyProperty( ExampleDeviceProp_Property1, false, false ); + AddProperty( ExampleDeviceProp_Property2, "ExampleValue", "Custom Settings" ); + ModifyProperty( ExampleDeviceProp_Property2, true, false ); + + // OPTIONAL: Hide unrelated properties + ModifyProperty(cPluginDeviceBase::kOrderPropName, true, true); // device serial + ModifyProperty(cPluginDeviceBase::kMasterSerialPropName, true, true); // master device serial + ModifyProperty(cPluginDeviceBase::kUseExternalClockPropName, true, true); // clock sync devices + ModifyProperty(cPluginDeviceBase::kExternalTriggerTerminalPropName, true, true); // trigger sync devices + ModifyProperty(cPluginDeviceBase::kCalOffsetPropName, true, true); // force plates + ModifyProperty(cPluginDeviceBase::kCalSquareRotationPropName, true, true); // force plates + ModifyProperty(cPluginDeviceBase::kZeroPropName, true, true); // zero + ModifyProperty(cPluginDeviceBase::kAssetPropName, true, true); // gloves. paired skeleton + ModifyProperty(cPluginDeviceBase::kUserDataPropName, true, true); // reserved +} + +// OPTIONAL: handle property change events +void ExampleDevice::OnPropertyChanged( const char* propertyName ) +{ + LogError( MessageType_StatusInfo, "[Example Device] Property changed (%s)", propertyName ); +} + +// OPTIONAL: handle channel property change events +void ExampleDevice::OnChannelPropertyChanged( const char* channelName, const char* propertyName ) +{ + AnalogChannelDescriptor* pChannel = ChannelDescriptor( channelName ); + + // Example : Handle when a user changes a channel's enabled state in Motive + if( strcmp( propertyName, ChannelProp_Enabled ) == 0 ) + { + bool enabled; + pChannel->GetProperty( ChannelProp_Enabled, enabled ); + LogError( MessageType_StatusInfo, "[Example Device ] Channel Enabled State Changed (Channel:%s Enabled:%d)", propertyName, (int) enabled ); + } +} + +bool ExampleDevice::Configure() +{ + bool success = Deconfigure(); + if( !success ) + return false; + + // update device's buffer allocation based on current channel and data type configuration + success = __super::Configure(); + if( !success ) + return false; + + // user specified enabled channels in Motive + int nChannels = 0; + for( int i = 0; i < ChannelDescriptorCount(); i++ ) + { + AnalogChannelDescriptor* pChannelDesc = ChannelDescriptor( i ); + bool enabled; + pChannelDesc->GetProperty( ChannelProp_Enabled, enabled ); + if( enabled ) + { + // optional - do something when channel enabled state changes + } + } + + return success; +} + +bool ExampleDevice::Deconfigure() +{ + bool success = __super::Deconfigure(); + return success; +} + +bool ExampleDevice::StartCapture() +{ + bool success = __super::StartCapture(); + + // REQUIRED: Start collecting data + // EXAMPLE: Start collecting from device using a polling thread + mCollecting = true; + mCollectionThread = CreateThread( nullptr, 0, CollectionThread, this, 0, nullptr ); + if( mCollectionThread == nullptr ) + { + return false; + mCollecting = false; + } + + return success; +} + +bool ExampleDevice::StopCapture() +{ + bool success = __super::StopCapture(); + + // REQUIRED: Stop collecting data. Terminate hardware device polling thread + mCollecting = false; + DWORD waitResult = WaitForSingleObject( mCollectionThread, 1000 ); + if( waitResult == WAIT_OBJECT_0 ) + { + CloseHandle( mCollectionThread ); + mCollectionThread = nullptr; + success = true; + } + else if( waitResult == WAIT_TIMEOUT ) + { + BOOL result = TerminateThread( mCollectionThread, 0 ); + success = ( result == TRUE ); + } + else + { + success = false; + } + + return success; +} + +// EXAMPLE: Hardware device polling thread +unsigned long ExampleDevice::CollectionThread( void* Context ) +{ + ExampleDevice* pThis = static_cast( Context ); + return pThis->DoCollectionThread(); +} + + +unsigned long ExampleDevice::DoCollectionThread() +{ + // simulated hardware device data structure + const int maxFrames = 10; + const int maxChannels = 5; + float deviceData[maxFrames][maxChannels]; + int channelsRead, framesRead; + + // simulate hardware device timing on windows + std::chrono::high_resolution_clock::time_point start, end; + std::chrono::milliseconds actualFrameDurationMS; + + double deviceRate; + GetProperty( cPluginDeviceBase::kRatePropName, deviceRate ); + double requestedRateMS = ( 1.0f / deviceRate ) * 1000.0f; + double adjustment = 0.0; + double durationDeltaMS = 0.0; + + while( mCollecting ) + { + start = std::chrono::high_resolution_clock::now(); + + // simulate reading data from hardware device + ReadDataFromHardware( deviceData, channelsRead, framesRead ); + + if( ( framesRead > 0 ) && ( channelsRead > 0 ) ) + { + // copy data from hardware device to PluginDevice frame buffer + for( int iFrame = 0; iFrame < framesRead; iFrame++ ) + { + // Retrieve the next available data slot from this Device's ring buffer, + // which is automatically allocated during the base Configure() routine + // based on selected data type and number of data channels. + // + // A DeviceFrameID is required for later use by the host/sync system during retrieval. + // The DeviceFrameID can come from the hardware itself, or from a simple software counter. + // It is used to frame align (synchronize) the host system with this device + + // establish device FrameID + long deviceFrameID = this->FrameCounter(); // use software frame counter + + // get frame from device buffer + AnalogFrame* pFrame = this->BeginFrameUpdate( deviceFrameID ); + if( pFrame ) + { + // fill in frame header + int flags = 0; + pFrame->SetID( deviceFrameID ); + pFrame->SetFlag( flags ); + + // fill in frame data + memcpy( pFrame->ChannelData(), &deviceData[iFrame], channelsRead * sizeof( float ) ); + + // update device buffer and unlock + this->EndFrameUpdate(); + + // update frame counter + this->SetFrameCounter( deviceFrameID + 1 ); + + // test: check value copy + float src = deviceData[iFrame][0]; + const AnalogFrame* pTestFrame = this->GetFrameByDeviceFrameID( pFrame->ID() ); + float dst = pTestFrame->ChannelData()[0]; + if( src != dst ) + { + this->LogError( MessageType_StatusError, "[Example Device] Error in data copy (source:%d, target:%d", src, dst ); + } + } + else + { + this->LogError( MessageType_StatusError, "[Example Device] Unable to get frame from buffer (frameID:%d)", deviceFrameID ); + } + } + } + + // End frame update. Sleep the thread for the remaining frame period. + std::this_thread::sleep_for(std::chrono::milliseconds((int)(requestedRateMS - adjustment))); + end = std::chrono::high_resolution_clock::now(); + actualFrameDurationMS = std::chrono::duration_cast(end - start); + durationDeltaMS = actualFrameDurationMS.count() - requestedRateMS; + + // calculating offset adjustment from the delta to apply to the next frame. + if (durationDeltaMS > 1.0) + adjustment = -1.0; + else if (durationDeltaMS > 2.0) + adjustment = -2.0; + else if (durationDeltaMS < -2.0) + adjustment = 2.0; + else if (durationDeltaMS < -1.0) + adjustment = 1.0; + else + adjustment = 0.0; + if (fabs(durationDeltaMS) > 1.0) + { + this->LogError(MessageType_StatusInfo, "[Example Device] Device timing resolution off by %3.1f ms (requested:%3.1f, actual:%3.1f)", durationDeltaMS, requestedRateMS, (double) actualFrameDurationMS.count()); + } + } + return 0; +} + +template +void ExampleDevice::ReadDataFromHardware( float( &deviceData )[M][N], int& channelsRead, int& framesRead ) +{ + // EXAMPLE: generate 1 sample with 2 channels : sine/cosine wave + + framesRead = 1; + channelsRead = this->ActiveChannelCount(); + for( int frame = 0; frame < framesRead; frame++ ) + { + for( int index = 0; index < channelsRead; index++ ) + { + int channel = ChannelID( index ); + float counter = (float) this->FrameCounter() / 10.0f; + float val; + if( channel == 0 ) + { + val = (float) this->FrameCounter(); + } + else + { + val = (float) sin( counter ); + } + deviceData[frame][index] = val; + } + } +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/ExampleDevice.h b/Optitrack Rokoko Glove/SimpleDeviceExample/ExampleDevice.h new file mode 100644 index 000000000..b035afb98 --- /dev/null +++ b/Optitrack Rokoko Glove/SimpleDeviceExample/ExampleDevice.h @@ -0,0 +1,82 @@ + +//====================================================================================================== +// Copyright 2023, NaturalPoint Inc. +//====================================================================================================== +/** + * This is a simple device example that demonstrates basic setups for the external device plugin in Motive + * which uses the peripheralimport LIB. + */ + +#include +#include + +// OptiTrack Peripheral Device API +#include "PluginDevice.h" +#include "PluginDeviceFactory.h" +using namespace AnalogSystem; + +namespace OptiTrackPluginDevices +{ + static const char* ExampleDeviceProp_Property1 = "ExampleProperty1"; + static const char* ExampleDeviceProp_Property2 = "ExampleProperty2"; + + /** + * ExampleDeviceFactory creates a specific instance of an ExampleDevice and transfers ownership + * of the device pointer to the caller. Each peripheral device class should have a factory class + * that inherits from the PluginDeviceFactory as instance of the factory is used to instantiate + * devices in Motive. + */ + class ExampleDeviceFactory : public PluginDeviceFactory + { + public: + ExampleDeviceFactory(const char* deviceName) : PluginDeviceFactory(deviceName) {} + + // return factory name + virtual const char* Name() const; + + // return new instance of device (transfers ownership) + virtual std::unique_ptr Create() const; + + }; + + /** + * ExampleDevice is a simple example of a Motive Plugin Device. This simple example is provided to demonstrate + * How a device class can be configured using the peripheralimport API. Example device is a simple device in Motive + * That will have 2-float channels in the data. + */ + class ExampleDevice : public cPluginDevice + { + friend class ExampleDeviceFactory; + public: + ExampleDevice(); + virtual ~ExampleDevice(); + + // IDevice + virtual bool Configure(); + virtual bool Deconfigure(); + virtual bool StartCapture(); + virtual bool StopCapture(); + virtual void OnPropertyChanged(const char* propertyName); + virtual void OnChannelPropertyChanged(const char* channelName, const char* propertyName); + + // ExampleDevice Specific + void SetDeviceProperties(); + + private: + + /** + * The following collection thread polls the hardware device data on a separate thread and populates + * the data channels. + */ + static unsigned long __stdcall CollectionThread(void* Context); + unsigned long DoCollectionThread(); + void* mCollectionThread; + bool mCollecting; + + /** + * Helper method for the example to simulate data from hardware. + */ + template void ReadDataFromHardware(float(&deviceData)[M][N], int& channelsRead, int& framesRead); + }; + +} \ No newline at end of file diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/SimpleDeviceExample.vcxproj b/Optitrack Rokoko Glove/SimpleDeviceExample/SimpleDeviceExample.vcxproj new file mode 100644 index 000000000..fa15da705 --- /dev/null +++ b/Optitrack Rokoko Glove/SimpleDeviceExample/SimpleDeviceExample.vcxproj @@ -0,0 +1,119 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {CBF66948-F8DC-4541-9DEE-AF6E2D201400} + Win32Proj + SimpleDeviceExample + 8.1 + SimpleDeviceExample + + + + 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 + + + + if not exist "C:/Program Files/OptiTrack/Motive/devices" mkdir "C:/Program Files/OptiTrack/Motive/devices" +copy "$(OutDir)SimpleDeviceExample.dll" "C:/Program Files/OptiTrack/Motive/devices" + + + + + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;ANALOGSYSTEM_IMPORTS;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;%(PreprocessorDefinitions) + false + ..\..\include;%(AdditionalIncludeDirectories) + + + Windows + true + NotSet + PeripheralImport.lib;%(AdditionalDependencies) + ..\..\lib;%(AdditionalLibraryDirectories) + + + + + Level3 + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;ANALOGSYSTEM_IMPORTS;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;%(PreprocessorDefinitions) + ..\..\include;%(AdditionalIncludeDirectories) + + + Windows + true + true + true + PeripheralImport.lib;%(AdditionalDependencies) + ..\..\lib;%(AdditionalLibraryDirectories) + + + + + + + + + false + + + false + + + + + + + + + + + + \ No newline at end of file diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/SimpleDeviceExample.vcxproj.filters b/Optitrack Rokoko Glove/SimpleDeviceExample/SimpleDeviceExample.vcxproj.filters new file mode 100644 index 000000000..ad3d15889 --- /dev/null +++ b/Optitrack Rokoko Glove/SimpleDeviceExample/SimpleDeviceExample.vcxproj.filters @@ -0,0 +1,36 @@ + + + + + {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 + + + + + Source Files + + + Source Files + + + + + + \ No newline at end of file diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/dllcommon.h b/Optitrack Rokoko Glove/SimpleDeviceExample/dllcommon.h new file mode 100644 index 000000000..71f9b68bd --- /dev/null +++ b/Optitrack Rokoko Glove/SimpleDeviceExample/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/SimpleDeviceExample/dllmain.cpp b/Optitrack Rokoko Glove/SimpleDeviceExample/dllmain.cpp new file mode 100644 index 000000000..0a0872c86 --- /dev/null +++ b/Optitrack Rokoko Glove/SimpleDeviceExample/dllmain.cpp @@ -0,0 +1,94 @@ +//====================================================================================================== +// 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 "ExampleDevice.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 + +// Report version to host +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; +} + +// Register device factories with host (1 factory per device) +OPTITRACKPERIPHERALEXAMPLE_API int DLLEnumerateDeviceFactories(IDeviceManager* pDeviceManager) +{ + unique_ptr deviceFactory; + + // REQUIRED: Device detection - 1 factory for each device + int nDevices = 2; + + // device factory / device 1 + deviceFactory = unique_ptr(new ExampleDeviceFactory("MyDevice1")); + pDeviceManager->RegisterDeviceFactory(std::move(deviceFactory)); // transfer ownership to host + + // device factory / device 2 + deviceFactory = unique_ptr(new ExampleDeviceFactory("MyDevice2")); + pDeviceManager->RegisterDeviceFactory(std::move(deviceFactory)); // transfer ownership to host + + return nDevices; +} + +// Cleanup any plugin/DLL resources here. +OPTITRACKPERIPHERALEXAMPLE_API int PluginDLLUnload(IDeviceManager* pDeviceManager) +{ + // OPTIONAL: perform device DLL shutdown here + 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/SimpleDeviceExample/readme.txt b/Optitrack Rokoko Glove/SimpleDeviceExample/readme.txt new file mode 100644 index 000000000..c913829a5 --- /dev/null +++ b/Optitrack Rokoko Glove/SimpleDeviceExample/readme.txt @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2c12cf5110c4b6c73d60bf2b773bd24fc3b8f535c2a2c90da8364a2ba09b572 +size 903 diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/ExampleDevice.obj b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/ExampleDevice.obj new file mode 100644 index 000000000..cc857f9ed --- /dev/null +++ b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/ExampleDevice.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0b615a6d80cc09fb0ea1dab1640970544042db75383581964d5d6716efa0982 +size 264988 diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/CL.command.1.tlog b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/CL.command.1.tlog new file mode 100644 index 000000000..d8c843a08 Binary files /dev/null and b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/CL.command.1.tlog differ diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/CL.read.1.tlog b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/CL.read.1.tlog new file mode 100644 index 000000000..4f7e3a8ad Binary files /dev/null and b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/CL.read.1.tlog differ diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/CL.write.1.tlog b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/CL.write.1.tlog new file mode 100644 index 000000000..6c0bbff23 Binary files /dev/null and b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/CL.write.1.tlog differ diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/SimpleDeviceExample.lastbuildstate b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/SimpleDeviceExample.lastbuildstate new file mode 100644 index 000000000..ca77c90af --- /dev/null +++ b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/SimpleDeviceExample.lastbuildstate @@ -0,0 +1,2 @@ +PlatformToolSet=v142:VCToolArchitecture=Native64Bit:VCToolsVersion=14.29.30133:TargetPlatformVersion=8.1:VcpkgTriplet=x64-windows: +Debug|x64|C:\Users\qscft\OneDrive\문서\OptiTrack\Motive\PeripheralAPI\example\| diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/SimpleDeviceExample.write.1u.tlog b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/SimpleDeviceExample.write.1u.tlog new file mode 100644 index 000000000..859604895 Binary files /dev/null and b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/SimpleDeviceExample.write.1u.tlog differ diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/link.command.1.tlog b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/link.command.1.tlog new file mode 100644 index 000000000..25fea2a51 Binary files /dev/null and b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/link.command.1.tlog differ diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/link.read.1.tlog b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/link.read.1.tlog new file mode 100644 index 000000000..acc43eea4 Binary files /dev/null and b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/link.read.1.tlog differ diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/link.write.1.tlog b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/link.write.1.tlog new file mode 100644 index 000000000..a202962e6 Binary files /dev/null and b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/link.write.1.tlog differ diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/unsuccessfulbuild b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDe.CBF66948.tlog/unsuccessfulbuild new file mode 100644 index 000000000..e69de29bb diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDeviceExample.dll.recipe b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDeviceExample.dll.recipe new file mode 100644 index 000000000..eba85519f --- /dev/null +++ b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDeviceExample.dll.recipe @@ -0,0 +1,11 @@ + + + + + C:\Users\qscft\OneDrive\문서\OptiTrack\Motive\PeripheralAPI\example\x64\Debug\SimpleDeviceExample.dll + + + + + + \ No newline at end of file diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDeviceExample.ilk b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDeviceExample.ilk new file mode 100644 index 000000000..cccb650fc Binary files /dev/null and b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDeviceExample.ilk differ diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDeviceExample.vcxproj.FileListAbsolute.txt b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/SimpleDeviceExample.vcxproj.FileListAbsolute.txt new file mode 100644 index 000000000..e69de29bb diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/dllmain.obj b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/dllmain.obj new file mode 100644 index 000000000..3fc151b91 --- /dev/null +++ b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/dllmain.obj @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d866e6a7e9d9cc51f0cf06aafbea365578bc1fe030bc1b4b3e2a7eb2eb4bd050 +size 88247 diff --git a/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/vc142.idb b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/vc142.idb new file mode 100644 index 000000000..6d9dea15c Binary files /dev/null and b/Optitrack Rokoko Glove/SimpleDeviceExample/x64/Debug/vc142.idb differ diff --git a/Optitrack Rokoko Glove/x64/Debug/ExampleGloveData.csv b/Optitrack Rokoko Glove/x64/Debug/ExampleGloveData.csv new file mode 100644 index 000000000..21e484c78 --- /dev/null +++ b/Optitrack Rokoko Glove/x64/Debug/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/Debug/GloveDeviceExample.exp b/Optitrack Rokoko Glove/x64/Debug/GloveDeviceExample.exp new file mode 100644 index 000000000..1233732d1 Binary files /dev/null and b/Optitrack Rokoko Glove/x64/Debug/GloveDeviceExample.exp differ diff --git a/Optitrack Rokoko Glove/x64/Debug/GloveDeviceExample.lib b/Optitrack Rokoko Glove/x64/Debug/GloveDeviceExample.lib new file mode 100644 index 000000000..ad6f94fe4 Binary files /dev/null and b/Optitrack Rokoko Glove/x64/Debug/GloveDeviceExample.lib differ diff --git a/Optitrack Rokoko Glove/x64/Debug/RokokoGloveDevice.dll b/Optitrack Rokoko Glove/x64/Debug/RokokoGloveDevice.dll new file mode 100644 index 000000000..2f20bff33 --- /dev/null +++ b/Optitrack Rokoko Glove/x64/Debug/RokokoGloveDevice.dll @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44ef9454d1e50ff848d4751a46453444fdf0055f51f1fab34406a4c3140154c6 +size 509440 diff --git a/Optitrack Rokoko Glove/x64/Debug/RokokoGloveDevice.exp b/Optitrack Rokoko Glove/x64/Debug/RokokoGloveDevice.exp new file mode 100644 index 000000000..f1c8af23e Binary files /dev/null and b/Optitrack Rokoko Glove/x64/Debug/RokokoGloveDevice.exp differ diff --git a/Optitrack Rokoko Glove/x64/Debug/RokokoGloveDevice.lib b/Optitrack Rokoko Glove/x64/Debug/RokokoGloveDevice.lib new file mode 100644 index 000000000..c37610722 Binary files /dev/null and b/Optitrack Rokoko Glove/x64/Debug/RokokoGloveDevice.lib differ diff --git a/Optitrack Rokoko Glove/x64/Debug/SimpleDeviceExample.dll b/Optitrack Rokoko Glove/x64/Debug/SimpleDeviceExample.dll new file mode 100644 index 000000000..458c0115f --- /dev/null +++ b/Optitrack Rokoko Glove/x64/Debug/SimpleDeviceExample.dll @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bf64bc3391f7a31ef3df2a2aa707000555370b7ef05f09de66a806e7b1d0b87 +size 114176 diff --git a/Optitrack Rokoko Glove/x64/Debug/SimpleDeviceExample.exp b/Optitrack Rokoko Glove/x64/Debug/SimpleDeviceExample.exp new file mode 100644 index 000000000..adc521c68 Binary files /dev/null and b/Optitrack Rokoko Glove/x64/Debug/SimpleDeviceExample.exp differ diff --git a/Optitrack Rokoko Glove/x64/Debug/SimpleDeviceExample.lib b/Optitrack Rokoko Glove/x64/Debug/SimpleDeviceExample.lib new file mode 100644 index 000000000..8f55be427 Binary files /dev/null and b/Optitrack Rokoko Glove/x64/Debug/SimpleDeviceExample.lib differ