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