implement USGHandTrackerComponent to allow easy retrieval or visualization of FXRHandTrackingState

This commit is contained in:
Mamadou Babaei
2026-02-24 10:42:41 +01:00
parent c4952f900d
commit cae50c0f6e
5 changed files with 444 additions and 0 deletions
+1
View File
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added a `USGHandTrackerComponent` to allow retrieval or visualization of `FXRHandTrackingState` without relying on low-level SenseGlove API or UE's generic `GetHandTrackingState` functionality. This is useful when developing a custom hand-interaction system using SenseGlove/UE OpenXR API or interfacing with third-party OpenXR-compatible plugins such as [VR Expansion Plugin (VRE)](https://vreue4.com/).
- A new `FSGDebugVirtualHand::Draw()` overload has been added to allow visualizing `FSGDebugGizmoSettings` directly. This is used internally by the new `USGHandTrackerComponent` to visualize its `FXRHandTrackingState` if `bVisualize` is enabled.
### Fixed
@@ -0,0 +1,181 @@
/**
* @file
*
* @author Mamadou Babaei <mamadou@senseglove.com>
*
* @section LICENSE
*
* (The MIT License)
*
* Copyright (c) 2020 - 2026 SenseGlove
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @section DESCRIPTION
*
*
*/
#include "SenseGlove/Components/SGHandTrackerComponent.h"
#include "SGDebug/SGDebugVirtualHand.h"
#include "SGTracking/SGXRTracker.h"
struct USGHandTrackerComponent::FImpl
{
/************************
* Static methods
************************/
/************************
* Member variables
************************/
FXRHandTrackingState HandTrackingState;
/************************
* Owner object
************************/
USGHandTrackerComponent* Owner;
/************************
* Constructor / Destructor
************************/
explicit FImpl(USGHandTrackerComponent* InOwner);
~FImpl();
/************************
* Default copy constructor & copy assignment operator
************************/
FImpl(const FImpl& Rhs) = default;
FImpl& operator=(const FImpl& Rhs) = default;
/************************
* Methods
************************/
void UpdateXRData();
void Visualize() const;
};
USGHandTrackerComponent::USGHandTrackerComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer),
Pimpl(TUniquePtr<FImpl, FImplDeleter>(new FImpl{this}, PimplDeleter))
{
PrimaryComponentTick.bTickEvenWhenPaused = false;
PrimaryComponentTick.bCanEverTick = true;
PrimaryComponentTick.bStartWithTickEnabled = true;
bTickInEditor = false;
bNeverNeedsRenderUpdate = true;
bAllowConcurrentTick = true;
bRight = true;
bVisualize = false;
VisualizationSettings.Length = 1.0f;
VisualizationSettings.XAxisColor = FColor::Red;
VisualizationSettings.YAxisColor = FColor::Green;
VisualizationSettings.ZAxisColor = FColor::Blue;
VisualizationSettings.bPersistentLines = false;
VisualizationSettings.LifeTimeModifier = 1.1f;
VisualizationSettings.DepthPriority = 0;
VisualizationSettings.Thickness = 0.15f;
}
void USGHandTrackerComponent::SetRight(const bool bInRight)
{
bRight = bInRight;
Pimpl->UpdateXRData();
}
void USGHandTrackerComponent::InitializeComponent()
{
Super::InitializeComponent();
Pimpl->UpdateXRData();
}
void USGHandTrackerComponent::BeginPlay()
{
Super::BeginPlay();
Pimpl->UpdateXRData();
}
void USGHandTrackerComponent::TickComponent(
const float DeltaTime, const ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
Pimpl->UpdateXRData();
if (IsVisualized())
{
Pimpl->Visualize();
}
}
const FXRHandTrackingState& USGHandTrackerComponent::GetHandTrackingState() const
{
return Pimpl->HandTrackingState;
}
USGHandTrackerComponent::FImpl::FImpl(USGHandTrackerComponent* InOwner)
: Owner(InOwner)
{
}
USGHandTrackerComponent::FImpl::~FImpl() = default;
void USGHandTrackerComponent::FImpl::UpdateXRData()
{
const EControllerHand Hand = Owner->IsRight() ? EControllerHand::Right : EControllerHand::Left;
FXRHandTrackingState State;
State.bValid = false;
const bool bGotHandTrackingState =
FSGXRTracker::GetHandTrackingState(
Owner, EXRSpaceType::UnrealWorldSpace, Hand, State);
(void) bGotHandTrackingState;
HandTrackingState = MoveTemp(State);
}
void USGHandTrackerComponent::FImpl::Visualize() const
{
const UWorld* World{Owner->GetWorld()};
if (!HandTrackingState.bValid)
{
return;
}
FSGDebugVirtualHand::Draw(World, HandTrackingState, Owner->VisualizationSettings);
}
void USGHandTrackerComponent::FImplDeleter::operator()(const FImpl* P) const
{
delete P;
}
@@ -0,0 +1,119 @@
/**
* @file
*
* @author Mamadou Babaei <mamadou@senseglove.com>
*
* @section LICENSE
*
* (The MIT License)
*
* Copyright (c) 2020 - 2026 SenseGlove
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @section DESCRIPTION
*
*
*/
#pragma once
#include "Components/SceneComponent.h"
#include "HeadMountedDisplayTypes.h"
#include "Templates/UniquePtr.h"
#include "SGSettings/SGVirtualHandSettings.h"
#include "SGHandTrackerComponent.generated.h"
class AActor;
UCLASS(Blueprintable, BlueprintType, ClassGroup=(SenseGlove), meta=(BlueprintSpawnableComponent))
class SENSEGLOVE_API USGHandTrackerComponent : public USceneComponent
{
GENERATED_UCLASS_BODY()
private:
/**
* Determines whether this component tracks the right or the left hand.
*/
UPROPERTY(EditAnywhere, Category="SenseGlove", meta=(AllowPrivateAccess="false"))
bool bRight;
/**
* If enabled, visualizes the XR hand-tracking data using debug gizmos.
*/
UPROPERTY(EditAnywhere, Category="SenseGlove", meta=(AllowPrivateAccess="false"))
bool bVisualize;
/**
* XR hand-tracking data will be visualized only if bVisualize is enabled.
*/
UPROPERTY(EditAnywhere, Category="SenseGlove",
meta=(AllowPrivateAccess="false",
EditCondition="bVisualize", EditConditionHides))
FSGDebugGizmoSettings VisualizationSettings;
private:
struct FImpl;
struct FImplDeleter
{
void operator()(const FImpl* P) const;
};
TUniquePtr<FImpl, FImplDeleter> Pimpl;
FImplDeleter PimplDeleter;
public:
FORCEINLINE bool IsLeft() const
{
return !IsRight();
}
FORCEINLINE bool IsRight() const
{
return bRight;
}
FORCEINLINE void SetRight(const bool bInRight);
FORCEINLINE bool IsVisualized() const
{
return bVisualize;
}
FORCEINLINE void SetVisualize(const bool bInVisualize)
{
bVisualize = bInVisualize;
}
public:
virtual void InitializeComponent() override;
virtual void BeginPlay() override;
virtual void TickComponent(float DeltaTime,
enum ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction) override;
public:
const FXRHandTrackingState& GetHandTrackingState() const;
};
@@ -0,0 +1,74 @@
/**
* @file
*
* @author Mamadou Babaei <mamadou@senseglove.com>
*
* @section LICENSE
*
* (The MIT License)
*
* Copyright (c) 2020 - 2026 SenseGlove
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @section DESCRIPTION
*
*
*/
#include "SGKismet/SGHandTrackerComponentKismetLibrary.h"
#include "SenseGlove/Components/SGHandTrackerComponent.h"
bool UHandTrackerComponentKismetLibrary::IsLeft(
const USGHandTrackerComponent* HandTrackerComponent)
{
return HandTrackerComponent->IsLeft();
}
bool UHandTrackerComponentKismetLibrary::IsRight(
const USGHandTrackerComponent* HandTrackerComponent)
{
return HandTrackerComponent->IsRight();
}
void UHandTrackerComponentKismetLibrary::SetRight(
USGHandTrackerComponent* HandTrackerComponent, const bool bInRight)
{
HandTrackerComponent->SetRight(bInRight);
}
bool UHandTrackerComponentKismetLibrary::IsVisualized(
const USGHandTrackerComponent* HandTrackerComponent)
{
return HandTrackerComponent->IsVisualized();
}
void UHandTrackerComponentKismetLibrary::SetVisualize(
USGHandTrackerComponent* HandTrackerComponent, const bool bInVisualize)
{
HandTrackerComponent->SetVisualize(bInVisualize);
}
const FXRHandTrackingState& UHandTrackerComponentKismetLibrary::GetHandTrackingState(
USGHandTrackerComponent* HandTrackerComponent)
{
return HandTrackerComponent->GetHandTrackingState();
}
@@ -0,0 +1,69 @@
/**
* @file
*
* @author Mamadou Babaei <mamadou@senseglove.com>
*
* @section LICENSE
*
* (The MIT License)
*
* Copyright (c) 2020 - 2026 SenseGlove
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @section DESCRIPTION
*
*
*/
#pragma once
#include "SGKismet/SGBlueprintFunctionLibrary.h"
#include "SGHandTrackerComponentKismetLibrary.generated.h"
class AActor;
class USGHandTrackerComponent;
UCLASS(meta=(ScriptName = "SenseGloveHandTrackerComponentKismetLibrary"))
class SENSEGLOVEKISMET_API UHandTrackerComponentKismetLibrary final : public USGBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, Category="SenseGlove | Components | Hand Tracker Component")
static bool IsLeft(const USGHandTrackerComponent* HandTrackerComponent);
UFUNCTION(BlueprintPure, Category="SenseGlove | Components | Hand Tracker Component")
static bool IsRight(const USGHandTrackerComponent* HandTrackerComponent);
UFUNCTION(BlueprintCallable, Category="SenseGlove | Components | Hand Tracker Component")
static void SetRight(UPARAM(ref) USGHandTrackerComponent* HandTrackerComponent, bool bInRight);
UFUNCTION(BlueprintPure, Category="SenseGlove | Components | Hand Tracker Component")
static bool IsVisualized(const USGHandTrackerComponent* HandTrackerComponent);
UFUNCTION(BlueprintCallable, Category="SenseGlove | Components | Hand Tracker Component")
static void SetVisualize(UPARAM(ref) USGHandTrackerComponent* HandTrackerComponent, bool bInVisualize);
UFUNCTION(BlueprintCallable, Category="SenseGlove | Components | Hand Tracker Component")
static const FXRHandTrackingState& GetHandTrackingState(
UPARAM(ref) USGHandTrackerComponent* HandTrackerComponent);
};