Add : 로코코 옵티 관련 기능 추가 패치

This commit is contained in:
qsxft258@gmail.com 2025-08-22 03:52:18 +09:00
parent 45bf6f8649
commit 1087a30949
58 changed files with 28007 additions and 296 deletions

View File

@ -269,7 +269,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: LeftFinger2Metacarpal
- boneName: LeftFinger2Proximal
humanName: Left Index Proximal
limit:
min: {x: 0, y: 0, z: 0}
@ -277,7 +277,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: LeftFinger2Proximal
- boneName: LeftFinger2Medial
humanName: Left Index Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@ -285,7 +285,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: LeftFinger2Medial
- boneName: LeftFinger2Distal
humanName: Left Index Distal
limit:
min: {x: 0, y: 0, z: 0}
@ -293,7 +293,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: LeftFinger3Metacarpal
- boneName: LeftFinger3Proximal
humanName: Left Middle Proximal
limit:
min: {x: 0, y: 0, z: 0}
@ -301,7 +301,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: LeftFinger3Proximal
- boneName: LeftFinger3Medial
humanName: Left Middle Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@ -309,7 +309,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: LeftFinger3Medial
- boneName: LeftFinger3Distal
humanName: Left Middle Distal
limit:
min: {x: 0, y: 0, z: 0}
@ -317,7 +317,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: LeftFinger4Metacarpal
- boneName: LeftFinger4Proximal
humanName: Left Ring Proximal
limit:
min: {x: 0, y: 0, z: 0}
@ -325,7 +325,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: LeftFinger4Proximal
- boneName: LeftFinger4Medial
humanName: Left Ring Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@ -333,7 +333,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: LeftFinger4Medial
- boneName: LeftFinger4Distal
humanName: Left Ring Distal
limit:
min: {x: 0, y: 0, z: 0}
@ -341,7 +341,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: LeftFinger5Metacarpal
- boneName: LeftFinger5Proximal
humanName: Left Little Proximal
limit:
min: {x: 0, y: 0, z: 0}
@ -349,7 +349,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: LeftFinger5Proximal
- boneName: LeftFinger5Medial
humanName: Left Little Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@ -357,7 +357,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: LeftFinger5Medial
- boneName: LeftFinger5Distal
humanName: Left Little Distal
limit:
min: {x: 0, y: 0, z: 0}
@ -389,7 +389,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: RightFinger2Metacarpal
- boneName: RightFinger2Proximal
humanName: Right Index Proximal
limit:
min: {x: 0, y: 0, z: 0}
@ -397,7 +397,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: RightFinger2Proximal
- boneName: RightFinger2Medial
humanName: Right Index Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@ -405,7 +405,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: RightFinger2Medial
- boneName: RightFinger2Distal
humanName: Right Index Distal
limit:
min: {x: 0, y: 0, z: 0}
@ -413,7 +413,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: RightFinger3Metacarpal
- boneName: RightFinger3Proximal
humanName: Right Middle Proximal
limit:
min: {x: 0, y: 0, z: 0}
@ -421,7 +421,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: RightFinger3Proximal
- boneName: RightFinger3Medial
humanName: Right Middle Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@ -429,7 +429,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: RightFinger3Medial
- boneName: RightFinger3Distal
humanName: Right Middle Distal
limit:
min: {x: 0, y: 0, z: 0}
@ -437,7 +437,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: RightFinger4Metacarpal
- boneName: RightFinger4Proximal
humanName: Right Ring Proximal
limit:
min: {x: 0, y: 0, z: 0}
@ -445,7 +445,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: RightFinger4Proximal
- boneName: RightFinger4Medial
humanName: Right Ring Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@ -453,7 +453,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: RightFinger4Medial
- boneName: RightFinger4Distal
humanName: Right Ring Distal
limit:
min: {x: 0, y: 0, z: 0}
@ -461,7 +461,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: RightFinger5Metacarpal
- boneName: RightFinger5Proximal
humanName: Right Little Proximal
limit:
min: {x: 0, y: 0, z: 0}
@ -469,7 +469,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: RightFinger5Proximal
- boneName: RightFinger5Medial
humanName: Right Little Intermediate
limit:
min: {x: 0, y: 0, z: 0}
@ -477,7 +477,7 @@ ModelImporter:
value: {x: 0, y: 0, z: 0}
length: 0
modified: 0
- boneName: RightFinger5Medial
- boneName: RightFinger5Distal
humanName: Right Little Distal
limit:
min: {x: 0, y: 0, z: 0}
@ -582,7 +582,7 @@ ModelImporter:
- name: LeftFinger1Metacarpal
parentName: LeftHand
position: {x: -0.027847009, y: 0.020784134, z: -0.001056083}
rotation: {x: 0.09904506, y: -0.36964288, z: 0.23911677, w: 0.89239985}
rotation: {x: 0.04233702, y: -0.5728508, z: 0.3376046, w: 0.74570274}
scale: {x: 1.0000001, y: 0.99999994, z: 1.0000001}
- name: LeftFinger1Proximal
parentName: LeftFinger1Metacarpal
@ -707,7 +707,7 @@ ModelImporter:
- name: RightFinger1Metacarpal
parentName: RightHand
position: {x: 0.027845163, y: 0.020768387, z: -0.0010154283}
rotation: {x: 0.09904415, y: 0.36964288, z: -0.23911713, w: 0.89239985}
rotation: {x: 0.04219337, y: 0.5729466, z: -0.33736277, w: 0.7457468}
scale: {x: 1, y: 0.99999994, z: 0.9999998}
- name: RightFinger1Proximal
parentName: RightFinger1Metacarpal

View File

@ -68,14 +68,14 @@ copy "$(OutDir)ExampleGloveData.csv" "C:/Program Files/OptiTrack/Motive/device
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;ANALOGSYSTEM_IMPORTS;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<GenerateXMLDocumentationFiles>false</GenerateXMLDocumentationFiles>
<AdditionalIncludeDirectories>..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ShowProgress>NotSet</ShowProgress>
<AdditionalDependencies>PeripheralImport.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalLibraryDirectories>..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<ProjectReference>
<LinkLibraryDependencies>false</LinkLibraryDependencies>
@ -88,7 +88,7 @@ copy "$(OutDir)ExampleGloveData.csv" "C:/Program Files/OptiTrack/Motive/device
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;ANALOGSYSTEM_IMPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -96,7 +96,7 @@ copy "$(OutDir)ExampleGloveData.csv" "C:/Program Files/OptiTrack/Motive/device
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>PeripheralImport.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalLibraryDirectories>..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemGroup>

View File

@ -134,9 +134,13 @@ namespace OptiTrackPluginDevices
* 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;
std::vector<uint64_t> mDetectedDevices;
std::unordered_map<uint64_t, sGloveDeviceData> mLatestGloveData;
std::unordered_map<uint64_t, sGloveDeviceBaseInfo> mLatestDeviceInfo;
// T-포즈 캘리브레이션 데이터 (로컬 로테이션 계산을 위해 필요)
std::unordered_map<uint64_t, std::vector<sFingerNode>> mTPoseReferences; // 장치별 T-포즈 기준값
std::unordered_map<uint64_t, bool> mTPoseCalibrated; // 장치별 캘리브레이션 상태
/**
* Rokoko integration members
@ -173,14 +177,9 @@ namespace OptiTrackPluginDevices
*/
void UpdateDeviceInfo(uint64_t deviceId);
/**
* Detect and create Rokoko devices dynamically
*/
// Dynamic Device Detection and Management
void DetectAndCreateRokokoDevices();
/**
* Process data for multiple devices (left/right hand)
*/
void DetectActorsFromRokokoData(const RokokoData::LiveFrame_v4& rokokoFrame);
void ProcessMultipleDeviceData(const RokokoData::LiveFrame_v4& rokokoFrame);
/**
@ -202,6 +201,128 @@ namespace OptiTrackPluginDevices
* Map individual joint data
*/
void MapJoint(const RokokoData::ActorJointFrame& rokokoJoint, sFingerNode& optiTrackNode);
// 로컬 로테이션 변환 함수들
void ConvertToLocalRotations(std::vector<sFingerNode>& nodes, eGloveHandSide handSide);
void ApplyLocalRotation(sFingerNode& childNode, const sFingerNode& parentNode);
// 쿼터니언 연산 헬퍼 함수들
void MultiplyQuaternions(const sFingerNode& q1, const sFingerNode& q2, sFingerNode& result);
void InverseQuaternion(const sFingerNode& q, sFingerNode& result);
void NormalizeQuaternion(sFingerNode& q);
// T-포즈 캘리브레이션 시스템
void CalibrateTPose(uint64_t deviceId, const std::vector<sFingerNode>& tPoseData);
void ApplyTPoseOffset(std::vector<sFingerNode>& nodes, uint64_t deviceId);
bool IsTPoseCalibrated(uint64_t deviceId) const;
void ResetTPoseCalibration(uint64_t deviceId);
// 손목 기준 로컬 변환 시스템 (기존)
void ConvertToWristLocalRotations(std::vector<sFingerNode>& nodes, const RokokoData::Body& body, eGloveHandSide handSide);
bool GetWristRotation(const RokokoData::Body& body, eGloveHandSide handSide, sFingerNode& wristRotation);
// 진짜 로컬 로테이션 변환 시스템 (계층적)
void ConvertToTrueLocalRotations(std::vector<sFingerNode>& nodes, const RokokoData::Body& body, eGloveHandSide handSide);
void CalculateFingerLocalRotations(const std::vector<RokokoData::ActorJointFrame*>& fingerJoints, std::vector<sFingerNode>& outputNodes, int startIndex, const RokokoData::Body& body, eGloveHandSide handSide);
bool GetFingerJointRotations(const RokokoData::Body& body, eGloveHandSide handSide, std::vector<std::vector<RokokoData::ActorJointFrame*>>& allFingers);
// 좌표계 변환 및 디버깅
void ConvertRokokoToOptiTrackCoordinates(sFingerNode& node, eGloveHandSide handSide);
void LogRotationData(const std::string& label, const sFingerNode& rotation);
void DebugCoordinateSystem(const RokokoData::Body& body, eGloveHandSide handSide);
// 절대 로컬 로테이션 시스템 (손목에 완전히 독립적)
void ConvertToAbsoluteLocalRotations(std::vector<sFingerNode>& nodes, const RokokoData::Body& body, eGloveHandSide handSide);
void CalculateAbsoluteLocalForFinger(const std::vector<RokokoData::ActorJointFrame*>& fingerJoints, std::vector<sFingerNode>& outputNodes, int startIndex, eGloveHandSide handSide);
sFingerNode CalculateRelativeRotation(const sFingerNode& parent, const sFingerNode& child);
// Unity 방식 Rokoko 데이터 처리 (원시 데이터 + T-포즈 오프셋)
void ConvertToUnityRokokoMethod(std::vector<sFingerNode>& nodes, const RokokoData::Body& body, eGloveHandSide handSide);
sFingerNode GetUnityTPoseOffset(int fingerIndex, int jointIndex, eGloveHandSide handSide);
void ApplyUnityRokokoRotation(sFingerNode& node, const RokokoData::ActorJointFrame* jointFrame, int fingerIndex, int jointIndex, eGloveHandSide handSide);
// 가상 Unity 아바타 시뮬레이션 시스템
void ConvertToVirtualUnityAvatar(std::vector<sFingerNode>& nodes, const RokokoData::Body& body, eGloveHandSide handSide);
// Unity Transform 시뮬레이션 구조
struct VirtualTransform {
sFingerNode localRotation; // Unity localRotation
sFingerNode worldRotation; // Unity rotation (world)
VirtualTransform* parent; // Unity parent transform
std::vector<VirtualTransform*> children; // Unity children
std::string name; // 디버깅용
};
struct VirtualUnityAvatar {
// 전체 스켈레톤 구조 (Unity Humanoid와 동일한 계층)
VirtualTransform root;
// 몸통 구조
VirtualTransform* hips; // 허리 (Root)
VirtualTransform* spine; // 스파인
VirtualTransform* chest; // 가슴
// 어깨 구조
VirtualTransform* leftShoulder; // 왼쪽 어깨
VirtualTransform* rightShoulder; // 오른쪽 어깨
// 팔 구조
VirtualTransform* leftUpperArm; // 왼쪽 상완
VirtualTransform* leftLowerArm; // 왼쪽 하완
VirtualTransform* leftHand; // 왼쪽 손
VirtualTransform* rightUpperArm; // 오른쪽 상완
VirtualTransform* rightLowerArm; // 오른쪽 하완
VirtualTransform* rightHand; // 오른쪽 손
// 손가락 구조 (Unity 계층과 동일)
VirtualTransform* leftThumb[3]; // Proximal, Intermediate, Distal
VirtualTransform* leftIndex[3];
VirtualTransform* leftMiddle[3];
VirtualTransform* leftRing[3];
VirtualTransform* leftLittle[3];
VirtualTransform* rightThumb[3];
VirtualTransform* rightIndex[3];
VirtualTransform* rightMiddle[3];
VirtualTransform* rightRing[3];
VirtualTransform* rightLittle[3];
};
// 가상 Unity 아바타 헬퍼 함수들
void InitializeVirtualUnityAvatar(VirtualUnityAvatar& avatar);
void ApplyRokokoDataToVirtualAvatar(VirtualUnityAvatar& avatar, const RokokoData::Body& body);
void CalculateLocalRotationsFromWorldRotations(VirtualUnityAvatar& avatar);
void ExtractLocalRotationsFromVirtualAvatar(const VirtualUnityAvatar& avatar, std::vector<sFingerNode>& nodes, eGloveHandSide handSide);
void UpdateWorldRotationFromParent(VirtualTransform* transform);
// 가상 손 노드 구조
struct VirtualJoint {
sFingerNode worldRotation;
sFingerNode localRotation;
VirtualJoint* parent;
};
struct VirtualFinger {
VirtualJoint mp; // Metacarpophalangeal
VirtualJoint pip; // Proximal Interphalangeal
VirtualJoint dip; // Distal Interphalangeal
};
struct VirtualHand {
sFingerNode wristRotation;
VirtualFinger thumb;
VirtualFinger index;
VirtualFinger middle;
VirtualFinger ring;
VirtualFinger little;
};
// 가상 손 구조 헬퍼 함수들
void BuildVirtualHandFromRokoko(VirtualHand& virtualHand, const RokokoData::Body& body, eGloveHandSide handSide);
void CalculateHierarchicalLocalRotations(VirtualHand& virtualHand);
void ExtractLocalRotationsToNodes(const VirtualHand& virtualHand, std::vector<sFingerNode>& nodes);
sFingerNode CalculateLocalRotationFromParent(const VirtualJoint& parent, const VirtualJoint& child);
protected:

