From cae50c0f6eb46c618b8d3b13906e40f9fc1d3924 Mon Sep 17 00:00:00 2001 From: Mamadou Babaei Date: Mon, 9 Feb 2026 14:40:54 +0100 Subject: [PATCH] implement USGHandTrackerComponent to allow easy retrieval or visualization of FXRHandTrackingState --- Handbook/src/appendix/changelog.md | 1 + .../Components/SGHandTrackerComponent.cpp | 181 ++++++++++++++++++ .../Components/SGHandTrackerComponent.h | 119 ++++++++++++ .../SGHandTrackerComponentKismetLibrary.cpp | 74 +++++++ .../SGHandTrackerComponentKismetLibrary.h | 69 +++++++ 5 files changed, 444 insertions(+) create mode 100644 Source/SenseGlove/Private/SenseGlove/Components/SGHandTrackerComponent.cpp create mode 100644 Source/SenseGlove/Public/SenseGlove/Components/SGHandTrackerComponent.h create mode 100644 Source/SenseGloveKismet/Private/SGKismet/SGHandTrackerComponentKismetLibrary.cpp create mode 100644 Source/SenseGloveKismet/Public/SGKismet/SGHandTrackerComponentKismetLibrary.h diff --git a/Handbook/src/appendix/changelog.md b/Handbook/src/appendix/changelog.md index 83e5cf98..b4f7df1a 100644 --- a/Handbook/src/appendix/changelog.md +++ b/Handbook/src/appendix/changelog.md @@ -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 diff --git a/Source/SenseGlove/Private/SenseGlove/Components/SGHandTrackerComponent.cpp b/Source/SenseGlove/Private/SenseGlove/Components/SGHandTrackerComponent.cpp new file mode 100644 index 00000000..ed889d56 --- /dev/null +++ b/Source/SenseGlove/Private/SenseGlove/Components/SGHandTrackerComponent.cpp @@ -0,0 +1,181 @@ +/** + * @file + * + * @author Mamadou Babaei + * + * @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(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; +} \ No newline at end of file diff --git a/Source/SenseGlove/Public/SenseGlove/Components/SGHandTrackerComponent.h b/Source/SenseGlove/Public/SenseGlove/Components/SGHandTrackerComponent.h new file mode 100644 index 00000000..f8d256c3 --- /dev/null +++ b/Source/SenseGlove/Public/SenseGlove/Components/SGHandTrackerComponent.h @@ -0,0 +1,119 @@ +/** +* @file + * + * @author Mamadou Babaei + * + * @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 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; +}; \ No newline at end of file diff --git a/Source/SenseGloveKismet/Private/SGKismet/SGHandTrackerComponentKismetLibrary.cpp b/Source/SenseGloveKismet/Private/SGKismet/SGHandTrackerComponentKismetLibrary.cpp new file mode 100644 index 00000000..b2d800e9 --- /dev/null +++ b/Source/SenseGloveKismet/Private/SGKismet/SGHandTrackerComponentKismetLibrary.cpp @@ -0,0 +1,74 @@ +/** + * @file + * + * @author Mamadou Babaei + * + * @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(); +} diff --git a/Source/SenseGloveKismet/Public/SGKismet/SGHandTrackerComponentKismetLibrary.h b/Source/SenseGloveKismet/Public/SGKismet/SGHandTrackerComponentKismetLibrary.h new file mode 100644 index 00000000..a09a3b23 --- /dev/null +++ b/Source/SenseGloveKismet/Public/SGKismet/SGHandTrackerComponentKismetLibrary.h @@ -0,0 +1,69 @@ +/** +* @file + * + * @author Mamadou Babaei + * + * @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); +}; \ No newline at end of file