Add : 옵티트랙 장갑 시스템 구축에 필요한 파일
This commit is contained in:
parent
d5f66f1351
commit
2d81882c77
BIN
Optitrack Rokoko Glove/.vscode/settings.json
(Stored with Git LFS)
vendored
Normal file
BIN
Optitrack Rokoko Glove/.vscode/settings.json
(Stored with Git LFS)
vendored
Normal file
Binary file not shown.
@ -0,0 +1,282 @@
|
||||
//======================================================================================================
|
||||
// Copyright 2023, NaturalPoint Inc.
|
||||
//======================================================================================================
|
||||
|
||||
#include <functional>
|
||||
#include <chrono>
|
||||
#include <windows.h>
|
||||
|
||||
// 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<ExampleGloveDeviceFactory> pDF =
|
||||
std::make_unique<ExampleGloveDeviceFactory>(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<SimulatedPluginDevices::SimulatedGloveFrameData>& 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<SimulatedPluginDevices::SimulatedDeviceInfo>& 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<sFingerNode>(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;
|
||||
}
|
||||
@ -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 <list>
|
||||
#include <mutex>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
// 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<ExampleGloveAdapterSingleton> 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<uint64_t> mDetectedDevices;
|
||||
std::unordered_map<uint64_t, sGloveDeviceData> mLatestGloveData;
|
||||
std::unordered_map<uint64_t, sGloveDeviceBaseInfo> 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<SimulatedPluginDevices::SimulatedGloveFrameData>& gloveFingerData);
|
||||
static void OnDeviceInfoCallback(std::vector<SimulatedPluginDevices::SimulatedDeviceInfo>& 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;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
BIN
Optitrack Rokoko Glove/GloveDeviceExample/ExampleGloveData.csv
(Stored with Git LFS)
Normal file
BIN
Optitrack Rokoko Glove/GloveDeviceExample/ExampleGloveData.csv
(Stored with Git LFS)
Normal file
Binary file not shown.
|
311
Optitrack Rokoko Glove/GloveDeviceExample/ExampleGloveDevice.cpp
Normal file
311
Optitrack Rokoko Glove/GloveDeviceExample/ExampleGloveDevice.cpp
Normal file
@ -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<std::unique_ptr<IDeviceFactory>>& dfs)
|
||||
{
|
||||
// Start server detection
|
||||
if (gGloveAdapter == nullptr) {
|
||||
gGloveAdapter = std::make_unique<ExampleGloveAdapterSingleton>(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<AnalogSystem::IDevice> OptiTrackPluginDevices::ExampleDevice::ExampleGloveDeviceFactory::Create() const
|
||||
{
|
||||
ExampleGloveDevice* pDevice = new ExampleGloveDevice(mDeviceSerial, mDeviceInfo);
|
||||
SetCommonGloveDeviceProperties(pDevice);
|
||||
SetQuaternionDataChannels(pDevice);
|
||||
|
||||
// Transfer ownership to host
|
||||
std::unique_ptr<AnalogSystem::IDevice> 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<ExampleGloveDevice*>(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<std::chrono::seconds>(uiTimerEnd - uiTimerStart);
|
||||
if (elapsedTime.count() > 5)
|
||||
{
|
||||
UpdateGloveProperty(mDeviceInfo);
|
||||
uiTimerStart = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
// Frame Begin: get frame from device buffer
|
||||
AnalogFrame<float>* 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<std::chrono::milliseconds>(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;
|
||||
}
|
||||
@ -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 <memory>
|
||||
#include <list>
|
||||
|
||||
// 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<std::unique_ptr<AnalogSystem::IDeviceFactory>>& 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<AnalogSystem::IDevice> 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
83
Optitrack Rokoko Glove/GloveDeviceExample/GloveDataFormat.h
Normal file
83
Optitrack Rokoko Glove/GloveDeviceExample/GloveDataFormat.h
Normal file
@ -0,0 +1,83 @@
|
||||
//======================================================================================================
|
||||
// Copyright 2023, NaturalPoint Inc.
|
||||
//======================================================================================================
|
||||
/**
|
||||
* This includes common glove data formats referenced in Motive.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
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<sFingerNode> nodes;
|
||||
sGloveDataTimestamp timestamp;
|
||||
};
|
||||
|
||||
struct sGloveDeviceBaseInfo
|
||||
{
|
||||
uint32_t gloveId = 0; // device serial id
|
||||
int battery = 0 ;
|
||||
int signalStrength= 0;
|
||||
eGloveHandSide handSide = eGloveHandSide::Unknown;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
258
Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceBase.cpp
Normal file
258
Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceBase.cpp
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
180
Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceBase.h
Normal file
180
Optitrack Rokoko Glove/GloveDeviceExample/GloveDeviceBase.h
Normal file
@ -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 <memory>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
// 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<float>
|
||||
{
|
||||
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;
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,135 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{DCD02920-D0DA-490D-86F2-3C2C9256A6B7}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>OptiTrackPeripheralExample</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
|
||||
<ProjectName>GloveDeviceExample</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="GloveDeviceDeveloperConfigDebug.props" Condition="Exists('GloveDeviceDeveloperConfigDebug.props')" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="GloveDeviceDeveloperConfigRelease.props" Condition="Exists('GloveDeviceDeveloperConfigRelease.props')" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(Platform)\$(Configuration)\</IntDir>
|
||||
<LocalDebuggerCommand>C:\Program Files\OptiTrack\Motive\Motive.exe</LocalDebuggerCommand>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(Platform)\$(Configuration)\</IntDir>
|
||||
<LocalDebuggerCommand>C:\Program Files\OptiTrack\Motive\Motive.exe</LocalDebuggerCommand>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="!Exists('GloveDeviceDeveloperConfigDebug.props')">
|
||||
<PostBuildEvent>
|
||||
<Command>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"</Command>
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;ANALOGSYSTEM_IMPORTS;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<GenerateXMLDocumentationFiles>false</GenerateXMLDocumentationFiles>
|
||||
<AdditionalIncludeDirectories>..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<ShowProgress>NotSet</ShowProgress>
|
||||
<AdditionalDependencies>PeripheralImport.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<ProjectReference>
|
||||
<LinkLibraryDependencies>false</LinkLibraryDependencies>
|
||||
</ProjectReference>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;ANALOGSYSTEM_IMPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>PeripheralImport.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalLibraryDirectories>..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="dllcommon.h" />
|
||||
<ClInclude Include="ExampleGloveAdapterSingleton.h" />
|
||||
<ClInclude Include="ExampleGloveDevice.h" />
|
||||
<ClInclude Include="GloveDeviceBase.h" />
|
||||
<ClInclude Include="GloveDataFormat.h" />
|
||||
<ClInclude Include="HardwareSimulator.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</CompileAsManaged>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
</PrecompiledHeader>
|
||||
<CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</CompileAsManaged>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ExampleGloveAdapterSingleton.cpp" />
|
||||
<ClCompile Include="ExampleGloveDevice.cpp" />
|
||||
<ClCompile Include="GloveDeviceBase.cpp" />
|
||||
<ClCompile Include="HardwareSimulator.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="readme.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CopyFileToFolders Include="ExampleGloveData.csv">
|
||||
<FileType>Document</FileType>
|
||||
</CopyFileToFolders>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="dllcommon.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ExampleGloveDevice.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GloveDataFormat.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GloveDeviceBase.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HardwareSimulator.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ExampleGloveAdapterSingleton.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ExampleGloveDevice.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GloveDeviceBase.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HardwareSimulator.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ExampleGloveAdapterSingleton.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="readme.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CopyFileToFolders Include="ExampleGloveData.csv" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
185
Optitrack Rokoko Glove/GloveDeviceExample/HardwareSimulator.cpp
Normal file
185
Optitrack Rokoko Glove/GloveDeviceExample/HardwareSimulator.cpp
Normal file
@ -0,0 +1,185 @@
|
||||
//======================================================================================================
|
||||
// Copyright 2023, NaturalPoint Inc.
|
||||
//======================================================================================================
|
||||
#include "HardwareSimulator.h"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <windows.h>
|
||||
|
||||
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<void(std::vector<SimulatedDeviceInfo>&)> 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<void(std::vector<SimulatedGloveFrameData>&)> 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<string> field_names;
|
||||
|
||||
// loop through lines of data
|
||||
std::string lineRead;
|
||||
while (bIsRunning)
|
||||
{
|
||||
std::vector<SimulatedFingerData> frame_data;
|
||||
std::vector<float> 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<SimulatedFingerData>& 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;
|
||||
}
|
||||
111
Optitrack Rokoko Glove/GloveDeviceExample/HardwareSimulator.h
Normal file
111
Optitrack Rokoko Glove/GloveDeviceExample/HardwareSimulator.h
Normal file
@ -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 <thread>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
|
||||
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<SimulatedFingerData> 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;
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Simple simulator for outputting sine wave channel data.
|
||||
/// </summary>
|
||||
class HardwareSimulator {
|
||||
public:
|
||||
HardwareSimulator();
|
||||
~HardwareSimulator();
|
||||
|
||||
void AddSimulatedGlove(int deviceId, int nodeCount, int handedness);
|
||||
void RegisterFrameDataCallback(std::function<void(std::vector<SimulatedGloveFrameData>&)> data_callback);
|
||||
void RegisterDeviceInfoCallback(std::function<void(std::vector<SimulatedDeviceInfo>&)> 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<SimulatedFingerData>& data);
|
||||
|
||||
std::vector<SimulatedGloveFrameData> mSimulatedFrameDataSet;
|
||||
std::vector<SimulatedDeviceInfo> mSimulatedDeviceInfoSet;
|
||||
std::vector<SimulatedDeviceInfo> mNewDeviceInfo;
|
||||
std::function<void(std::vector<SimulatedGloveFrameData>&)> mOnFrameDataUpdate;
|
||||
std::function<void(std::vector<SimulatedDeviceInfo>&)> mOnDeviceInfoUpdate;
|
||||
|
||||
const double mDataSampleRate = DATA_SAMPLERATE;
|
||||
|
||||
protected:
|
||||
std::recursive_mutex mDataLock;
|
||||
};
|
||||
}
|
||||
12
Optitrack Rokoko Glove/GloveDeviceExample/dllcommon.h
Normal file
12
Optitrack Rokoko Glove/GloveDeviceExample/dllcommon.h
Normal file
@ -0,0 +1,12 @@
|
||||
//======================================================================================================
|
||||
// Copyright 2016, NaturalPoint Inc.
|
||||
//======================================================================================================
|
||||
|
||||
// windows
|
||||
#include <SDKDDKVer.h>
|
||||
#define _CRTDBG_MAP_ALLOC
|
||||
#include <stdlib.h>
|
||||
#include <crtdbg.h>
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
103
Optitrack Rokoko Glove/GloveDeviceExample/dllmain.cpp
Normal file
103
Optitrack Rokoko Glove/GloveDeviceExample/dllmain.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
//======================================================================================================
|
||||
// Copyright 2016, NaturalPoint Inc.
|
||||
//======================================================================================================
|
||||
//
|
||||
// dllmain.cpp : Defines the entry point for the DLL application.
|
||||
//
|
||||
|
||||
#include "dllcommon.h"
|
||||
|
||||
// stl
|
||||
#include <list>
|
||||
#include <memory>
|
||||
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<unique_ptr<IDeviceFactory>> availDFs;
|
||||
|
||||
ExampleDevice::ExampleGlove_EnumerateDeviceFactories(pDeviceManager, availDFs);
|
||||
for (list<unique_ptr<IDeviceFactory>>::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;
|
||||
}
|
||||
BIN
Optitrack Rokoko Glove/GloveDeviceExample/readme.txt
(Stored with Git LFS)
Normal file
BIN
Optitrack Rokoko Glove/GloveDeviceExample/readme.txt
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project>
|
||||
<ProjectOutputs>
|
||||
<ProjectOutput>
|
||||
<FullPath>C:\Program Files\OptiTrack\Motive\PeripheralAPI\example\x64\Debug\GloveDeviceExample.dll</FullPath>
|
||||
</ProjectOutput>
|
||||
</ProjectOutputs>
|
||||
<ContentFiles />
|
||||
<SatelliteDlls />
|
||||
<NonRecipeFileRefs />
|
||||
</Project>
|
||||
8
Optitrack Rokoko Glove/Rokoko Unity Scripts/Core.meta
Normal file
8
Optitrack Rokoko Glove/Rokoko Unity Scripts/Core.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7417c9a13b2bd14ab1fea1d7ffa8e0a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f43a84310e40244c970e95e1d934b1a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 721c3fac4cc3fbe4188ec6ec27cb4708
|
||||
folderAsset: yes
|
||||
timeCreated: 1564996317
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
|
||||
/// <summary>
|
||||
/// Count down in seconds before calibration is executed. -1 will use default setting.
|
||||
/// </summary>
|
||||
[SerializeField] private int countdown_delay = -1; // countdown in seconds
|
||||
|
||||
/// <summary>
|
||||
/// Skip Smartsuit Pro calibration.
|
||||
/// </summary>
|
||||
[SerializeField] private bool skip_suit = false; // should we skip suit from a processing (calibration)
|
||||
|
||||
/// <summary>
|
||||
/// Skip Smartgloves calibration.
|
||||
/// </summary>
|
||||
[SerializeField] private bool skip_gloves = false; // should we skip gloves from a processing (calibration)
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8982b58c0932ebd4eb488b92e43b178f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9029de61abd14c54194939b0c16fc45f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6f300f4cf7bd8a44bb2bb5453228193e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 617231f45130160448cfef6a1670c3ff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8f171d10619acd4193a50877e3a6856
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5ec1713e30442a19275b49b0572d01b
|
||||
timeCreated: 1552057825
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8aa37890ebb422840868b7ece59e5570
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f38fe44d6bfb49dbabd93aea7da287ed
|
||||
timeCreated: 1565078235
|
||||
@ -0,0 +1,151 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace Rokoko.CommandAPI
|
||||
{
|
||||
/// <summary>
|
||||
/// This component provides access to Studio's Command API.
|
||||
/// </summary>
|
||||
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<string> SendRequest(string endpoint, string json)
|
||||
{
|
||||
var tcs = new TaskCompletionSource<string>();
|
||||
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<string> 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<ResponseMessage>(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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23be0de9580d445683a1b786461b328d
|
||||
timeCreated: 1565078385
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd7aa49a68dc87441bc55e7e5d2db0d8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61943ed9a14442b4aa24885f7e8a735e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b437a01c2f9fde489b7a18b6bac93d7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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<LiveFrame_v4> 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<LiveFrame_v4>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cb1306ef28465d49b3d63bc97885931
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
109
Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/UDPReceiver.cs
Normal file
109
Optitrack Rokoko Glove/Rokoko Unity Scripts/Core/UDPReceiver.cs
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9af01aaa3a63e7a4499c28b22dd1b20d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono.meta
Normal file
8
Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9fd28f925f7e38846913c7631653e97a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b9e800521674004693e0a38816ed885
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
397
Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Actor.cs
Normal file
397
Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Actor.cs
Normal file
@ -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<HumanBodyBones, Transform> animatorHumanBones = new Dictionary<HumanBodyBones, Transform>();
|
||||
private Dictionary<HumanBodyBones, Quaternion> offsets = new Dictionary<HumanBodyBones, Quaternion>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register Actor override in StudioManager.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Store Character's T Pose.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate Character's offset based on its T Pose and Newton's T Pose.
|
||||
/// </summary>
|
||||
protected void InitializeBoneOffsets()
|
||||
{
|
||||
// Calculate offsets based on Smartsuit T pose
|
||||
offsets = CalculateRotationOffsets();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cache the bone transforms from Animator.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Update Skeleton and Face data based on ActorFrame.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create Idle/Default Actor.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Transform from a given HumanBodyBones.
|
||||
/// </summary>
|
||||
private Transform GetBone(HumanBodyBones bone)
|
||||
{
|
||||
switch (boneMapping)
|
||||
{
|
||||
case BoneMappingEnum.Animator:
|
||||
return animatorHumanBones[bone];
|
||||
case BoneMappingEnum.Custom:
|
||||
return customBoneMapping.customBodyBones[(int)bone];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update Humanoid Skeleton based on BodyData.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update Human bone.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
/// <summary>
|
||||
/// Get the rotational difference between 2 humanoid T poses.
|
||||
/// </summary>
|
||||
private Dictionary<HumanBodyBones, Quaternion> CalculateRotationOffsets()
|
||||
{
|
||||
Dictionary<HumanBodyBones, Quaternion> offsets = new Dictionary<HumanBodyBones, Quaternion>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get Smartsuit T pose data
|
||||
/// </summary>
|
||||
private static Dictionary<HumanBodyBones, Quaternion> SmartsuitTPose = new Dictionary<HumanBodyBones, Quaternion>() {
|
||||
{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)}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b3d830ef66b485459e15992199096b7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7694c1eb6726a2d49a01558f46c8ef83
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07a3f22e5c105474e9725d5438558a99
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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<string, Transform> _skeletonJoints = new Dictionary<string, Transform>();
|
||||
|
||||
#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<Transform>();
|
||||
for (int i=0; i<transforms.Length; ++i)
|
||||
{
|
||||
if (transforms[i].GetComponentInChildren<SkinnedMeshRenderer>() != null)
|
||||
continue;
|
||||
|
||||
_skeletonJoints.Add(transforms[i].name, transforms[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register Actor override in StudioManager.
|
||||
/// </summary>
|
||||
private void Start()
|
||||
{
|
||||
//if (animator != null && !animator.isHuman) return;
|
||||
|
||||
if (!string.IsNullOrEmpty(profileName))
|
||||
StudioManager.AddCharacterOverride(this);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Update Skeleton and Face data based on ActorFrame.
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create Idle/Default Actor.
|
||||
/// </summary>
|
||||
public virtual void CreateIdle(string actorName)
|
||||
{
|
||||
this.profileName = actorName;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Logic
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update Humanoid Skeleton based on BodyData.
|
||||
/// </summary>
|
||||
protected void UpdateSkeleton(CharacterFrame frame)
|
||||
{
|
||||
for (int i=0; i<frame.joints.Length; ++i)
|
||||
{
|
||||
if (_skeletonJoints.TryGetValue(frame.joints[i].name, out Transform transform))
|
||||
{
|
||||
bool shouldUpdatePosition = transform.name == hipsName;
|
||||
|
||||
Quaternion worldRotation = frame.joints[i].rotation.ToQuaternion();
|
||||
Vector3 worldPosition = frame.joints[i].position.ToVector3();
|
||||
|
||||
UpdateBone(transform, worldPosition, worldRotation, shouldUpdatePosition, positionSpace, rotationSpace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update Human bone.
|
||||
/// </summary>
|
||||
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
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78192e8160352b7458fd2bc8ec3501ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
111
Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Face.cs
Normal file
111
Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/Inputs/Face.cs
Normal file
@ -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<string, int> blendshapeNamesToIndex = new Dictionary<string, int>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b6f1b7bab29ae9c4ab510759d0fbaa91
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a162b99fb9dfe542b5844d7b3d7c9fe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8274508f93c440949969e644e7b64e65
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 93bc1b702e5f6fa4c9d905909f4741e6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,46 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Rokoko
|
||||
{
|
||||
public class PrefabInstancer<T, P> where P : MonoBehaviour
|
||||
{
|
||||
private PrefabPool<P> pool;
|
||||
private Dictionary<T, P> objects;
|
||||
|
||||
public PrefabInstancer(P prefab, Transform container, int poolNumber = 0)
|
||||
{
|
||||
pool = new PrefabPool<P>(prefab, container, poolNumber);
|
||||
objects = new Dictionary<T, P>();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ded564a0c7575434ebb9c2faf540099d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,53 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Rokoko
|
||||
{
|
||||
public class PrefabPool<T> where T : MonoBehaviour
|
||||
{
|
||||
public int poolNumber = 3;
|
||||
public T prefab;
|
||||
public Transform container;
|
||||
|
||||
private Queue<T> pool = new Queue<T>();
|
||||
|
||||
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<T>(prefab);
|
||||
instance.transform.SetParent(container);
|
||||
instance.transform.position = Vector3.zero;
|
||||
instance.transform.rotation = Quaternion.identity;
|
||||
instance.name = prefab.name;
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47b981a3ed34dc64b9ab5ae02663b220
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
344
Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/RokokoHelper.cs
Normal file
344
Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/RokokoHelper.cs
Normal file
@ -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<Transform> children = new List<Transform>();
|
||||
foreach (Transform child in transform)
|
||||
children.Add(child);
|
||||
|
||||
foreach (Transform child in children)
|
||||
GameObject.Destroy(child.gameObject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if actor name is present in live data
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if character name is present in live data
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if prop name is present in live data
|
||||
/// </summary>
|
||||
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<string, int> GetAllBlendshapes(this Mesh mesh)
|
||||
{
|
||||
Dictionary<string, int> blendshapeNamesToIndex = new Dictionary<string, int>();
|
||||
for (int i = 0; i < mesh.blendShapeCount; i++)
|
||||
{
|
||||
blendshapeNamesToIndex.Add(mesh.GetBlendShapeName(i).ToLower(), i);
|
||||
}
|
||||
return blendshapeNamesToIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all missing blendshapes comparing to ARKit 52 blendshapes
|
||||
/// </summary>
|
||||
public static List<string> GetAllMissingBlendshapes(this Mesh mesh)
|
||||
{
|
||||
List<string> missingBlendshapes = new List<string>();
|
||||
List<string> blendshapeNames = new List<string>(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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5fb415e868ac58149ae137d5bd2d006a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 363721fc8fef7dc40a9243ec4954d2af
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,7 @@
|
||||
using Rokoko.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Create a simple serialized version of a Dictionary in order to able to persist in Editor play mode.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class BlendshapesDictionary : SerializableDictionary<BlendShapes, string> { }
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9681c86ca06b0de479fcf425d8560c3b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Create a simple serialized version of a Dictionary in order to able to persist in Editor play mode.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class HumanTPoseDictionary : SerializableDictionary<HumanBodyBones, Quaternion> { }
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a32ea12a419b4544a5ef5263af08a26
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,63 @@
|
||||
using Rokoko.Core;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Create a simple serialized version of a Dictionary in order to able to persist in Editor play mode.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public abstract class SerializableDictionary<TKey, TValue>
|
||||
{
|
||||
public List<TKey> keys = new List<TKey>();
|
||||
public List<TValue> values = new List<TValue>();
|
||||
|
||||
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<TKey, TValue> this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (keys.Count < index)
|
||||
throw new System.IndexOutOfRangeException();
|
||||
return new KeyValuePair<TKey, TValue>(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;
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40cfb04b525907c409046edb971ff8f1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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<Actor> actorOverrides = new List<Actor>();
|
||||
public List<Character> characterOverrides = new List<Character>();
|
||||
public List<Prop> propOverrides = new List<Prop>();
|
||||
|
||||
[Header("Extra Behiavours")]
|
||||
public bool autoGenerateInputsWhenNoOverridesFound = false;
|
||||
public bool showDefaultActorWhenNoData = false;
|
||||
|
||||
private StudioReceiver studioReceiver;
|
||||
private PrefabInstancer<string, Actor> actors;
|
||||
private PrefabInstancer<string, Character> characters;
|
||||
private PrefabInstancer<string, Prop> props;
|
||||
|
||||
private object actionsOnMainThread = new object();
|
||||
private List<LiveFrame_v4> packetsToProcess = new List<LiveFrame_v4>();
|
||||
|
||||
#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<string, Actor>(actorPrefab, this.transform);
|
||||
if (characterPrefab != null)
|
||||
characters = new PrefabInstancer<string, Character>(characterPrefab, this.transform);
|
||||
if (propPrefab != null)
|
||||
props = new PrefabInstancer<string, Prop>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main process logic of live data
|
||||
/// </summary>
|
||||
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<Actor> 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<Character> 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<Prop> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show default T pose character when not playback data
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all default Actors that doesn't exist in data
|
||||
/// </summary>
|
||||
private void ClearUnusedDefaultInputs(LiveFrame_v4 frame)
|
||||
{
|
||||
if (actors != null)
|
||||
{
|
||||
foreach (Actor actor in new List<Actor>((IEnumerable<Actor>)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<Character>((IEnumerable<Character>)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<Prop>((IEnumerable<Prop>)props.Values))
|
||||
{
|
||||
if (!frame.HasProp(prop.propName))
|
||||
props.Remove(prop.propName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<Actor> GetActorOverride(string profileName)
|
||||
{
|
||||
List<Actor> overrides = new List<Actor>();
|
||||
for (int i = 0; i < actorOverrides.Count; i++)
|
||||
{
|
||||
if (profileName.ToLower() == actorOverrides[i].profileName.ToLower())
|
||||
overrides.Add(actorOverrides[i]);
|
||||
}
|
||||
return overrides;
|
||||
}
|
||||
|
||||
public List<Character> GetCharacterOverride(string profileName)
|
||||
{
|
||||
List<Character> overrides = new List<Character>();
|
||||
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<Prop> GetPropOverride(string profileName)
|
||||
{
|
||||
List<Prop> overrides = new List<Prop>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de63a1dd9ec769e4080fd4da352fac11
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4296b318fcf88b240a52b1fbe7be783e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI.meta
Normal file
8
Optitrack Rokoko Glove/Rokoko Unity Scripts/Mono/UI.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e6ff7aa69142344db48e66a446c3b21
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ad284b4954ca4e4e87c771f198afd80
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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<string, InputHierarchyRow> rows;
|
||||
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
// Destroy children before PrefabInstancer creates the pool
|
||||
prefabContainer.DestroyChildren();
|
||||
|
||||
rows = new PrefabInstancer<string, InputHierarchyRow>(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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all rows that no longer exists in live data
|
||||
/// </summary>
|
||||
private void ClearUnusedInputRows(LiveFrame_v4 frame)
|
||||
{
|
||||
foreach (InputHierarchyRow row in new List<InputHierarchyRow>((IEnumerable<InputHierarchyRow>)rows.Values))
|
||||
{
|
||||
if (!frame.HasProfile(row.profileName) && !frame.HasProp(row.profileName) && !frame.HasCharacter(row.profileName))
|
||||
rows.Remove(row.profileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0726fb4ef719b9d46a2afcc8c682dc54
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins.meta
Normal file
8
Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bbb359d9a38c1004bbe87c4f14822488
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26a3fe8611eb3a44aa1ae9081871b540
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 82985f460b91d5d4ba2aa833b74fef35
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea56c1cd6eae28e479bc6dd92c6898f0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5261e97e6db9c3c49bf370285d19c389
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/arm64-v8a/liblz4.so
(Stored with Git LFS)
Normal file
BIN
Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/arm64-v8a/liblz4.so
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff5c59bfbd7cdc145932e75bd2c77813
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/armeabi-v7a/liblz4.so
(Stored with Git LFS)
Normal file
BIN
Optitrack Rokoko Glove/Rokoko Unity Scripts/Plugins/LZ4/Android/libs/armeabi-v7a/liblz4.so
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user