View File

@ -74,6 +74,7 @@ struct sGloveDeviceBaseInfo
int battery = 0 ;
int signalStrength= 0;
eGloveHandSide handSide = eGloveHandSide::Unknown;
std::string actorName = ""; // Actor 이름 (다중 장비 구분용)
};

View File

@ -3,9 +3,48 @@
//======================================================================================================
#include "LZ4Wrapper.h"
#include "lz4.h"
#include <cstring>
#include <stdexcept>
#include <windows.h>
// Unity LZ4 DLL 동적 로딩
static HMODULE lz4Module = nullptr;
static decltype(&Unity_LZ4_uncompressSize) pUnity_LZ4_uncompressSize = nullptr;
static decltype(&Unity_LZ4_decompress) pUnity_LZ4_decompress = nullptr;
static bool LoadUnityLZ4()
{
if (lz4Module == nullptr) {
// 다양한 경로에서 Unity LZ4 DLL 로드 시도
const wchar_t* dllPaths[] = {
L"lz4.dll", // 현재 디렉토리
L".\\lz4.dll", // 명시적 현재 디렉토리
L"x64\\Debug\\lz4.dll", // 디버그 폴더
L"C:\\Users\\user\\Documents\\Streamingle_URP\\Assets\\External\\Rokoko\\Scripts\\Plugins\\LZ4\\x86_64\\lz4.dll" // Unity 절대 경로
};
for (const auto& path : dllPaths) {
lz4Module = LoadLibrary(path);
if (lz4Module != nullptr) {
// DLL 로드 성공 - 함수 포인터 획득
pUnity_LZ4_uncompressSize = (decltype(pUnity_LZ4_uncompressSize))GetProcAddress(lz4Module, "Unity_LZ4_uncompressSize");
pUnity_LZ4_decompress = (decltype(pUnity_LZ4_decompress))GetProcAddress(lz4Module, "Unity_LZ4_decompress");
if (pUnity_LZ4_uncompressSize != nullptr && pUnity_LZ4_decompress != nullptr) {
// 성공
break;
} else {
// 함수를 찾지 못함 - DLL 언로드하고 다음 시도
FreeLibrary(lz4Module);
lz4Module = nullptr;
pUnity_LZ4_uncompressSize = nullptr;
pUnity_LZ4_decompress = nullptr;
}
}
}
}
return (lz4Module != nullptr && pUnity_LZ4_uncompressSize != nullptr && pUnity_LZ4_decompress != nullptr);
}
namespace RokokoIntegration
{
@ -17,33 +56,38 @@ namespace RokokoIntegration
return {};
}
// LZ4 데이터 유효성 검사
if (!IsValidLZ4Data(compressedData, compressedSize)) {
// Unity LZ4 DLL 로드
if (!LoadUnityLZ4()) {
return {};
}
// 압축 해제된 크기 추정
int decompressedSize = GetDecompressedSize(compressedData, compressedSize);
if (decompressedSize <= 0) {
// Unity 방식 정확히 구현
// 1. Unity_LZ4_uncompressSize로 압축 해제된 크기 구하기
int uncompressedSize = pUnity_LZ4_uncompressSize(
reinterpret_cast<const char*>(compressedData),
compressedSize
);
if (uncompressedSize <= 0) {
return {};
}
// 크기 유효성 검사
if (!ValidateDecompressedSize(compressedSize, decompressedSize)) {
if (uncompressedSize > MAX_DECOMPRESSED_SIZE) {
return {};
}
// 압축 해제 실행
std::vector<uint8_t> decompressed(decompressedSize);
int actualSize = LZ4_decompress_safe(
// 2. Unity_LZ4_decompress로 압축 해제
std::vector<uint8_t> decompressed(uncompressedSize);
int result = pUnity_LZ4_decompress(
reinterpret_cast<const char*>(compressedData),
reinterpret_cast<char*>(decompressed.data()),
compressedSize,
decompressedSize
reinterpret_cast<char*>(decompressed.data()),
uncompressedSize
);
// 압축 해제 결과 검증
if (actualSize != decompressedSize) {
// Unity에서는 result != 0이면 실패
if (result != 0) {
return {};
}
@ -61,20 +105,37 @@ namespace RokokoIntegration
return false;
}
// LZ4 매직 넘버 확인 (간단한 검증)
// 실제로는 더 정교한 검증이 필요할 수 있음
// Rokoko Studio의 LZ4 데이터는 일반적으로 JSON이 아니므로
// 첫 번째 바이트가 '{'가 아닌 경우 LZ4로 간주
if (size > 0 && data[0] == '{') {
return false; // JSON 데이터는 LZ4가 아님
}
// 기본적인 LZ4 매직 넘버 확인
// LZ4 프레임 헤더의 일부 패턴 확인
if (size >= 4) {
// LZ4 프레임 헤더의 매직 넘버 (0x184D2204)
if (data[0] == 0x04 && data[1] == 0x22 && data[2] == 0x4D && data[3] == 0x18) {
return true;
}
}
// 매직 넘버가 없어도 압축된 데이터로 간주 (Rokoko Studio 특성상)
return true;
}
int LZ4Wrapper::GetDecompressedSize(const uint8_t* compressedData, int compressedSize)
{
try {
// LZ4에서 압축 해제된 크기 추정
int decompressedSize = LZ4_decompress_safe(
// Unity LZ4 DLL 로드
if (!LoadUnityLZ4()) {
return -1;
}
// Unity 방식으로 압축 해제된 크기 구하기
int decompressedSize = pUnity_LZ4_uncompressSize(
reinterpret_cast<const char*>(compressedData),
nullptr,
compressedSize,
0
compressedSize
);
return decompressedSize;

View File

@ -10,6 +10,12 @@
#include <vector>
#include <cstdint>
// Unity LZ4 함수들 선언
extern "C" {
int Unity_LZ4_uncompressSize(const char* srcBuffer, int srcSize);
int Unity_LZ4_decompress(const char* src, int srcSize, char* dst, int dstCapacity);
}
namespace RokokoIntegration
{
class LZ4Wrapper

View File

@ -37,9 +37,39 @@ namespace RokokoData
Vector4Frame rotation;
};
// Body structure containing finger joints
// Body structure containing full skeleton (Unity BodyFrame와 동일)
struct Body
{
// 전신 스켈레톤 데이터 (Unity BodyFrame과 동일)
std::shared_ptr<ActorJointFrame> hip;
std::shared_ptr<ActorJointFrame> spine;
std::shared_ptr<ActorJointFrame> chest;
std::shared_ptr<ActorJointFrame> neck;
std::shared_ptr<ActorJointFrame> head;
std::shared_ptr<ActorJointFrame> leftShoulder;
std::shared_ptr<ActorJointFrame> leftUpperArm;
std::shared_ptr<ActorJointFrame> leftLowerArm;
std::shared_ptr<ActorJointFrame> leftHand;
std::shared_ptr<ActorJointFrame> rightShoulder;
std::shared_ptr<ActorJointFrame> rightUpperArm;
std::shared_ptr<ActorJointFrame> rightLowerArm;
std::shared_ptr<ActorJointFrame> rightHand;
// 다리 데이터 (필요시)
std::shared_ptr<ActorJointFrame> leftUpLeg;
std::shared_ptr<ActorJointFrame> leftLeg;
std::shared_ptr<ActorJointFrame> leftFoot;
std::shared_ptr<ActorJointFrame> leftToe;
std::shared_ptr<ActorJointFrame> leftToeEnd;
std::shared_ptr<ActorJointFrame> rightUpLeg;
std::shared_ptr<ActorJointFrame> rightLeg;
std::shared_ptr<ActorJointFrame> rightFoot;
std::shared_ptr<ActorJointFrame> rightToe;
std::shared_ptr<ActorJointFrame> rightToeEnd;
// Left hand finger joints
std::shared_ptr<ActorJointFrame> leftThumbProximal;
std::shared_ptr<ActorJointFrame> leftThumbMedial;

View File

@ -14,101 +14,173 @@ namespace RokokoIntegration
bool RokokoDataParser::ParseLiveFrame(const std::string& jsonString, RokokoData::LiveFrame_v4& frame)
{
try {
// 간단한 JSON 파싱 구현 (실제로는 nlohmann/json 라이브러리 사용 권장)
// 현재는 기본 구조만 생성
// nlohmann/json을 사용하여 JSON 파싱
nlohmann::json j = nlohmann::json::parse(jsonString);
// 기본 프레임 데이터 초기화
frame.scene.actors.clear();
frame.scene.actors.push_back(RokokoData::ActorData());
// Unity JsonLiveSerializerV3.cs와 동일한 구조로 파싱
// 기본 손가락 데이터 구조 생성
auto& actor = frame.scene.actors[0];
// 왼손 데이터 초기화 (모든 손가락)
// 엄지
actor.body.leftThumbMedial = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.leftThumbDistal = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.leftThumbTip = std::make_shared<RokokoData::ActorJointFrame>();
// 검지
actor.body.leftIndexMedial = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.leftIndexDistal = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.leftIndexTip = std::make_shared<RokokoData::ActorJointFrame>();
// 중지
actor.body.leftMiddleMedial = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.leftMiddleDistal = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.leftMiddleTip = std::make_shared<RokokoData::ActorJointFrame>();
// 약지
actor.body.leftRingMedial = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.leftRingDistal = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.leftRingTip = std::make_shared<RokokoData::ActorJointFrame>();
// 새끼
actor.body.leftLittleMedial = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.leftLittleDistal = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.leftLittleTip = std::make_shared<RokokoData::ActorJointFrame>();
// 오른손 데이터 초기화 (모든 손가락)
// 엄지
actor.body.rightThumbMedial = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.rightThumbDistal = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.rightThumbTip = std::make_shared<RokokoData::ActorJointFrame>();
// 검지
actor.body.rightIndexMedial = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.rightIndexDistal = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.rightIndexTip = std::make_shared<RokokoData::ActorJointFrame>();
// 중지
actor.body.rightMiddleMedial = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.rightMiddleDistal = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.rightMiddleTip = std::make_shared<RokokoData::ActorJointFrame>();
// 약지
actor.body.rightRingMedial = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.rightRingDistal = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.rightRingTip = std::make_shared<RokokoData::ActorJointFrame>();
// 새끼
actor.body.rightLittleMedial = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.rightLittleDistal = std::make_shared<RokokoData::ActorJointFrame>();
actor.body.rightLittleTip = std::make_shared<RokokoData::ActorJointFrame>();
// 기본 쿼터니언 값 설정 (정규화된 단위 쿼터니언)
RokokoData::Vector4Frame defaultRotation = {1.0f, 0.0f, 0.0f, 0.0f};
// 왼손 기본값 설정
actor.body.leftThumbMedial->rotation = defaultRotation;
actor.body.leftThumbDistal->rotation = defaultRotation;
actor.body.leftThumbTip->rotation = defaultRotation;
actor.body.leftIndexMedial->rotation = defaultRotation;
actor.body.leftIndexDistal->rotation = defaultRotation;
actor.body.leftIndexTip->rotation = defaultRotation;
actor.body.leftMiddleMedial->rotation = defaultRotation;
actor.body.leftMiddleDistal->rotation = defaultRotation;
actor.body.leftMiddleTip->rotation = defaultRotation;
actor.body.leftRingMedial->rotation = defaultRotation;
actor.body.leftRingDistal->rotation = defaultRotation;
actor.body.leftRingTip->rotation = defaultRotation;
actor.body.leftLittleMedial->rotation = defaultRotation;
actor.body.leftLittleDistal->rotation = defaultRotation;
actor.body.leftLittleTip->rotation = defaultRotation;
// 오른손 기본값 설정
actor.body.rightThumbMedial->rotation = defaultRotation;
actor.body.rightThumbDistal->rotation = defaultRotation;
actor.body.rightThumbTip->rotation = defaultRotation;
actor.body.rightIndexMedial->rotation = defaultRotation;
actor.body.rightIndexDistal->rotation = defaultRotation;
actor.body.rightIndexTip->rotation = defaultRotation;
actor.body.rightMiddleMedial->rotation = defaultRotation;
actor.body.rightMiddleDistal->rotation = defaultRotation;
actor.body.rightMiddleTip->rotation = defaultRotation;
actor.body.rightRingMedial->rotation = defaultRotation;
actor.body.rightRingDistal->rotation = defaultRotation;
actor.body.rightRingTip->rotation = defaultRotation;
actor.body.rightLittleMedial->rotation = defaultRotation;
actor.body.rightLittleDistal->rotation = defaultRotation;
actor.body.rightLittleTip->rotation = defaultRotation;
// Scene 데이터 파싱
if (j.contains("scene")) {
auto& scene = j["scene"];
// 타임스탬프
if (scene.contains("timestamp")) {
frame.timestamp = scene["timestamp"];
}
// Actors 파싱
if (scene.contains("actors") && scene["actors"].is_array()) {
auto& actors = scene["actors"];
for (const auto& actorJson : actors) {
RokokoData::ActorData actor;
// Actor 기본 정보
if (actorJson.contains("name")) {
actor.name = actorJson["name"];
}
// Body 데이터 파싱
if (actorJson.contains("body")) {
ParseBodyFrame(actorJson["body"], actor.body);
}
frame.scene.actors.push_back(actor);
}
}
}
return true;
} catch (...) {
} catch (const nlohmann::json::exception& e) {
// JSON 파싱 에러 처리
std::cerr << "JSON parsing error: " << e.what() << std::endl;
return false;
} catch (...) {
// 기타 예외 처리
std::cerr << "Unknown error during JSON parsing" << std::endl;
return false;
}
}
void RokokoDataParser::ParseBodyFrame(const nlohmann::json& bodyJson, RokokoData::Body& body)
{
// Unity JsonLiveSerializerV3.cs의 BodyFrame 구조와 동일하게 파싱
// === 전신 스켈레톤 데이터 파싱 (Unity BodyFrame과 동일) ===
// 몸통 파싱
ParseFingerJoint(bodyJson, "hip", body.hip);
ParseFingerJoint(bodyJson, "spine", body.spine);
ParseFingerJoint(bodyJson, "chest", body.chest);
ParseFingerJoint(bodyJson, "neck", body.neck);
ParseFingerJoint(bodyJson, "head", body.head);
// 왼쪽 팔 파싱
ParseFingerJoint(bodyJson, "leftShoulder", body.leftShoulder);
ParseFingerJoint(bodyJson, "leftUpperArm", body.leftUpperArm);
ParseFingerJoint(bodyJson, "leftLowerArm", body.leftLowerArm);
ParseFingerJoint(bodyJson, "leftHand", body.leftHand);
// 오른쪽 팔 파싱
ParseFingerJoint(bodyJson, "rightShoulder", body.rightShoulder);
ParseFingerJoint(bodyJson, "rightUpperArm", body.rightUpperArm);
ParseFingerJoint(bodyJson, "rightLowerArm", body.rightLowerArm);
ParseFingerJoint(bodyJson, "rightHand", body.rightHand);
// 다리 파싱 (필요시)
ParseFingerJoint(bodyJson, "leftUpLeg", body.leftUpLeg);
ParseFingerJoint(bodyJson, "leftLeg", body.leftLeg);
ParseFingerJoint(bodyJson, "leftFoot", body.leftFoot);
ParseFingerJoint(bodyJson, "leftToe", body.leftToe);
ParseFingerJoint(bodyJson, "leftToeEnd", body.leftToeEnd);
ParseFingerJoint(bodyJson, "rightUpLeg", body.rightUpLeg);
ParseFingerJoint(bodyJson, "rightLeg", body.rightLeg);
ParseFingerJoint(bodyJson, "rightFoot", body.rightFoot);
ParseFingerJoint(bodyJson, "rightToe", body.rightToe);
ParseFingerJoint(bodyJson, "rightToeEnd", body.rightToeEnd);
// === 손가락 데이터 파싱 ===
// 왼손 손가락 데이터 파싱
ParseFingerJoint(bodyJson, "leftThumbProximal", body.leftThumbProximal);
ParseFingerJoint(bodyJson, "leftThumbMedial", body.leftThumbMedial);
ParseFingerJoint(bodyJson, "leftThumbDistal", body.leftThumbDistal);
ParseFingerJoint(bodyJson, "leftThumbTip", body.leftThumbTip);
ParseFingerJoint(bodyJson, "leftIndexProximal", body.leftIndexProximal);
ParseFingerJoint(bodyJson, "leftIndexMedial", body.leftIndexMedial);
ParseFingerJoint(bodyJson, "leftIndexDistal", body.leftIndexDistal);
ParseFingerJoint(bodyJson, "leftIndexTip", body.leftIndexTip);
ParseFingerJoint(bodyJson, "leftMiddleProximal", body.leftMiddleProximal);
ParseFingerJoint(bodyJson, "leftMiddleMedial", body.leftMiddleMedial);
ParseFingerJoint(bodyJson, "leftMiddleDistal", body.leftMiddleDistal);
ParseFingerJoint(bodyJson, "leftMiddleTip", body.leftMiddleTip);
ParseFingerJoint(bodyJson, "leftRingProximal", body.leftRingProximal);
ParseFingerJoint(bodyJson, "leftRingMedial", body.leftRingMedial);
ParseFingerJoint(bodyJson, "leftRingDistal", body.leftRingDistal);
ParseFingerJoint(bodyJson, "leftRingTip", body.leftRingTip);
ParseFingerJoint(bodyJson, "leftLittleProximal", body.leftLittleProximal);
ParseFingerJoint(bodyJson, "leftLittleMedial", body.leftLittleMedial);
ParseFingerJoint(bodyJson, "leftLittleDistal", body.leftLittleDistal);
ParseFingerJoint(bodyJson, "leftLittleTip", body.leftLittleTip);
// 오른손 손가락 데이터 파싱
ParseFingerJoint(bodyJson, "rightThumbProximal", body.rightThumbProximal);
ParseFingerJoint(bodyJson, "rightThumbMedial", body.rightThumbMedial);
ParseFingerJoint(bodyJson, "rightThumbDistal", body.rightThumbDistal);
ParseFingerJoint(bodyJson, "rightThumbTip", body.rightThumbTip);
ParseFingerJoint(bodyJson, "rightIndexProximal", body.rightIndexProximal);
ParseFingerJoint(bodyJson, "rightIndexMedial", body.rightIndexMedial);
ParseFingerJoint(bodyJson, "rightIndexDistal", body.rightIndexDistal);
ParseFingerJoint(bodyJson, "rightIndexTip", body.rightIndexTip);
ParseFingerJoint(bodyJson, "rightMiddleProximal", body.rightMiddleProximal);
ParseFingerJoint(bodyJson, "rightMiddleMedial", body.rightMiddleMedial);
ParseFingerJoint(bodyJson, "rightMiddleDistal", body.rightMiddleDistal);
ParseFingerJoint(bodyJson, "rightMiddleTip", body.rightMiddleTip);
ParseFingerJoint(bodyJson, "rightRingProximal", body.rightRingProximal);
ParseFingerJoint(bodyJson, "rightRingMedial", body.rightRingMedial);
ParseFingerJoint(bodyJson, "rightRingDistal", body.rightRingDistal);
ParseFingerJoint(bodyJson, "rightRingTip", body.rightRingTip);
ParseFingerJoint(bodyJson, "rightLittleProximal", body.rightLittleProximal);
ParseFingerJoint(bodyJson, "rightLittleMedial", body.rightLittleMedial);
ParseFingerJoint(bodyJson, "rightLittleDistal", body.rightLittleDistal);
ParseFingerJoint(bodyJson, "rightLittleTip", body.rightLittleTip);
}
void RokokoDataParser::ParseFingerJoint(const nlohmann::json& bodyJson, const std::string& jointName, std::shared_ptr<RokokoData::ActorJointFrame>& joint)
{
if (bodyJson.contains(jointName)) {
auto& jointJson = bodyJson[jointName];
joint = std::make_shared<RokokoData::ActorJointFrame>();
// Position 파싱
if (jointJson.contains("position")) {
auto& pos = jointJson["position"];
joint->position.x = pos.value("x", 0.0f);
joint->position.y = pos.value("y", 0.0f);
joint->position.z = pos.value("z", 0.0f);
}
// Rotation 파싱
if (jointJson.contains("rotation")) {
auto& rot = jointJson["rotation"];
joint->rotation.x = rot.value("x", 0.0f);
joint->rotation.y = rot.value("y", 0.0f);
joint->rotation.z = rot.value("z", 0.0f);
joint->rotation.w = rot.value("w", 1.0f);
// 쿼터니언 정규화
NormalizeQuaternion(joint->rotation.x, joint->rotation.y, joint->rotation.z, joint->rotation.w);
}
}
}
@ -122,7 +194,7 @@ namespace RokokoIntegration
const auto& actor = frame.scene.actors[0];
// 손가락 데이터 검증
// 손가락 데이터 검증 (왼손 엄지손가락만 체크)
if (!actor.body.leftThumbMedial || !actor.body.leftThumbDistal || !actor.body.leftThumbTip) {
return false;
}
@ -148,19 +220,57 @@ namespace RokokoIntegration
const auto& actor = frame.scene.actors[0];
// 왼손 데이터 추출
if (actor.body.leftThumbMedial) {
leftHandFingers.push_back(*actor.body.leftThumbMedial);
}
if (actor.body.leftThumbDistal) {
leftHandFingers.push_back(*actor.body.leftThumbDistal);
}
if (actor.body.leftThumbTip) {
leftHandFingers.push_back(*actor.body.leftThumbTip);
}
// 왼손 데이터 추출 (20개 관절)
if (actor.body.leftThumbProximal) leftHandFingers.push_back(*actor.body.leftThumbProximal);
if (actor.body.leftThumbMedial) leftHandFingers.push_back(*actor.body.leftThumbMedial);
if (actor.body.leftThumbDistal) leftHandFingers.push_back(*actor.body.leftThumbDistal);
if (actor.body.leftThumbTip) leftHandFingers.push_back(*actor.body.leftThumbTip);
// 오른손 데이터는 현재 구현되지 않음 (기본값 사용)
// 실제 구현에서는 actor.body.rightThumb* 데이터 사용
if (actor.body.leftIndexProximal) leftHandFingers.push_back(*actor.body.leftIndexProximal);
if (actor.body.leftIndexMedial) leftHandFingers.push_back(*actor.body.leftIndexMedial);
if (actor.body.leftIndexDistal) leftHandFingers.push_back(*actor.body.leftIndexDistal);
if (actor.body.leftIndexTip) leftHandFingers.push_back(*actor.body.leftIndexTip);
if (actor.body.leftMiddleProximal) leftHandFingers.push_back(*actor.body.leftMiddleProximal);
if (actor.body.leftMiddleMedial) leftHandFingers.push_back(*actor.body.leftMiddleMedial);
if (actor.body.leftMiddleDistal) leftHandFingers.push_back(*actor.body.leftMiddleDistal);
if (actor.body.leftMiddleTip) leftHandFingers.push_back(*actor.body.leftMiddleTip);
if (actor.body.leftRingProximal) leftHandFingers.push_back(*actor.body.leftRingProximal);
if (actor.body.leftRingMedial) leftHandFingers.push_back(*actor.body.leftRingMedial);
if (actor.body.leftRingDistal) leftHandFingers.push_back(*actor.body.leftRingDistal);
if (actor.body.leftRingTip) leftHandFingers.push_back(*actor.body.leftRingTip);
if (actor.body.leftLittleProximal) leftHandFingers.push_back(*actor.body.leftLittleProximal);
if (actor.body.leftLittleMedial) leftHandFingers.push_back(*actor.body.leftLittleMedial);
if (actor.body.leftLittleDistal) leftHandFingers.push_back(*actor.body.leftLittleDistal);
if (actor.body.leftLittleTip) leftHandFingers.push_back(*actor.body.leftLittleTip);
// 오른손 데이터 추출 (20개 관절)
if (actor.body.rightThumbProximal) rightHandFingers.push_back(*actor.body.rightThumbProximal);
if (actor.body.rightThumbMedial) rightHandFingers.push_back(*actor.body.rightThumbMedial);
if (actor.body.rightThumbDistal) rightHandFingers.push_back(*actor.body.rightThumbDistal);
if (actor.body.rightThumbTip) rightHandFingers.push_back(*actor.body.rightThumbTip);
if (actor.body.rightIndexProximal) rightHandFingers.push_back(*actor.body.rightIndexProximal);
if (actor.body.rightIndexMedial) rightHandFingers.push_back(*actor.body.rightIndexMedial);
if (actor.body.rightIndexDistal) rightHandFingers.push_back(*actor.body.rightIndexDistal);
if (actor.body.rightIndexTip) rightHandFingers.push_back(*actor.body.rightIndexTip);
if (actor.body.rightMiddleProximal) rightHandFingers.push_back(*actor.body.rightMiddleProximal);
if (actor.body.rightMiddleMedial) rightHandFingers.push_back(*actor.body.rightMiddleMedial);
if (actor.body.rightMiddleDistal) rightHandFingers.push_back(*actor.body.rightMiddleDistal);
if (actor.body.rightMiddleTip) rightHandFingers.push_back(*actor.body.rightMiddleTip);
if (actor.body.rightRingProximal) rightHandFingers.push_back(*actor.body.rightRingProximal);
if (actor.body.rightRingMedial) rightHandFingers.push_back(*actor.body.rightRingMedial);
if (actor.body.rightRingDistal) rightHandFingers.push_back(*actor.body.rightRingDistal);
if (actor.body.rightRingTip) rightHandFingers.push_back(*actor.body.rightRingTip);
if (actor.body.rightLittleProximal) rightHandFingers.push_back(*actor.body.rightLittleProximal);
if (actor.body.rightLittleMedial) rightHandFingers.push_back(*actor.body.rightLittleMedial);
if (actor.body.rightLittleDistal) rightHandFingers.push_back(*actor.body.rightLittleDistal);
if (actor.body.rightLittleTip) rightHandFingers.push_back(*actor.body.rightLittleTip);
return true;
@ -197,7 +307,7 @@ namespace RokokoIntegration
// 쿼터니언 길이 체크 (정규화된 경우 1.0에 가까워야 함)
float length = std::sqrt(x * x + y * y + z * z + w * w);
if (std::abs(length - 1.0f) > 0.1f) {
if (length < 0.1f || length > 10.0f) {
return false;
}
@ -207,15 +317,13 @@ namespace RokokoIntegration
void RokokoDataParser::NormalizeQuaternion(float& x, float& y, float& z, float& w)
{
float length = std::sqrt(x * x + y * y + z * z + w * w);
if (length > 0.0f) {
float invLength = 1.0f / length;
x *= invLength;
y *= invLength;
z *= invLength;
w *= invLength;
if (length > 0.0001f) {
x /= length;
y /= length;
z /= length;
w /= length;
} else {
// 유효하지 않은 쿼터니언인 경우 기본값 설정
// 기본값 설정
x = 0.0f;
y = 0.0f;
z = 0.0f;

View File

@ -10,6 +10,7 @@
#include <string>
#include <vector>
#include <memory>
#include <nlohmann/json.hpp>
// Forward declarations for JSON data structures
namespace RokokoData
@ -19,6 +20,7 @@ namespace RokokoData
struct ActorJointFrame;
struct ActorData;
struct LiveFrame_v4;
struct Body;
}
namespace RokokoIntegration
@ -53,6 +55,22 @@ namespace RokokoIntegration
std::vector<RokokoData::ActorJointFrame>& rightHandFingers);
private:
/**
* Parses body frame data from JSON
* @param bodyJson JSON object containing body data
* @param body Output body data structure
*/
static void ParseBodyFrame(const nlohmann::json& bodyJson, RokokoData::Body& body);
/**
* Parses individual finger joint data from JSON
* @param bodyJson JSON object containing body data
* @param jointName Name of the joint to parse
* @param joint Output joint data structure
*/
static void ParseFingerJoint(const nlohmann::json& bodyJson, const std::string& jointName,
std::shared_ptr<RokokoData::ActorJointFrame>& joint);
/**
* Parses actor data from JSON
* @param actorJson JSON object containing actor data

View File

@ -72,14 +72,15 @@ if exist "$(OutDir)ExampleGloveData.csv" copy "$(OutDir)ExampleGloveData.csv" "C
<LanguageStandard>stdcpp17</LanguageStandard>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;ANALOGSYSTEM_IMPORTS;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<GenerateXMLDocumentationFiles>false</GenerateXMLDocumentationFiles>
<AdditionalIncludeDirectories>..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\include;..\external;..\external\nlohmann;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ShowProgress>NotSet</ShowProgress>
<AdditionalDependencies>PeripheralImport.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalLibraryDirectories>..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<ProjectReference>
<LinkLibraryDependencies>false</LinkLibraryDependencies>
@ -93,7 +94,8 @@ if exist "$(OutDir)ExampleGloveData.csv" copy "$(OutDir)ExampleGloveData.csv" "C
<IntrinsicFunctions>true</IntrinsicFunctions>
<LanguageStandard>stdcpp17</LanguageStandard>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;ANALOGSYSTEM_IMPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\include;..\external;..\external\nlohmann;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -101,7 +103,7 @@ if exist "$(OutDir)ExampleGloveData.csv" copy "$(OutDir)ExampleGloveData.csv" "C
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>PeripheralImport.lib;ws2_32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalLibraryDirectories>..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
@ -111,7 +113,7 @@ if exist "$(OutDir)ExampleGloveData.csv" copy "$(OutDir)ExampleGloveData.csv" "C
<ClInclude Include="GloveDeviceBase.h" />
<ClInclude Include="GloveDataFormat.h" />
<ClInclude Include="LZ4Wrapper.h" />
<ClInclude Include="lz4.h" />
<ClInclude Include="RokokoData.h" />
<ClInclude Include="RokokoDataParser.h" />
<ClInclude Include="RokokoUDPReceiver.h" />
@ -130,7 +132,7 @@ if exist "$(OutDir)ExampleGloveData.csv" copy "$(OutDir)ExampleGloveData.csv" "C
<ClCompile Include="ExampleGloveDevice.cpp" />
<ClCompile Include="GloveDeviceBase.cpp" />
<ClCompile Include="LZ4Wrapper.cpp" />
<ClCompile Include="lz4.c" />
<ClCompile Include="RokokoUDPReceiver.cpp" />
<ClCompile Include="RokokoDataParser.cpp" />
<ClCompile Include="RokokoDataConverter.cpp" />

View File

@ -195,8 +195,7 @@ namespace RokokoIntegration
}
}
// CPU 사용률 조절
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// 🚀 실시간성 최우선 - sleep 제거 (CPU 사용률보다 반응성 우선)
} catch (...) {
SetError("Exception in receive thread");

Binary file not shown.

View File

@ -1,2 +1,2 @@
^C:\USERS\QSCFT\ONEDRIVE\문서\OPTITRACK\MOTIVE\PERIPHERALAPI\EXAMPLE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV
C:\Users\qscft\OneDrive\문서\OptiTrack\Motive\PeripheralAPI\example\x64\Debug\ExampleGloveData.csv
^C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV
C:\Users\user\Documents\Streamingle_URP\Optitrack Rokoko Glove\x64\Debug\ExampleGloveData.csv

View File

@ -1 +1 @@
^C:\USERS\QSCFT\ONEDRIVE\문서\OPTITRACK\MOTIVE\PERIPHERALAPI\EXAMPLE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV
^C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV

View File

@ -1,2 +1,2 @@
^C:\USERS\QSCFT\ONEDRIVE\문서\OPTITRACK\MOTIVE\PERIPHERALAPI\EXAMPLE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV
C:\USERS\QSCFT\ONEDRIVE\문서\OPTITRACK\MOTIVE\PERIPHERALAPI\EXAMPLE\X64\DEBUG\EXAMPLEGLOVEDATA.CSV
^C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\ROKOKOGLOVEDEVICE\EXAMPLEGLOVEDATA.CSV
C:\USERS\USER\DOCUMENTS\STREAMINGLE_URP\OPTITRACK ROKOKO GLOVE\X64\DEBUG\EXAMPLEGLOVEDATA.CSV

View File

@ -1,2 +1,2 @@
PlatformToolSet=v142:VCToolArchitecture=Native64Bit:VCToolsVersion=14.29.30133:TargetPlatformVersion=8.1:VcpkgTriplet=x64-windows:
Debug|x64|C:\Users\qscft\OneDrive\문서\OptiTrack\Motive\PeripheralAPI\example\|
PlatformToolSet=v142:VCToolArchitecture=Native64Bit:VCToolsVersion=14.29.30133:TargetPlatformVersion=8.1:
Debug|x64|C:\Users\user\Documents\Streamingle_URP\Optitrack Rokoko Glove\|

View File

@ -2,7 +2,7 @@
<Project>
<ProjectOutputs>
<ProjectOutput>
<FullPath>C:\Users\qscft\OneDrive\문서\OptiTrack\Motive\PeripheralAPI\example\x64\Debug\RokokoGloveDevice.dll</FullPath>
<FullPath>C:\Users\user\Documents\Streamingle_URP\Optitrack Rokoko Glove\x64\Debug\RokokoGloveDevice.dll</FullPath>
</ProjectOutput>
</ProjectOutputs>
<ContentFiles />

Binary file not shown.

Binary file not shown.

View File

@ -67,14 +67,14 @@ copy "$(OutDir)SimpleDeviceExample.dll" "C:/Program Files/OptiTrack/Motive/dev
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;ANALOGSYSTEM_IMPORTS;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<GenerateXMLDocumentationFiles>false</GenerateXMLDocumentationFiles>
<AdditionalIncludeDirectories>..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ShowProgress>NotSet</ShowProgress>
<AdditionalDependencies>PeripheralImport.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalLibraryDirectories>..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@ -84,7 +84,7 @@ copy "$(OutDir)SimpleDeviceExample.dll" "C:/Program Files/OptiTrack/Motive/dev
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;ANALOGSYSTEM_IMPORTS;OPTITRACKPERIPHERALEXAMPLE_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@ -92,7 +92,7 @@ copy "$(OutDir)SimpleDeviceExample.dll" "C:/Program Files/OptiTrack/Motive/dev
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>PeripheralImport.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalLibraryDirectories>..\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>
<ItemGroup>

View File

@ -1,2 +1,2 @@
PlatformToolSet=v142:VCToolArchitecture=Native64Bit:VCToolsVersion=14.29.30133:TargetPlatformVersion=8.1:VcpkgTriplet=x64-windows:
Debug|x64|C:\Users\qscft\OneDrive\문서\OptiTrack\Motive\PeripheralAPI\example\|
PlatformToolSet=v142:VCToolArchitecture=Native64Bit:VCToolsVersion=14.29.30133:TargetPlatformVersion=8.1:
Debug|x64|C:\Users\user\Documents\Streamingle_URP\Optitrack Rokoko Glove\|

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,93 @@
//======================================================================================================
// Copyright 2016, NaturalPoint Inc.
//======================================================================================================
#pragma once
#include <string>
#include "AnalogSystemBuildConfig.h"
#include "PropertySet.h"
#pragma warning( push )
#pragma warning( disable: 4251 )
namespace tinyxml2wc
{
class XMLElement;
}
namespace AnalogSystem
{
class AnalogChannelDescriptor;
class IDeviceManager;
// Channel properties
static const char* ChannelProp_Enabled = "AnalogChannel_Enabled";
static const char* ChannelProp_EnabledLabel = "Enabled";
static const char* ChannelProp_Name = "AnalogChannel_Name";
static const char* ChannelProp_NameLabel = "Channel Name";
static const char* ChannelProp_TerminalName = "AnalogChannel_TerminalName";
static const char* ChannelProp_TerminalNameLabel = "Terminal Name";
static const char* ChannelProp_DataType = "AnalogChannel_DataType";
static const char* ChannelProp_MinVoltage = "AnalogChannel_MinVoltage";
static const char* ChannelProp_MinVoltageLabel = "Min. Voltage";
static const char* ChannelProp_MaxVoltage = "AnalogChannel_MaxVoltage";
static const char* ChannelProp_MaxVoltageLabel = "Max. Voltage";
static const char* ChannelProp_TerminalType = "AnalogChannel_TerminalType";
static const char* ChannelProp_TerminalTypeLabel = "Terminal Type";
static const char* ChannelProp_Units = "AnalogChannel_Units";
static const char* ChannelProp_Selected = "AnalogChannel_Selected";
static const char* ChannelProp_OwnerSerial = "AnalogChannel_OwnerSerial";
/// <summary>
/// Channel physical signal type (Analog capture devices)
/// </summary>
enum TerminalConfigurationType
{
Terminal_RSE = 0, // Referenced single ended. Measurement with respect to ground (e.g. AI_GND) (Default)
Terminal_NRSE, // NonReferenced single ended. Measurement with respect to single analog input (e.g. AISENSE)
Terminal_Diff, // Differential. Measurement between two inputs (e.g. AI0+, AI0-)
Terminal_PseudoDiff // Differential. Measurement between two inputs and impeded common ground.
};
/// <summary>
/// Channel data type
/// </summary>
enum ChannelDataType
{
ChannelType_Int = 0,
ChannelType_Float,
ChannelType_Double,
ChannelType_Unknown
};
/// <summary>
/// AnalogChannelDescriptor describes a device's physical channel for exposing in the UI,
/// including channel name (user-definable), channel signal type, measurement range.
/// </summary>
class ANALOGSYSTEM_API AnalogChannelDescriptor : public cPropertySet
{
public:
AnalogChannelDescriptor();
~AnalogChannelDescriptor();
// IProfileSubscriber Interface
virtual std::wstring ProfileTypeName() const;
virtual std::wstring ProfileLabel() const;
virtual std::wstring ProfileDescription() const;
virtual void SaveToProfile( tinyxml2wc::XMLElement& saveTo ) const;
virtual bool LoadFromProfile( const tinyxml2wc::XMLElement& source );
private:
// Create the channel property definitions
void CreatePropertyDefinitions();
std::wstring mProfileName;
static Core::cUID sPropertyDefinitionsID;
AnalogChannelDescriptor( const AnalogChannelDescriptor& other ) = delete;
AnalogChannelDescriptor& operator=( const AnalogChannelDescriptor& other ) = delete;
};
}
#pragma warning( pop )

View File

@ -0,0 +1,144 @@
//======================================================================================================
// Copyright 2016, NaturalPoint Inc.
//======================================================================================================
#pragma once
#include <memory>
#include "AnalogSystemBuildConfig.h"
namespace AnalogSystem
{
/// <summary>
/// AnalogFrameBase is the abstract base implementation for a device's 'frame' of data.
/// A device 'frame' is a single sample of data from an analog device, one sample per
/// active channel, of the same data type, along with any header information for that sampling.
///
/// Analog frames contain a templated value for each physical signal (channel) the device reports.
/// A force plate device typically contains 6 channels (Fx,Fy,Fz,Mx,My,Mz), so the analog frame
/// would contain 6 float values, 1 for each active channel. A NIDAQ device has a user-configurable
/// active number of analog and digital channels, so the number of channels and frame types may
/// be variable.
/// </summary>
class ANALOGSYSTEM_API AnalogFrameBase
{
public:
AnalogFrameBase( int channelCount = 1 );
AnalogFrameBase( const AnalogFrameBase& src );
AnalogFrameBase& operator=( const AnalogFrameBase& src );
virtual ~AnalogFrameBase() = default;
/// <summary>template specific copy with alloc (implemented by derived class)</summary>
virtual std::unique_ptr<AnalogFrameBase> Copy() const = 0;
/// <summary>Return the number of channels in the frame</summary>
virtual const int ChannelCount() const;
/// <summary>Returns a pointer to the channel data array</summary>
void* ChannelData() const;
/// <summary>Returns a pointer to the channel data array</summary>
void* ChannelData( int channel ) { return ( (char*) mChannelData + ( channel * mChannelSize ) ); }
const void* ChannelData( int channel ) const { return ( (char*) mChannelData + ( channel * mChannelSize ) ); }
/// <summary>Gets the memory size of the channel type(in bytes)</summary>
int ChannelSize() const { return mChannelSize; }
/// <summary>Sets the frame ID</summary>
/// <param name="id">frame ID</param>
virtual void SetID( long id );
/// <summary>Gets the frame ID</summary>
virtual const long ID() const;
/// <summary>Sets the ID of the device that generated this frame</summary>
/// <param name="id">device identifier</param>
virtual void SetDeviceID( int id );
/// <summary>Gets the ID of the device that generated this frame</summary>
virtual const int DeviceID() const;
/// <summary>Sets the timestamp of the frame</summary>
/// <param name="id">device identifier</param>
virtual void SetTimestamp( double timestamp );
/// <summary>Gets the timestamp of the frame</summary>
virtual const double Timestamp() const;
/// <summary>Sets flags for this frame (frame specific bit information (e.g. trigger bit set on this frame).</summary>
/// <param name="flag">flag word</param>
virtual void SetFlag( int flag );
/// <summary>Gets device-specific flag bits for this frame</summary>
virtual const int Flag() const;
protected:
/// <summary>The number of channels</summary>
int mChannelCount;
/// <summary>Channel size (bytes)</summary>
int mChannelSize;
/// <summary>Flat array of channel data </summary>
void* mChannelData;
private:
long mID; // frame ID, usually a counter from the device source
int mDeviceID; // ID for the device associated with this data
double mTimestamp; // NTP compatible timestamp (64 bit fixed point)
int mFlag; // user defined data
};
/// <summary>
/// AnalogFrame is a type-specific implementation of a device frame.
/// </summary>
template <typename T>
class AnalogFrame : public AnalogFrameBase
{
public:
/// <summary>Initializes a new instance of the <see cref="AnalogFrame"/> class/// </summary>
/// <param name="channelCount">Number of typed data channels to allocate.</param>
AnalogFrame( int channelCount = 1 ) : AnalogFrameBase( channelCount )
{
mChannelData = new T[channelCount];
mChannelSize = sizeof( T );
::memset( mChannelData, 0, channelCount * sizeof( T ) );
}
AnalogFrame( const AnalogFrame& src ) : AnalogFrameBase( src )
{
mChannelData = new T[mChannelCount];
mChannelSize = sizeof( T );
::memcpy( this->mChannelData, src.mChannelData, (size_t) ( mChannelCount * sizeof( T ) ) );
}
AnalogFrame& operator=( const AnalogFrame& src )
{
if( this != &src )
{
__super::operator=( src );
if( mChannelData )
delete[] mChannelData;
mChannelData = new T[mChannelCount];
mChannelSize = sizeof( T );
::memcpy( this->mChannelData, src.mChannelData, mChannelCount * sizeof( T ) );
}
return *this;
}
/// <summary>Finalizes an instance of the <see cref="AnalogFrame"/> class</summary>
virtual ~AnalogFrame() { delete[] mChannelData; }
/// <summary>Allocates a new typed frame from an existing frame in the thread's memory context</summary>
std::unique_ptr<AnalogFrameBase> Copy() const override { return std::make_unique<AnalogFrame<T>>( *this ); }
/// <summary>Returns a typed pointer to the channel data array</summary>
const T* ChannelData() const { return reinterpret_cast<T*>( mChannelData ); }
T* ChannelData() { return reinterpret_cast<T*>( mChannelData ); }
/// <summary>Returns a typed pointer to the channel value</summary>
T* ChannelData( int channel ) { return &( reinterpret_cast<T*>( mChannelData )[channel] ); }
};
}

View File

@ -0,0 +1,12 @@
//======================================================================================================
// Copyright 2017, NaturalPoint Inc.
//======================================================================================================
#pragma once
#ifdef ANALOGSYSTEM_EXPORTS
#define ANALOGSYSTEM_API __declspec(dllexport)
#elif defined ANALOGSYSTEM_IMPORTS
#define ANALOGSYSTEM_API __declspec(dllimport)
#else
#define ANALOGSYSTEM_API
#endif

View File

@ -0,0 +1,81 @@
//======================================================================================================
// Copyright 2016, NaturalPoint Inc.
//======================================================================================================
#pragma once
#include <string>
#include "AnalogSystemBuildConfig.h"
namespace tinyxml2wc
{
class XMLElement;
}
namespace AnalogSystem
{
class AnalogChannelDescriptor;
class AnalogFrameBase;
class IDeviceManager;
class IPropVal;
/// <summary>
/// Interface for a Motive Device. Refer to PluginDeviceBase for details.
/// </summary>
class ANALOGSYSTEM_API IDevice
{
public:
IDevice() = default;
virtual ~IDevice() = default;
// operations
virtual bool Configure() = 0;
virtual bool Deconfigure() = 0;
virtual void Zero() = 0;
virtual bool PrepareForSync() = 0;
virtual bool PreCapture() = 0;
virtual bool StartCapture() = 0;
virtual bool StopCapture() = 0;
virtual bool PostCapture() = 0;
virtual bool IsCapturing() const = 0;
// channels
virtual int ChannelDescriptorCount() const = 0;
virtual AnalogChannelDescriptor* ChannelDescriptor( int index ) = 0;
virtual AnalogChannelDescriptor* ChannelDescriptor( const char* channelName ) = 0;
virtual int ActiveChannelCount() const = 0;
virtual int ChannelID( int index ) const = 0;
virtual AnalogChannelDescriptor* ActiveChannelDescriptor( int index ) = 0;
// data
virtual AnalogFrameBase* BeginFrameUpdate( long DeviceFrameID, bool wait = true ) = 0;
virtual void EndFrameUpdate() = 0;
virtual void UpdateDriftCorrection( long mocapFrameID ) = 0;
virtual const AnalogFrameBase* GetFrameByDeviceFrameID( long frameID ) const = 0;
virtual const AnalogFrameBase* GetFrameByBufferIndex( int index ) const = 0;
virtual int GetMostRecentFrameIndex() const = 0;
virtual long GetMostRecentFrameID() const = 0;
virtual bool MocapFrameIDToDeviceFrameIDs( long requestedMocapFrameID, long& deviceStart, long& deviceStop ) const = 0;
// events
virtual void OnPropertyChanged( const char* ) = 0;
virtual void OnChannelPropertyChanged( const char* channelName, const char* propertyName ) = 0;
virtual bool ValidatePropertyChange( const char* propertyName, const IPropVal& val ) = 0;
// IProfileSubscriber Interface
virtual std::wstring ProfileTypeName() const = 0;
virtual std::wstring ProfileLabel() const = 0;
virtual std::wstring ProfileDescription() const = 0;
virtual void SaveToProfile( tinyxml2wc::XMLElement& saveTo ) const = 0;
virtual bool LoadFromProfile( const tinyxml2wc::XMLElement& source ) = 0;
// helpers
virtual void SetDeviceManager( IDeviceManager* pDevManager ) = 0;
virtual IDeviceManager* DeviceManager() = 0;
virtual const char* LastError() const = 0;
virtual int MessageFromHost( const char* ) const = 0;
IDevice( const IDevice& other ) = delete;
IDevice& operator=( const IDevice& other ) = delete;
};
}

View File

@ -0,0 +1,58 @@
//======================================================================================================
// Copyright 2016, NaturalPoint Inc.
//======================================================================================================
#pragma once
#include <memory>
#include "AnalogSystemBuildConfig.h"
namespace AnalogSystem
{
class IDevice;
/// <summary>
/// IDeviceFactory creates a specific instance of a device.
/// </summary>
class ANALOGSYSTEM_API IDeviceFactory
{
public:
virtual ~IDeviceFactory() = default;
///<summary>Returns factory name</summary>
virtual const char* Name() const = 0;
///<summary>Returns device instance name</summary>
virtual const char* DeviceName() const = 0;
///<summary>Creates a new instance of the device.</summary>
virtual std::unique_ptr<IDevice> Create() const = 0;
};
/// <summary>
/// Base implementation for a Plugin Device factory.
/// See Plugin Device SDK for example implementation
/// </summary>
/// <seealso cref="IDeviceFactory" />
class ANALOGSYSTEM_API PluginDeviceFactory : public IDeviceFactory
{
public:
PluginDeviceFactory( const char* deviceName )
{
strcpy_s( mDeviceName, deviceName );
}
PluginDeviceFactory( const PluginDeviceFactory& ) = delete;
PluginDeviceFactory& operator=( const PluginDeviceFactory& ) = delete;
virtual ~PluginDeviceFactory() = default;
const char* DeviceName() const override
{
return mDeviceName;
}
private:
char mDeviceName[255];
};
}

View File

@ -0,0 +1,92 @@
//======================================================================================================
// Copyright 2016, NaturalPoint Inc.
//======================================================================================================
#pragma once
#include <memory>
#include "AnalogSystemBuildConfig.h"
namespace AnalogSystem
{
class IDeviceFactory;
class IDevice;
/// <summary>Error message type </summary>
enum MessageType
{
MessageType_StatusInfo = 0, // To be displayed as info item in StatusLog Panel
MessageType_StatusError, // To be displayed as error item in StatusLog Panel
MessageType_StatusErrorAggregate, // To be displayed as error item in StatusLog Panel (aggregate)
MessageType_DialogError, // To be displayed as error MessageBox (critical stop)
MessageType_Internal, // Do not display
MessageType_RequestRestart, // Device requests restart
MessageType_PrepareForSync, // Prepare device for live sync startup
MessageType_RequestResync, // request resync
MessageType_UpdateFromProfile, // apply profile settings to device
MessageType_Hotplug_Add, // add device after startup (hotplugged)
MessageType_Hotplug_Remove // remove device after startup (hotplugged)
};
/// <summary>App run mode state </summary>
enum AppRunMode
{
RunMode_Undefined = -1,
RunMode_Live = 0,
RunMode_Recording = 1,
RunMode_Playback = 2
};
/// <summary>Plugin to Motive communication interface</summary>
class ANALOGSYSTEM_API IDeviceManager
{
public:
IDeviceManager() = default;
virtual ~IDeviceManager() = default;
/// <summary>Registers the device factory with Motive. 1 Factory per device instance.</summary>
/// <param name="df">The plugin device factory</param>
virtual void RegisterDeviceFactory( std::unique_ptr<IDeviceFactory> df ) = 0;
/// <summary>Dynamically add device (hotplug) from Motive's list of devices.</summary>
/// <param name="df">The device factorye</param>
virtual bool AddDevice( std::unique_ptr<IDeviceFactory> df ) = 0;
/// <summary>Dynamically remove a device (hotplug) from Motive's list of devices.</summary>
/// <param name="deviceName">The plugin device</param>
virtual bool RemoveDevice( const char* deviceName ) = 0;
/// <summary>Notifies Motive when device configuration requires an application configuration update.</summary>
/// <param name="pDevice">The plugin device</param>
virtual bool UpdateDeviceConfiguration( IDevice* pDevice ) = 0;
/// <summary>Notifies Motive with a message.</summary>
virtual void MessageToHost( const char* message, int messageType = 0 ) = 0;
/// <summary>Notifies Motive with a message.</summary>
/// <param name="pDevice">The plugin device</param>
virtual void MessageToHost( IDevice* pDevice, const char* message, int messageType = 0 ) = 0;
/// <summary>Gets the number of slaves associated with a master device.</summary>
/// <param name="pMasterDevice">The master device.</param>
virtual int SlaveDeviceCount( IDevice* pMasterDevice ) = 0;
/// <summary>Get a slave deive by index</summary>
/// <param name="pMasterDevice">The master device.</param>
/// <param name="slaveIndex">Slave index.</param>
virtual IDevice* SlaveDevice( IDevice* pMasterDevice, int slaveIndex ) = 0;
/// <summary>Get a device by serial number</summary>
/// <param name="serial">Deviceserial number</param>
virtual IDevice* GetDevice( const char* serial ) = 0;
/// <summary>Gets current app playback state</summary>
virtual AppRunMode HostRunMode() = 0;
/// <summary>Gets an Motive application-wide setting</summary>
virtual bool GetProperty(const char* propertyName, char** propertyValue) = 0;
IDeviceManager( const IDeviceManager& other ) = delete;
IDeviceManager& operator=( const IDeviceManager& other ) = delete;
};
}

View File

@ -0,0 +1,49 @@
//======================================================================================================
// Copyright 2016, NaturalPoint Inc.
//======================================================================================================
#pragma once
#include "AnalogSystemBuildConfig.h"
#include "PluginDeviceBase.h"
namespace AnalogSystem
{
template <class T> class AnalogFrame;
/// <summary>
/// cPluginDevice is a type-specific implementation of a Plugin Device.
/// It provides type specific frame allocation and frame buffer access.
/// See Plugin Device SDK for example implementation
/// </summary>
template <typename T>
class cPluginDevice : public cPluginDeviceBase
{
public:
cPluginDevice() : cPluginDeviceBase()
{
mDataSize = sizeof( T );
}
/// <summary>Gets a pointer to next slot in the device's data buffer to write data into</summary>
/// <param name="frameID">The device frame ID the device will associate with this slot</param>
AnalogFrame<T>* BeginFrameUpdate( long frameID, bool wait = true ) override
{
return dynamic_cast<AnalogFrame<T>*>( __super::BeginFrameUpdate( frameID, wait ) );
}
/// <summary>Gets the frame by device frame identifier.</summary>
/// <param name="frameID">The device frame ID</param>
const AnalogFrame<T>* GetFrameByDeviceFrameID( long frameID ) const override
{
return dynamic_cast<const AnalogFrame<T>*>( __super::GetFrameByDeviceFrameID( frameID ) );
}
protected:
/// <summary>Allocates a frame in the plugin's memory context</summary>
/// <param name="channelCount">The channel count</param>
virtual AnalogFrame<T>* AllocateFrame( int channelCount ) override
{
return new AnalogFrame<T>( channelCount );
}
};
}

View File

@ -0,0 +1,398 @@
//======================================================================================================
// Copyright 2016, NaturalPoint Inc.
//======================================================================================================
#pragma once
// STL
// Private STL members are OK in exported class since they do not cross the DLL boundary
#pragma warning( push )
#pragma warning( disable : 4251 )
#pragma warning( disable : 4275 )
#include <vector>
#include <map>
#include <list>
#include <memory>
#include <chrono>
#include <ctime>
// OptiTrack PluginDevice API
#include "AnalogSystemBuildConfig.h"
#include "IDevice.h"
#include "AnalogFrame.h"
#include "AnalogChannelDescriptor.h"
#include "PropertySet.h"
#define ANALOG_DEVICEBUFFERSIZE 1000
#define ANALOG_ERRORMESSAGESIZE 4096
#define ANALOG_MAXSAMPLESPERMOCAPFRAME 16
namespace MoCapCore
{
class cPropertyCollectionDefinition;
}
namespace AnalogSystem
{
class AnalogChannelDescriptor;
class IDeviceManager;
class AnalogFrameGroup;
/// <summary>
/// Device sync mode
/// </summary>
enum DeviceSyncMode
{
DeviceSync_FreeRun = 0, // Free running
DeviceSync_Triggered // Waits for a trigger signal in data packet before starting
};
/// <summary>
/// Device Type (Devices can register themselves as a known type (e.g. Force Plate)
/// which can help Motive in interpreting the device in the UI, used derived node classes, etc.
/// </summary>
enum DeviceType
{
DeviceType_None = 0, // No particular type
DeviceType_NIDAQ, // NIDAQ card
DeviceType_ForcePlate, // Force plate
DeviceType_EyeTracker, // Eye Tracker
DeviceType_Pointer, // Pointer
DeviceType_Glove // Glove
};
/// <summary>
/// Frame Flags
/// </summary>
enum FrameFlag
{
FrameFlag_SyncBit = 0x01, // this frame contained a sync bit from the device
FrameFlag_DropFrame = 0x02, // this frame was dropped by the device and is padded with 0's
FrameFlag_Synthetic = 0x04 // this frame contains synthetic (padded or device filled) data
};
/// <summary>
/// Device frame buffer state
/// </summary>
enum DeviceBufferState
{
Buffer_Ready = 0,
Buffer_NeedSync = -1,
Buffer_Behind = -2,
Buffer_Ahead = -3,
Buffer_Empty = -4,
Buffer_MocapFrameOutOfRange = -5
};
/// <summary>
/// Device data state (not ready, ready, data flowing)
/// </summary>
enum DeviceDataState
{
DeviceState_NotReady = 0, // Device not ready (channels not configured, not calibrated, etc)
DeviceState_Ready, // Device ready for capture (channels enabled and correctly configured, calibrated, etc)
DeviceState_ReceivingData // Device actually actively receiving data
};
/// <summary>
/// Device sync state
/// </summary>
enum DeviceSyncStatus
{
DeviceSyncStatus_NeedSync = 0,
DeviceSyncStatus_Resyncing,
DeviceSyncStatus_ReadyForSync,
DeviceSyncStatus_Synced
};
/// <summary>
/// cPluginDeviceBase is the base implementation class for a Motive Plugin Device/
///
/// Implementors should derive from the template PluginDevice subclass
//
/// A PluginDevice provides
/// - Device operation callbacks, to be overridden by device implementor
/// - Device channel and property management, for exposing device channel information
/// and custom properties to the Motive UI. Also provides property change event
/// communication between Motive and Device
/// - Data management, providing a thread-safe user-typed data buffer for the device to
/// quickly write to, and for Motive to read from.
/// </summary>
/// <seealso cref="IDevice" />
class ANALOGSYSTEM_API cPluginDeviceBase : public IDevice, public cPropertySet
{
public:
cPluginDeviceBase();
virtual ~cPluginDeviceBase();
///////////////////////////////////////////////////////////////////////
//
// Plugin Device Interface (override in derived plugin device)
//
///<summary>Perform device specific configuration, allocates channel buffer. (Call base when implementing)</summary>
bool Configure() override;
/// <summary>Deconfigure this instance</summary>
bool Deconfigure() override;
/// <summary>Zero the device</summary>
void Zero() override;
/// <summary>Prepare live clocked devices for sync</summary>
bool PrepareForSync() override;
/// <summary>Perform any device-specific pre-capture operations</summary>
bool PreCapture() override;
/// <summary>Start device sampling (Call base when implementing)</summary>
bool StartCapture() override;
/// <summary>Stop device sampling (Call base when implementing)</summary>
bool StopCapture() override;
/// <summary>Perform any device-specific post-capture operations</summary>
bool PostCapture() override;
/// <summary>Return whether device is actively capturing data</summary>
bool IsCapturing() const override;
/// <summary>Return whether device is actively capturing data</summary>
bool IsReady() const;
/// <summary>Gives device a chance to validate a property value before applying it</summary>
bool ValidatePropertyChange( const char* propertyName, const IPropVal& val ) override;
/// <summary>Handle device specific property change events</summary>
void OnPropertyChanged( const char* propertyName ) override;
/// <summary>Handle device channel property change events</summary>
void OnChannelPropertyChanged( const char* channelName, const char* propertyName ) override;
/// <summary>Generic device-defined message passed from host (e.g. Motive) to device</summary>
int MessageFromHost( const char* ) const override;
///////////////////////////////////////////////////////////////////////
//
// Plugin Device Base Implementation (do not override)
//
// channels
/// <summary>Retrieves the ChannelDescriptor at the specified index</summary>
AnalogChannelDescriptor* ChannelDescriptor( int index ) override;
/// <summary>Retrieves the ChannelDescriptor at the specified channel name</summary>
AnalogChannelDescriptor* ChannelDescriptor( const char* channelName ) override;
/// <summary>Retrieves the number of Channels</summary>
int ChannelDescriptorCount() const override;
/// <summary>Retrieves the number of enabled channels</summary>
int ActiveChannelCount() const override;
/// <summary>Retrieves the ChannelDescriptor at the specified active channel index</summary>
AnalogChannelDescriptor* ActiveChannelDescriptor( int index ) override;
/// <summary>Retrieves the ChannelID at the specified index</summary>
int ChannelID( int index ) const override;
/// <summary>Retrieves the ChannelID associated with the specified channel name</summary>
int ChannelID( const char* channelName ) const;
// data
/// <summary>Called by Device before updating the frame buffer to lock the buffer and provide a slot</summary>
/// <param name="DeviceFrameID">The device frame ID.</param>
/// <returns>Slot to write data to</returns>
AnalogFrameBase* BeginFrameUpdate( long DeviceFrameID, bool wait = true ) override;
/// <summary>Called by device when frame update is done, unlocking the buffer and updating the indexer</summary>
void EndFrameUpdate() override;
/// <summary>Gets a frame of data from the device buffer at the specified buffer index</summary>
const AnalogFrameBase* GetFrameByBufferIndex( int index ) const override;
/// <summary>Gets a frame of data from the device buffer at the specified frame ID</summary>
const AnalogFrameBase* GetFrameByDeviceFrameID( long frameID ) const override;
/// <summary>Gets a newly allocated mocap aligned group of device frames. Caller is responsible for relasing framegroup</summary>
/// <param name="mocapFrameID">The mocap frame ID</param>
/// <param name="resultCode">0 if successful, error code otherwise</param>
/// <returns>Collection of analog frames</returns>
AnalogFrameGroup* GetFrameGroupByMocapFrameID( long mocapFrameID, DeviceBufferState& resultCode, bool recording = false );
/// <summary>Gets a mocap aligned frame group</summary>
/// <param name="frameGroup">caller provided frame group</param>
/// <param name="mocapFrameID">The mocap frame ID</param>
/// <returns>0 if successful, error code otherwise</returns>
DeviceBufferState GetFrameGroupByMocapFrameID( AnalogFrameGroup& frameGroup, long mocapFrameID, bool recording = false );
/// <summary>Gets the buffer index of the most recently added frame</summary>
int GetMostRecentFrameIndex() const override;
/// <summary>Gets the frame ID of the most recently added frame</summary>
long GetMostRecentFrameID() const override;
/// <summary>Gets the frame ID of the oldest frame in the buffer</summary>
long GetOldestFrameID();
/// <summary>Updates the software drift correction based on the most recent mocap and device frames</summary>
/// <param name="mocapFrameID">Most recent mocap frame ID</param>
void UpdateDriftCorrection( long mocapFrameID ) override;
/// <summary>Resets the drift correction</summary>
void ResetDriftCorrection();
/// <summary>Resets all sync counters (frame id, timestamp)</summary>
void ResetSyncCounters();
/// <summary>Gets the most recent Frame ID</summary>
long FrameCounter() const;
/// <summary>Sets the most recent Frame ID</summary>
void SetFrameCounter( long val );
/// <summary>Sets the DeviceManager (Device to Motive communication interface)</summary>
void SetDeviceManager( IDeviceManager* pDeviceManager ) override;
/// <summary>Gets the DeviceManager</summary>
IDeviceManager* DeviceManager() override;
/// <summary>Logs an error message</summary>
void LogError( int errorType, const char* fmt, ... );
/// <summary>Gets the last logged error message</summary>
const char* LastError() const override;
/// <summary>Gets the size (in bytes) of a channel</summary>
int DataSize() const;
/// <summary>Returns the number of device frame sampled per mocap frame</summary>
int DeviceFramesPerMocapFrame() const;
/// <summary>Return the sync aligned device frame IDs for the specified mocap frame ID</summary>
/// <param name="requestedMocapFrameID">Mocap frame</param>
/// <param name="deviceStart">Device frame start</param>
/// <param name="deviceStop">Device frame stop</param>
/// <returns>true if successful, false otherwise</returns>
bool MocapFrameIDToDeviceFrameIDs( long requestedMocapFrameID, long& deviceStart, long& deviceStop ) const override;
bool HaveSyncFrames() const;
void UpdateDeviceSyncStatus();
uint64_t LastArrivedSampleTimestamp() const;
// IProfileSubscriber Interface
std::wstring ProfileTypeName() const override;
std::wstring ProfileLabel() const override;
std::wstring ProfileDescription() const override;
void SaveToProfile( tinyxml2wc::XMLElement& saveTo ) const override;
bool LoadFromProfile( const tinyxml2wc::XMLElement& source ) override;
// deprecated
void AddFrameToBuffer( AnalogFrameBase* pFrame );
virtual void RegisterFactory( int channelCount ) { };
// PluginDevice Standard Properties. Plugins can add UI exposed custom properties via AddProperty()
static const char* kEnabledPropName;
static const char* kNamePropName;
static const char* kDisplayNamePropName;
static const char* kModelPropName;
static const char* kSerialPropName;
static const char* kChannelCountPropName;
static const char* kRatePropName;
static const char* kMocapRateMultiplePropName;
static const char* kUseExternalClockPropName;
static const char* kDeviceTypePropName;
static const char* kExternalClockTerminalPropName;
static const char* kExternalTriggerTerminalPropName;
static const char* kOrderPropName;
static const char* kMocapRatePropName;
static const char* kSyncStatusPropName;
static const char* kSyncModePropName;
static const char* kMocapSyncFramePropName;
static const char* kSyncFramePropName;
static const char* kNeedDeviceSyncFramePropName;
static const char* kNeedMocapSyncFramePropName;
static const char* kDriftCorrectionPropName;
static const char* kUseDriftCorrectionPropName;
static const char* kExternalClockChannelPropName;
static const char* kMasterSerialPropName;
static const char* kSlaveIndexPropName;
static const char* kNoiseAveragePropName;
static const char* kZeroPropName;
static const char* kDataStatePropName;
static const char* kAppRunModePropName;
static const char* kGroupNamePropName;
static const char* kAssetPropName;
static const char* kConnectedPropName;
static const char* kDummySerialPropName;
static const char* kUserDataPropName;
static const char* kCorner0PropName;
static const char* kCorner1PropName;
static const char* kCorner2PropName;
static const char* kCorner3PropName;
static const char* kScalePropName;
static const char* kCalOffsetPropName;
static const char* kCalSquareRotationPropName;
protected:
/// <summary>Adds a channel definition to the device</summary>
/// <param name="channelName">Name of the channel.</param>
/// <param name="dataType">Type of the data.</param>
/// <returns>ID of the new channel</returns>
int AddChannelDescriptor( char* channelName, int dataType = (int) ChannelType_Float );
// allocate a new frame (implemented in derived template class)
virtual AnalogFrameBase* AllocateFrame( int channelCount ) = 0;
/// <summary>Size (in bytes) of a data channel</summary>
int mDataSize;
bool mIsReady;
bool mIsCapturing;
long mRecordingDeviceSyncFrame = -1;
long mLiveDeviceSyncFrame = -1;
private:
// properties
std::shared_ptr<MoCapCore::cPropertyCollectionDefinition> CreatePropertyDefinitions();
// channels
std::vector<AnalogChannelDescriptor*> mChannelDescriptors;
std::vector<int> mActiveChannelMap;
// data
AnalogFrameBase* GetNextSlot( long DeviceFrameID );
bool LockBuffer( bool wait = true ) const;
void UnlockBuffer() const;
void ReleaseBuffer();
std::vector<AnalogFrameBase*> mFrameBuffer;
mutable void* mFrameBufferLock;
int mFrameBufferSize;
int mLastAddedIndex;
std::map<long, int> mAnalogFrameIDToBufferIndex;
long mFrameCounter;
long mDriftCorrection;
// helpers
IDeviceManager* mDeviceManager;
char mLastErrorMsg[ANALOG_ERRORMESSAGESIZE];
void* mMessageLock;
std::wstring mDeviceName;
static const std::wstring kPropertyDefinitionName;
static Core::cUID sPropertyDefinitionsID;
std::chrono::time_point<std::chrono::high_resolution_clock> mLastTimestamp;
bool mCached_NeedDeviceSyncFrame = true;
};
}
#pragma warning( pop )

View File

@ -0,0 +1,6 @@
//======================================================================================================
// Copyright 2016, NaturalPoint Inc.
//======================================================================================================
#pragma once
#include "IDeviceFactory.h"

View File

@ -0,0 +1,125 @@
//======================================================================================================
// Copyright 2016, NaturalPoint Inc.
//======================================================================================================
#pragma once
#include <memory>
#include "AnalogSystemBuildConfig.h"
#pragma warning( push )
#pragma warning( disable : 4251 )
namespace Core
{
class cUID;
}
namespace MoCapCore
{
class cPropertyCollection;
}
namespace AnalogSystem
{
class IPropVal
{
};
/// <summary>
/// cPropertyValue is a property value wrapper
/// </summary>
template<typename T>
class cPropVal : public IPropVal
{
public:
cPropVal( const T value ) { mValue = value; }
void SetValue( const T value ) { mValue = value; }
const T GetValue() { return mValue; }
private:
T mValue;
};
/// <summary>
/// cPropertySet is a wrapper for a Motive property collection, providing:
/// - Access to Motive PluginDevice base properties
/// - Ability to add custom properties, which will be settable in the UI
/// - Ability to respond to and generate property change events
///
/// See Plugin Device SDK for example.
///
/// Devices and DeviceChannels derive from this.
/// </summary>
class ANALOGSYSTEM_API cPropertySet
{
public:
cPropertySet() = default;
virtual ~cPropertySet() = default;
/// <summary>Sets the property.</summary>
/// <param name="propertyName">Name of the property.</param>
/// <param name="newValue">The new value.</param>
/// <returns></returns>
bool SetProperty( const char* propertyName, const char* newValue );
bool SetProperty( const char* propertyName, bool newValue );
bool SetProperty( const char* propertyName, int newValue );
bool SetProperty( const char* propertyName, double newValue );
bool SetProperty( const char* propertyName, double x, double y, double z );
/// <summary>Gets the property.</summary>
/// <param name="propertyName">Name of the property.</param>
/// <param name="value">The value.</param>
/// <returns></returns>
bool GetProperty( const char* propertyName, char* value, size_t bufferSize ) const;
bool GetProperty( const char* propertyName, bool& value ) const;
bool GetProperty( const char* propertyName, int& value ) const;
bool GetProperty( const char* propertyName, double& value ) const;
bool GetProperty( const char* propertyName, double& x, double& y, double& z ) const;
/// <summary>(Internal) Gets the shared pointer to the Motive cPropertyCollection</summary>
/// <returns>Motive cPropertyCollection</returns>
std::shared_ptr<MoCapCore::cPropertyCollection> PropertyCollection();
std::shared_ptr<const MoCapCore::cPropertyCollection> PropertyCollection() const;
/// <summary>
/// Modifies a property's attributes.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
/// <param name="readOnly">if set to <c>true</c> [read only].</param>
/// <param name="hidden">if set to <c>true</c> [hidden].</param>
/// <param name="group">The group.</param>
/// <returns>true if successful, false otherwise</returns>
bool ModifyProperty( const char* propertyName, bool readOnly, bool hidden, bool advanced = false, bool serializable = true );
protected:
/// <summary>Create and initializes the cPropertyCollection.</summary>
/// <param name="propertySetName">Name of the property set.</param>
/// <returns>true if successful, false otherwise</returns>
bool Initialize( const char* propertySetName );
/// <summary>
/// Adds the property with the default value, which determines the property type.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>true if successful, false otherwise</returns>
bool AddProperty( const char* propertyName, const char* defaultValue, const char* group = "", bool serializable = true );
bool AddProperty( const char* propertyName, bool defaultValue, const char* group = "", bool serializable = true );
bool AddProperty( const char* propertyName, int defaultValue, const char* group = "", bool serializable = true );
bool AddProperty( const char* propertyName, double defaultValue, const char* group = "", bool serializable = true );
bool AddProperty( const char* propertyName, const char* constraintNames[], int constraintCount, int defaultValue, const char* group = "", bool serializable = true );
// Internal use only
bool Initialize( const Core::cUID& definitionID );
private:
template<typename T2> bool GetPropertyInternal( const char* propertyName, T2& value ) const;
template<typename T2> bool SetPropertyInternal( const char* propertyName, T2 value );
std::shared_ptr<MoCapCore::cPropertyCollection> mPropertyCollection;
};
}
#pragma warning( pop )

Binary file not shown.

Binary file not shown.

BIN
Optitrack Rokoko Glove/x64/Debug/lz4.dll (Stored with Git LFS) Normal file

Binary file not shown.