October3d55/M/UltraleapTracking/Source/UltraleapTrackingCore/Private/OpenXRToLeapWrapper.cpp

889 lines
30 KiB
C++
Raw Normal View History

2025-07-21 10:22:56 +08:00
/******************************************************************************
* Copyright (C) Ultraleap, Inc. 2011-2021. *
* *
* Use subject to the terms of the Apache License 2.0 available at *
* http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
* between Ultraleap and you, your company or other organization. *
******************************************************************************/
#include "OpenXRToLeapWrapper.h"
#include "HeadMountedDisplayTypes.h"
#include "IHandTracker.h"
#include "IXRTrackingSystem.h"
#include "Kismet/GameplayStatics.h"
#include "LeapBlueprintFunctionLibrary.h"
#include "LeapUtility.h"
#include "Runtime/Engine/Classes/Engine/World.h"
#include "FUltraleapDevice.h"
#include "Kismet/KismetMathLibrary.h"
FOpenXRToLeapWrapper::FOpenXRToLeapWrapper()
{
static int32 OpenXRDeviceID = OpenXRBaseDeviceID;
OpenXRDeviceID++;
DeviceID = OpenXRDeviceID;
CurrentDeviceInfo = &DummyDeviceInfo;
DummyDeviceInfo = {0};
DummyDeviceInfo.size = sizeof(LEAP_DEVICE_INFO);
DummyDeviceInfo.serial = (char*) ("OpenXRDummyDevice");
DummyDeviceInfo.serial_length = strlen(DummyDeviceInfo.serial) + 1;
DummyLeapHands[0] = {0};
DummyLeapHands[1] = {0};
DummyLeapHands[0].type = eLeapHandType::eLeapHandType_Left;
DummyLeapHands[1].type = eLeapHandType::eLeapHandType_Right;
DummyLeapHands[0].id = -1;
DummyLeapHands[1].id = -1;
DummyLeapFrame = {{0}};
DummyLeapFrame.framerate = 90;
DummyLeapFrame.pHands = DummyLeapHands;
Device = MakeShared<FUltraleapDevice>((IHandTrackingWrapper*) this, (ITrackingDeviceWrapper*) this, true);
// finger IDs are the index into the digits
for (int i = 0; i < 5; ++i)
{
DummyLeapHands[0].digits[i].finger_id = DummyLeapHands[1].digits[i].finger_id = i;
}
}
FOpenXRToLeapWrapper::~FOpenXRToLeapWrapper()
{
}
LEAP_CONNECTION* FOpenXRToLeapWrapper::OpenConnection(LeapWrapperCallbackInterface* InCallbackDelegate, bool UseMultiDeviceMode)
{
if (InCallbackDelegate!=nullptr)
{
CallbackDelegate = InCallbackDelegate;
}
InitOpenXRHandTrackingModule();
return nullptr;
}
void FOpenXRToLeapWrapper::InitOpenXRHandTrackingModule()
{
static FName SystemName(TEXT("OpenXR"));
if (!GEngine)
{
UE_LOG(UltraleapTrackingLog, Error, TEXT("Error: FOpenXRToLeapWrapper::InitOpenXRHandTrackingModule() - GEngine is NULL"));
return;
}
if (!GEngine->XRSystem.IsValid())
{
UE_LOG(UltraleapTrackingLog, Warning, TEXT("Warning: FOpenXRToLeapWrapper::InitOpenXRHandTrackingModule() No XR System found, is an HMD connected?"));
return;
}
if (GEngine->XRSystem->GetSystemName() == SystemName)
{
XRTrackingSystem = GEngine->XRSystem.Get();
}
if (XRTrackingSystem == nullptr)
{
UE_LOG(UltraleapTrackingLog, Warning, TEXT("Warning: FOpenXRToLeapWrapper::InitOpenXRHandTrackingModule() No OpenXR System found, are OpenXR plugins enabled"));
return;
}
IModuleInterface* ModuleInterface = FModuleManager::Get().LoadModule("OpenXRULHandTrackingExt");
if (!ModuleInterface)
{
ModuleInterface = FModuleManager::Get().LoadModule("OpenXRHandTracking");
}
IModularFeatures& ModularFeatures = IModularFeatures::Get();
if (ModularFeatures.IsModularFeatureAvailable(IHandTracker::GetModularFeatureName()))
{
TArray<IHandTracker*, FDefaultAllocator> Implementations = IModularFeatures::Get().GetModularFeatureImplementations<IHandTracker>(IHandTracker::GetModularFeatureName());
for (IHandTracker* Implementation : Implementations)
{
if (Implementation->GetHandTrackerDeviceTypeName() == "UltraleapOpenXRHandTracking")
{
HandTracker = Implementation;
UsingUltraleapExtension = true;
}
}
// fallback to default hand tracking
if (!HandTracker)
{
HandTracker = &IModularFeatures::Get().GetModularFeature<IHandTracker>(IHandTracker::GetModularFeatureName());
UsingUltraleapExtension = false;
}
bIsConnected = true;
if (CallbackDelegate)
{
CallbackDelegate->OnDeviceFound(&DummyDeviceInfo);
}
}
else
{
UE_LOG(UltraleapTrackingLog, Log,
TEXT(" FOpenXRToLeapWrapper::InitOpenXRHandTrackingModule() - OpenXRHandTracking module not found, is the "
"OpenXRHandTracking plugin enabled?"));
}
}
LEAP_VECTOR ConvertPositionToLeap(const FVector& FromOpenXR)
{
LEAP_VECTOR Ret = FLeapUtility::ConvertAndScaleUEToLeap(FromOpenXR);
return Ret;
}
int Sign(const ELeapQuatSwizzleAxisB& QuatSwizzleAxis)
{
return (QuatSwizzleAxis > ELeapQuatSwizzleAxisB::W) ? -1 : 1;
}
LEAP_QUATERNION FOpenXRToLeapWrapper::ConvertOrientationToLeap(const FQuat& FromOpenXR)
{
LEAP_QUATERNION Ret = {{{0}}};
FVector4 OldRotVector(FromOpenXR.X, FromOpenXR.Y, FromOpenXR.Z, FromOpenXR.W);
Ret.x = Sign(SwizzleX) * OldRotVector[StaticCast<uint8>(SwizzleX) % 4];
Ret.y = Sign(SwizzleY) * OldRotVector[StaticCast<uint8>(SwizzleY) % 4];
Ret.z = Sign(SwizzleZ) * OldRotVector[StaticCast<uint8>(SwizzleZ) % 4];
Ret.w = Sign(SwizzleW) * OldRotVector[StaticCast<uint8>(SwizzleW) % 4];
return Ret;
}
LEAP_VECTOR ConvertFVectorToLeapVector(const FVector& UEVector)
{
LEAP_VECTOR Ret;
// inverse of FVector(LeapVector.y, -LeapVector.x, -LeapVector.z);
Ret.x = -UEVector.Y;
Ret.y = UEVector.X;
Ret.z = -UEVector.Z;
return Ret;
}
void FOpenXRToLeapWrapper::SetHandJointFromKeypoint(
const int KeyPoint, LEAP_HAND& LeapHand, const FVector& Position, const FQuat& Rotation)
{
EHandKeypoint eKeyPoint = (EHandKeypoint) KeyPoint;
switch (eKeyPoint)
{
case EHandKeypoint::Palm:
// wrist orientation comes from palm orientation in bodystate
// palm orientation is calculated from palm direction in LeapHandData
{
LeapHand.palm.orientation = ConvertOrientationToLeap(Rotation);
LeapHand.palm.position = ConvertPositionToLeap(Position);
}
break;
case EHandKeypoint::Wrist:
// wrist comes from arm next joint in bodystate
LeapHand.arm.prev_joint = LeapHand.arm.next_joint = ConvertPositionToLeap(Position);
// set arm rotation from Wrist
LeapHand.arm.rotation = ConvertOrientationToLeap(Rotation);
LeapHand.arm.width = 10;
break;
// Thumb ////////////////////////////////////////////////////
/** From the leap data header
*
* For thumbs, this bone is set to have zero length and width, an identity basis matrix,
* and its joint positions are equal.
* Note that this is anatomically incorrect; in anatomical terms, the intermediate phalange
* is absent in a real thumb, rather than the metacarpal bone. In the Leap Motion model,
* however, we use a "zero" metacarpal bone instead for ease of programming.
* @since 3.0.0
*/
case EHandKeypoint::ThumbMetacarpal:
LeapHand.thumb.metacarpal.prev_joint = LeapHand.thumb.proximal.prev_joint = ConvertPositionToLeap(Position);
LeapHand.thumb.metacarpal.rotation = LeapHand.thumb.proximal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::ThumbProximal:
LeapHand.thumb.intermediate.prev_joint = LeapHand.thumb.metacarpal.next_joint = LeapHand.thumb.proximal.next_joint =
ConvertPositionToLeap(Position);
LeapHand.thumb.intermediate.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::ThumbDistal:
LeapHand.thumb.distal.prev_joint = LeapHand.thumb.intermediate.next_joint = ConvertPositionToLeap(Position);
LeapHand.thumb.distal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::ThumbTip:
// tip is next of distal
LeapHand.thumb.distal.next_joint = ConvertPositionToLeap(Position);
break;
// Index ////////////////////////////////////////////////////
case EHandKeypoint::IndexMetacarpal:
LeapHand.index.metacarpal.prev_joint = ConvertPositionToLeap(Position);
LeapHand.index.metacarpal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::IndexProximal:
LeapHand.index.proximal.prev_joint = LeapHand.index.metacarpal.next_joint = ConvertPositionToLeap(Position);
LeapHand.index.proximal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::IndexIntermediate:
LeapHand.index.intermediate.prev_joint = LeapHand.index.proximal.next_joint = ConvertPositionToLeap(Position);
LeapHand.index.intermediate.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::IndexDistal:
LeapHand.index.distal.prev_joint = LeapHand.index.intermediate.next_joint = ConvertPositionToLeap(Position);
LeapHand.index.distal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::IndexTip:
LeapHand.index.distal.next_joint = ConvertPositionToLeap(Position);
break;
// Middle ////////////////////////////////////////////////////
case EHandKeypoint::MiddleMetacarpal:
LeapHand.middle.metacarpal.prev_joint = ConvertPositionToLeap(Position);
LeapHand.middle.metacarpal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::MiddleProximal:
LeapHand.middle.proximal.prev_joint = LeapHand.middle.metacarpal.next_joint = ConvertPositionToLeap(Position);
LeapHand.middle.proximal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::MiddleIntermediate:
LeapHand.middle.intermediate.prev_joint = LeapHand.middle.proximal.next_joint = ConvertPositionToLeap(Position);
LeapHand.middle.intermediate.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::MiddleDistal:
LeapHand.middle.distal.prev_joint = LeapHand.middle.intermediate.next_joint = ConvertPositionToLeap(Position);
LeapHand.middle.distal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::MiddleTip:
LeapHand.middle.distal.next_joint = ConvertPositionToLeap(Position);
break;
// Ring ////////////////////////////////////////////////////
case EHandKeypoint::RingMetacarpal:
LeapHand.ring.metacarpal.prev_joint = ConvertPositionToLeap(Position);
LeapHand.ring.metacarpal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::RingProximal:
LeapHand.ring.proximal.prev_joint = LeapHand.ring.metacarpal.next_joint = ConvertPositionToLeap(Position);
LeapHand.ring.proximal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::RingIntermediate:
LeapHand.ring.intermediate.prev_joint = LeapHand.ring.proximal.next_joint = ConvertPositionToLeap(Position);
LeapHand.ring.intermediate.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::RingDistal:
LeapHand.ring.distal.prev_joint = LeapHand.ring.intermediate.next_joint = ConvertPositionToLeap(Position);
LeapHand.ring.distal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::RingTip:
LeapHand.ring.distal.next_joint = ConvertPositionToLeap(Position);
break;
// Little/pinky ////////////////////////////////////////////////////
case EHandKeypoint::LittleMetacarpal:
LeapHand.pinky.metacarpal.prev_joint = ConvertPositionToLeap(Position);
LeapHand.pinky.metacarpal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::LittleProximal:
LeapHand.pinky.proximal.prev_joint = LeapHand.pinky.metacarpal.next_joint = ConvertPositionToLeap(Position);
LeapHand.pinky.proximal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::LittleIntermediate:
LeapHand.pinky.intermediate.prev_joint = LeapHand.pinky.proximal.next_joint = ConvertPositionToLeap(Position);
LeapHand.pinky.intermediate.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::LittleDistal:
LeapHand.pinky.distal.prev_joint = LeapHand.pinky.intermediate.next_joint = ConvertPositionToLeap(Position);
LeapHand.pinky.distal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypoint::LittleTip:
LeapHand.pinky.distal.next_joint = ConvertPositionToLeap(Position);
break;
default:
UE_LOG(UltraleapTrackingLog, Log,
TEXT("FOpenXRToLeapWrapper::ConvertToLeapSpace() - Unknown keypoint found in OpenXR data"));
break;
}
}
void FOpenXRToLeapWrapper::SetHandJointFromKeypointExt(const int KeyPoint, LEAP_HAND& LeapHand, const FVector& Position, const FQuat& Rotation)
{
EHandKeypointUL eKeyPoint = (EHandKeypointUL) KeyPoint;
switch (eKeyPoint)
{
case EHandKeypointUL::Palm:
// wrist orientation comes from palm orientation in bodystate
// palm orientation is calculated from palm direction in LeapHandData
{
LeapHand.palm.orientation = ConvertOrientationToLeap(Rotation);
LeapHand.palm.position = ConvertPositionToLeap(Position);
}
break;
case EHandKeypointUL::Wrist:
// wrist comes from arm next joint in bodystate
LeapHand.arm.next_joint = ConvertPositionToLeap(Position);
// set arm rotation from Wrist
//LeapHand.arm.rotation = ConvertOrientationToLeap(Rotation);
LeapHand.arm.width = 10;
break;
case EHandKeypointUL::Elbow:
LeapHand.arm.prev_joint = ConvertPositionToLeap(Position);
LeapHand.arm.rotation = ConvertOrientationToLeap(Rotation);
break;
// Thumb ////////////////////////////////////////////////////
/** From the leap data header
*
* For thumbs, this bone is set to have zero length and width, an identity basis matrix,
* and its joint positions are equal.
* Note that this is anatomically incorrect; in anatomical terms, the intermediate phalange
* is absent in a real thumb, rather than the metacarpal bone. In the Leap Motion model,
* however, we use a "zero" metacarpal bone instead for ease of programming.
* @since 3.0.0
*/
case EHandKeypointUL::ThumbMetacarpal:
LeapHand.thumb.metacarpal.prev_joint = LeapHand.thumb.proximal.prev_joint = ConvertPositionToLeap(Position);
LeapHand.thumb.metacarpal.rotation = LeapHand.thumb.proximal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::ThumbProximal:
LeapHand.thumb.intermediate.prev_joint = LeapHand.thumb.metacarpal.next_joint = LeapHand.thumb.proximal.next_joint =
ConvertPositionToLeap(Position);
LeapHand.thumb.intermediate.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::ThumbDistal:
LeapHand.thumb.distal.prev_joint = LeapHand.thumb.intermediate.next_joint = ConvertPositionToLeap(Position);
LeapHand.thumb.distal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::ThumbTip:
// tip is next of distal
LeapHand.thumb.distal.next_joint = ConvertPositionToLeap(Position);
break;
// Index ////////////////////////////////////////////////////
case EHandKeypointUL::IndexMetacarpal:
LeapHand.index.metacarpal.prev_joint = ConvertPositionToLeap(Position);
LeapHand.index.metacarpal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::IndexProximal:
LeapHand.index.proximal.prev_joint = LeapHand.index.metacarpal.next_joint = ConvertPositionToLeap(Position);
LeapHand.index.proximal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::IndexIntermediate:
LeapHand.index.intermediate.prev_joint = LeapHand.index.proximal.next_joint = ConvertPositionToLeap(Position);
LeapHand.index.intermediate.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::IndexDistal:
LeapHand.index.distal.prev_joint = LeapHand.index.intermediate.next_joint = ConvertPositionToLeap(Position);
LeapHand.index.distal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::IndexTip:
LeapHand.index.distal.next_joint = ConvertPositionToLeap(Position);
break;
// Middle ////////////////////////////////////////////////////
case EHandKeypointUL::MiddleMetacarpal:
LeapHand.middle.metacarpal.prev_joint = ConvertPositionToLeap(Position);
LeapHand.middle.metacarpal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::MiddleProximal:
LeapHand.middle.proximal.prev_joint = LeapHand.middle.metacarpal.next_joint = ConvertPositionToLeap(Position);
LeapHand.middle.proximal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::MiddleIntermediate:
LeapHand.middle.intermediate.prev_joint = LeapHand.middle.proximal.next_joint = ConvertPositionToLeap(Position);
LeapHand.middle.intermediate.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::MiddleDistal:
LeapHand.middle.distal.prev_joint = LeapHand.middle.intermediate.next_joint = ConvertPositionToLeap(Position);
LeapHand.middle.distal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::MiddleTip:
LeapHand.middle.distal.next_joint = ConvertPositionToLeap(Position);
break;
// Ring ////////////////////////////////////////////////////
case EHandKeypointUL::RingMetacarpal:
LeapHand.ring.metacarpal.prev_joint = ConvertPositionToLeap(Position);
LeapHand.ring.metacarpal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::RingProximal:
LeapHand.ring.proximal.prev_joint = LeapHand.ring.metacarpal.next_joint = ConvertPositionToLeap(Position);
LeapHand.ring.proximal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::RingIntermediate:
LeapHand.ring.intermediate.prev_joint = LeapHand.ring.proximal.next_joint = ConvertPositionToLeap(Position);
LeapHand.ring.intermediate.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::RingDistal:
LeapHand.ring.distal.prev_joint = LeapHand.ring.intermediate.next_joint = ConvertPositionToLeap(Position);
LeapHand.ring.distal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::RingTip:
LeapHand.ring.distal.next_joint = ConvertPositionToLeap(Position);
break;
// Little/pinky ////////////////////////////////////////////////////
case EHandKeypointUL::LittleMetacarpal:
LeapHand.pinky.metacarpal.prev_joint = ConvertPositionToLeap(Position);
LeapHand.pinky.metacarpal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::LittleProximal:
LeapHand.pinky.proximal.prev_joint = LeapHand.pinky.metacarpal.next_joint = ConvertPositionToLeap(Position);
LeapHand.pinky.proximal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::LittleIntermediate:
LeapHand.pinky.intermediate.prev_joint = LeapHand.pinky.proximal.next_joint = ConvertPositionToLeap(Position);
LeapHand.pinky.intermediate.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::LittleDistal:
LeapHand.pinky.distal.prev_joint = LeapHand.pinky.intermediate.next_joint = ConvertPositionToLeap(Position);
LeapHand.pinky.distal.rotation = ConvertOrientationToLeap(Rotation);
break;
case EHandKeypointUL::LittleTip:
LeapHand.pinky.distal.next_joint = ConvertPositionToLeap(Position);
break;
default:
UE_LOG(UltraleapTrackingLog, Log,
TEXT("FOpenXRToLeapWrapper::ConvertToLeapSpace() - Unknown keypoint found in OpenXR data"));
break;
}
}
void FOpenXRToLeapWrapper::ConvertToLeapSpace(LEAP_HAND& LeapHand, const TArray<FVector>& Positions, const TArray<FQuat>& Rotations)
{
if (!XRTrackingSystem)
{
return;
}
// Enums for each bone are in EHandKeypoint/UL
uint8 KeyPoint = 0;
for (FVector Position : Positions)
{
FQuat Rotation = Rotations[KeyPoint];
// Take out the player transform, this isn't valid as we want Leap Space which knows nothing about
// the player world position
const FTransform& TrackingToWorldTransform = XRTrackingSystem->GetTrackingToWorldTransform();
Position = TrackingToWorldTransform.InverseTransformPosition(Position);
Rotation = TrackingToWorldTransform.InverseTransformRotation(Rotation);
// additional rotate all to get into Leap rotation space
// see FLeapUtility::LeapRotationOffset()
FTransform RotateToLeap;
RotateToLeap.SetRotation(FRotator(90, 0, 180).Quaternion());
Position = RotateToLeap.TransformPosition(Position);
Rotation = RotateToLeap.TransformRotation(Rotation);
if (UsingUltraleapExtension)
{
SetHandJointFromKeypointExt(KeyPoint, LeapHand, Position, Rotation);
}
else
{
SetHandJointFromKeypoint(KeyPoint, LeapHand, Position, Rotation);
}
KeyPoint++;
}
}
LEAP_TRACKING_EVENT* FOpenXRToLeapWrapper::GetInterpolatedFrameAtTime(int64 TimeStamp)
{
return GetFrame();
}
void FOpenXRToLeapWrapper::UpdateHandState()
{
}
LEAP_DEVICE_INFO* FOpenXRToLeapWrapper::GetDeviceProperties()
{
return CurrentDeviceInfo;
}
// TODO: update hand Ids on tracking lost and found
// TODO: work out hand poses and confidence based on how the MS/WMR code does it (pinch and grab)
LEAP_TRACKING_EVENT* FOpenXRToLeapWrapper::GetFrame()
{
if (HandTracker == nullptr)
{
return &DummyLeapFrame;
}
TArray<FVector> OutPositions[2];
TArray<FQuat> OutRotations[2];
TArray<float> OutRadii[2];
// status only true when the hand is being tracked/visible to the tracking device
// these are in world space
//
// IMPORTANT: OpenXR tracking only works in VR mode, this will always return false in desktop mode
bool StatusLeft;
bool StatusRight;
bool OutIsTrackedLeft;
bool OutIsTrackedRight;
StatusLeft = HandTracker->GetAllKeypointStates(EControllerHand::Left, OutPositions[0], OutRotations[0], OutRadii[0], OutIsTrackedLeft);
StatusRight = HandTracker->GetAllKeypointStates(EControllerHand::Right, OutPositions[1], OutRotations[1], OutRadii[1], OutIsTrackedRight);
DummyLeapFrame.nHands = StatusLeft + StatusRight;
DummyLeapFrame.info.frame_id++;
UWorld* World = nullptr;
// time in microseconds
DummyLeapFrame.info.timestamp = GetDummyLeapTime();
DummyLeapFrame.tracking_frame_id++;
if (!StatusLeft)
{
DummyLeapFrame.pHands = &DummyLeapHands[1];
}
else
{
DummyLeapFrame.pHands = &DummyLeapHands[0];
}
if (StatusLeft)
{
ConvertToLeapSpace(DummyLeapHands[0], OutPositions[0], OutRotations[0]);
// not tracking -> tracking = update hand IDs
if (!LeftHandVisible)
{
LeftHandVisible = true;
DummyLeapHands[0].id = HandID++;
FirstSeenLeft = GetGameTimeInSeconds();
}
}
else
{
LeftHandVisible = false;
}
if (StatusRight)
{
ConvertToLeapSpace(DummyLeapHands[1], OutPositions[1], OutRotations[1]);
// not tracking -> tracking = update hand IDs
if (!RightHandVisible)
{
RightHandVisible = true;
DummyLeapHands[1].id = HandID++;
FirstSeenRight = GetGameTimeInSeconds();
}
}
else
{
RightHandVisible = false;
}
return &DummyLeapFrame;
}
int64_t FOpenXRToLeapWrapper::GetDummyLeapTime()
{
// time in microseconds
if (!CurrentWorld)
{
return 0;
}
{
return CurrentWorld->GetTimeSeconds() * 1000000.0f;
}
}
void FOpenXRToLeapWrapper::SetWorld(UWorld* World)
{
if (World!=nullptr)
{
FLeapWrapperBase::SetWorld(World);
}
/* no delta TODO: get world framerate from somewhere if (World)
{
float WorldDelta = World->GetDeltaSeconds();
if (WorldDelta)
{
DummyLeapFrame.framerate = 1.0f / WorldDelta;
}
}*/
}
void FOpenXRToLeapWrapper::CloseConnection()
{
if (!bIsConnected)
{
// Not connected, already done
UE_LOG(UltraleapTrackingLog, Warning, TEXT("FOpenXRToLeapWrapper Attempt at closing an already closed connection."));
return;
}
bIsConnected = false;
// Nullify the callback delegate. Any outstanding task graphs will not run if the delegate is nullified.
CallbackDelegate = nullptr;
UE_LOG(UltraleapTrackingLog, Log, TEXT("FOpenXRToLeapWrapper Connection successfully closed."));
}
void FOpenXRToLeapWrapper::SetTrackingMode(eLeapTrackingMode TrackingMode)
{
// needed to make sure delegates in BP get called back on mode change
if (CallbackDelegate)
{
CallbackDelegate->OnTrackingMode(TrackingMode);
}
}
void FOpenXRToLeapWrapper::PostLeapHandUpdate(FLeapFrameData& Frame)
{
// simulate pinch and grab state (in LeapC this comes from the tracking service)
for (FLeapHandData& Hand : Frame.Hands)
{
UpdatePinchAndGrab(Hand);
}
}
void FOpenXRToLeapWrapper::SetDeviceHints(TArray<FString>& Hints, const uint32_t LeapDeviceID)
{
// TODO implement this when XR is ready
}
IHandTrackingDevice* FOpenXRToLeapWrapper::GetDevice()
{
return Device.Get();
}
float FOpenXRToLeapWrapper::CalculatePinchStrength(const FLeapHandData& Hand, float PalmWidth)
{
// Magic values taken from existing LeapC implementation (scaled to metres)
float HandScale = PalmWidth / 0.08425f;
float DistanceZero = 0.0600f * HandScale;
float DistanceOne = 0.0220f * HandScale;
// Get the thumb position.
// TipPosition in Unity, is Distal->Next the same?
FVector ThumbTipPosition = Hand.Thumb.Distal.NextJoint;
// Compute the distance midpoints between the thumb and the each finger and find the smallest.
float MinDistanceSquared = TNumericLimits<float>::Max();
int32 FingerIndex = 0;
for(const FLeapDigitData& Finger : Hand.Digits)
{
// skip 1
if (!FingerIndex)
{
FingerIndex++;
continue;
}
FVector Diff = Finger.Distal.NextJoint - ThumbTipPosition;
float DistanceSquared = Diff.SizeSquared();
MinDistanceSquared = FMath::Min(DistanceSquared, MinDistanceSquared);
FingerIndex++;
}
// Compute the pinch strength.
return FMath::Clamp<float>((FMath::Sqrt(MinDistanceSquared) - DistanceZero) / (DistanceOne - DistanceZero), 0.0, 1.0);
}
float FOpenXRToLeapWrapper::CalculateBoneDistanceSquared(const FLeapBoneData& BoneA, const FLeapBoneData& BoneB)
{
// Denormalize directions to bone length.
const FVector BoneAJoint = BoneA.PrevJoint;
const FVector BoneBJoint = BoneB.PrevJoint;
const FVector BoneADirection = BoneA.Rotation.Vector() * ((BoneA.NextJoint - BoneA.PrevJoint).Size());
const FVector BoneBDirection = BoneB.Rotation.Vector() * ((BoneB.NextJoint - BoneB.PrevJoint).Size());
// Compute the minimum (squared) distance between two bones.
const FVector Diff = BoneBJoint - BoneAJoint;
const float D1 = FVector::DotProduct(BoneADirection, Diff);
const float D2 = FVector::DotProduct(BoneBDirection, Diff);
const float A = BoneADirection.SizeSquared();
const float B = FVector::DotProduct(BoneADirection, BoneBDirection);
const float C = BoneBDirection.SizeSquared();
const float Det = B * B - A * C;
const float T1 = FMath::Clamp<float>((B * D2 - C * D1) / Det, 0.0, 1.0);
const float T2 = FMath::Clamp<float>((A * D2 - B * D1) / Det, 0.0, 1.0);
const FVector Pa = BoneAJoint + T1 * BoneADirection;
const FVector Pb = BoneBJoint + T2 * BoneBDirection;
return (Pa - Pb).SizeSquared();
}
float FOpenXRToLeapWrapper::CalculatePinchDistance(const FLeapHandData& Hand)
{
// Get the farthest 2 segments of thumb and index finger, respectively, and compute distances.
float MinDistanceSquared = TNumericLimits<float>::Max();
int32 ThumbBoneIndex = 0;
for (const FLeapBoneData& ThumbBone : Hand.Thumb.Bones)
{
// skip 2
if (ThumbBoneIndex < 2)
{
ThumbBoneIndex++;
continue;
}
int IndexBoneIndex = 0;
for (const FLeapBoneData& IndexBone : Hand.Index.Bones)
{
// skip 2
if (IndexBoneIndex < 2)
{
IndexBoneIndex++;
continue;
}
const float DistanceSquared = CalculateBoneDistanceSquared(ThumbBone, IndexBone);
MinDistanceSquared = FMath::Min(DistanceSquared, MinDistanceSquared);
IndexBoneIndex++;
}
ThumbBoneIndex++;
}
// Return the pinch distance
return FMath::Sqrt(MinDistanceSquared);
}
/// Returns the the direction towards the thumb that is perpendicular to the palmar
/// and distal axes. Left and right hands will return opposing directions.
///
/// The direction away from the thumb would be called the ulnar axis.
FVector HandRadialAxis(const FLeapHandData& Hand)
{
FTransform Basis(Hand.Palm.Orientation, Hand.Palm.Position);
const FVector RightVector = UKismetMathLibrary::GetRightVector(Hand.Palm.Orientation);
if (Hand.HandType == LEAP_HAND_RIGHT)
{
return -RightVector;
}
else
{
return RightVector;
}
}
/// Returns the direction towards the fingers that is perpendicular to the palmar
/// and radial axes.
///
/// The direction towards the wrist would be called the proximal axis.
FVector HandDistalAxis(const FLeapHandData& Hand)
{
// in BS Space forward is Z = UpVector in UE
return UKismetMathLibrary::GetUpVector(Hand.Palm.Orientation);
}
FVector FingerDirection(const FLeapDigitData& Finger)
{
// The direction in which this finger or tool is pointing.The direction is expressed
// as a unit vector pointing in the same direction as the tip.
return UKismetMathLibrary::GetUpVector(Finger.Distal.Rotation);
}
/// Maps the value between valueMin and valueMax to its linearly proportional equivalent between resultMin and resultMax.
/// The input value is clamped between valueMin and valueMax; if this is not desired, see MapUnclamped.
float MapRange(const float Value,const float ValueMin,const float ValueMax,const float ResultMin,const float ResultMax)
{
if (ValueMin == ValueMax)
return ResultMin;
return FMath::Lerp(ResultMin, ResultMax, ((Value - ValueMin) / (ValueMax - ValueMin)));
}
float GetFingerStrength(const FLeapHandData& Hand, int Finger)
{
return MapRange(FVector::DotProduct(FingerDirection(Hand.Digits[Finger]), -HandDistalAxis(Hand)), -1, 1, 0, 1);
}
float FOpenXRToLeapWrapper::CalculateGrabStrength(const FLeapHandData& Hand)
{
// magic numbers so it approximately lines up with the leap results
const float BendZero = 0.25f;
const float BendOne = 0.85f;
// Find the minimum bend angle for the non-thumb fingers.
float MinBend = TNumericLimits<float>::Max();
for (int FingerIdx = 1; FingerIdx < 5; FingerIdx++)
{
MinBend = FMath::Min( GetFingerStrength(Hand, FingerIdx), MinBend);
}
// Return the grab strength.
return FMath::Clamp<float>((MinBend - BendZero) / (BendOne - BendZero), 0.0, 1.0);
}
float FOpenXRToLeapWrapper::CalculateGrabAngle(const FLeapHandData& Hand)
{
// Compute the sum of the angles between the fingertips and hands.
// For every finger, the angle is the sumb of bend + pitch + bow.
float AngleSum = 0.0f;
for (int FingerIdx = 1; FingerIdx < 5; FingerIdx++)
{
AngleSum += FMath::Lerp< float > (0, PI, GetFingerStrength(Hand, FingerIdx));
}
// Average between all fingers
return AngleSum / 4.0f;
}
void FOpenXRToLeapWrapper::UpdatePinchAndGrab(FLeapHandData& Hand)
{
Hand.PinchDistance = CalculatePinchDistance(Hand);
Hand.PinchStrength =
CalculatePinchStrength(Hand, FVector::Distance(Hand.Thumb.Metacarpal.NextJoint, Hand.Pinky.Metacarpal.NextJoint));
Hand.GrabAngle = CalculateGrabAngle(Hand);
Hand.GrabStrength = CalculateGrabStrength(Hand);
float TimeNow = GetGameTimeInSeconds();
switch (Hand.HandType)
{
case LEAP_HAND_LEFT:
{
if (LeftHandVisible)
{
Hand.VisibleTime = TimeNow - FirstSeenLeft;
}
break;
}
case LEAP_HAND_RIGHT:
{
if (RightHandVisible)
{
Hand.VisibleTime = TimeNow - FirstSeenRight;
}
break;
}
}
int FingerIndex = 0;
for (FLeapDigitData& Finger : Hand.Digits)
{
Finger.IsExtended = GetFingerStrength(Hand, FingerIndex) < 0.4;
FingerIndex++;
}
}
float FOpenXRToLeapWrapper::GetGameTimeInSeconds()
{
return CurrentWorld ? CurrentWorld->GetTimeSeconds() : 0.f;
}