214 lines
8.5 KiB
C++
214 lines
8.5 KiB
C++
//======================================================================================================
|
|
// Copyright 2025, Rokoko Glove OptiTrack Integration
|
|
//======================================================================================================
|
|
|
|
#include "RokokoDataConverter.h"
|
|
#include "RokokoData.h"
|
|
#include <cmath>
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
|
|
namespace RokokoIntegration
|
|
{
|
|
// 정적 상수는 헤더에서 inline으로 정의됨
|
|
|
|
sGloveDeviceData RokokoDataConverter::ConvertRokokoToOptiTrack(const RokokoData::LiveFrame_v4& rokokoFrame)
|
|
{
|
|
sGloveDeviceData optiTrackData;
|
|
|
|
try {
|
|
// 기본 데이터 설정
|
|
optiTrackData.gloveId = 1; // 기본 장갑 ID
|
|
optiTrackData.timestamp = 0.0; // 타임스탬프는 나중에 설정
|
|
|
|
// 15개 OptiTrack 관절 노드 초기화
|
|
optiTrackData.nodes.resize(TOTAL_OPTITRACK_JOINTS);
|
|
|
|
// 검증: actors가 존재하는지 확인
|
|
if (rokokoFrame.scene.actors.empty()) {
|
|
optiTrackData.nodes.clear();
|
|
return optiTrackData;
|
|
}
|
|
|
|
const auto& actor = rokokoFrame.scene.actors[0];
|
|
|
|
// 손가락별 데이터 매핑 (왼손)
|
|
// 엄지손가락 (Thumb)
|
|
if (actor.body.leftThumbMedial) ConvertJoint(*actor.body.leftThumbMedial, optiTrackData.nodes[0]); // MP
|
|
if (actor.body.leftThumbDistal) ConvertJoint(*actor.body.leftThumbDistal, optiTrackData.nodes[1]); // PIP
|
|
if (actor.body.leftThumbTip) ConvertJoint(*actor.body.leftThumbTip, optiTrackData.nodes[2]); // DIP
|
|
|
|
// 검지손가락 (Index)
|
|
if (actor.body.leftIndexMedial) ConvertJoint(*actor.body.leftIndexMedial, optiTrackData.nodes[3]); // MP
|
|
if (actor.body.leftIndexDistal) ConvertJoint(*actor.body.leftIndexDistal, optiTrackData.nodes[4]); // PIP
|
|
if (actor.body.leftIndexTip) ConvertJoint(*actor.body.leftIndexTip, optiTrackData.nodes[5]); // DIP
|
|
|
|
// 중지손가락 (Middle)
|
|
if (actor.body.leftMiddleMedial) ConvertJoint(*actor.body.leftMiddleMedial, optiTrackData.nodes[6]); // MP
|
|
if (actor.body.leftMiddleDistal) ConvertJoint(*actor.body.leftMiddleDistal, optiTrackData.nodes[7]); // PIP
|
|
if (actor.body.leftMiddleTip) ConvertJoint(*actor.body.leftMiddleTip, optiTrackData.nodes[8]); // DIP
|
|
|
|
// 약지손가락 (Ring)
|
|
if (actor.body.leftRingMedial) ConvertJoint(*actor.body.leftRingMedial, optiTrackData.nodes[9]); // MP
|
|
if (actor.body.leftRingDistal) ConvertJoint(*actor.body.leftRingDistal, optiTrackData.nodes[10]); // PIP
|
|
if (actor.body.leftRingTip) ConvertJoint(*actor.body.leftRingTip, optiTrackData.nodes[11]); // DIP
|
|
|
|
// 새끼손가락 (Little)
|
|
if (actor.body.leftLittleMedial) ConvertJoint(*actor.body.leftLittleMedial, optiTrackData.nodes[12]); // MP
|
|
if (actor.body.leftLittleDistal) ConvertJoint(*actor.body.leftLittleDistal, optiTrackData.nodes[13]); // PIP
|
|
if (actor.body.leftLittleTip) ConvertJoint(*actor.body.leftLittleTip, optiTrackData.nodes[14]); // DIP
|
|
|
|
// 노드 ID 설정
|
|
for (int i = 0; i < TOTAL_OPTITRACK_JOINTS; i++) {
|
|
optiTrackData.nodes[i].node_id = i;
|
|
}
|
|
|
|
} catch (...) {
|
|
// 에러 발생 시 빈 데이터 반환
|
|
optiTrackData.nodes.clear();
|
|
}
|
|
|
|
return optiTrackData;
|
|
}
|
|
|
|
bool RokokoDataConverter::ConvertJoint(const RokokoData::ActorJointFrame& rokokoJoint, sFingerNode& optiTrackNode)
|
|
{
|
|
try {
|
|
// 쿼터니언 변환
|
|
ConvertQuaternion(rokokoJoint.rotation,
|
|
optiTrackNode.quat_w,
|
|
optiTrackNode.quat_x,
|
|
optiTrackNode.quat_y,
|
|
optiTrackNode.quat_z);
|
|
|
|
// 쿼터니언 정규화
|
|
NormalizeQuaternion(optiTrackNode.quat_w,
|
|
optiTrackNode.quat_x,
|
|
optiTrackNode.quat_y,
|
|
optiTrackNode.quat_z);
|
|
|
|
// 쿼터니언 유효성 검사
|
|
if (!ValidateQuaternion(optiTrackNode.quat_w,
|
|
optiTrackNode.quat_x,
|
|
optiTrackNode.quat_y,
|
|
optiTrackNode.quat_z)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool RokokoDataConverter::ValidateOptiTrackData(const sGloveDeviceData& gloveData)
|
|
{
|
|
// 기본 검증
|
|
if (gloveData.nodes.size() != TOTAL_OPTITRACK_JOINTS) {
|
|
return false;
|
|
}
|
|
|
|
// 각 노드 검증
|
|
for (const auto& node : gloveData.nodes) {
|
|
if (!ValidateQuaternion(node.quat_w, node.quat_x, node.quat_y, node.quat_z)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string RokokoDataConverter::GetMappingInfo()
|
|
{
|
|
std::ostringstream oss;
|
|
oss << "Rokoko to OptiTrack Finger Joint Mapping:\n";
|
|
oss << "Total Rokoko joints: " << TOTAL_ROKOKO_JOINTS << " (4 per finger)\n";
|
|
oss << "Total OptiTrack joints: " << TOTAL_OPTITRACK_JOINTS << " (3 per finger)\n";
|
|
oss << "Mapping removes proximal joints and keeps:\n";
|
|
oss << " - Medial (MP): Metacarpophalangeal\n";
|
|
oss << " - Distal (PIP): Proximal Interphalangeal\n";
|
|
oss << " - Tip (DIP): Distal Interphalangeal\n";
|
|
|
|
return oss.str();
|
|
}
|
|
|
|
void RokokoDataConverter::MapFingerJoints(const std::vector<RokokoData::ActorJointFrame>& rokokoFingers,
|
|
std::vector<sFingerNode>& optiTrackNodes)
|
|
{
|
|
if (rokokoFingers.size() != TOTAL_ROKOKO_JOINTS || optiTrackNodes.size() != TOTAL_OPTITRACK_JOINTS) {
|
|
return;
|
|
}
|
|
|
|
// 매핑 테이블을 사용하여 관절 변환
|
|
for (int i = 0; i < TOTAL_OPTITRACK_JOINTS; i++) {
|
|
int rokokoIndex = JOINT_MAPPING[i];
|
|
if (rokokoIndex >= 0 && rokokoIndex < TOTAL_ROKOKO_JOINTS) {
|
|
ConvertJoint(rokokoFingers[rokokoIndex], optiTrackNodes[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RokokoDataConverter::ConvertQuaternion(const RokokoData::Vector4Frame& rokokoQuat,
|
|
float& quat_w, float& quat_x, float& quat_y, float& quat_z)
|
|
{
|
|
// Rokoko 쿼터니언을 OptiTrack 형식으로 복사
|
|
quat_w = rokokoQuat.w;
|
|
quat_x = rokokoQuat.x;
|
|
quat_y = rokokoQuat.y;
|
|
quat_z = rokokoQuat.z;
|
|
}
|
|
|
|
void RokokoDataConverter::NormalizeQuaternion(float& w, float& x, float& y, float& z)
|
|
{
|
|
float length = std::sqrt(w * w + x * x + y * y + z * z);
|
|
|
|
if (length > 0.0f) {
|
|
float invLength = 1.0f / length;
|
|
w *= invLength;
|
|
x *= invLength;
|
|
y *= invLength;
|
|
z *= invLength;
|
|
} else {
|
|
// 유효하지 않은 쿼터니언인 경우 기본값 설정
|
|
w = 1.0f;
|
|
x = 0.0f;
|
|
y = 0.0f;
|
|
z = 0.0f;
|
|
}
|
|
}
|
|
|
|
bool RokokoDataConverter::ValidateQuaternion(float w, float x, float y, float z)
|
|
{
|
|
// NaN 체크
|
|
if (std::isnan(w) || std::isnan(x) || std::isnan(y) || std::isnan(z)) {
|
|
return false;
|
|
}
|
|
|
|
// 무한대 체크
|
|
if (std::isinf(w) || std::isinf(x) || std::isinf(y) || std::isinf(z)) {
|
|
return false;
|
|
}
|
|
|
|
// 쿼터니언 길이 체크 (정규화된 경우 1.0에 가까워야 함)
|
|
float length = std::sqrt(w * w + x * x + y * y + z * z);
|
|
if (std::abs(length - 1.0f) > 0.1f) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void RokokoDataConverter::ApplyHandCoordinateSystem(bool isLeftHand, float& x, float& y, float& z)
|
|
{
|
|
// 좌우손 좌표계 변환
|
|
if (isLeftHand) {
|
|
// 왼손: +X축이 손가락 끝 방향
|
|
// 변환 없음 (기본값)
|
|
} else {
|
|
// 오른손: +X축이 손목/몸 방향
|
|
x = -x; // X축 반전
|
|
}
|
|
}
|
|
}
|