Add : 옵티트랙 장갑 시스템 구축에 필요한 파일

This commit is contained in:
KINDNICK 2025-08-21 02:30:35 +09:00
parent d5f66f1351
commit 2d81882c77
197 changed files with 14124 additions and 0 deletions

BIN
Optitrack Rokoko Glove/.vscode/settings.json (Stored with Git LFS) vendored Normal file

Binary file not shown.

View File

@ -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;
}

View File

@ -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;
};
}
}

Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:816498d40aca6a7060d48313bd1946b20553894715095670b14192d4639bdce5
3 size 549783

View 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;
}

View File

@ -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;
};
}
}

View 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;
};

View 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);
}
}

View 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;
};
}

View File

@ -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>

View File

@ -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>

View 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;
}

View 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;
};
}

View 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>

View 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

Binary file not shown.

View File

@ -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>

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d7417c9a13b2bd14ab1fea1d7ffa8e0a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9f43a84310e40244c970e95e1d934b1a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 721c3fac4cc3fbe4188ec6ec27cb4708
folderAsset: yes
timeCreated: 1564996317
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8982b58c0932ebd4eb488b92e43b178f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9029de61abd14c54194939b0c16fc45f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6f300f4cf7bd8a44bb2bb5453228193e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 617231f45130160448cfef6a1670c3ff
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d8f171d10619acd4193a50877e3a6856
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d5ec1713e30442a19275b49b0572d01b
timeCreated: 1552057825

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8aa37890ebb422840868b7ece59e5570
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f38fe44d6bfb49dbabd93aea7da287ed
timeCreated: 1565078235

View File

@ -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;
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 23be0de9580d445683a1b786461b328d
timeCreated: 1565078385

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dd7aa49a68dc87441bc55e7e5d2db0d8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 61943ed9a14442b4aa24885f7e8a735e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8b437a01c2f9fde489b7a18b6bac93d7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8cb1306ef28465d49b3d63bc97885931
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9af01aaa3a63e7a4499c28b22dd1b20d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9fd28f925f7e38846913c7631653e97a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0b9e800521674004693e0a38816ed885
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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)}
};
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0b3d830ef66b485459e15992199096b7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7694c1eb6726a2d49a01558f46c8ef83
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 07a3f22e5c105474e9725d5438558a99
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 78192e8160352b7458fd2bc8ec3501ce
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b6f1b7bab29ae9c4ab510759d0fbaa91
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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];
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4a162b99fb9dfe542b5844d7b3d7c9fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8274508f93c440949969e644e7b64e65
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 93bc1b702e5f6fa4c9d905909f4741e6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ded564a0c7575434ebb9c2faf540099d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 47b981a3ed34dc64b9ab5ae02663b220
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5fb415e868ac58149ae137d5bd2d006a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 363721fc8fef7dc40a9243ec4954d2af
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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> { }

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9681c86ca06b0de479fcf425d8560c3b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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> { }

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9a32ea12a419b4544a5ef5263af08a26
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 40cfb04b525907c409046edb971ff8f1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: de63a1dd9ec769e4080fd4da352fac11
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4296b318fcf88b240a52b1fbe7be783e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7e6ff7aa69142344db48e66a446c3b21
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1ad284b4954ca4e4e87c771f198afd80
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0726fb4ef719b9d46a2afcc8c682dc54
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bbb359d9a38c1004bbe87c4f14822488
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 26a3fe8611eb3a44aa1ae9081871b540
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 82985f460b91d5d4ba2aa833b74fef35
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ea56c1cd6eae28e479bc6dd92c6898f0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5261e97e6db9c3c49bf370285d19c389
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ff5c59bfbd7cdc145932e75bd2c77813
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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