bump the senseglove unreal engine plugin to v2.8.0

This commit is contained in:
Mamadou Babaei
2026-02-24 14:50:37 +01:00
parent 6695f712d9
commit 6dcfa95abc
854 changed files with 4030 additions and 2203 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
# #
# (The MIT License) # (The MIT License)
# #
# Copyright (c) 2020 - 2025 SenseGlove # Copyright (c) 2020 - 2026 SenseGlove
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
+12 -5
View File
@@ -3,7 +3,7 @@
# #
# (The MIT License) # (The MIT License)
# #
# Copyright (c) 2020 - 2025 SenseGlove # Copyright (c) 2020 - 2026 SenseGlove
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@@ -119,6 +119,12 @@ SG_PLUGIN_LICENSE_THIRD_PARTY_FILE := $(SG_PLUGIN_DIR)/LICENSE_THIRD_PARTY.m
SG_PLUGIN_PLANNED_FEATURES_COMPLETION_STATUS_FILE := $(SG_PLUGIN_DIR)/PLANNED_FEATURES_COMPLETION_STATUS.md SG_PLUGIN_PLANNED_FEATURES_COMPLETION_STATUS_FILE := $(SG_PLUGIN_DIR)/PLANNED_FEATURES_COMPLETION_STATUS.md
SG_PLUGIN_PLATFORM_SUPPORT_MATRIX_FILE := $(SG_PLUGIN_DIR)/PLATFORM_SUPPORT_MATRIX.md SG_PLUGIN_PLATFORM_SUPPORT_MATRIX_FILE := $(SG_PLUGIN_DIR)/PLATFORM_SUPPORT_MATRIX.md
################################################################################
# Rust/Cargo Settings
################################################################################
RUST_TOOLCHAIN_VERSION := 1.93.0
################################################################################ ################################################################################
# Version and Build Information # Version and Build Information
################################################################################ ################################################################################
@@ -365,17 +371,18 @@ clean:
.PHONY: install-build-deps .PHONY: install-build-deps
install-build-deps: install-build-deps:
@echo "Installing build dependencies if they have not already been installed..." @echo "Installing build dependencies if they have not already been installed..."
@cargo install mdbook --version "0.4.49" --locked @rustup install $(RUST_TOOLCHAIN_VERSION)
@rustup override set $(RUST_TOOLCHAIN_VERSION)
@cargo install mdbook --version "0.5.2" --locked
@cargo install xq --vers "^0.4" --locked @cargo install xq --vers "^0.4" --locked
@cargo install mdbook-alerts --vers "^0.7" --locked @cargo install mdbook-pdf --vers "0.1.13" --locked
@cargo install mdbook-pdf --vers "0.1.11" --locked
ifeq ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX)) ifeq ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
@echo "Installing mdbook-pdf-outline..." @echo "Installing mdbook-pdf-outline..."
@python -m venv $(VIRTUAL_ENV) \ @python -m venv $(VIRTUAL_ENV) \
&& . $(SG_LINUX_PYTHON_VENV_BIN_DIR)/activate \ && . $(SG_LINUX_PYTHON_VENV_BIN_DIR)/activate \
&& pip install mdbook-pdf-outline 2>/dev/null && pip install mdbook-pdf-outline 2>/dev/null
endif # ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX)) endif # ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
@cargo install --git https://github.com/Michael-F-Bryan/mdbook-epub --rev cac03b7f4b151f106f7f05b13da4c33fc098dd2c --locked @cargo install --git https://github.com/Michael-F-Bryan/mdbook-epub --rev 21a1c8134134201a2d555313447c96e56e2a8996 --locked
################################################################################ ################################################################################
# Make Targets - mdBook # Make Targets - mdBook
@@ -5,7 +5,7 @@
# #
# (The MIT License) # (The MIT License)
# #
# Copyright (c) 2020 - 2025 SenseGlove # Copyright (c) 2020 - 2026 SenseGlove
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@@ -33,8 +33,8 @@ fi
readonly JSON_INPUT=$(cat) readonly JSON_INPUT=$(cat)
readonly CONTEXT=$(echo "${JSON_INPUT}" | xq '.[0]') readonly CONTEXT=$(printf '%s' "${JSON_INPUT}" | xq '.[0]')
readonly BOOK=$(echo "${JSON_INPUT}" | xq '.[1]') readonly BOOK=$(printf '%s' "${JSON_INPUT}" | xq '.[1]')
escape_string() { escape_string() {
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
@@ -63,8 +63,8 @@ SG_PLUGIN_VERSION_PATCH_ESCAPED=$(escape_string "${SG_PLUGIN_VERSION_PATCH}")
SG_BUILD_HOST_ESCAPED=$(escape_string "${SG_BUILD_HOST}") SG_BUILD_HOST_ESCAPED=$(escape_string "${SG_BUILD_HOST}")
SG_BUILD_TIME_ESCAPED=$(escape_string "${SG_BUILD_TIME}") SG_BUILD_TIME_ESCAPED=$(escape_string "${SG_BUILD_TIME}")
PARSED_BOOK=$(echo "${BOOK}" | xq " PARSED_BOOK=$(printf '%s' "${BOOK}" | xq "
.sections[] |= ( .items[] |= (
if has(\"Chapter\") then if has(\"Chapter\") then
.Chapter.content |= .Chapter.content |=
walk(if type == \"string\" then walk(if type == \"string\" then
@@ -103,4 +103,4 @@ PARSED_BOOK=$(echo "${BOOK}" | xq "
#echo "BOOK: ${BOOK}" >&2 #echo "BOOK: ${BOOK}" >&2
#echo "PARSED_BOOK: ${PARSED_BOOK}" >&2 #echo "PARSED_BOOK: ${PARSED_BOOK}" >&2
echo "${PARSED_BOOK}" | xq '.' printf '%s' "${PARSED_BOOK}"
+2 -6
View File
@@ -3,7 +3,7 @@
# #
# (The MIT License) # (The MIT License)
# #
# Copyright (c) 2020 - 2025 SenseGlove # Copyright (c) 2020 - 2026 SenseGlove
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy # Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal # of this software and associated documentation files (the "Software"), to deal
@@ -28,7 +28,6 @@
authors = ["Mamadou Babaei"] authors = ["Mamadou Babaei"]
description = "Welcome to the SenseGlove Unreal Engine documentation!" description = "Welcome to the SenseGlove Unreal Engine documentation!"
language = "en" language = "en"
multilingual = false
src = "src" src = "src"
text-direction = "ltr" text-direction = "ltr"
title = "The SenseGlove Unreal Engine Handbook" title = "The SenseGlove Unreal Engine Handbook"
@@ -43,8 +42,6 @@ use-default-preprocessors = true
[preprocessor.links] [preprocessor.links]
[preprocessor.alerts]
[preprocessor.senseglove] [preprocessor.senseglove]
after = [ "index", "links" ] after = [ "index", "links" ]
command = "sh ./bin/sg-preprocessor.sh" command = "sh ./bin/sg-preprocessor.sh"
@@ -59,10 +56,9 @@ footnote-backrefs = true
additional-css = ["styles/custom.css"] additional-css = ["styles/custom.css"]
additional-js = [] additional-js = []
cname = "unreal.docs.senseglove.com" cname = "unreal.docs.senseglove.com"
copy-fonts = true
default-theme = "navy" default-theme = "navy"
git-repository-url = "https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal" git-repository-url = "https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal"
git-repository-icon = "fa-git" git-repository-icon = "fab-git"
input-404 = "404.md" input-404 = "404.md"
mathjax-support = false mathjax-support = false
no-section-label = false no-section-label = false
+4 -3
View File
@@ -73,13 +73,14 @@ Welcom to the SenseGlove Unreal Engine Handbook!
# 🛠️ Advanced Topics # 🛠️ Advanced Topics
- [Safe Glove Access in Blueprint](advanced-topics/safe-glove-access-blueprint/README.md) - [Safe Glove Access in Blueprint](advanced-topics/safe-glove-access-blueprint/README.md)
- [Roll Your Own Hand Manipulation System](advanced-topics/roll-your-own-hand-manipulation-system/README.md)
- [SGPawn Events](advanced-topics/roll-your-own-hand-manipulation-system/sgpawn-events-puppeteer-puppet-architecture/README.md)
- [SGHandTrackerComponent](advanced-topics/roll-your-own-hand-manipulation-system/sghandtrackercomponent/README.md)
- [SGHapticsComponent](advanced-topics/roll-your-own-hand-manipulation-system/sghapticscomponent/README.md)
- [OpenXR](advanced-topics/openxr/README.md) - [OpenXR](advanced-topics/openxr/README.md)
- [Consuming FXRHandTrackingState](advanced-topics/openxr/consuming-fxrhandtrackingstate/README.md) - [Consuming FXRHandTrackingState](advanced-topics/openxr/consuming-fxrhandtrackingstate/README.md)
- [Blueprint](advanced-topics/openxr/consuming-fxrhandtrackingstate/blueprint.md) - [Blueprint](advanced-topics/openxr/consuming-fxrhandtrackingstate/blueprint.md)
- [C++](advanced-topics/openxr/consuming-fxrhandtrackingstate/cpp.md) - [C++](advanced-topics/openxr/consuming-fxrhandtrackingstate/cpp.md)
- [Consuming FXRMotionControllerData](advanced-topics/openxr/consuming-fxrmotioncontrollerdata/README.md)
- [Blueprint](advanced-topics/openxr/consuming-fxrmotioncontrollerdata/blueprint.md)
- [C++](advanced-topics/openxr/consuming-fxrmotioncontrollerdata/cpp.md)
- [Third-Party Integrations](advanced-topics/openxr/third-party-integrations/README.md) - [Third-Party Integrations](advanced-topics/openxr/third-party-integrations/README.md)
- [Third-Party Tutorials](advanced-topics/openxr/third-party-tutorials/README.md) - [Third-Party Tutorials](advanced-topics/openxr/third-party-tutorials/README.md)
@@ -1,37 +1,156 @@
# OpenXR # OpenXR
The SenseGlove Unreal Engine Plugin has provided OpenXR-compatible hand tracking by implementing `XR_EXT_hand_tracking` since `v2.1.0`. The SenseGlove Unreal Engine Plugin has provided OpenXR-compatible hand tracking by implementing `XR_EXT_hand_tracking` since [`v2.1.0`](../../appendix/changelog.html#210---2024-08-16).
Typically a user does not need to know anything about OpenXR to use the plugin, so this section of the handbook is for advanced users who are looking for a way to directly consume the OpenXR data coming directly from either a SenseGlove device or if enabled in the plugin settings from hand-tracking. Typically a user does not need to know anything about OpenXR to use the plugin, so this section of the handbook is for advanced users who are looking for a way to directly consume the OpenXR data coming directly from either a SenseGlove device or if enabled in the plugin settings from hand-tracking.
Since the SenseGlove Unreal Engine Plugin registers itself as an `OpenXRHandTracking` motion controller device it becomes a hand-tracking provider for Unreal Engine, thus the OpenXR data from SenseGlove could always be retrieved from the Unreal Engine's `IXTrackingSystem` with one caveat. The caveat is if another OpenXR-compatible hand-tracking plugin, e.g. Epic's own OpenXRHandTracking, is enabled simultaneously it's not guaranteed that the `FXRMotionControllerData` and `FXRHandTrackingState` structs retrieved from the `IXTrackingSystem::GetMotionControllerData()` and `IXTrackingSystem::GetHandTrackingState()` methods are coming from SenseGlove, as these methods return the first hand-tracking plugin they could find. Thus, SenseGlove provides its own implementation of `GetMotionControllerData()` and `GetHandTrackingState()` which guarantee the retrieved `FXRMotionControllerData` or `FXRHandTrackingState` are coming from the SenseGlove Unreal Engine Plugin; and this is the preferred way to that. Since the SenseGlove Unreal Engine Plugin registers itself as an `OpenXRHandTracking` device it becomes a hand-tracking provider for Unreal Engine, thus the OpenXR data from SenseGlove could always be retrieved from the Unreal Engine's `IXTrackingSystem` with two caveats:
> [!NOTE] - The first caveat is, if another OpenXR-compatible hand-tracking plugin, e.g. Epic's own OpenXRHandTracking, is enabled simultaneously it's not guaranteed that the `FXRHandTrackingState` struct retrieved from the `IXTrackingSystem::GetHandTrackingState()` method is coming from SenseGlove, as these methods return the first hand-tracking plugin they could find. Thus, SenseGlove provides its own implementation of `GetHandTrackingState()` which guarantees the retrieved `FXRHandTrackingState` is coming from the SenseGlove Unreal Engine Plugin; and this is the preferred way to that.
> In order to retrieve the latest `FXRMotionControllerState` available, The - The second caveat is, Unreal `IXTrackingSystem::GetHandTrackingState()` method is blind to SenseGlove's [wrist-tracking settings and offsets](../../plugin-configuration/plugin-settings/tracking/wrist-tracking/), therefore the returned `FXRHandTrackingState` retrieved using this engine function won't take into account the wrist-tracker offsets, and the hands end up at the wrong orientation or position. In contrast,
> SenseGlove Unreal Engine Plugin provides an alternative implementation for
> `IXTrackingSystem::GetMotionControllerState()` as well . However, since this
> method does not rely on the `OpenXRHandTracking` provider, it may become
> redundant. As a result, we might consider removing this functionality in
> future updates in favor of the one that Unreal Engine provides.
> [!IMPORTANT] > [!IMPORTANT]
> Unreal Engine versions `5.2`, `5.3`, and `5.4` are limited to > In order to retrieve the latest `FXRHandTrackingState` available, The
> `FXRMotionControllerData` since at the time of their release no > SenseGlove Unreal Engine Plugin provides an alternative implementation for
> `FXRHandTrackingState` was available. > `IXTrackingSystem::GetHandTrackingState()`, which guarantees the OpenXR
> Also please keep in mind that, while `FXRMotionControllerData` is pretty much > hand-tracking data is coming from a SenseGlove device and also takes into
> usable and functional in Unreal Engine `5.5`, it is recommended to utilize > account the SenseGlove's wrist-tracker offsets automatically.
> `FXRHandTrackingState` instead. This is because this version of UE has >
> deprecated `FXRMotionControllerData` in favor of the > This is the recommended approach over Unreal Engine's own
> `FXRMotionControllerState` and `FXRHandTrackingState` structs. Prior to > `IXTrackingSystem::GetHandTrackingState()` or you have to ensure the data
> version `5.5`, `FXRMotionControllerData` handled both motion controller and > received is coming from a SenseGlove device yourself, and also take into
> hand tracking data. From `5.5` onward, these responsibilities have been > account the SenseGlove's wrist-tracker settings and calculate the wrist
> separated into the two distinct structs, providing clearer and more > offsets either using one of the `SGHapticGlove::GetWristLocation()` variants,
> specialized handling of each. > or manually.
>
> In short `IXTrackingSystem::GetHandTrackingState()` does not respect the
> offsets from
> `Project Settings > SenseGlove > Tracking > Wrist-Tracking Settings`
> [!CAUTION]
> In order to retrieve the latest `FXRHandTrackingState` available, The
> SenseGlove Unreal Engine Plugin provides an alternative implementation for
> `IXTrackingSystem::GetHandTrackingState()`, which guarantees the OpenXR
> hand-tracking data is coming from a SenseGlove device and also takes into
> account the SenseGlove's wrist-tracker offsets automatically.
>
> This is the recommended approach over Unreal Engine's own
> `IXTrackingSystem::GetHandTrackingState()` or you have to ensure the data
> received is coming from a SenseGlove device yourself, and also take into
> account the SenseGlove's wrist-tracker settings and calculate the wrist
> offsets either using one of the `GetWristLocation()` variants, e.g.,
> `SGHandLayer::GetWristLocation()`, `SGHapticGlove::GetWristLocation()`, etc,
> or manually.
>
> In short `IXTrackingSystem::GetHandTrackingState()` does not respect the
> offsets from
> `Project Settings > SenseGlove > Tracking Settings > Wrist Tracking Settings`.
> [!TIP]
> Starting with version `v2.8.0`, the SenseGlove Unreal Engine Plugin provides
> a highly convenient, high-level abstraction that eliminates all of the manual
> steps described above. By using
> [`SGHandTrackerComponent`](../roll-your-own-hand-manipulation-system/sghandtrackercomponent/)
> you can simply add the component to your Pawn class (or any actor that
> requires hand-tracking data), configure its properties, and retrieve the
> tracking data with a single function call when needed.
>
> The SenseGlove UE Plugin automatically handles all required settings and
> offset calculations for your positional tracking hardware, whether you are
> using pure hand tracking or a SenseGlove device. It also provides an optional
> debug hand out of the box, allowing you to instantly visualize hand-tracking
> data without writing a single line of code.
>
> Furthermore, version `v2.8.0` ships with a companion component,
> [`SGHapticsComponent`](../roll-your-own-hand-manipulation-system/sghapticscomponent/),
> which makes it easy to add haptic feedback to your own custom or third-party
> hand manipulation systems.
> [!TIP]
> Sometimes, using `IXTrackingSystem::GetHandTrackingState()` is unavoidable,
> e.g., when using a third-party hand manipulation system such as
> [**VR Expansion Plugin (VRE)**](https://vreue4.com/), which internally relies
> on `IXTrackingSystem::GetHandTrackingState()` to retrieve the hand-tracking
> data. In that case SenseGlove provides methods such as
> `SGHandLayer::GetWristLocation()`, `SGHapticGlove::GetWristLocation()`, etc,
> which you can use to reliably calculate the wrist offsets:
>
> ```cpp
> // Get the OpenXR hand-tracking data for the right hand
> FXRHandTrackingState HandTrackingState;
> IXTrackingSystem::GetHandTrackingState(
> GetWorld(),
> EXRSpaceType::UnrealWorldSpace,
> EControllerHand::Right,
> HandTrackingState);
>
> // Return if the struct data is invalid!
> if (!bGotHandTrackingState || !HandTrackingState.bValid)
> {
> return;
> }
>
> // Return if the device is not being tracked!
> if (HandTrackingState.TrackingStatus == ETrackingStatus::NotTracked)
> {
> return;
> }
>
> // Ensure that HandTrackingState.HandKeyLocations has the location data
> // for 26 joints!
> if (!ensureAlwaysMsgf(HandTrackingState.HandKeyLocations.Num()
> == EHandKeypointCount,
> TEXT("Invalid HandKeyLocations count!")))
> {
> return;
> }
>
> // Ensure that HandTrackingState.HandKeyRotations has the rotation data
> // for 26 joints!
> if (!ensureAlwaysMsgf(HandTrackingState.HandKeyRotations.Num()
> == EHandKeypointCount,
> TEXT("Invalid HandKeyRotations count!")))
> {
> return;
> }
>
> {
> // FQuat variant of FSGHandLayer::GetWristLocation()
> // Get the OpenXR hand-tracking data with the correct wrist offsets for
> // Meta Quest 3 Controllers
> FVector WristLocation;
> FQuat WristRotation;
> FSGHandLayer::GetWristLocation(
> true, // true for a right-handed glove and false for a left-handed one
> HandTrackingState.HandKeyLocations[0], // 0 is the Wrist joint location
> HandTrackingState.HandKeyRotations[0], // 0 is the wrist joint rotation
> ESGPositionalTrackingHardware::Quest3Controller,
> WristLocation, WristRotation);
>
> // WristLocation and WristRotation variables now contain the correct
> // OpenXR hand-tracking data with the wrist offsets applied correctly
> }
>
> {
> // FRotator variant of FSGHandLayer::GetWristLocation()
> // Get the OpenXR hand-tracking data with the correct wrist offsets for
> // Meta Quest 3 Controllers
> FVector WristLocation;
> FRotator WristRotation;
> FSGHandLayer::GetWristLocation(
> true, // true for a right-handed glove and false for a left-handed one
> HandTrackingState.HandKeyLocations[0], // 0 is the Wrist joint location
> HandTrackingState.HandKeyRotations[0].Rotator, // 0 is the wrist joint rotation
> ESGPositionalTrackingHardware::Quest3Controller,
> WristLocation, WristRotation);
>
> // WristLocation and WristRotation variables now contain the correct
> // OpenXR hand-tracking data with the wrist offsets applied correctly
> }
> ```
In the next sections we'll see: In the next sections we'll see:
- [How we can directly consume the `FXRMotionControllerData`](consuming-fxrmotioncontrollerdata/) on UE `5.2`, `5.3`, `5.4`, and `5.5` to draw and animate debug virtual hands in both [Blueprint](consuming-fxrmotioncontrollerdata/blueprint.md) and [C++](consuming-fxrmotioncontrollerdata/cpp.md).
- [How we can directly consume the `FXRHandTrackingState`](consuming-fxrhandtrackingstate/) on UE `5.5` to draw and animate debug virtual hands in both [Blueprint](consuming-fxrhandtrackingstate/blueprint.md) and [C++](consuming-fxrhandtrackingstate/cpp.md). - [How we can directly consume the `FXRHandTrackingState`](consuming-fxrhandtrackingstate/) on UE `5.5` to draw and animate debug virtual hands in both [Blueprint](consuming-fxrhandtrackingstate/blueprint.md) and [C++](consuming-fxrhandtrackingstate/cpp.md).
- [The Consuming OpenXR Hand-Tracking Data tutorial series](third-party-tutorials/) provides a comprehensive introduction to virtual reality, OpenXR hand-tracking, and gesture detection in Unreal Engine. Additionally, this tutorial series covers procedural virtual hand mesh animation using the OpenXR hand-tracking data. - [The Consuming OpenXR Hand-Tracking Data tutorial series](third-party-tutorials/) provides a comprehensive introduction to virtual reality, OpenXR hand-tracking, and gesture detection in Unreal Engine. Additionally, this tutorial series covers procedural virtual hand mesh animation using the OpenXR hand-tracking data.
@@ -1,18 +1,18 @@
# Consuming FXRHandTrackingState # Consuming FXRHandTrackingState
> [!IMPORTANT] > [!TIP]
> Unreal Engine versions `5.2`, `5.3`, and `5.4` are limited to > Starting with version `v2.8.0`, a highly convenient high-level approach for
> `FXRMotionControllerData` since at the time of their release no > retrieving `FXRHandTrackingState` has been introduced. It allows you to obtain
> `FXRHandTrackingState` was available. > OpenXR hand-tracking data without worrying about the caveats mentioned in the
> Also please keep in mind that, while `FXRMotionControllerData` is pretty much > [`main OpenXR section`](../).
> usable and functional in Unreal Engine `5.5`, it is recommended to utilize >
> `FXRHandTrackingState` instead. This is because this version of UE has > [`SGHandTrackerComponent`](../../roll-your-own-hand-manipulation-system/sghandtrackercomponent/)
> deprecated `FXRMotionControllerData` in favor of the > abstracts these complexities away and provides correctly adjusted
> `FXRMotionControllerState` and `FXRHandTrackingState` structs. Prior to > hand-tracking data with a single function call.
> version `5.5`, `FXRMotionControllerData` handled both motion controller and >
> hand-tracking data. From `5.5` onward, these responsibilities have been > This version also introduces a companion
> separated into the two distinct structs, providing clearer and more > [`SGHapticsComponent`](../../roll-your-own-hand-manipulation-system/sghapticscomponent/)
> specialized handling of each. > is also available for easily integrating haptic feedback.
Taking a closer look at the `FXRHandTrackingState` declaration inside the Unreal Engine's `HeadMountedDisplay` module at [`Engine/Source/Runtime/HeadMountedDisplay/Public/HeadMountedDisplayTypes.h`](https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/HeadMountedDisplay/Public/HeadMountedDisplayTypes.h), figuring out the data structure might not seem very straightforward: Taking a closer look at the `FXRHandTrackingState` declaration inside the Unreal Engine's `HeadMountedDisplay` module at [`Engine/Source/Runtime/HeadMountedDisplay/Public/HeadMountedDisplayTypes.h`](https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/HeadMountedDisplay/Public/HeadMountedDisplayTypes.h), figuring out the data structure might not seem very straightforward:
@@ -1,21 +1,21 @@
# Consuming FXRHandTrackingState in Blueprint # Consuming FXRHandTrackingState in Blueprint
> [!IMPORTANT]
> Unreal Engine versions `5.2`, `5.3`, and `5.4` are limited to
> `FXRMotionControllerData` since at the time of their release no
> `FXRHandTrackingState` was available.
> Also please keep in mind that, while `FXRMotionControllerData` is pretty much
> usable and functional in Unreal Engine `5.5`, it is recommended to utilize
> `FXRHandTrackingState` instead. This is because this version of UE has
> deprecated `FXRMotionControllerData` in favor of the
> `FXRMotionControllerState` and `FXRHandTrackingState` structs. Prior to
> version `5.5`, `FXRMotionControllerData` handled both motion controller and
> hand tracking data. From `5.5` onward, these responsibilities have been
> separated into the two distinct structs, providing clearer and more
> specialized handling of each.
Before continuing this section, please ensure you've studied the [Consuming FXRHandTrackingState](./) section, first. Before continuing this section, please ensure you've studied the [Consuming FXRHandTrackingState](./) section, first.
> [!TIP]
> Starting with version `v2.8.0`, a highly convenient high-level approach for
> retrieving `FXRHandTrackingState` has been introduced. It allows you to obtain
> OpenXR hand-tracking data without worrying about the caveats mentioned in the
> [`main OpenXR section`](../).
>
> [`SGHandTrackerComponent`](../../roll-your-own-hand-manipulation-system/sghandtrackercomponent/)
> abstracts these complexities away and provides correctly adjusted
> hand-tracking data with a single function call.
>
> This version also introduces a companion
> [`SGHapticsComponent`](../../roll-your-own-hand-manipulation-system/sghapticscomponent/)
> is also available for easily integrating haptic feedback.
## Drawing and Animating Virtual Hands ## Drawing and Animating Virtual Hands
1. Create a new Virtual Reality project based [the Unreal VR Template](https://dev.epicgames.com/documentation/en-us/unreal-engine/vr-template-in-unreal-engine). 1. Create a new Virtual Reality project based [the Unreal VR Template](https://dev.epicgames.com/documentation/en-us/unreal-engine/vr-template-in-unreal-engine).
@@ -62,4 +62,4 @@ Before continuing this section, please ensure you've studied the [Consuming FXRH
13. Now, go back to the `VRTemplateMap` and use the VR Preview button to run the game. If everything's done correctly, you should be able to see the virtual hands inside your VR simulation. 13. Now, go back to the `VRTemplateMap` and use the VR Preview button to run the game. If everything's done correctly, you should be able to see the virtual hands inside your VR simulation.
![FXRHandTrackingState animated debug virtual hands](../consuming-fxrhandtrackingstate-fxrmotioncontrollerdata-animated-debug-virtual-hands.gif "FXRHandTrackingState animated debug virtual hands") ![FXRHandTrackingState animated debug virtual hands](../consuming-fxrhandtrackingstate-animated-debug-virtual-hands.gif "FXRHandTrackingState animated debug virtual hands")
@@ -1,21 +1,21 @@
# Consuming FXRHandTrackingState in C++ # Consuming FXRHandTrackingState in C++
> [!IMPORTANT]
> Unreal Engine versions `5.2`, `5.3`, and `5.4` are limited to
> `FXRMotionControllerData` since at the time of their release no
> `FXRHandTrackingState` was available.
> Also please keep in mind that, while `FXRMotionControllerData` is pretty much
> usable and functional in Unreal Engine `5.5`, it is recommended to utilize
> `FXRHandTrackingState` instead. This is because this version of UE has
> deprecated `FXRMotionControllerData` in favor of the
> `FXRMotionControllerState` and `FXRHandTrackingState` structs. Prior to
> version `5.5`, `FXRMotionControllerData` handled both motion controller and
> hand tracking data. From `5.5` onward, these responsibilities have been
> separated into the two distinct structs, providing clearer and more
> specialized handling of each.
Before continuing this section, please ensure you've first studied the [Consuming FXRHandTrackingState](./) section. Before continuing this section, please ensure you've first studied the [Consuming FXRHandTrackingState](./) section.
> [!TIP]
> Starting with version `v2.8.0`, a highly convenient high-level approach for
> retrieving `FXRHandTrackingState` has been introduced. It allows you to obtain
> OpenXR hand-tracking data without worrying about the caveats mentioned in the
> [`main OpenXR section`](../).
>
> [`SGHandTrackerComponent`](../../roll-your-own-hand-manipulation-system/sghandtrackercomponent/)
> abstracts these complexities away and provides correctly adjusted
> hand-tracking data with a single function call.
>
> This version also introduces a companion
> [`SGHapticsComponent`](../../roll-your-own-hand-manipulation-system/sghapticscomponent/)
> is also available for easily integrating haptic feedback.
## Drawing and Animating Virtual Hands ## Drawing and Animating Virtual Hands
1. Create a new Virtual Reality project based [the Unreal VR Template](https://dev.epicgames.com/documentation/en-us/unreal-engine/vr-template-in-unreal-engine). 1. Create a new Virtual Reality project based [the Unreal VR Template](https://dev.epicgames.com/documentation/en-us/unreal-engine/vr-template-in-unreal-engine).
@@ -256,4 +256,4 @@ void ADebugPawn::DrawHand(const EControllerHand Hand) const
15. Now, rebuild the source code and go back to the `VRTemplateMap`, then use the VR Preview button to run the game. If everything's done correctly, you should be able to see the virtual hands inside your VR simulation. 15. Now, rebuild the source code and go back to the `VRTemplateMap`, then use the VR Preview button to run the game. If everything's done correctly, you should be able to see the virtual hands inside your VR simulation.
![FXRHandTrackingState animated debug virtual hands](../consuming-fxrhandtrackingstate-fxrmotioncontrollerdata-animated-debug-virtual-hands.gif "FXRHandTrackingState animated debug virtual hands") ![FXRHandTrackingState animated debug virtual hands](../consuming-fxrhandtrackingstate-animated-debug-virtual-hands.gif "FXRHandTrackingState animated debug virtual hands")
@@ -1,277 +0,0 @@
# Consuming FXRMotionControllerData
> [!IMPORTANT]
> Unreal Engine versions `5.2`, `5.3`, and `5.4` are limited to
> `FXRMotionControllerData` since at the time of their release no
> `FXRHandTrackingState` was available.
> Also please keep in mind that, while `FXRMotionControllerData` is pretty much
> usable and functional in Unreal Engine `5.5`, it is recommended to utilize
> `FXRHandTrackingState` instead. This is because this version of UE has
> deprecated `FXRMotionControllerData` in favor of the
> `FXRMotionControllerState` and `FXRHandTrackingState` structs. Prior to
> version `5.5`, `FXRMotionControllerData` handled both motion controller and
> hand tracking data. From `5.5` onward, these responsibilities have been
> separated into the two distinct structs, providing clearer and more
> specialized handling of each.
Taking a closer look at the `FXRMotionControllerData` declaration inside the Unreal Engine's `HeadMountedDisplay` module at [`Engine/Source/Runtime/HeadMountedDisplay/Public/HeadMountedDisplayTypes.h`](https://github.com/EpicGames/UnrealEngine/blob/5.5/Engine/Source/Runtime/HeadMountedDisplay/Public/HeadMountedDisplayTypes.h), figuring out the data structure might not seem very straightforward:
```cpp
USTRUCT(BlueprintType)
struct FXRMotionControllerData
{
GENERATED_USTRUCT_BODY();
UPROPERTY(BlueprintReadOnly, Category = "XR")
bool bValid = false;
UPROPERTY(BlueprintReadOnly, Category = "XR")
FName DeviceName;
UPROPERTY(BlueprintReadOnly, Category = "XR")
FGuid ApplicationInstanceID;
UPROPERTY(BlueprintReadOnly, Category = "XR")
EXRVisualType DeviceVisualType = EXRVisualType::Controller;
UPROPERTY(BlueprintReadOnly, Category = "XR")
EControllerHand HandIndex = EControllerHand::Left;
UPROPERTY(BlueprintReadOnly, Category = "XR")
ETrackingStatus TrackingStatus = ETrackingStatus::NotTracked;
// Vector representing an object being held in the player's hand
UPROPERTY(BlueprintReadOnly, Category = "XR")
FVector GripPosition = FVector(0.0f);
// Quaternion representing an object being held in the player's hand
UPROPERTY(BlueprintReadOnly, Category = "XR")
FQuat GripRotation = FQuat(EForceInit::ForceInitToZero);
// For handheld controllers, gives a vector for pointing at objects
UPROPERTY(BlueprintReadOnly, Category = "XR")
FVector AimPosition = FVector(0.0f);
// For handheld controllers, gives a quaternion for pointing at objects
UPROPERTY(BlueprintReadOnly, Category = "XR")
FQuat AimRotation = FQuat(EForceInit::ForceInitToZero);
// For handheld controllers, gives a vector for representing the hand
UPROPERTY(BlueprintReadOnly, Category = "XR")
FVector PalmPosition = FVector(0.0f);
// For handheld controllers, gives a quaternion for representing the hand
UPROPERTY(BlueprintReadOnly, Category = "XR")
FQuat PalmRotation = FQuat(EForceInit::ForceInitToZero);
// The indices of this array are the values of EHandKeypoint (Palm, Wrist, ThumbMetacarpal, etc).
UPROPERTY(BlueprintReadOnly, Category = "XR")
TArray<FVector> HandKeyPositions;
// The indices of this array are the values of EHandKeypoint (Palm, Wrist, ThumbMetacarpal, etc).
UPROPERTY(BlueprintReadOnly, Category = "XR")
TArray<FQuat> HandKeyRotations;
// The indices of this array are the values of EHandKeypoint (Palm, Wrist, ThumbMetacarpal, etc).
UPROPERTY(BlueprintReadOnly, Category = "XR")
TArray<float> HandKeyRadii;
UPROPERTY(BlueprintReadOnly, Category = "XR")
bool bIsGrasped = false;
};
```
Which on the Blueprint side it looks like this:
![FXRMotionControllerData Blueprint representation](consuming-fxrmotioncontrollerdata-blueprint-representation.png "FXRMotionControllerData Blueprint representation")
But, fear not, we've got you covered!
## FXRMotionControllerData in Unreal Engine
`FXRMotionControllerData` is a structure in Unreal Engine designed to hold detailed information about the state of a motion controller device at a given moment. This structure is essential for handling motion controller inputs in virtual reality (VR) applications, providing the necessary data to accurately track and represent the user's hand movements and actions within the virtual environment.
### Structure Members of FXRMotionControllerData
- **bValid**
- **Description**: A boolean flag indicating whether the data is valid or not.
- **Usage**: This is used to check if the motion controller data is correctly initialized and can be used for further processing.
- **DeviceName**
- **Type**: `FName`
- **Description**: The name of the device.
- **Usage**: Identifies which motion controller device the data is coming from, useful when multiple devices are in use.
- **ApplicationInstanceID**
- **Type**: `FString`
- **Description**: A unique identifier for the application instance.
- **Usage**: Helps in differentiating data from different instances of an application, ensuring the correct instance processes the data.
- **DeviceVisualType**
- **Type**: `EXRVisualType`
- **Description**: Enum specifying the visual type of the device (e.g., controller, hand).
- **Usage**: Used to differentiate between various motion controller devices or hand-tracking representations for rendering and interaction purposes.
- **HandIndex**
- **Type**: `EControllerHand`
- **Description**: Enum indicating which hand is being tracked (left or right).
- **Usage**: Helps identify whether the motion data pertains to the left or right hand, essential for hand-specific actions or interactions.
- **TrackingStatus**
- **Type**: `EXRTrackingStatus`
- **Description**: Enum indicating the tracking status of the motion controller.
- **Usage**: Shows whether the controller is being tracked accurately, with possible statuses like `Tracked`, `NotTracked`, etc.
- **GripPosition**
- **Type**: `FVector`
- **Description**: The position of the grip in world coordinates.
- **Usage**: Provides the 3D coordinates of the controller's grip, essential for positioning the virtual representation of the controller.
- **GripRotation**
- **Type**: `FQuat`
- **Description**: The rotation of the grip in world coordinates.
- **Usage**: Provides the orientation of the controller's grip, allowing for accurate rotation and alignment in the virtual space.
- **AimPosition**
- **Type**: `FVector`
- **Description**: The position of the aim point in world coordinates.
- **Usage**: Specifies where the controller is aiming, useful for aiming or pointing actions.
- **AimRotation**
- **Type**: `FQuat`
- **Description**: The rotation of the aim point in world coordinates.
- **Usage**: Determines the orientation of the aim direction, important for actions like shooting or selecting objects in VR.
- **PalmPosition**
- **Type**: `FVector`
- **Description**: The position of the palm in world coordinates.
- **Usage**: Provides the 3D location of the palm, important for determining hand gestures or interactions in VR.
- **PalmRotation**
- **Type**: `FQuat`
- **Description**: The rotation of the palm in world coordinates.
- **Usage**: Defines the orientation of the palm, crucial for hand-based interaction accuracy and realism in VR experiences.
- **HandKeyPositions**
- **Type**: `TArray<FVector>`
- **Description**: An array of vectors representing key positions of the hand.
- **Usage**: Provides detailed positions of key points on the hand, useful for precise hand tracking and interaction.
- **HandKeyRotations**
- **Type**: `TArray<FQuat>`
- **Description**: An array of quaternions representing key rotations of the hand.
- **Usage**: Complements the hand key positions with rotational data, ensuring accurate representation of hand movements.
- **HandKeyRadii**
- **Type**: `TArray<float>`
- **Description**: An array of floats representing the radii of key points of the hand.
- **Usage**: Gives the size of the hand key points, aiding in collision detection and interaction fidelity.
- **bIsGrasped**
- **Type**: `bool`
- **Description**: A boolean indicating whether the controller is currently grasping an object.
- **Usage**: Determines if the user is holding something, affecting interactions and animations.
### Organization of FXRMotionControllerData
The structure is organized to encapsulate all relevant data needed for hand and motion controller tracking in a coherent and accessible manner. Boolean flags `bValid` and `bIsGrasped` provide quick checks on the state of the controller data. Identifiers `DeviceName` and `ApplicationInstanceID` ensure the correct association of data. Positional and rotational data `GripPosition`, `GripRotation`, `AimPosition`, and `AimRotation` offer precise tracking of the controller's movement. Arrays `HandKeyPositions`, `HandKeyRotations`, and `HandKeyRadii` allow detailed hand tracking, which is critical for immersive VR experiences. Lastly, the tracking status `TrackingStatus` informs the system of the reliability of the data being processed and whether the motion controller is actively being tracked or it's inactive at the moment.
### Processing the Data for Drawing and Animating a Virtual Hand
In order to draw and animate a virtual hand in real-time whether the data is coming from hand-tracking or a SenseGlove device, we could consume the data from the `HandKeyPositions` and `HandKeyRotations` fields of the `FXRMotionControllerData` struct.
Both `HandKeyPositions` and `HandKeyRotations` contain 26 elements as defined by OpenXR's [`XR_HAND_JOINT_COUNT_EXT`](https://registry.khronos.org/OpenXR/specs/1.1/man/html/XR_HAND_JOINT_COUNT_EXT.html) and [`XrHandJointLocationsEXT`](https://registry.khronos.org/OpenXR/specs/1.0/man/html/XrHandJointLocationsEXT.html), etc.
Unreal Engine also provides an enum called `EHandKeypoint` naming the 26 joints, and the equivalent of `XR_HAND_JOINT_COUNT_EXT` as `EHandKeypointCount` inside [`Engine/Source/Runtime/HeadMountedDisplay/Public/HeadMountedDisplayTypes.h`](https://github.com/EpicGames/UnrealEngine/blob/5.5/Engine/Source/Runtime/HeadMountedDisplay/Public/HeadMountedDisplayTypes.h) as follows:
```cpp
/**
* Transforms that are tracked on the hand.
* Matches the enums from WMR to make it a direct mapping
*/
UENUM(BlueprintType)
enum class EHandKeypoint : uint8
{
Palm,
Wrist,
ThumbMetacarpal,
ThumbProximal,
ThumbDistal,
ThumbTip,
IndexMetacarpal,
IndexProximal,
IndexIntermediate,
IndexDistal,
IndexTip,
MiddleMetacarpal,
MiddleProximal,
MiddleIntermediate,
MiddleDistal,
MiddleTip,
RingMetacarpal,
RingProximal,
RingIntermediate,
RingDistal,
RingTip,
LittleMetacarpal,
LittleProximal,
LittleIntermediate,
LittleDistal,
LittleTip
};
const int32 EHandKeypointCount = static_cast<int32>(EHandKeypoint::LittleTip) + 1;
```
So, getting the any joint's position or rotation is as easy as casting the enum value and passing it as the array index.
```cpp
FXRMotionControllerData MotionControllerData;
const bool bGotMotionControllerData = FSGXRTracker::GetMotionControllerData(
GetWorld(), EControllerHand::Left, MotionControllerData);
// Return if the struct data is invalid!
if (!bGotMotionControllerData || !MotionControllerData.bValid)
{
return;
}
// Return if the device is not being tracked!
if (MotionControllerData.TrackingStatus == ETrackingStatus::NotTracked)
{
return;
}
// Ensure that MotionControllerData.DeviceVisualType is a hand!
if (!ensureAlwaysMsgf(MotionControllerData.DeviceVisualType
== EXRVisualType::Hand,
TEXT("Invalid DeviceVisualType type!")))
{
}
// Ensure that MotionControllerData.HandKeyPositions has the position data
// for 26 joints!
if (!ensureAlwaysMsgf(MotionControllerData.HandKeyPositions.Num()
== EHandKeypointCount,
TEXT("Invalid HandKeyPositions count!")))
{
return;
}
// Ensure that MotionControllerData.HandKeyRotations has the rotation data
// for 26 joints!
if (!ensureAlwaysMsgf(MotionControllerData.HandKeyRotations.Num()
== EHandKeypointCount,
TEXT("Invalid HandKeyRotations count!")))
{
return;
}
static constexpr int32 PalmIndex = static_cast<int32>(EHandKeypoint::Palm);
const FVector& PalmPosition{
MotionControllerData.HandKeyPositions[PalmIndex]
};
const FRotator& PalmRotation{
MotionControllerData.HandKeyRotations[PalmIndex].Rotator()
};
```
The equivalent Blueprint code for the above looks something like this:
![Get a joint position and rotation from FXRMotionControllerData in Blueprint](consuming-fxrmotioncontrollerdata-blueprint-get-joint-position-rotation.png "Get a joint position and rotation from FXRMotionControllerData in Blueprint")
OK, now that we've got a glimpse of how the virtual hand's joint data could be processed we are going to draw and animate a virtual hand in both [Blueprint](blueprint.md) and [C++](cpp.md) in the upcoming sections.
@@ -1,65 +0,0 @@
# Consuming FXRMotionControllerData in Blueprint
> [!IMPORTANT]
> Unreal Engine versions `5.2`, `5.3`, and `5.4` are limited to
> `FXRMotionControllerData` since at the time of their release no
> `FXRHandTrackingState` was available.
> Also please keep in mind that, while `FXRMotionControllerData` is pretty much
> usable and functional in Unreal Engine `5.5`, it is recommended to utilize
> `FXRHandTrackingState` instead. This is because this version of UE has
> deprecated `FXRMotionControllerData` in favor of the
> `FXRMotionControllerState` and `FXRHandTrackingState` structs. Prior to
> version `5.5`, `FXRMotionControllerData` handled both motion controller and
> hand tracking data. From `5.5` onward, these responsibilities have been
> separated into the two distinct structs, providing clearer and more
> specialized handling of each.
Before continuing this section, please ensure you've studied the [Consuming FXRMotionControllerData](./) section, first.
## Drawing and Animating Virtual Hands
1. Create a new Virtual Reality project based [the Unreal VR Template](https://dev.epicgames.com/documentation/en-us/unreal-engine/vr-template-in-unreal-engine).
2. Make sure the [SenseGlove UnrealEngine plugin is installed and enabled](/getting-started/installation.md) inside your new project.
![Enabling the SenseGlove Unreal Engine Plugin](enabling-senseglove-unrealp-engine-plugin.png "Enabling the SenseGlove Unreal Engine Plugin")
3. You could use either hand-tracking or a SenseGlove device as the input data, or both of the inside the same project. Whether you would like to use hand-tracking or a SenseGlove device, please make sure the required steps are taken for each of those first.
4. You could add the required Blueprint code for drawing virtual hands to either your Level Buleprint or the VRPawn Blueprint Class located at `/Content/VRTemplate/Blueprints/VRPawn`. In this guide we are going to add the code to our VRPawn.
5. Add a new function named `Draw Hand` with an input parameter of type `EController Hand` named `Hand`.
![Adding the Blueprint Draw Hand function](consuming-fxrmotioncontrollerdata-blueprint-add-draw-hand-function.png "Adding the Blueprint Draw Hand function")
6. Inside this function's event graph add a `Get Motion Controller Data` node from `SenseGlove > Tracking > XR Tracker > Get Motion Controller Data`.
![Adding the Get Motion Controller Data node](consuming-fxrmotioncontrollerdata-blueprint-add-get-motion-controller-data-node.png "Adding the Get Motion Controller Data node")
7. Then connect the functions `Hand` input parameter to the `Get Motion Controller Data`'s `Hand` input and right-click on the `OutMotionControllerData` parameter and use the `Break XRMotionControllerData` node to break the struct to it's fields.
![Breaking the XR Motion Controller Data node](consuming-fxrmotioncontrollerdata-blueprint-break-xr-motion-controller-data.png "Breaking the XR Motion Controller Data node")
8. After this, we need to perform data validation by checking the return status of the `Get Motion Controller Data` function and `FXRMotionControllerData`'s `Valid` field. Then, we check if the motion controller device is being tracked and indeed coming from a hand-tracking source. And, finally, we check whether we have the positions and rotations for exactly `26` joints or not.
![FXRMotionControllerData validation](consuming-fxrmotioncontrollerdata-blueprint-validation.png "FXRMotionControllerData validation")
9. OK, now it's time to draw the joints! If we check out the SenseGlove Debug module's draw option, we notice there are various ways to draw the debug virtual hand. Drawing a cube or a gizmo per joint, or draw the whole hand all at once by passing the retrieved `FXRMotionControllerData` to the `DebugVirtualHand::Draw` function! But, since the point of this tutorial is to learn how to consume the `FXRMotionControllerData` we ignore the last option. Between the debug cubes or gizmos, we are going to choose the gizmos since they better represent the rotations than the cubes.
![Some options for drawing a debug virtual hand](consuming-fxrmotioncontrollerdata-blueprint-debug-virtual-hand-draw-options.png "Some options for drawing a debug virtual hand")
10. In the last step inside the `Draw Hand` function, in order to draw a virtual hand with `26` joints, we have to first iterate through either of the `Hand Key Positions` or `Hand Key Rotations` arrays from the `FXRMotionControllerData` struct. Since we made sure both arrays have `26` elements before we reached this step, it's safe to just iterate over one and use the `Array Index` inside a `For Each Loop` or a `For Loop` to access the position and rotation of every joint. Then we use each array `Get (a ref)` method to access the position and rotation data inside the loop and call the `Draw` function from `SenseGlove > Debug > Gizmo` per every joint. Please note that there are two `Draw` functions and the only difference between the two is that one accepts an `FQuat` and the other a `FRotator` for its `Rotation` input parameter. In this case, we use the `FQuat` variant to avoid an extra conversion to `FRotator`. Also, please adjust the `Thickness` option for the `Settings` parameter from `1.0` to `0.2`, as the default value might be too thick for drawing a joint gizmo.
![Drawing a debug gizmo per each hand's joint](consuming-fxrmotioncontrollerdata-blueprint-draw-debug-gizmo-per-joint.png "Drawing a debug gizmo per each hand's joint")
11. Well, now the full implementation for the `Draw Hand` function insde the `VRPawn` should look something like this:
![VRPawn Draw Hand function final implementation](consuming-fxrmotioncontrollerdata-blueprint-draw-hand-function-implementation.png "VRPawn Draw Hand function final implementation")
12. Finally, go back to `VRPawn`'s event graph and the following code to the `Tick` event. Basically what we do here is call our newly implemented `Draw Hand` twice, once for each hand.
![Attempt to draw the left and right virtual hands every frame from the VR Pawn Tick event](consuming-fxrmotioncontrollerdata-blueprint-vrpawn-draw-virtual-hands-every-frame.png "Attempt to draw the left and right virtual hands every frame from the VR Pawn Tick event")
13. Now, go back to the `VRTemplateMap` and use the VR Preview button to run the game. If everything's done correctly, you should be able to see the virtual hands inside your VR simulation.
![FXRMotionControllerData animated debug virtual hands](../consuming-fxrhandtrackingstate-fxrmotioncontrollerdata-animated-debug-virtual-hands.gif "FXRMotionControllerData animated debug virtual hands")
@@ -1,266 +0,0 @@
# Consuming FXRMotionControllerData in C++
> [!IMPORTANT]
> Unreal Engine versions `5.2`, `5.3`, and `5.4` are limited to
> `FXRMotionControllerData` since at the time of their release no
> `FXRHandTrackingState` was available.
> Also please keep in mind that, while `FXRMotionControllerData` is pretty much
> usable and functional in Unreal Engine `5.5`, it is recommended to utilize
> `FXRHandTrackingState` instead. This is because this version of UE has
> deprecated `FXRMotionControllerData` in favor of the
> `FXRMotionControllerState` and `FXRHandTrackingState` structs. Prior to
> version `5.5`, `FXRMotionControllerData` handled both motion controller and
> hand tracking data. From `5.5` onward, these responsibilities have been
> separated into the two distinct structs, providing clearer and more
> specialized handling of each.
Before continuing this section, please ensure you've first studied the [Consuming FXRMotionControllerData](./) section.
## Drawing and Animating Virtual Hands
1. Create a new Virtual Reality project based [the Unreal VR Template](https://dev.epicgames.com/documentation/en-us/unreal-engine/vr-template-in-unreal-engine).
2. Make sure the [SenseGlove UnrealEngine plugin is installed and enabled](/getting-started/installation.md) inside your new project.
![Enabling the SenseGlove Unreal Engine Plugin](enabling-senseglove-unrealp-engine-plugin.png "Enabling the SenseGlove Unreal Engine Plugin")
3. You could use either hand-tracking or a SenseGlove device as the input data, or both of the inside the same project. Whether you would like to use hand-tracking or a SenseGlove device, please make sure the required steps are taken for each of those first.
4. From the `Tools` menu choose `New C++ class...`.
![Creating a new C++ class](consuming-fxrmotioncontrollerdata-cpp-new-class.png "Creating a new C++ class")
5. Choose the Unreal Engine's `APawn` class as the parent class for the new C++ pawn class.
![Choosing APawn as the parent class](consuming-fxrmotioncontrollerdata-cpp-add-class-choose-name.png "Choosing APawn as the parent class")
6. Name the new pawn class `DebugPawn`.
![Naming the new C++ class DebugPawn](consuming-fxrmotioncontrollerdata-cpp-add-class-choose-parent.png "Naming the new C++ class DebugPawn")
7. Since we have created a new C++ class, this converts the current Blueprint VRTemplateMap project to a C++ one. That's why the Unreal Editor will give us a few prompts regarding opening the project in the default IDE and rebuilding the code. It might be simpler to just close the editor, then rebuild the source code inside your favorite IDE, and then start the editor with the converted project again.
8. Find and open the VRPawn Blueprint Class located at `/Content/VRTemplate/Blueprints/VRPawn` inside the Blueprint Editor and from the `File` menu choose the `Reparent Blueprint` class.
![Reparenting the VRPawn Blueprint class](consuming-fxrmotioncontrollerdata-cpp-reparent.png "Reparenting the VRPawn Blueprint class")
9. In the new `Reparent blueprint` window choose `DebugPawn` as the new parent.
![Reparenting the VRPawn Blueprint class to ADebugPawn](consuming-fxrmotioncontrollerdata-cpp-reparent-debug-pawn.png "Reparenting the VRPawn Blueprint class to ADebugPawn")
10. By looking at the `Parent Class` label located under the Blueprint Editor window control buttons verify that the `ADebugPawn` class has been set as the new parent.
![Veifying whether the VRPawn Blueprint class set to ADebugPawn or not](consuming-fxrmotioncontrollerdata-cpp-verify-parent-class.png "Veifying whether the VRPawn Blueprint class set to ADebugPawn or not")
11. Locate the project's main Build file, in our case `VirtualHandCpp/Source/VirtualHandCpp/VirtualHandCpp.Build.cs` and add the `InputDevice`, `OpenXRHMD`, `SenseGloveBuildHacks`, `SenseGloveDebug`, `SenseGloveSettings`, and `SenseGloveTracking` modules as either a private or public dependency.
```csharp
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
public class VirtualHandCpp : ModuleRules
{
public VirtualHandCpp(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
PrivateDependencyModuleNames.AddRange(new string[]
{
"InputDevice",
"OpenXRHMD",
"SenseGloveBuildHacks",
"SenseGloveDebug",
"SenseGloveSettings",
"SenseGloveTracking"
});
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}
```
12. Locate the C++ header and source file for the `ADebugPawn` inside the project in your C++ IDE. In our case they are located at `VirtualHandCpp/Source/VirtualHandCpp/DebugPawn.h` and `VirtualHandCpp/Source/VirtualHandCpp/DebugPawn.cpp`.
13. Modify the `DebugPawn.h` header file to look like this:
```cpp
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "SGSettings/SGDebugGizmoSettings.h"
#include "DebugPawn.generated.h"
UCLASS()
class VIRTUALHANDCPP_API ADebugPawn : public APawn
{
GENERATED_BODY()
private:
// The virtual hand drawing settings.
UPROPERTY(EditDefaultsOnly, Category="DebugPawn",
meta=(AllowPrivateAccess="false"))
FSGDebugGizmoSettings HandDrawingSettings;
public:
// Sets default values for this pawn's properties
ADebugPawn();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
private:
// The method responsible for drawing a virtual hand.
void DrawHand(EControllerHand Hand) const;
};
```
14. Modify the `DebugPawn.cpp` implementation file to look like this:
```cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "DebugPawn.h"
#include "SGDebug/SGDebugGizmo.h"
#include "SGTracking/SGXRTracker.h"
// Sets default values
ADebugPawn::ADebugPawn()
{
// Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
// Set the default virtual hand drawing settings.
HandDrawingSettings = FSGDebugGizmoSettings{
1.0f,
FColor{255, 0, 0, 255},
FColor{0, 255, 0, 255},
FColor{0, 0, 255, 255},
false,
1.1f,
0,
0.2f,
};
}
// Called when the game starts or when spawned
void ADebugPawn::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void ADebugPawn::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
// Attempt at drawing the left/right virtual hands every frame.
DrawHand(EControllerHand::Left);
DrawHand(EControllerHand::Right);
}
// Called to bind functionality to input
void ADebugPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
}
void ADebugPawn::DrawHand(const EControllerHand Hand) const
{
// Get the world and cache it, if it's null we return early.
UWorld* World{GetWorld()};
if (!IsValid(World))
{
return;
}
FXRMotionControllerData MotionControllerData;
const bool bGotMotionControllerData = FSGXRTracker::GetMotionControllerData(
World, Hand, MotionControllerData);
// Return if the struct data is invalid!
if (!bGotMotionControllerData || !MotionControllerData.bValid)
{
return;
}
// Return if the device is not being tracked!
if (MotionControllerData.TrackingStatus == ETrackingStatus::NotTracked)
{
return;
}
// Ensure that MotionControllerData.DeviceVisualType is a hand!
if (!ensureAlwaysMsgf(MotionControllerData.DeviceVisualType
== EXRVisualType::Hand,
TEXT("Invalid DeviceVisualType type!")))
{
}
// Ensure that MotionControllerData.HandKeyPositions has the position data
// for 26 joints!
if (!ensureAlwaysMsgf(MotionControllerData.HandKeyPositions.Num()
== EHandKeypointCount,
TEXT("Invalid HandKeyPositions count!")))
{
return;
}
// Ensure that MotionControllerData.HandKeyRotations has the rotation data
// for 26 joints!
if (!ensureAlwaysMsgf(MotionControllerData.HandKeyRotations.Num()
== EHandKeypointCount,
TEXT("Invalid HandKeyRotations count!")))
{
return;
}
// Iterate over the hand joint positions and rotations!
for (int32 JointIndex = 0; JointIndex < EHandKeypointCount; ++JointIndex)
{
const FVector& JointPosition{
MotionControllerData.HandKeyPositions[JointIndex]
};
const FQuat& JointRotation{
MotionControllerData.HandKeyRotations[JointIndex]
};
// Draw a single joint's gizmo!
// Please note that we could alternatively:
// Use FSGDebugCube::Draw() to draw a cube.
// Or use the FSGDebugVirtualHand::Draw() method and pass the
// MotionControllerData directly to draw the virtual hand
// all at once without iterating the joints. But, that's not
// goal of this tutorial.
FSGDebugGizmo::Draw(World, JointPosition, JointRotation, HandDrawingSettings);
}
}
```
15. Now, rebuild the source code and go back to the `VRTemplateMap`, then use the VR Preview button to run the game. If everything's done correctly, you should be able to see the virtual hands inside your VR simulation.
![FXRMotionControllerData animated debug virtual hands](../consuming-fxrhandtrackingstate-fxrmotioncontrollerdata-animated-debug-virtual-hands.gif "FXRMotionControllerData animated debug virtual hands")
@@ -41,7 +41,7 @@ As the SenseGlove Unreal Engine Plugin is fully OpenXR-compliant, it provides Op
> need to figure out how to manually apply the appropriate offsets within your > need to figure out how to manually apply the appropriate offsets within your
> chosen OpenXR hand interaction system. > chosen OpenXR hand interaction system.
> >
> For example, the VRE plugin provide similar configurations in their plugin's > For example, the VRE plugin provides similar configurations in their plugin's
> settings section. For more information refer to the > settings section. For more information refer to the
> [Changing Wrist-Tracker Offsets](#changing-wrist-tracker-offsets) section > [Changing Wrist-Tracker Offsets](#changing-wrist-tracker-offsets) section
> below. > below.
@@ -0,0 +1,41 @@
# Roll Your Own Customized Hand Manipulation and Interaction System
The default hand interaction system shipped with the **SenseGlove Unreal Engine Plugin** consists of various components, including `SGPawn`, `SGPlayerController`, `SGVirtualHandComponent`, `SGGrabComponent`, `SGTouchComponent`, and others. This system is very easy to get started with and is thoroughly documented throughout this handbook.
However, this simplicity comes at a cost: limited functionality. At SenseGlove, we prioritize usability and practicality. That said, developing a comprehensive hand interaction system that suits every possible use case is not an easy task. For example, projects such as the [VR Expansion Plugin (VRE)](https://vreue4.com/) — an Epic MegaGrants recipient — have been in development for over a decade, and development is still ongoing. Naturally, such depth also comes with a trade-off: a steep learning curve and reduced beginner-friendliness.
To bridge this gap and serve different groups of users, we provide the `SGPawn` system as a simple, intentionally limited, and beginner-friendly default solution that allows anyone to get up and running quickly.
At the same time, to support intermediate and advanced users, we have aimed for full OpenXR compatibility, opening the door to a wide range of advanced possibilities. Once enabled and loaded in Unreal Engine, the SenseGlove Unreal Engine Plugin registers itself as an `OpenXRHandTracking` provider. This makes it a fully compatible, drop-in replacement for Epics **OpenXRHandTracking** plugin.
As a result, [it can integrate seamlessly with any third-party system or plugin that consumes OpenXR hand-tracking data](../openxr/third-party-integrations/). Because the SenseGlove plugin is fully OpenXR-compliant, it provides hand-tracking data in the expected OpenXR format and becomes the active provider within Unreal. If your existing interaction system (for example, the VRE plugin) already relies on OpenXR hand-tracking, SenseGlove can function as a direct tracking source instead of a physical hand.
Furthermore, the SenseGlove OpenXR backend allows you to [develop and build your own hand interaction system from scratch](../openxr/third-party-tutorials/). This system can operate either via standard OpenXR hand-tracking or with a SenseGlove device interchangeably.
## Comparison of Available Approaches
The following table provides an overview and comparison of different hand interaction approaches available within the SenseGlove Unreal Engine Plugin ecosystem when it comes to hand-interaction systems:
| | **Built-in?** | **Works out of the box?** | **Beginner-friendly?** | **Learning Curve** | **Featureful** | **Customizable?** | **Supports Custom Gestures?** |
|-------------------------------------|---------------|------------------------------------|--------------------------------------------------|-----------------------------|--------------------------------------------------------------------------|-----------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|
| **SGPawn** | ✅ Yes | ✅ Yes | ✅ Most beginer-friendly | ✅ Very easy | ⚠️ Very basic | ⚠️ Very limited | ❌ Not yet, maybe added in the future |
| **SenseGlove OpenXR** | ✅ Yes | ❌ Requires Blueprint or C++ coding | ✅ Requires a few hours of watching tutotrials | ✅ Moderate | ❌ You need to develop your own features and functionalities from scratch | ✅ Your imagination, creativity, and skill level are your limits | ✅ You have to implement your own recognition logic; example pinch detection algorithm is taught in the tutorial series |
| **VR Expansion Plugin** | ❌ No | ⚠️ Partially requires setup | ❌ Best suited for intermediate or advanced users | ⚠️ Steep | ✅ Diverse features and functionalities | ✅ Highly customizable | ✅ Via custom logic |
| **Other OpenXR-compatible Plugins** | ❌ No | ❓ Check their documentation | ❓ Check their documentation | ❓ Check their documentation | ❓ Check their documentation | ❓ Check their documentation | ❓ Check their documentation |
## Going Beyond SGPawn
In the following sections, we will cover:
- [**The Puppeteer (Controller) / Puppet (Pawn) Architecture**](./sgpawn-events-puppeteer-puppet-architecture/): how to customize and control `SGPawn` through events.
- [**SGHandTrackerComponent**](./sghandtrackercomponent/): how to obtain and consume SenseGlove hand-tracking data, the easy way.
- [**SGHapticsComponent**](./sghapticscomponent/): how to add haptic feedback to your own or third-party hand interaction systems.
We have also covered more in-depth and advanced topics in other parts of this handbook, available in the following sections:
- [**OpenXR**](../advanced-topics/openxr/): an introduction to OpenXR fundamentals in Unreal Engine.
- [**Consuming FXRHandTrackingState**](../advanced-topics/openxr/consuming-fxrhandtrackingstate/): explains the data layout of Unreal Engines `FXRHandTrackingState`.
- [**Blueprint**](../advanced-topics/openxr/consuming-fxrhandtrackingstate/blueprint.md): demonstrates how to use `FXRHandTrackingState` data and render a debug hand in Blueprint.
- [**C++**](../advanced-topics/openxr/consuming-fxrhandtrackingstate/cpp.md): demonstrates how to use `FXRHandTrackingState` data and render a debug hand in C++.
- [**Third-Party Integrations**](../advanced-topics/openxr/third-party-integrations/): provides a sample Unreal Engine `5.4` project demonstrating how to integrate SenseGlove with the VR Expansion (VRE) Plugin.
- [**Third-Party Tutorials**](../advanced-topics/openxr/third-party-tutorials/):— a tutorial series that guides you from beginner to advanced level in using `FXRHandTrackingState` to build your own hand interaction system by animating virtual hand meshes.
@@ -0,0 +1,78 @@
# SGHandTrackerComponent
Since [`v2.1.0`](../../../appendix/changelog.html#210---2024-08-16), the first version to introduce OpenXR support, the **SenseGlove Unreal Engine Plugin** has provided a convenient way to retrieve `FXRHandTrackingState` for SenseGlove devices. This eliminated the need to manually calculate and apply [SenseGlove wrist-tracker settings and offsets](../../../plugin-configuration/plugin-settings/tracking/wrist-tracking/), or to fetch the `Project Settings > SenseGlove > Tracking Settings > Wrist Tracking Settings` and pass them to `GetWristLocation()` in an additional step, [as described in the relevant documentation](../openxr).
`SGHandTrackerComponent` simplifies this process even further by abstracting all of that away entirely in a high-level manner:
1. Simply add this component to your Pawn class (or any actor that requires hand-tracking data).
2. Configure and adjust its properties.
3. Retrieve the tracking data with a single function call when needed.
The SenseGlove UE Plugin automatically handles all required settings and offset calculations for your positional tracking hardware, regardless of whether you are using pure hand tracking or a SenseGlove device. It also provides an optional debug hand for free, allowing you to visualize the hand-tracking data instantly, without writing a single line of code.
## Adding the Component to Your Actors
Adding `SGHandTrackerComponent` is straightforward. In the `Components` panel, click the `Add` button and locate it under the `SenseGlove` section:
![SGHandTrackerComponent - Adding component](sghandtrackercomponent-add-component.png "SGHandTrackerComponent - Adding Component")
![SGHandTrackerComponent - Added component](sghandtrackercomponent-added-component.png "SGHandTrackerComponent - Added Component")
## Blueprint Properties
`SGHandTrackerComponent` exposes the following properties through the `Details` panel in Unreals Blueprint Editor:
![SGHandTrackerComponent - Exposed Blueprint properties accessible via the Details panel](sghandtrackercomponent-details-panel-uproperties.png "SGHandTrackerComponent - Exposed Blueprint properties accessible via the Details panel")
- `Right`: If enabled, the component tracks and provides hand-tracking data for the **right** hand. If disabled, it tracks the **left** hand instead.
- `Visualize`: If enabled, the component visualizes the hand-tracking data by rendering a debug hand. The appearance of this debug hand can be further customized, as shown below.
![SGHandTrackerComponent - Debug hand's visualization settings customization](sghapticscomponent-debug-hand-visualization-settings.png "SGHandTrackerComponent - Debug hand's visualization settings customization")
![SGHandTrackerComponent - Visualized debug virtual hands](../../openxr/consuming-fxrhandtrackingstate-animated-debug-virtual-hands.gif "SGHandTrackerComponent - Visualized debug virtual hands")
## C++ and Blueprint Functions
`SGHandTrackerComponent` provdies the following C++ methods:
```cpp
public:
FORCEINLINE bool IsLeft() const
{
return !IsRight();
}
FORCEINLINE bool IsRight() const
{
return bRight;
}
void SetRight(const bool bInRight);
FORCEINLINE bool IsVisualized() const
{
return bVisualize;
}
FORCEINLINE void SetVisualize(const bool bInVisualize)
{
bVisualize = bInVisualize;
}
public:
const FXRHandTrackingState& GetHandTrackingState() const;
```
The same set of functions are also exposed to Blueprint:
![SGHandTrackerComponent - Blueprint functions](sghandtrackercomponent-functions.png "SGHandTrackerComponent - Blueprint functions")
## GetHandTrackingState
The most important function accessible via `SGHandTrackerComponent` is `GetHandTrackingState()`:
![SGHandTrackerComponent - GetHandTrackingState Blueprint function](sghandtrackercomponent-get-hand-tracking-state.png "SGHandTrackerComponent - GetHandTrackingState Blueprint function")
This function returns a snapshot of the OpenXR hand-tracking data as an `FXRHandTrackingState` struct.
For more details on [what this data contains and how to use it, please refer to the Consuming FXRHandTrackingState section](../../../advanced-topics/openxr/consuming-fxrhandtrackingstate/).
@@ -0,0 +1,574 @@
# SGHapticsComponent
`SGHapticsComponent`, introduced in the SenseGlove Unreal Engine Plugin `v2.8.0`, provides a highly convenient, high-level interface for sending various types of haptic feedback to a SenseGlove device directly from Unreal Engine.
Prior to this release, integrating haptic feedback into a custom hand interaction system was possible in several ways:
- SenseGlove low-level C++ API:
- Via the [SGHandLayer API](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGloveCore/Public/SGCore/SGHandLayer.h).
- Via the [SGHpaticGlove API](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGloveCore/Public/SGCore/SGHapticGlove.h).
- SenseGlove Blueprint API:
- Via the [SGHandLayer API](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGloveCoreKismet/Public/SGCoreKismet/SGHandLayerKismetLibrary.h) which provides a higher-level abstraction compared to the `SGHapticGlove` API.
- Via the [SGHapticGlove API](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGloveCoreKismet/Public/SGCoreKismet/SGHapticGloveKismetLibrary.h),
which offers a lower-level interface than the `SGHandLayer` API and requires some boilerplate code to safely obtain an instance of the desired glove (see [Safe and Reliable Glove Access in Blueprint](../../safe-glove-access-blueprint/)).
- Additionally, there is the [`SGTouchComponent`](../../../getting-started/setup-touch-system/), which provides simplified and limited functionality. On its own, it cannot trigger haptics. It is designed to work in conjunction with the stock `SGPlayerController` shipped with the SenseGlove Unreal Engine plugin.
While all of the above approaches remain fully supported, whether in C++ or Blueprint, `SGHapticsComponent` eliminates some of the caveats associated with them, while still giving you full control in a significantly more convenient and streamlined manner.
> [!IMPORTANT]
> For more detailed information on
> [Nova 2 Glove Vibration Tips & Tricks](https://senseglove.gitlab.io/SenseGloveDocs/nova2-vibration.html),
> please visit the in-depth guide available on
> [SenseGlove Docs](https://senseglove.gitlab.io/SenseGloveDocs/).
>
> We strongly recommend reviewing that comprehensive upstream haptics documentation,
> as this guide focuses solely on applying haptic feedback from Unreal Engine.
>
> A solid understanding of the SenseGlove haptics API and its hardware capabilities
> will help you follow and apply this guide effectively, while also enabling you
> to troubleshoot haptics-based Unreal Engine projects with confidence.
## Adding the Component to Your Actors
Adding `SGHapticsComponent` is straightforward. In the `Components` panel, click the `Add` button and locate it under the `SenseGlove` section:
![SGHapticsComponent - Adding component](sghapticscomponent-add-component.png "SGHapticsComponent - Adding Component")
![SGHapticsComponent - Added component](sghapticscomponent-added-component.png "SGHapticsComponent - Added Component")
## Blueprint Properties
`SGHapticsComponent` exposes the following properties through the `Details` panel in Unreals Blueprint Editor:
![SGHapticsComponent - Exposed Blueprint properties accessible via the Details panel](sghapticscomponent-details-panel-uproperties.png "SGHapticsComponent - Exposed Blueprint properties accessible via the Details panel")
- `Right`: If enabled, the component controls haptics feedback for the **right** hand. If disabled, it controls haptics for the **left** hand instead.
- `AutoStopAllHaptics`: If enabled, automatically calls the `StopHaptics()` function when: 1) The component is uninitialized 2) the `EndPlay` event occurs 3) or, the **handedness** changes. This ensures that vibrations won't continue after the simulation ends, or when the active glove it controls, is switched mid-simulation.
## C++ and Blueprint Functions
`SGHapticsComponent` provdies the following C++ methods:
```cpp
public:
FORCEINLINE bool IsLeft() const
{
return !IsRight();
}
FORCEINLINE bool IsRight() const
{
return bRight;
}
void SetRight(const bool bInRight);
FORCEINLINE bool AutoStopsAllHaptics() const
{
return bAutoStopAllHaptics;
}
void SetAutoStopAllHaptics(const bool bInAutoStopAllHaptics)
{
bAutoStopAllHaptics = bInAutoStopAllHaptics;
}
public:
/**
* Stops all Haptic effects if any are currently playing. Useful at the end of simulations or when restarting the
* level.
*/
void StopHaptics();
/**
* Stops only vibrations.
*/
void StopVibrations();
/**
* Take all active commands in the device queue, compile them into one and send them to the device.
*
* @return Returns true if the message was successfully sent to SenseCom.
*/
bool SendHaptics();
/**
* Returns true if the haptic glove supports vibration feedback at the specified location.
*
* @param AtLocation
*/
bool SupportsCustomWaveform(ESGHapticLocation AtLocation) const;
/**
* Sends a custom waveform to the location specified, provided that the glove has a motor there, and can support
* custom waveforms.
*
* @param OutWaveform
* @param Location
*/
bool SendCustomWaveform(USGCustomWaveform* OutWaveform, ESGHapticLocation Location);
/**
* Sends a custom waveform to the location specified, provided that the glove has a motor there, and can support
* custom waveforms.
*
* @param Amplitude
* @param Duration
* @param Location
*/
bool SendCustomWaveform(float Amplitude, float Duration, ESGHapticLocation Location);
/**
* Sends a custom waveform to the location specified, provided that the glove has a motor there, and can support
* custom waveforms.
*
* @param Amplitude
* @param Duration
* @param Frequency
* @param Location
*/
bool SendCustomWaveform(float Amplitude, float Duration, float Frequency, ESGHapticLocation Location);
/**
* Queue a list of force-feedback levels, between 0.0f and 1.0f. Your list should be sorted from thumb to pinky.
*
* @param Levels01 Array containing the Force-Feedback levels, from 0.0f (no FFB) to 1.0f. A value < 0.0f will be
* ignored.
*
* @remarks Devices that 'only' have on/off FFB will treat any value > 0.0 as 1.0.
*/
bool QueueForceFeedbackLevels(const TArray<float>& Levels01);
/**
* Set the Force-Feedback value of a particular finger to a specific level </summary>
*
* @param Level01 Value will be clamped between [0...1], where 0.0f means no Force-Feedback, and 1.0 means full
* force-feedback.
* @param Finger The finger to which to send the command.
*/
bool QueueForceFeedbackLevel(int32 Finger, float Level01);
/**
* Queue a list of vibration levels, between 0.0 and 1.0. Your list should be sorted from thumb to pinky.
*
* @param Levels01 Array containing the vibration levels, from 0.0 (no vibration) to 1.0. A value < 0.0f will be
* ignored.
*
* @remarks Devices that 'only' have on/off FFB will treat any value > 0.0 as 1.0.
*/
bool QueueVibroLevels(const TArray<float>& Levels01);
/**
* Queue a command to set the (continuous) vibration level at a specific location to a set amplitude.
*
* @param Location
* @param Level01 Value will be clamped between [0...1], where 0.0f means no vibration, and 1.0 means full
* vibration.
*/
bool QueueVibroLevel(ESGHapticLocation Location, float Level01);
/**
* Returns true if the chosen glove supports active contact feedback on the Wrist.
*/
bool SupportsWristSqueeze() const;
/**
* Queue a command to set the amount of squeeze level (a.k.a. squeeze-feedback) to the desired level
* (0 = no squeeze, 1 = full squeeze) on the wrist, and optionally send it right away.
*
* @param SqueezeLevel01
* @param bSendImmediate
*/
bool QueueWristSqueeze(float SqueezeLevel01, bool bSendImmediate);
```
The same set of functions are also exposed to Blueprint:
![SGHapticsComponent - Blueprint functions](sghapticscomponent-functions1.png "SGHapticsComponent - Blueprint functions")
![SGHapticsComponent - Blueprint functions](sghapticscomponent-functions2.png "SGHapticsComponent - Blueprint functions")
## Quick Blueprint Functions Reference
Here is a brief at-a-glance reference of all `SGHapticsComponent` Blueprint functions related to haptic feedback.
### Stop Haptics
Stops **all active haptic effects** currently playing on the glove.
This includes:
- Vibrations.
- Force-feedback (FFB).
- Wrist-squeeze.
- Any queued but unsent haptic commands.
![SGHapticsComponent - Blueprint function: Stop Haptics](sghapticscomponent-blueprint-function-stop-haptics.png "SGHapticsComponent - Blueprint function: Stop Haptics")
**Typical Use Cases:**
- Resetting the glove at the end of a simulation.
- Restarting a level.
- Emergency stop logic.
- Cleaning up when disabling an actor.
**Returns:**
This Blueprint node does not return a value.
### Stop Vibrations
Stops only **vibration feedback**, without affecting:
- Force-feedback
- Wrist-squeeze
![SGHapticsComponent - Blueprint function: Stop Vibrations](sghapticscomponent-blueprint-function-stop-vibrations.png "SGHapticsComponent - Blueprint function: Stop Vibrations")
**Typical Use Cases:**
It is useful for example if you want to keep finger resistance active while stopping tactile feedback.
**Returns:**
This Blueprint node does not return a value.
### Send Haptics
Compiles all currently queued haptic commands and sends them to the glove.
![SGHapticsComponent - Blueprint function: Send Haptics](sghapticscomponent-blueprint-function-stop-haptics.png "SGHapticsComponent - Blueprint function: Send Haptics")
The component works using a **queue-based system**:
1. You queue multiple commands (Force-feedback, Vibro, Wrist, etc..)
2. You call **Send Haptics**.
3. Everything is compiled into one device message.
**Returns:**
- `true`: Indicates message has been successfully sent to SenseCom.
- `false`: Failed to send haptics.
> [!CAUTION]
> Avoid calling `Send Haptics` repeatedly in rapid succession.
>
> For optimal performance, queue all required haptic commands first
> (Force-Feedback, Vibro, Wrist, etc.), then call `Send Haptics`
> **once per logical update cycle**.
>
> Continuously queueing commands and flushing them every frame (or multiple
> times per frame) increases device communication frequency and computational
> overhead. It may also cause Bluetooth instability and, in extreme cases, lead
> to the glove disconnecting.
>
> Instead, batch multiple haptic updates together and send them in a single
> compiled message whenever possible. This reduces processing cost, lowers
> communication load, and results in more stable and efficient haptic
> performance.
### Supports Custom Waveform
Checks whether the glove supports **custom waveform vibration** at a specific location.
![SGHapticsComponent - Blueprint function: Supports Custom Waveform](sghapticscomponent-blueprint-function-supports-custom-waveform.png "SGHapticsComponent - Blueprint function: Supports Custom Waveform")
**Parameters**:
- **At Location:** The vibration location to test (e.g., Thumb Tip, Index Tip, Palm Index Side, etc.).
**Returns:**
- `true`: Custom waveform is supported.
- `false`: Not supported at this location.
You can call this before using `Send Custom Waveform` to see if your glove model at the specified location supports vibration.
### Send Custom Waveform
Sends a **custom vibration waveform** to a specific haptic location.
![SGHapticsComponent - Blueprint function: Send Custom Waveform](sghapticscomponent-blueprint-function-send-custom-waveform.png "SGHapticsComponent - Blueprint function: Send Custom Waveform")
This function has **three overloads** in C++ and is exposed accordingly in Blueprint.
#### 1) Send a Custom Waveform Asset
**Parameters:**
- **OutWaveform:** A predefined waveform asset that allows you to configure additional custom waveform parameters not available in the other two overloads, giving you more fine-grained control over the vibrations behavior and timing.
| Name | Unit | Range | Description |
|------------------------|-----------|--------------|-----------------------------------------------------------------------------|
| Amplitude | | 0.0 … 1.0 | Vibration intensity |
| Start Frequency | Hz | 10 … 500 | Vibration Frequency at the start of the vibration |
| End Frequency | Hz | 10 … 500 | Vibration Frequency at the end of the vibration |
| Attack Time | s | 0.0 … 1.0 | Time to reach from 0.0 to Amplitude |
| Sustain Time | s | 0.0 … 1.0 | Time for which the signal will stay at Amplitude |
| Decay Time | s | 0.0 … 1.0 | Time to reach from Amplitude down to 0.0. |
| Pause Time | s | 0.0 … 1.0 | Time between each vibration, when repeating the waveform. |
| Repeat Amount | | 1 .. 100 | How often the waveform is repeated before stopping. |
| Infinite | | True / False | If true, the glove will keep playing this waveform until a new one is played. |
| Waveform Type | EWaveType | 0 .. 5 | The shape of the waveform: Sine / Square / SawUp / SawDown / Triangle / Noise. |
| FrequencySwitchTime* | | 0.0 … 1.0 | At this position in the waveform (0.0 being start, 1.0 being the end), we start multiply the Frequency by FrequencySwitchFactor |
| FrequencySwitchFactor* | | 1.0 .. 3.0 | How much to multiply the frequency by, after FrequencySwitchTime has passed. |
- **Location:** Where to play the waveform.
**Returns:**
- `true`: If command successfully sent.
- `false`: If it fails.
#### 2) Send Amplitude + Duration
**Parameters:**
- **Amplitude:** Vibration strength (0.0 1.0).
- **Duration:** Duration in seconds.
- **Location:** Target haptic location.
**Returns:**
- `true`: If command successfully sent.
- `false`: If it fails.
#### 3) Send Amplitude + Duration + Frequency
**Parameters:**
- **Amplitude** — Vibration strength (0.0 1.0).
- **Duration** — Duration in seconds.
- **Frequency** — Vibration frequency in Hz.
- **Location** — Target haptic location.
**Returns:**
- `true`: If command successfully sent.
- `false`: If it fails.
### Queue Force Feedback Levels
Queues force-feedback levels for **all fingers** at once.
![SGHapticsComponent - Blueprint function: Queue Force Feedback Levels](sghapticscomponent-blueprint-function-queue-force-feedback-levels.png "SGHapticsComponent - Blueprint function: Queue Force Feedback Levels")
**Parameters:**
- **Levels 01:** Array containing the Force-Feedback levels between `0.0` (no FFB) to `1.0` (full FFB); ordered from **Thumb → Index → Middle → Ring → Pinky**.
> [!NOTE]
> Force-feedback value behavior:
>
> - `0.0` = No resistance.
> - `1.0` = Full resistance.
> - Values `< 0.0` are ignored.
> - Devices that only support on/off FFB treat any value > `0.0` as full force.
**Returns:**
- `true`: If queued successfully.
- `false`: If it fails.
### Queue Force Feedback Level
Queues force-feedback on a **particular finger** to a specific level.
![SGHapticsComponent - Blueprint function: Queue Force Feedback Level](sghapticscomponent-blueprint-function-queue-force-feedback-level.png "SGHapticsComponent - Blueprint function: Queue Force Feedback Level")
**Parameters:**
- **Finger:** Index of the finger; indexed from **Thumb → Index → Middle → Ring → Pinky**.
- **Level 01:** Value clamped between `0.0` (no FFB) to `1.0` (full FFB).
> [!NOTE]
> Force-feedback value behavior:
>
> - `0.0` = No resistance.
> - `1.0` = Full resistance.
> - Values `< 0.0` are ignored.
> - Devices that only support on/off FFB treat any value > `0.0` as full force.
**Returns:**
- `true`: If queued successfully.
- `false`: If it fails.
### Queue Vibro Levels
> [!IMPORTANT]
> **Legacy Function Use Custom Waveforms Instead**
>
> `Queue Vibro Levels` is retained for backward compatibility with older API
> releases.
>
> Internally, it delegates to `Send Custom Waveform`, which is the recommended
> method for applying vibrotactile feedback.
>
> For new projects, prefer `Send Custom Waveform`, as it provides more
> fine-grained control over amplitude, frequency, timing, and waveform shaping.
Queues continuous vibrotactile levels for **all fingers** at once to a set amplitude.
![SGHapticsComponent - Blueprint function: Queue Vibro Levels](sghapticscomponent-blueprint-function-queue-vibro-levels.png "SGHapticsComponent - Blueprint function: Queue Vibro Levels")
**Parameters:**
- **Levels 01:** Array containing the vibro levels between `0.0` (no vibration) to `1.0` (full vibration); ordered from **Thumb → Index → Middle → Ring → Pinky**.
> [!NOTE]
> Force-feedback value behavior:
>
> - `0.0` = No vibration.
> - `1.0` = Full vibration.
> - Values `< 0.0` are ignored.
**Returns:**
- `true`: If queued successfully.
- `false`: If it fails.
### Queue Vibro Level
> [!IMPORTANT]
> **Legacy Function Use Custom Waveforms Instead**
>
> `Queue Vibro Level` is retained for backward compatibility with older API
> releases.
>
> Internally, it delegates to `Send Custom Waveform`, which is the recommended
> method for applying vibrotactile feedback.
>
> For new projects, prefer `Send Custom Waveform`, as it provides more
> fine-grained control over amplitude, frequency, timing, and waveform shaping.
Queues continuous vibration at a **specific location** to a set amplitude.
![SGHapticsComponent - Blueprint function: Queue Vibro Level](sghapticscomponent-blueprint-function-queue-vibro-level.png "SGHapticsComponent - Blueprint function: Queue Vibro Level")
**Parameters:**
- **Location:** Target location to apply vibration.
- **Level01 (float):** Value clamped between `0.0` (no vibration) to `1.0` (full vibration).
> [!NOTE]
> Force-feedback value behavior:
>
> - `0.0` = No vibration.
> - `1.0` = Full vibration.
> - Values `< 0.0` are ignored.
**Returns:**
- `true`: If queued successfully.
- `false`: If it fails.
### Supports Wrist Squeeze
Checks if the connected glove supports **active wrist-squeeze feedback**.
![SGHapticsComponent - Blueprint function: Supports Wrist Squeeze](sghapticscomponent-blueprint-function-supports-wrist-squeeze.png "SGHapticsComponent - Blueprint function: Supports Wrist Squeeze")
**Returns:**
- `true`: If wrist-squeeze is supported.
- `false`: If it's not supported.
### Queue Wrist Squeeze
Queues a wrist-squeeze feedback at the desired level, and optionally if chosen, sends it right away.
![SGHapticsComponent - Blueprint function: Queue Wrist Squeeze](sghapticscomponent-blueprint-function-queue-wrist-squeeze.png "SGHapticsComponent - Blueprint function: Queue Wrist Squeeze")
**Parameters:**
- **Squeeze Level 01:** Value clamped between `0.0` (no squeeze) to `1.0` (full squeeze).
- **Send Immediate** If set to `true`, immediately sends the command, otherwise only queues until `Send Haptics` function is called.
> [!NOTE]
> Wrist-squeeze value behavior:
>
> - `0.0` = No squeeze.
> - `1.0` = Full squeeze.
> [!CAUTION]
> Avoid using `Send Immediate` unless absolutely necessary.
>
> For optimal performance, queue all haptic commands first and call the
> `Send Haptics` function once after all commands are prepared.
>
> Sending commands immediately increases device communication frequency and
> computational overhead. Batching commands using `Send Haptics` reduces
> processing cost and improves performance.
**Returns:**
- `true`: If queued successfully.
- `false`: If it fails.
## Blueprint Haptics Examples
Below are practical Blueprint examples demonstrating how to combine the different `SGHapticsComponent` functions into complete interaction flows.
### Force-Feedback Example
This example demonstrates:
- How to queue force-feedback with **full resistance on all fingers**.
- How to flush all queued haptics (including the recently queued force-feedback) using `Send Haptics`.
- How to stop all haptic effects after `2` seconds, if the send operation succeeds.
![SGHapticsComponent - Blueprint example: Force-feedback](sghapticscomponent-blueprint-example-force-feedback.png "SGHapticsComponent - Blueprint example: Force-feedback")
In this flow:
1. Force-feedback levels are queued for all fingers.
2. `Send Haptics` compiles and sends the command to the glove.
3. If successful, `Stop Haptics` is used to clear all active effects after `2` seconds.
### Vibrotactile Example
This example demonstrates:
- How to check if the glove at the current hand supports custom wave forms at the `Plam Pinky Side`.
- If so, it constructs a `SGCustomWaveform` with a duration of `500` milliseconds, amplitude of `1.0` at the frequency of `180.0` (maximum vibration on Nova 2).
- It then sets other parameters such as the `WaveType` to `Square` and the `RepeatAmount` to `10`.
- And, finally sends the custom waveforms to the glove, which is going to stop after `10` times playing.
This example demonstrates:
- How to check whether the current glove supports **custom waveforms** at the `Palm Pinky Side` location.
- How to construct a `SGCustomWaveform` with:
- `Duration``500 ms`
- `Amplitude``1.0`
- `Frequency``180.0 Hz` (maximum vibration on Nova 2)
- How to configure additional parameters such as:
- `Wave Type``Square`
- `Repeat Amount``10`
- How to send the custom waveform to the glove.
![SGHapticsComponent - Blueprint example: Vibrotactile](sghapticscomponent-blueprint-example-vibrotactile.png "SGHapticsComponent - Blueprint example: Vibrotactile")
The waveform will automatically stop after playing **10 repetitions**.
### Wrist-Squeeze Example
This example demonstrates:
- How to check whether the connected glove supports **wrist squeeze feedback**.
- How to apply a wrist squeeze at **50% intensity**.
- How to send the command immediately without requiring an additional `Send Haptics` call.
![SGHapticsComponent - Blueprint example: Wrist-squeeze](sghapticscomponent-blueprint-example-wrist-squeeze.png "SGHapticsComponent - Blueprint example: Wrist-squeeze")
Because `Send Immediate` is enabled, the squeeze is transmitted instantly instead of being queued.
> [!CAUTION]
> Avoid using `Send Immediate` unless absolutely necessary.
>
> For optimal performance, queue all haptic commands first and call the
> `Send Haptics` function once after all commands are prepared.
>
> Sending commands immediately increases device communication frequency and
> computational overhead. Batching commands using `Send Haptics` reduces
> processing cost and improves performance.
@@ -0,0 +1,594 @@
# SGPawn Events: The Puppeteer (Controller) / Puppet (Pawn) Architecture
The `SGPawn` (SenseGlove Pawn) is intentionally designed as a **data/event-driven puppet**. It detects touch, grab candidates, and hand state, but it does **not make gameplay decisions** on its own. Instead, it delegates the decisions via firing events
Usually these decisions are delegated to the `SGPlayerController` (or your own controller if you want to customize the behaviors), which acts as the **puppeteer** for SGPawn (the **puppet**):
- It registers to `SGPawn` events at the `BeginPlay` event.
- It listens to `SGPawn` events.
- It decides when to grab or release when certain conditions are met.
- It applies gameplay logic.
- It drives haptics or other responses.
That's [how `SGPlayerController` works under the hood](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGlove/Private/SenseGlove/GameFramework/SGPlayerController.cpp).
This separation ensures:
- Clean architecture.
- Full and exnsible customization.
- No hidden behavior inside `SGPawn`.
- Deterministic control over interaction rules.
## Architecture Overview
```
SGPawn ---> Emits State Events ---> SGPlayerController decides what to do
```
`SGPawn`:
- Tracks touch state.
- Tracks grab candidates.
- Tracks grabbed actors.
- Emits events.
`SGPlayerController`:
- Subscribes to events.
- Calls `Grab()` / `Release()`.
- Applies custom interaction logic.
- Updates haptics.
## Exposed Events
`SGPawn` provides the following event exposed to both C++ and Blueprint:
- `OnGrabStateUpdated`
- `OnTouchStateUpdated`
- `OnActorGrabbed`
- `OnActorReleased`
- `OnActorBeginTouch`
- `OnActorEndTouch`
![SGPawn Events](sgpawn-events.png "SGPawn Events]")
> [!IMPORTANT]
> The current implementation of `SGPawn` relies on `3` grab colliders and `5`
> touch colliders for grab and touch detection.
![SGPawn Grab and Touch Colliders](sgpawn-grab-touch-colliders.png "SGPawn Grab and Touch Colliders]")
### On Grab State Updated Event
This is the **main decision event** for grabbing logic. The Pawn informs you:
> "Here is the current grab state. You decide what to do."
It is defined in C++ like this:
```cpp
DECLARE_EVENT_OneParam(ASGPawn, FGrabStateUpdatedEvent, const FSGGrabState& GrabState);
```
In Blueprint, the event appears as shown below:
![Blueprint Event OnGrabStateUpdated](sgpawn-event-on-grab-state-updated.png "Blueprint Event OnGrabStateUpdated]")
This event is triggered only when the hand is visible and when any finger on the left or right hand, equipped with a grab collider, begins overlapping (colliding with) or ends overlapping (stops colliding with) an actor that owns an [`SGGrabComponent`](../../../getting-started/setup-grab-release-system/). So in summary it fires when the following conditions are met:
- The hand is **visible**.
- Any finger (left or right hand) equipped with a **grab collider**:
- **Begins overlapping** (starts colliding with), or
- **Ends overlapping** (stops colliding with).
- The overlapped actor owns an `SGGrabComponent`.
Subscribers to this event receive a snapshot of the `FSGGrabState` struct. At the moment the event is fired, the struct contains the following data:
- `Hand`: The `SGVirtualHandComponent` whose grab state was updated due to a finger beginning or ending an overlap with another actor.
- `PreviousHandLocation`: `SGPawn` continuously records hand movement every engine tick. This field stores the hands location from the previous tick. It can be used to calculate object velocity or apply impulse forces when an object is thrown.
- `HandVelocityHistory`: A history of previous hand locations, up to `SGPawn::MaxNumberOfHandVelocitySamples`. `MaxNumberOfHandVelocitySamples` is a `UPROPERTY` in `SGPawn` that defaults to `10` but can be adjusted as needed.
- `ActorThumbCanGrab`: The actor currently overlapping with the thumbs grab collider. If `null`, the thumb is not overlapping any grabbable actor (which means the actor has an `SGGrabComponent`).
- `ActorIndexCanGrab`: The actor currently overlapping with the index fingers grab collider. If `null`, the index finger is not overlapping any grabbable actor.
- `ActorMiddleCanGrab`: The actor currently overlapping with the middle fingers grab collider. If `null`, the middle finger is not overlapping any grabbable actor.
- `GrabbedActor`: The actor currently being grabbed by this hand. If `null`, the hand is not grabbing anything at that moment.
Here is how the current `SGPlayerController` performs grab detection and instructs the `SGPawn` it controls to execute grab and release actions:
```cpp
void ASGPlayerController::BeginPlay()
{
Super::BeginPlay();
ASGPawn* SGPawn{Cast<ASGPawn>(GetPawn())};
if (!ensureAlwaysMsgf(IsValid(SGPawn), TEXT("%s"), TEXT("ERROR: invalid SenseGlove pawn!")))
{
return;
}
SGPawn->OnGrabStateUpdated().AddWeakLambda(
this, [= SG_CAPTURE_THIS](const FSGGrabState& GrabState) -> void
{
if (!IsValid(SGPawn))
{
return;
}
if (!IsValid(GrabState.Hand))
{
return;
}
const bool bHandVisible = GrabState.Hand->IsVisible();
if (!bHandVisible)
{
if (SGPawn->IsGrabbing(GrabState.Hand))
{
SGPawn->Release(GrabState.Hand);
}
return;
}
if (SGPawn->IsGrabbing(GrabState.Hand))
{
if (!IsValid(GrabState.ActorThumbCanGrab) ||
(GrabState.ActorIndexCanGrab != GrabState.ActorThumbCanGrab
&& GrabState.ActorMiddleCanGrab != GrabState.ActorThumbCanGrab))
{
SGPawn->Release(GrabState.Hand);
}
}
else
{
if (SGPawn->CanGrab(GrabState.Hand, GrabState.ActorThumbCanGrab))
{
SGPawn->Grab(GrabState.Hand, GrabState.ActorThumbCanGrab);
}
}
});
}
```
In this implementation, the `SGPlayerController` listens for grab state updates and determines whether the hand should grab or release an actor based on visibility and finger overlap conditions.
> [!TIP]
> Haptic feedback is also handled and enforced through `SGPlayerController`.
> You can
> [review the full implementation in the plguin source code for `SGPlayerController`](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGlove/Private/SenseGlove/GameFramework/SGPlayerController.cpp).
>
> In general, with the current version of the plugin, you can integrate haptic
> feedback into your own hand interaction system in several ways:
>
> - The [`SGHapticsComponent`](../sghapticscomponent/) high-level approach.
> - The SenseGlove C++ API:
> - Via the
> [SGHandLayer API](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGloveCore/Public/SGCore/SGHandLayer.h).
> - Via the
> [SGHpaticGlove API](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGloveCore/Public/SGCore/SGHapticGlove.h).
> - The SenseGlove Blueprint API:
> - Via the
> [SGHandLayer API](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGloveCoreKismet/Public/SGCoreKismet/SGHandLayerKismetLibrary.h) which provides a higher-level abstraction compared to the `SGHapticGlove` API.
> - Via the
> [SGHapticGlove API](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGloveCoreKismet/Public/SGCoreKismet/SGHapticGloveKismetLibrary.h),
which offers a lower-level interface than the `SGHandLayer` API and requires some boilerplate code to safely obtain an instance of the desired glove (see [Safe and Reliable Glove Access in Blueprint](../../safe-glove-access-blueprint/)).
> - Additionally, there is the
> [`SGTouchComponent`](../../../getting-started/setup-touch-system/), which
> provides simplified and limited functionality. On its own, it cannot trigger
> haptics. It is designed to work in conjunction with the stock
> `SGPlayerController` shipped with the SenseGlove Unreal Engine plugin.
### Touch State Updated Event
This is the **main decision event** for controlling the touch logic. The Pawn informs you:
> "Here is the current touch state. You decide what to do."
It is defined in C++ like this:
```cpp
DECLARE_EVENT_OneParam(ASGPawn, FTouchStateUpdatedEvent, const FSGTouchState& TouchState);
```
In Blueprint, the event appears as shown below:
![Blueprint Event OnTouchStateUpdated](sgpawn-event-on-touch-state-updated.png "Blueprint Event OnTouchStateUpdated]")
This event is triggered only when the hand is visible and when any finger on the left or right hand, equipped with a grab collider, begins overlapping (colliding with) or ends overlapping (stops colliding with) an actor that owns an [`SGGrabComponent`](../../../getting-started/setup-grab-release-system/). So in summary it fires when the following conditions are met:
- The hand is **visible**.
- Any finger (left or right hand) equipped with a **touch collider**:
- **Begins overlapping** (starts colliding with), or
- **Ends overlapping** (stops colliding with).
- The overlapped actor owns an `SGTouchComponent`.
Subscribers to this event receive a snapshot of the `FSGTouchState` struct. At the moment the event is fired, the struct contains the following data:
- `Hand`: The `SGVirtualHandComponent` whose touch state was updated due to a finger beginning or ending an overlap with another actor.
- `ActorThumbTouching`: The actor currently overlapping with the thumbs touch collider. If `null`, the thumb is not overlapping any touchable actor (which means the actor has an `SGTouchComponent`).
- `ActorIndexTouching`: The actor currently overlapping with the indexs touch collider. If `null`, the index is not overlapping any touchable actor.
- `ActorMiddleTouching`: The actor currently overlapping with the middles touch collider. If `null`, the middle is not overlapping any touchable actor.
- `ActorRingTouching`: The actor currently overlapping with the rings touch collider. If `null`, the ring is not overlapping any touchable actor.
- `ActorPinkyTouching`: The actor currently overlapping with the pinkys touch collider. If `null`, the pinky is not overlapping any touchable actor.
Here is how the current `SGPlayerController` performs touch detection and instructs the `SGPawn` it controls to apply haptics feedback:
```cpp
void ASGPlayerController::BeginPlay()
{
Super::BeginPlay();
ASGPawn* SGPawn{Cast<ASGPawn>(GetPawn())};
if (!ensureAlwaysMsgf(IsValid(SGPawn), TEXT("%s"), TEXT("ERROR: invalid SenseGlove pawn!")))
{
return;
}
SGPawn->OnTouchStateUpdated().AddWeakLambda(
this, [= SG_CAPTURE_THIS](const FSGTouchState& TouchState) -> void
{
if (!IsValid(SGPawn))
{
return;
}
if (!IsValid(TouchState.Hand))
{
return;
}
const bool bHandVisible = TouchState.Hand->IsVisible();
if (!bHandVisible)
{
return;
}
const bool bGloveConnected = TouchState.Hand->IsGloveConnected();
if (!bGloveConnected)
{
return;
}
Pimpl->UpdateHapticsFeedback(TouchState);
});
}
```
In this implementation, the `SGPlayerController` listens for touch state updates and determines whether the haptic feedbacks should be applied to the glove on that hand, or not. This decision is determined based on various conditions such as hand visibility and finger overlap conditions. Since each fingers haptic feedback application and the type of haptic feedback is decided individually, for the sake of readability the logic has been offloaded to an `SGPlayerController`'s internal function `Pimpl->UpdateHapticsFeedback()`. For example, it applies vibrotactile feedback to eligible fingers like this:
In this implementation, the `SGPlayerController` listens for touch state updates and determines whether haptic feedback should be applied to the glove on that hand. This decision is based on several conditions, such as hand visibility and finger overlap states. Since each fingers haptic feedback and feedback type are evaluated individually, the detailed logic has been offloaded to the internal `SGPlayerController` function `Pimpl->UpdateHapticsFeedback()` for readability and separation of concerns.
For example, vibrotactile feedback is applied to eligible fingers as follows:
```cpp
void ASGPlayerController::FImpl::UpdateHapticsFeedback(const FSGTouchState& TouchState)
{
if (!IsValid(TouchState.Hand))
{
return;
}
USGHapticGlove* Glove{TouchState.Hand->GetConnectedGlove()};
if (!IsValid(Glove))
{
return;
}
const bool bGloveConnected = Glove->IsConnected();
if (!bGloveConnected)
{
return;
}
// some omitted code due to irrelevance
....
// Send Vibrotactile to the thumb finger if it's touching an actor...
if (IsValid(TouchState.ActorThumbTouching))
{
USGCustomWaveform* CustomWaveform(GetCustomWaveform(TouchState.ActorThumbTouching));
Glove->SendCustomWaveform(CustomWaveform, ESGHapticLocation::ThumbTip);
}
// Send Vibrotactile to the index finger if it's touching an actor...
if (IsValid(TouchState.ActorIndexTouching))
{
USGCustomWaveform* CustomWaveform(GetCustomWaveform(TouchState.ActorIndexTouching));
Glove->SendCustomWaveform(CustomWaveform, ESGHapticLocation::IndexTip);
}
// Send Vibrotactile to the middle finger if it's touching an actor...
if (IsValid(TouchState.ActorMiddleTouching))
{
USGCustomWaveform* CustomWaveform(GetCustomWaveform(TouchState.ActorMiddleTouching));
Glove->SendCustomWaveform(CustomWaveform, ESGHapticLocation::MiddleTip);
}
// Send Vibrotactile to the ring finger if it's touching an actor...
if (IsValid(TouchState.ActorRingTouching))
{
USGCustomWaveform* CustomWaveform(GetCustomWaveform(TouchState.ActorRingTouching));
Glove->SendCustomWaveform(CustomWaveform, ESGHapticLocation::RingTip);
}
// Send Vibrotactile to the pinky finger if it's touching an actor...
if (IsValid(TouchState.ActorPinkyTouching))
{
USGCustomWaveform* CustomWaveform(GetCustomWaveform(TouchState.ActorPinkyTouching));
Glove->SendCustomWaveform(CustomWaveform, ESGHapticLocation::PinkyTip);
}
}
```
As can be seen from the above code, the `SGCustomWaveform` is constructed via a separate helper function:
```cpp
USGCustomWaveform* ASGPlayerController::FImpl::GetCustomWaveform(const AActor* Actor)
{
float Amplitude = 0.0f;
float Duration = 0.0f;
float Frequency = 0.0f;
if (IsValid(Actor))
{
const USGTouchComponent* TouchComponent{USGTouchComponent::GetTouchComponent(Actor)};
if (IsValid(TouchComponent))
{
Amplitude = TouchComponent->GetVibrotactileAmplitude();
Duration = TouchComponent->GetVibrotactileDuration();
Frequency = TouchComponent->GetVibrotactileFrequency();
}
}
USGCustomWaveform* CustomWaveform{
USGCustomWaveform::NewCustomWaveform(Owner, Amplitude, Duration, Frequency)
};
return CustomWaveform;
}
```
When it comes to force-feedback, the controller sends force-feedback to all fingers at once, while still constructing the force-feedback levels array via a separate function. `5` elements for `5` fingers indexed from thumb to pinky, where element `0` corresponds to the thumb, `1` to the index finger, and so on, with `4` representing the pinky; [see the `SGTouchComponent` documentation for more details](../../../getting-started/setup-touch-system/). This is how `UpdateHapticsFeedback()` sends force-feedback to the glove:
```cpp
void ASGPlayerController::FImpl::UpdateHapticsFeedback(const FSGTouchState& TouchState)
{
if (!IsValid(TouchState.Hand))
{
return;
}
USGHapticGlove* Glove{TouchState.Hand->GetConnectedGlove()};
if (!IsValid(Glove))
{
return;
}
const bool bGloveConnected = Glove->IsConnected();
if (!bGloveConnected)
{
return;
}
// Queue the Force-Feedback command...
TArray<float> ForceFeedbackLevels{
GetForceFeedbackLevels(
TouchState.ActorThumbTouching, TouchState.ActorIndexTouching, TouchState.ActorMiddleTouching,
TouchState.ActorRingTouching, TouchState.ActorPinkyTouching)
};
Glove->QueueForceFeedbackLevels(MoveTemp(ForceFeedbackLevels));
// Send the haptics commands!
Glove->SendHaptics();
}
```
Here is the current implementation for `GetForceFeedbackLevels()`:
```cpp
TArray<float> ASGPlayerController::FImpl::GetForceFeedbackLevels(
const AActor* ActorThumbTouching,
const AActor* ActorIndexTouching,
const AActor* ActorMiddleTouching,
const AActor* ActorRingTouching,
const AActor* ActorPinkyTouching)
{
const float ThumbForceFeedbackLevel = GetForceFeedbackLevel(ActorThumbTouching);
const float IndexForceFeedbackLevel = GetForceFeedbackLevel(ActorIndexTouching);
const float MiddleForceFeedbackLevel = GetForceFeedbackLevel(ActorMiddleTouching);
const float RingForceFeedbackLevel = GetForceFeedbackLevel(ActorRingTouching);
const float PinkyForceFeedbackLevel = GetForceFeedbackLevel(ActorPinkyTouching);
const TArray<float> ForceFeedbackLevels{
ThumbForceFeedbackLevel,
IndexForceFeedbackLevel,
MiddleForceFeedbackLevel,
RingForceFeedbackLevel,
PinkyForceFeedbackLevel,
};
return ForceFeedbackLevels;
}
```
> [!TIP]
> You can
> [review the full implementation in the plguin source code for `SGPlayerController`](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGlove/Private/SenseGlove/GameFramework/SGPlayerController.cpp).
>
> In general, with the current version of the plugin, you can integrate haptic
> feedback into your own hand interaction system in several ways:
>
> - The [`SGHapticsComponent`](../sghapticscomponent/) high-level approach.
> - The SenseGlove C++ API:
> - Via the
> [SGHandLayer API](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGloveCore/Public/SGCore/SGHandLayer.h).
> - Via the
> [SGHpaticGlove API](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGloveCore/Public/SGCore/SGHapticGlove.h).
> - The SenseGlove Blueprint API:
> [SGHandLayer API](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGloveCoreKismet/Public/SGCoreKismet/SGHandLayerKismetLibrary.h) which provides a higher-level abstraction compared to the `SGHapticGlove` API.
> - Via the
> [SGHapticGlove API](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGloveCoreKismet/Public/SGCoreKismet/SGHapticGloveKismetLibrary.h),
which offers a lower-level interface than the `SGHandLayer` API and requires some boilerplate code to safely obtain an instance of the desired glove (see [Safe and Reliable Glove Access in Blueprint](../../safe-glove-access-blueprint/)).
> - Additionally, there is the
> [`SGTouchComponent`](../../../getting-started/setup-touch-system/), which
> provides simplified and limited functionality. On its own, it cannot trigger
> haptics. It is designed to work in conjunction with the stock
> `SGPlayerController` shipped with the SenseGlove Unreal Engine plugin.
### Actor Grabbed Event
This event is triggered whenever a grab is successfully performed by either the left or right hand. Subscribers to this event are notified about **which hand** performed the grab and **which grabbable actor** (an actor that owns an `SGGrabComponent`) was grabbed.
It is defined in C++ like this:
```cpp
DECLARE_EVENT_TwoParams(ASGPawn, FActorGrabbedEvent,
const USGVirtualHandComponent* Hand,
const AActor* Actor);
```
In Blueprint, the event appears as shown below:
![Blueprint Event OnActorGrabbed](sgpawn-event-on-actor-grabbed.png "Blueprint Event OnActorGrabbed]")
### Actor Released Event
This event is triggered whenever a release is successfully performed by either the left or right hand. Subscribers to this event are notified about **which hand** performed the release and **which grabbable actor** (an actor that owns an `SGGrabComponent`) was released.
It is defined in C++ like this:
```cpp
DECLARE_EVENT_TwoParams(ASGPawn, FActorReleasedEvent,
const USGVirtualHandComponent* Hand,
const AActor* Actor);
```
In Blueprint, the event appears as shown below:
![Blueprint Event OnActorReleased](sgpawn-event-on-actor-released.png "Blueprint Event OnActorReleased]")
### Actor Begin Touch Event
This event is triggered whenever **any finger** on the left or right hand comes into contact with another actor. Subscribers to this event are notified about **which hand** initiated the overlap and **which touchable actor** (an actor that owns an `SGTouchComponent`) was touched.
It is defined in C++ like this:
```cpp
DECLARE_EVENT_TwoParams(ASGPawn, FActorBeginTouchEvent,
const USGVirtualHandComponent* Hand,
const AActor* Actor);
```
In Blueprint, the event appears as shown below:
![Blueprint Event OnActorBeginTouch](sgpawn-event-on-actor-begin-touch.png "Blueprint Event OnActorBeginTouch]")
### Actor End Touch Event
This event is triggered whenever **any finger** on the left or right hand ends contact with another actor that was previously touched by that finger. Subscribers to this event are notified about **which hand**'s finger ended the overlap and **which touchable actor** (an actor that owns an `SGTouchComponent`) is no longer being touched by that finger.
It is defined in C++ like this:
```cpp
DECLARE_EVENT_TwoParams(ASGPawn, FActorEndTouchEvent,
const USGVirtualHandComponent* Hand,
const AActor* Actor);
```
In Blueprint, the event appears as shown below:
![Blueprint Event OnActorEndTouch](sgpawn-event-on-actor-end-touch.png "Blueprint Event OnActorEndTouch]")
## SGPawn Grab/Realase-Related Functions
In addition to the events described above, `SGPawn` provides a set of helper functions related to grabbing and releasing actors.
These functions allow you to:
- Check whether a specific hand can grab a given actor.
- Determine whether a hand is currently grabbing an actor.
- Retrieve the currently grabbed actor via an output parameter.
- Trigger grab and release actions programmatically for either hand.
These helper functions are exposed to both [C++](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGlove/Public/SenseGlove/GameFramework/SGPawn.h) and [Blueprint](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal?path=/Source/SenseGloveKismet/Public/SGKismet/SGPawnKismetLibrary.h):
```cpp
public:
FORCEINLINE bool CanLeftHandGrab(const AActor* Actor) const
{
return !IsLeftHandGrabbing() && ((IsValid(Actor) && Actor == LeftHandGrabState.ActorThumbCanGrab)
&& (Actor == LeftHandGrabState.ActorIndexCanGrab || Actor == LeftHandGrabState.ActorMiddleCanGrab));
}
FORCEINLINE bool IsLeftHandGrabbing(const AActor* Actor) const
{
return IsValid(Actor) && LeftHandGrabState.GrabbedActor == Actor;
}
bool IsLeftHandGrabbing(AActor*& OutActor) const;
FORCEINLINE bool IsLeftHandGrabbing() const
{
return IsValid(LeftHandGrabState.GrabbedActor);
}
FORCEINLINE bool CanRightHandGrab(const AActor* Actor) const
{
return !IsRightHandGrabbing() && ((IsValid(Actor) && Actor == RightHandGrabState.ActorThumbCanGrab)
&& (Actor == RightHandGrabState.ActorIndexCanGrab || Actor == RightHandGrabState.ActorMiddleCanGrab));
}
FORCEINLINE bool IsRightHandGrabbing(const AActor* Actor) const
{
return IsValid(Actor) && RightHandGrabState.GrabbedActor == Actor;
}
FORCEINLINE bool IsRightHandGrabbing() const
{
return IsValid(RightHandGrabState.GrabbedActor);
}
bool IsRightHandGrabbing(AActor*& OutActor) const;
FORCEINLINE bool CanGrab(const USGVirtualHandComponent* Hand, const AActor* Actor) const
{
return Hand == HandRight ? CanRightHandGrab(Actor) : CanLeftHandGrab(Actor);
}
FORCEINLINE bool IsGrabbing(const USGVirtualHandComponent* Hand, const AActor* Actor) const
{
return Hand == HandRight ? IsRightHandGrabbing(Actor) : IsLeftHandGrabbing(Actor);
}
bool IsGrabbing(const USGVirtualHandComponent* Hand, AActor*& OutActor) const;
FORCEINLINE bool IsGrabbing(const USGVirtualHandComponent* Hand) const
{
return Hand == HandRight ? IsRightHandGrabbing() : IsLeftHandGrabbing();
}
public:
FORCEINLINE void GrabLeft(AActor* Actor)
{
Grab(HandLeft, Actor);
}
FORCEINLINE void GrabRight(AActor* Actor)
{
Grab(HandRight, Actor);
}
void Grab(USGVirtualHandComponent* Hand, AActor* Actor);
void ReleaseLeft();
void ReleaseRight();
FORCEINLINE void Release(const USGVirtualHandComponent* Hand)
{
return Hand == HandRight ? ReleaseRight() : ReleaseLeft();
}
```
The same functions can be searched within the Blueprint Editor or accessed under the `SenseGlove > Game Framework > Pawn` category:
![SGPawn Grab/Realase-Related Blueprint Functions](sgpawn-grab-release-related-functions-1.png "SGPawn Grab/Realase-Related Blueprint Functions]")
![SGPawn Grab/Realase-Related Blueprint Functions](sgpawn-grab-release-related-functions-2.png "SGPawn Grab/Realase-Related Blueprint Functions]")
@@ -5,11 +5,77 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### [Unreleased] ## [2.8.0] - 2026-02-24
This minor release introduces high-level haptics and hand-tracking components, simplifies OpenXR integration around `FXRHandTrackingState`, and removes legacy `FXRMotionControllerData` APIs. It also includes ABI-breaking changes, Unreal Engine deprecations, documentation expansions, and various internal improvements.
### Added
- Added a `USGHapticsComponent` to allow sending a variety of haptic feedbacks (force-feedback, vibrotactile, custom waveforms, and wrist-squeeze) to the gloves without touching SenseGlove's low-level API.
- 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/). Using this component removes the need to calculate the wrist offsets manually, or an extra call to `USGHapticGlove::GetWristLocation()`, in comparison to when the `FXRHandTrackingState` is retrieved via UE's `GetHandTrackingState()`.
- 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.
- Added a new `FSGHandLayer::GetWristLocation()` overload to allow passing `FRotator`s instead of `FQuat`s as input or output parameters.
### Fixed
- Fix some copyright notices with wrong copyright owner. These propably has happend during bulk replaces with class or struct names.
- Additional minor fixes and improvements that may not be listed here.
### Changed
- The [rename of `SGDeviceList::SenseCommRunning()` to `SGDeviceList::SenseComRunning()`](https://dev.azure.com/SenseGlove/SenseGlove-Unreal/_git/SenseGlove-Unreal/commit/663ea6a2e7b7d84991005f33ddced8d8abdc11c3?refName=refs/heads/dev&path=/Source/SenseGloveCore/Private/SGCore/SGDeviceList.cpp&_a=contents) — previously listed as part of the [v2.7.0 release](#270---2025-11-18) — was not actually included in the `master` branch due to a missed commit during cherry-picking from `dev` to `master`. Since Microsoft Azure DevOps Repositories tags are always created from `master`, the `v2.7.0`, `v2.7.1`, `v2.7.2`, and `v2.7.3` tags also do not contain this change. The rename is, however, included in the Unreal Engine `5.7`, `5.6`, `5.5`, and `5.4` packages submitted to [Epic's Fab Store](https://www.fab.com/), as those archives were built from their respective engine-specific branches, which already contained the change. The rename is now correctly applied to the `master` branch and the `v2.8.0` tag as part of the `v2.8.0` release. If you are using the source code directly from `master`, or from one of the mentioned `v2.7.x` tags obtained via Microsoft Azure DevOps Repositories (instead of the version distributed via Fab or other engine-specific branches), this introduces an ABI and API breaking change affecting both C++ and Blueprint code if your exsiting plugin version `v2.7.x` does not already include this rename.
- List existing SenseGlove components under `SenseGlove` `ClassGroup` inside Unreal's Blueprint Editor. This chagnes `ClassGroup` for `USGGrabComponent`, `USGTouchComponent`, `USGVirtualHandComponent`, and `USGWristTrackerComponent`.
- Force `USGVirtualHandComponent` and `USGWristTrackerComponent` to update their XR hand-tracking data when their handedness is updated.
- Bumped the SenseGlove Unreal Engine Marketplace Packager to `v0.6.2-b675bab`.
- Bumped the copyright years.
### Removed
- Dropped support for Unreal Engine `5.4`, which was already deprecated via the `v2.7.x` release series.
- Dropped support for Epic Native/Cross Toolchains `v22` (previously used for building UE `5.3` and `5.4` Linux dependencies), as they were already deprecated via previous releases.
- Removed support for the deprecated `FXRMotionControllerData`. The plugin now exclusively uses `FXRHandTrackingState` (introduced in Unreal Engine 5.5+ and supported by The SenseGlove Unreal Engine Plugin since [`v2.2.0`](#220---2024-10-22) for OpenXR hand tracking. This affects only projects that directly consume `FXRMotionControllerData` from the SenseGlove plugin in their own custom hand-tracking or interaction systems. Please see [the v2.7.x to v2.8.x migration guide](../misc/upgrade-guide/#upgrading-from-v27x-to-v28x) for more details.
- Removed SenseGlove's `GetMotionControllerData()`; the alternative implementation to `IXTrackingSystem::GetMotionControllerData()`. You can now use SenseGlove's `GetHandTrackingState()` instead of Unreal's `IXTrackingSystem::GetHandTrackingState()` which guarantees the OpenXR hand-tracking data is coming from a SenseGlove device and also takes into account the SenseGlove's wrist-tracker offsets automatically.
### Documentation ### Documentation
- Added a [v2.7.x to v2.8.x migration guide](../misc/upgrade-guide/#upgrading-from-v27x-to-v28x) for transitioning from `FXRMotionControllerData` to `FXRHandTrackingState` for projects that directly consume SenseGlove OpenXR data.
- Added documentation section [Roll Your Own Hand Manipulation System](../advanced-topics/roll-your-own-hand-manipulation-system/).
- Added documentation section [SGPawn Events](../advanced-topics/roll-your-own-hand-manipulation-system/sgpawn-events-puppeteer-puppet-architecture/).
- Added documentation section [SGHandTrackerComponent](../advanced-topics/roll-your-own-hand-manipulation-system/sghandtrackercomponent/).
- Added documentation section [SGHapticsComponent](../advanced-topics/roll-your-own-hand-manipulation-system/sghapticscomponent/).
- Updated [Enabling XR_EXT_hand_tracking on VR Headsets](../getting-started/enabling-xr-ext-hand-tracking-vr-headsets/) documentation, replacing `FXRMotionControllerData` usage with `FXRHandTrackingState`.
- Updated [Setup the Virtual Hand Meshes](../getting-started/setup-virtual-hand-meshes/) documentation, replacing `FXRMotionControllerData` usage with `FXRHandTrackingState`.
- Updated [Plugin Configuration > Plugin Settings > Virtual Hand > Mesh](../plugin-configuration/plugin-settings/virtual-hand/mesh.md) documentation, replacing `FXRMotionControllerData` usage with `FXRHandTrackingState`.
- Updated [Advanced Topics > OpenXR](../advanced-topics/openxr/) documentation section with more relevant and plenty of useful information reflecting the recent changes.
- Updated [Advanced Topics > OpenXR > Consuming FXRHandTrackingState](../advanced-topics/openxr/consuming-fxrhandtrackingstate/) documentation section with information reflecting the recent changes.
- Updated [Advanced Topics > OpenXR > Consuming FXRHandTrackingState > Blueprint](../advanced-topics/openxr/consuming-fxrhandtrackingstate/blueprint.html) documentation section with information reflecting the recent changes.
- Updated [Advanced Topics > OpenXR > Consuming FXRHandTrackingState > C++](../advanced-topics/openxr/consuming-fxrhandtrackingstate/cpp.html) documentation section with information reflecting the recent changes.
- Removed the **Consuming FXRMotionControllerData** documentation section as it's no longer relevant.
- Removed the **Consuming FXRMotionControllerData > Blueprint** documentation section as it's no longer relevant.
- Removed the **Consuming FXRMotionControllerData > C++** documentation section as it's no longer relevant.
- Ensure deterministic dependency builds by pinning Rust toolchain.
- Changelog errata fixes.
- Minor changelog formatting fixes. - Minor changelog formatting fixes.
- Additional minor fixes and improvements that may not be listed here.
## [2.7.3] - 2026-02-17
This patch release resolves documentation build pipeline failures by updating broken mdBook tooling.
### Documentation
- Bumped [mdBook](https://rust-lang.github.io/mdBook/) to `v0.5.2`
- Bumped the [Michael-F-Bryan/mdbook-epub](https://github.com/Michael-F-Bryan/mdbook-epub) crate to [`21a1c8134134201a2d555313447c96e56e2a8996`](https://github.com/Michael-F-Bryan/mdbook-epub/commit/21a1c8134134201a2d555313447c96e56e2a8996), which addresses issue [#133](https://github.com/Michael-F-Bryan/mdbook-epub/issues/133). This allowed upgrading mdBook from `0.4.49` to `v0.5.2` without breaking images inside the generated ePub version of the handbook.
- Bumped [HollowMan6/mdbook-pdf](https://github.com/HollowMan6/mdbook-pdf) to `v0.1.13`.
- Removed [lambdalisue/rs-mdbook-alerts](https://github.com/lambdalisue/rs-mdbook-alerts) as it's incompatible with mdBook `v0.5.x`. The good news is that mdBook itself is now able to support this feature natively.
## [2.7.2] - 2026-02-17
This patch release resolves build issues affecting Unreal Engine `5.7` on Linux `Arm64` architecture.
### Fixed
- Fixed an issue in UE `5.7` projects where Linux `Arm64` builds were incorrectly linking against third-party libraries compiled with Epic Native/Cross Toolchain `v25` (shipped with UE `5.6`), instead of the required Toolchain `v26` binaries (shipped with UE `5.7`).
## [2.7.1] - 2025-12-09 ## [2.7.1] - 2025-12-09
@@ -169,7 +235,7 @@ This minor release delivers broad compatibility, stability, and maintainability
- `SGLog` now relies on `TAtomic<bool>` for thread-safe initialization. - `SGLog` now relies on `TAtomic<bool>` for thread-safe initialization.
- `SGBackend` now relies on `TAtomic<bool>` for thread-safe initialization. - `SGBackend` now relies on `TAtomic<bool>` for thread-safe initialization.
- `USGBackend::IsBackendInitialized()` is no longer inlined and the initialization flag has been moved to the private implementation of `USGBackend`. - `USGBackend::IsBackendInitialized()` is no longer inlined and the initialization flag has been moved to the private implementation of `USGBackend`.
- Bumped the SenseGlove Unreal Engine Marketplace Packager `v0.6.0-4108c6f`. - Bumped the SenseGlove Unreal Engine Marketplace Packager to `v0.6.0-4108c6f`.
### Removed ### Removed
@@ -265,7 +331,7 @@ This minor release brings various improvements and, notably, the first version t
- Now the motion sources for the wrist-tracking hardware or hand-tracking are queried and populated dynamically rather than relying on the hardcoded `EControllerHand` enum. This allows the SenseGlove Unreal Engine Plugin to integrate better into other plugins such as `ViveOpenXR`, which when enabled, provides many more options as the motion source for their various wrist-tracking hardware. - Now the motion sources for the wrist-tracking hardware or hand-tracking are queried and populated dynamically rather than relying on the hardcoded `EControllerHand` enum. This allows the SenseGlove Unreal Engine Plugin to integrate better into other plugins such as `ViveOpenXR`, which when enabled, provides many more options as the motion source for their various wrist-tracking hardware.
- `FSGWristTrackingSettings::LeftHandMotionSource` and `FSGWristTrackingSettings::RightHandMotionSource` types have changed from `EControllerHand` to `FName`. - `FSGWristTrackingSettings::LeftHandMotionSource` and `FSGWristTrackingSettings::RightHandMotionSource` types have changed from `EControllerHand` to `FName`.
- Bumped the SenseGlove libraries to `v2.105.3-97ea18cb`. - Bumped the SenseGlove libraries to `v2.105.3-97ea18cb`.
- Bumped the SenseGlove Unreal Engine Marketplace Packager `v0.5.0-7df1183`. - Bumped the SenseGlove Unreal Engine Marketplace Packager to `v0.5.0-7df1183`.
- Bumped the copyright years. - Bumped the copyright years.
- This is the last release to support Unreal Engine `5.2`. From `v2.5.x` onwards only UE `5.3` and newer will be supported. - This is the last release to support Unreal Engine `5.2`. From `v2.5.x` onwards only UE `5.3` and newer will be supported.
- The `ESGViveHMDDetectionPriority` enum items have changed and are no longer backward-compatible. - The `ESGViveHMDDetectionPriority` enum items have changed and are no longer backward-compatible.
@@ -1,13 +1,13 @@
# Extra Resources # Extra Resources
There are various resources available for older versions of the SenseGlove Unreal Engine Plugin prior to `v2.1.x` that might still be partially relevant. These include example projects, demo scenes, and tutorials. Plans are underway to provide new example projects, demo scenes, and tutorials for the latest release. In the meantime, the outdated resources can still be beneficial There are various resources available for older versions of the SenseGlove Unreal Engine Plugin prior to [`v2.1.x`](changelog.html#210---2024-08-16) that might still be partially relevant. These include example projects, demo scenes, and tutorials. Plans are underway to provide new example projects, demo scenes, and tutorials for the latest release. In the meantime, the outdated resources can still be beneficial
## Examples and Demo Projects ## Examples and Demo Projects
- [A basic OpenXR-compatible Blueprint demo demonstrating basic functionality such as grab/release, touch with buzz and force-feedback, etc (compatible with versions v2.1.0+).](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal-SGBasicDemo-OpenXR) - [A basic OpenXR-compatible Blueprint demo demonstrating basic functionality such as grab/release, touch with buzz and force-feedback, etc (compatible with versions `v2.1.0+`).](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal-SGBasicDemo-OpenXR)
- [A basic Blueprint demo demonstrating basic functionality such as grab/release, touch with buzz and force-feedback, etc (compatible with versions >= v1.4.x and <= v2.0.x).](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal-SGBasicDemo) - [A basic Blueprint demo demonstrating basic functionality such as grab/release, touch with buzz and force-feedback, etc (compatible with versions >= `v1.4.x` and <= `v2.0.x)`.](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal-SGBasicDemo)
- [Example C++ API Project (only compatible with early v1.x.x releases)](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal-SGPlaygroundCpp) - [Example C++ API Project (only compatible with early `v1.x.x` releases)](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal-SGPlaygroundCpp)
- [Example Blueprint API Project (only compatible with early v1.x.x releases)](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal-SGPlaygroundBP) - [Example Blueprint API Project (only compatible with early `v1.x.x` releases)](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal-SGPlaygroundBP)
### Third-Party OpenXR Integration Demos ### Third-Party OpenXR Integration Demos
@@ -1,7 +1,8 @@
## Planned Features Completion Status ## Planned Features Completion Status
### Implemented as of v2.7.x ### Implemented as of v2.8.x
- [X] SenseGlove Unreal Engine Handbook, a comprehensive guide to the SenseGlove Unreal Engine Plugin accessible online via [unreal.docs.senseglove.com](http://unreal.docs.senseglove.com/), and also in PDF and EPUB formats.
- [X] Full SenseGlove low-level core API access through Unreal C++. - [X] Full SenseGlove low-level core API access through Unreal C++.
- [X] Full SenseGlove low-level core API access through Blueprint. - [X] Full SenseGlove low-level core API access through Blueprint.
- [X] DK 1 Support. - [X] DK 1 Support.
@@ -41,8 +42,10 @@
- [X] Ability to manage the Engine Scalability Settings through the SenseGlove plugin in order to change the graphics settings on the fly. - [X] Ability to manage the Engine Scalability Settings through the SenseGlove plugin in order to change the graphics settings on the fly.
- [X] Ability to automatically ask for the required permissions on Android when the plugin is enabled in any UE project. - [X] Ability to automatically ask for the required permissions on Android when the plugin is enabled in any UE project.
- [X] `ViveOpenXR` plugin compatibility. - [X] `ViveOpenXR` plugin compatibility.
- [X] A highly convenient OpenXR hand-tracking data provider component to easily feed SenseGlove data to your own custom or third-party hand-manipulation systems.
- [X] A highly convenient haptics component to easily add haptics to your own custom or third-party hand-manipulation systems.
### Upcoming features planned for the v2.7.x release ### Upcoming features planned for the v2.9.x release
### Planned features long-term ### Planned features long-term
@@ -2,10 +2,10 @@
| | **Windows (MSVC 2017)** | **Windows (MSVC 2019)** | **Windows (MSVC 2022)** | **Linux x86-64 (Native Toolchain)** | **Linux AArch64 (Native Toolchain)** | **Meta Quest Standalone (Android NDK)** | **HTC VIVE Standalone (Android NDK)** | **Fab** | **Azure DevOps Version** | | | **Windows (MSVC 2017)** | **Windows (MSVC 2019)** | **Windows (MSVC 2022)** | **Linux x86-64 (Native Toolchain)** | **Linux AArch64 (Native Toolchain)** | **Meta Quest Standalone (Android NDK)** | **HTC VIVE Standalone (Android NDK)** | **Fab** | **Azure DevOps Version** |
|:--------:|-------------------------|-------------------------|-------------------------|:-----------------------------------:|:------------------------------------:|:---------------------------------------:|:-------------------------------------:|:--------:|:------------------------:| |:--------:|-------------------------|-------------------------|-------------------------|:-----------------------------------:|:------------------------------------:|:---------------------------------------:|:-------------------------------------:|:--------:|:------------------------:|
| **5.7** | ❌ | ❌ | ✅ v2.7.x | ✅ v2.7.x | ✅ v2.7.x | ✅ v2.7.x (r25b) | ❓ | ✅ v2.7.1 | ✅ v2.7.1 | | **5.7** | ❌ | ❌ | ✅ v2.8.x | ✅ v2.8.x | ✅ v2.8.x | ✅ v2.8.x (r25b) | ❓ | ✅ v2.8.0 | ✅ v2.8.0 |
| **5.6** | ❌ | ❌ | ✅ v2.7.x | ✅ v2.7.x | ✅ v2.7.x | ✅ v2.7.x (r25b) | ❓ | ✅ v2.7.1 | ✅ v2.7.1 | | **5.6** | ❌ | ❌ | ✅ v2.8.x | ✅ v2.8.x | ✅ v2.8.x | ✅ v2.8.x (r25b) | ❓ | ✅ v2.8.0 | ✅ v2.8.0 |
| **5.5** | ❌ | ❌ | ✅ v2.7.x | ✅ v2.7.x | ✅ v2.7.x | ✅ v2.7.x (r25b) | ❓ | ✅ v2.7.1 | ✅ v2.7.1 | | **5.5** | ❌ | ❌ | ✅ v2.8.x | ✅ v2.8.x | ✅ v2.8.x | ✅ v2.8.x (r25b) | ❓ | ✅ v2.8.0 | ✅ v2.8.0 |
| **5.4** | ❌ | ❌ | ⚠️ v2.7.x | ⚠️ v2.7.x | ⚠️ v2.7.x | ⚠️ v2.7.x (r25b) | ⚠️ v2.7.x (r25b) | ⚠️ v2.7.1 | ⚠️ v2.7.1 | | **5.4** | ❌ | ❌ | ⚠️ v2.7.x | ⚠️ v2.7.x | ⚠️ v2.7.x | ⚠️ v2.7.x (r25b) | ⚠️ v2.7.x (r25b) | ⚠️ v2.7.3 | ⚠️ v2.7.3 |
| **5.3** | ❌ | ⚠️ v2.6.x | ⚠️ v2.6.x | ⚠️ v2.6.x | ⚠️ v2.6.x | ⚠️ v2.6.x (r25b) | ⚠️ v2.6.x (r25b) | ⚠️ v2.6.3 | ⚠️ v2.6.3 | | **5.3** | ❌ | ⚠️ v2.6.x | ⚠️ v2.6.x | ⚠️ v2.6.x | ⚠️ v2.6.x | ⚠️ v2.6.x (r25b) | ⚠️ v2.6.x (r25b) | ⚠️ v2.6.3 | ⚠️ v2.6.3 |
| **5.2** | ❌ | ⚠️ v2.4.x | ⚠️ v2.4.x | ⚠️ v2.4.x | ⚠️ v2.4.x | ⚠️ v2.4.x (r25b) | ❓ | ⚠️ v2.4.2 | ⚠️ v2.4.2 | | **5.2** | ❌ | ⚠️ v2.4.x | ⚠️ v2.4.x | ⚠️ v2.4.x | ⚠️ v2.4.x | ⚠️ v2.4.x (r25b) | ❓ | ⚠️ v2.4.2 | ⚠️ v2.4.2 |
| **5.1** | ❌ | ⚠️ v2.0.x | ⚠️ v2.0.x | ⚠️ v2.0.x | ⚠️ v2.0.x | ⚠️ v2.0.x (r25b) | ❓ | ⚠️ v2.0.0 | ⚠️ v2.0.2 | | **5.1** | ❌ | ⚠️ v2.0.x | ⚠️ v2.0.x | ⚠️ v2.0.x | ⚠️ v2.0.x | ⚠️ v2.0.x (r25b) | ❓ | ⚠️ v2.0.0 | ⚠️ v2.0.2 |
@@ -1,11 +1,13 @@
# Enabling XR_EXT_hand_tracking OpenXR Extension on VR Headsets # Enabling XR_EXT_hand_tracking OpenXR Extension on VR Headsets
> [!IMPORTANT] > [!IMPORTANT]
> Starting from version `v2.1.0`, the SenseGlove Unreal Engine Plugin requires > Starting from version
> the `XR_EXT_hand_tracking` OpenXR extension to function. Without this > [`v2.1.0`](../../appendix/changelog.html#210---2024-08-16),
> OpenXR extension the plugin won't output any glove data. > the SenseGlove Unreal Engine Plugin requires the `XR_EXT_hand_tracking` OpenXR
> extension to function. Without this OpenXR extension the plugin won't output
> any glove data.
Since version `v2.1.0`, the SenseGlove Unreal Engine Plugin requires the `XR_EXT_hand_tracking` OpenXR extension to function. Whether you are streaming your immersive 3D VR application from your PC to your VR headset, or deploying it to your VR headset in standalone mode, enabling `XR_EXT_hand_tracking` support, might require additional plugins or settings depending on the HMD's vendor or model. Since version [`v2.1.0`](../../appendix/changelog.html#210---2024-08-16), the SenseGlove Unreal Engine Plugin requires the `XR_EXT_hand_tracking` OpenXR extension to function. Whether you are streaming your immersive 3D VR application from your PC to your VR headset, or deploying it to your VR headset in standalone mode, enabling `XR_EXT_hand_tracking` support, might require additional plugins or settings depending on the HMD's vendor or model.
## PCVR Mode ## PCVR Mode
@@ -1,6 +1,6 @@
# Enabling XR_EXT_hand_tracking OpenXR Extension on VR Headsets in PCVR Mode # Enabling XR_EXT_hand_tracking OpenXR Extension on VR Headsets in PCVR Mode
Starting from version `v2.1.0`, the SenseGlove Unreal Engine Plugin requires the `XR_EXT_hand_tracking` OpenXR extension to function. If you are streaming your immersive 3D VR application from your PC to your VR headset, enabling `XR_EXT_hand_tracking` support, requires additional plugins and settings depending on the HMD's vendor or model. Starting from version [`v2.1.0`](../../../appendix/changelog.html#210---2024-08-16), the SenseGlove Unreal Engine Plugin requires the `XR_EXT_hand_tracking` OpenXR extension to function. If you are streaming your immersive 3D VR application from your PC to your VR headset, enabling `XR_EXT_hand_tracking` support, requires additional plugins and settings depending on the HMD's vendor or model.
## Enabling OpenXR Plugin and Disabling OpenXRHandTracking Plugin ## Enabling OpenXR Plugin and Disabling OpenXRHandTracking Plugin
@@ -61,7 +61,7 @@ For VIVE headsets relying on the VIVE Business Streaming application, ensure the
![VIVE Business Streaming - Hand tracking - Enabling XR_EXT_hand_tracking and Emulating VIVE Wrist Tracker as VIVE Tracker](vive-business-streaming-hand-tracking.png "VIVE Business Streaming - Hand tracking - Enabling XR_EXT_hand_tracking and Emulating VIVE Wrist Tracker as VIVE Tracker") ![VIVE Business Streaming - Hand tracking - Enabling XR_EXT_hand_tracking and Emulating VIVE Wrist Tracker as VIVE Tracker](vive-business-streaming-hand-tracking.png "VIVE Business Streaming - Hand tracking - Enabling XR_EXT_hand_tracking and Emulating VIVE Wrist Tracker as VIVE Tracker")
> [!NOTE] > [!NOTE]
> Tracking and accessing `FXRMotionControllerData` output from SenseGlove > Tracking and accessing `FXRHandTrackingState` output from SenseGlove
> devices do not require Hand and Body Tracking to be enabled on the HMD device. > devices do not require Hand and Body Tracking to be enabled on the HMD device.
> Enabling this feature is only necessary if you wish to use hand-tracking as a > Enabling this feature is only necessary if you wish to use hand-tracking as a
> fallback option when no glove is connected to your PC. > fallback option when no glove is connected to your PC.
@@ -1,6 +1,6 @@
# Enabling XR_EXT_hand_tracking OpenXR Extension on VR Headsets in Standalone Mode # Enabling XR_EXT_hand_tracking OpenXR Extension on VR Headsets in Standalone Mode
Starting from version `v2.1.0`, the SenseGlove Unreal Engine Plugin requires the `XR_EXT_hand_tracking` OpenXR extension to function. If you are deploying your immersive 3D VR application to your VR headset in standalone mode, enabling `XR_EXT_hand_tracking` support, requires additional plugins and settings depending on the HMD's vendor or model. Starting from version [`v2.1.0`](../../../appendix/changelog.html#210---2024-08-16), the SenseGlove Unreal Engine Plugin requires the `XR_EXT_hand_tracking` OpenXR extension to function. If you are deploying your immersive 3D VR application to your VR headset in standalone mode, enabling `XR_EXT_hand_tracking` support, requires additional plugins and settings depending on the HMD's vendor or model.
## Enabling OpenXR Plugin and Disabling OpenXRHandTracking Plugin ## Enabling OpenXR Plugin and Disabling OpenXRHandTracking Plugin
@@ -282,6 +282,6 @@ Next, navigate to `Project Settings > Plugins > SenseGlove > Virtual Hand Settin
- `Left Hand Reference Mesh` - `Left Hand Reference Mesh`
- `Right Hand Reference Mesh` - `Right Hand Reference Mesh`
This configuration guarantees that the tracking system correctly interprets the bone transforms of the virtual hand meshes when generating `FXRMotionControllerData`. Additionally, it allows the animation system to accurately use these bone transforms when processing `FXRMotionControllerData` and animating the virtual hand meshes. This configuration guarantees that the tracking system correctly interprets the bone transforms of the virtual hand meshes when generating `FXRHandTrackingState`. Additionally, it allows the animation system to accurately use these bone transforms when processing `FXRHandTrackingState` and animating the virtual hand meshes.
![The SGPawn and the Plugin Virtual Hand Mesh Settings - Plugin settings](virtual-hand-mesh-settings-plugin.png "[The SGPawn and the Plugin Virtual Hand Mesh Settings - Plugin settings") ![The SGPawn and the Plugin Virtual Hand Mesh Settings - Plugin settings](virtual-hand-mesh-settings-plugin.png "[The SGPawn and the Plugin Virtual Hand Mesh Settings - Plugin settings")
@@ -3,7 +3,7 @@
``` ```
SENSEGLOVE SDK LICENSE SENSEGLOVE SDK LICENSE
Copyright (c) 2020 - 2025 SenseGlove Copyright (c) 2020 - 2026 SenseGlove
Purchase of the Product does not entitle you to ownership or a license to any Purchase of the Product does not entitle you to ownership or a license to any
software generated by SenseGlove for use with the Product (the “Software”). software generated by SenseGlove for use with the Product (the “Software”).
@@ -5,7 +5,7 @@ The SenseGlove Unreal Engine Handbook is licensed under the terms of the CC BY (
``` ```
Attribution 4.0 International Attribution 4.0 International
Copyright (c) 2020 - 2025 SenseGlove Copyright (c) 2020 - 2026 SenseGlove
======================================================================= =======================================================================
@@ -5,7 +5,7 @@ The SenseGlove Unreal Engine Plugin is licensed under the terms of the MIT Licen
``` ```
MIT License MIT License
Copyright (c) 2020 - 2025 SenseGlove Copyright (c) 2020 - 2026 SenseGlove
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@@ -17,7 +17,7 @@ Here are a few important notes to consider:
</div> </div>
> [!CAUTION] > [!CAUTION]
> As of the `v2.1.0` release of the SenseGlove Unreal Engine Plugin, the > As of the [`v2.1.0`](../../appendix/changelog.html#210---2024-08-16) release of the SenseGlove Unreal Engine Plugin, the
> `XR_EXT_hand_tracking` OpenXR extension is required for the plugin to > `XR_EXT_hand_tracking` OpenXR extension is required for the plugin to
> function. Without this OpenXR extension, the plugin won't output any glove > function. Without this OpenXR extension, the plugin won't output any glove
> data. > data.
@@ -1,5 +1,59 @@
# Upgrade Guide # Upgrade Guide
## Upgrading from v2.7.x to v2.8.x
### Summary of Breaking Changes
Starting with **SenseGlove Unreal Engine Plugin v2.8.x**, support for `FXRMotionControllerData` has been **removed**.
Although:
* Unreal Engine **5.5** and **5.6** still support the deprecated `FXRMotionControllerData`, and
* These engine versions are still supported by plugin v2.8.x,
the plugin now fully transitions to **`FXRHandTrackingState`**, which is the modern and recommended API introduced in **Unreal Engine 5.5+** for OpenXR hand tracking.
Additionally:
* Plugin v2.8.x no longer supports **Unreal Engine 5.4**.
* Because `FXRHandTrackingState` is the forward-compatible OpenXR API, continued support for `FXRMotionControllerData` is no longer maintained in the plugin.
We have supported `FXRHandTrackingState` since [`v2.2.0` (released 2024-10-22)](../../appendix/changelog.html#220---2024-10-22), and it has been stable in production for several releases.
## Who Is Affected?
This change only affects you if:
* You maintain a **custom hand manipulation or tracking system**, and
* Your implementation directly consumes `FXRMotionControllerData` provided by the SenseGlove plugin.
If you rely solely on the plugins provided components and standard integration workflow, no action is required.
## Required Migration
If you are directly consuming `FXRMotionControllerData`, for your project to build or function as expected, you must migrate to:
```
FXRHandTrackingState
```
The migration is straightforward because both structures represent similar hand tracking data concepts within Unreals OpenXR framework.
A step-by-step explanation of how to work with `FXRHandTrackingState` is available in this third-party tutorial:
👉 [Introduction to Virtual Reality, OpenXR Hand Tracking, and Gesture Detection in Unreal Engine](../../advanced-topics/openxr/third-party-tutorials/index.html#introduction-to-virtual-reality-openxr-hand-tracking-and-gesture-detection-in-unreal-engine)
## Why This Change?
* `FXRMotionControllerData` is deprecated.
* `FXRHandTrackingState` is the future-proof OpenXR hand tracking API.
* The existing Unreal Engines OpenXR implementation is centered around `FXRHandTrackingState` and `FXRMotionControllerData` has been completely removed form UE `5.7`+.
* Removing legacy support simplifies the plugin architecture and aligns it with Unreals forward direction.
If you are already using `FXRHandTrackingState` or not directly consuming the OpenXR data, no changes are required when upgrading to v2.8.x.
## Upgrading from v2.0.x to v2.1.x
The transition from `v2.0.x` to `v2.1.x` introduces [numerous changes](../../appendix/changelog.md), including several breaking changes. The effort required to upgrade your project will vary depending on its complexity and which features of the SenseGlove Unreal Engine Project you are using. However, if you are working with a simple Blueprint project like [SGBasicDemo](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal-SGBasicDemo), the upgrade process is quite straightforward. We successfully upgraded SGBasicDemo to [SGBasicDemo-OpenXR](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal-SGBasicDemo-OpenXR) by following the procedure outlined below. The transition from `v2.0.x` to `v2.1.x` introduces [numerous changes](../../appendix/changelog.md), including several breaking changes. The effort required to upgrade your project will vary depending on its complexity and which features of the SenseGlove Unreal Engine Project you are using. However, if you are working with a simple Blueprint project like [SGBasicDemo](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal-SGBasicDemo), the upgrade process is quite straightforward. We successfully upgraded SGBasicDemo to [SGBasicDemo-OpenXR](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal-SGBasicDemo-OpenXR) by following the procedure outlined below.
These are the notable changes that might affect your project: These are the notable changes that might affect your project:
@@ -13,7 +67,8 @@ These are the notable changes that might affect your project:
> your project to see if any change affects or breaks your current project. > your project to see if any change affects or breaks your current project.
> [!NOTE] > [!NOTE]
> For upgrading older versions of the plugin to `v2.0.0`, > For upgrading older versions of the plugin to
> [`v2.0.0`](../../appendix/changelog.html#200---2024-03-22),
> [a YouTube tutorial](https://youtu.be/VbWfoep-Hsg) is available. > [a YouTube tutorial](https://youtu.be/VbWfoep-Hsg) is available.
> <div style="margin: 0 auto; width: 560px;"> > <div style="margin: 0 auto; width: 560px;">
> <iframe width="560" height="315" src="https://www.youtube.com/embed/VbWfoep-Hsg" frameborder="0" allowfullscreen></iframe> > <iframe width="560" height="315" src="https://www.youtube.com/embed/VbWfoep-Hsg" frameborder="0" allowfullscreen></iframe>
@@ -75,13 +75,14 @@ Toipcs that do not fall under any specific category:
For users familiar with the basics, this section explores advanced features of the plugin: For users familiar with the basics, this section explores advanced features of the plugin:
- [Safe Glove Access in Blueprint](../advanced-topics/safe-glove-access-blueprint/) - [Safe Glove Access in Blueprint](../advanced-topics/safe-glove-access-blueprint/)
- [Roll Your Own Hand Manipulation System](../advanced-topics/roll-your-own-hand-manipulation-system/)
- [SGPawn Events](../advanced-topics/roll-your-own-hand-manipulation-system/sgpawn-events-puppeteer-puppet-architecture/)
- [SGHandTrackerComponent](../advanced-topics/roll-your-own-hand-manipulation-system/sghandtrackercomponent/)
- [SGHapticsComponent](../advanced-topics/roll-your-own-hand-manipulation-system/sghapticscomponent/)
- [OpenXR](../advanced-topics/openxr/) - [OpenXR](../advanced-topics/openxr/)
- [Consuming FXRHandTrackingState](../advanced-topics/openxr/consuming-fxrhandtrackingstate/) - [Consuming FXRHandTrackingState](../advanced-topics/openxr/consuming-fxrhandtrackingstate/)
- [Blueprint](../advanced-topics/openxr/consuming-fxrhandtrackingstate/blueprint.md) - [Blueprint](../advanced-topics/openxr/consuming-fxrhandtrackingstate/blueprint.md)
- [C++](../advanced-topics/openxr/consuming-fxrhandtrackingstate/cpp.md) - [C++](../advanced-topics/openxr/consuming-fxrhandtrackingstate/cpp.md)
- [Consuming FXRMotionControllerData](../advanced-topics/openxr/consuming-fxrmotioncontrollerdata/)
- [Blueprint](../advanced-topics/openxr/consuming-fxrmotioncontrollerdata/blueprint.md)
- [C++](../advanced-topics/openxr/consuming-fxrmotioncontrollerdata/cpp.md)
- [Third-Party Integrations](../advanced-topics/openxr/third-party-integrations/) - [Third-Party Integrations](../advanced-topics/openxr/third-party-integrations/)
- [Third-Party Tutorials](../advanced-topics/openxr/third-party-tutorials/) - [Third-Party Tutorials](../advanced-topics/openxr/third-party-tutorials/)
@@ -6,7 +6,7 @@ Utilized by the SenseGlove `Tracking` module to account for the current virtual
# LeftHandReferenceMesh # LeftHandReferenceMesh
The virtual hand model for the left hand is to be used by the SenseGlove `Tracking` module to generate all the `26` joint data present in the `FXRMotionControllerData`. The main reason the `Tracking` module requires a virtual hand mesh as a reference is the SenseGlove Hand Pose format only provides `15` joints. So, the remaining joint data for `FXRMotionControllerData` are calculated from a virtual hand mesh compatible with the Epic rig and also the values specified by `DistalPhalangesLengthSettings`. Furthermore, when calculating the existing joints data, their current locations and rotations are taken into account in calculating the resulting `FXRMotionControllerData`. The virtual hand model for the left hand is to be used by the SenseGlove `Tracking` module to generate all the `26` joint data present in the `FXRHandTrackingState`. The main reason the `Tracking` module requires a virtual hand mesh as a reference is the SenseGlove Hand Pose format only provides `15` joints. So, the remaining joint data for `FXRHandTrackingState` are calculated from a virtual hand mesh compatible with the Epic rig and also the values specified by `DistalPhalangesLengthSettings`. Furthermore, when calculating the existing joints data, their current locations and rotations are taken into account in calculating the resulting `FXRHandTrackingState`.
By default, no virtual hand mesh is set. By default, no virtual hand mesh is set.
@@ -19,7 +19,7 @@ By default, no virtual hand mesh is set.
# RightHandReferenceMesh # RightHandReferenceMesh
The virtual hand model for the right hand is to be used by the SenseGlove `Tracking` module to generate all the `26` joint data present in the `FXRMotionControllerData`. The main reason the `Tracking` module requires a virtual hand mesh as a reference is the SenseGlove Hand Pose format only provides `15` joints. So, the remaining joint data for `FXRMotionControllerData` are calculated from a virtual hand mesh compatible with the Epic rig and also the values specified by `DistalPhalangesLengthSettings`. Furthermore, when calculating the existing joints data, their current locations and rotations are taken into account in calculating the resulting `FXRMotionControllerData`. The virtual hand model for the right hand is to be used by the SenseGlove `Tracking` module to generate all the `26` joint data present in the `FXRHandTrackingState`. The main reason the `Tracking` module requires a virtual hand mesh as a reference is the SenseGlove Hand Pose format only provides `15` joints. So, the remaining joint data for `FXRHandTrackingState` are calculated from a virtual hand mesh compatible with the Epic rig and also the values specified by `DistalPhalangesLengthSettings`. Furthermore, when calculating the existing joints data, their current locations and rotations are taken into account in calculating the resulting `FXRHandTrackingState`.
By default, no virtual hand mesh is set. By default, no virtual hand mesh is set.
@@ -32,7 +32,7 @@ By default, no virtual hand mesh is set.
# DistalPhalangesLengthSettings # DistalPhalangesLengthSettings
The length of distal phalanges that cannot be retrieved from any virtual hand mesh compliant with the Epic standard rig. Also, the SenseGlove Hand Pose format does not provide these. This is used by SenseGlove `Tracking` module to calculate an `FXRMotionControllerData` the all `26` joints. The values you specify here depend on the shape of the virtual hand mesh and the defaults are approximated for the virtual hand model shipped with the Unreal Engine VRTemplate. The length of distal phalanges that cannot be retrieved from any virtual hand mesh compliant with the Epic standard rig. Also, the SenseGlove Hand Pose format does not provide these. This is used by SenseGlove `Tracking` module to calculate an `FXRHandTrackingState` the all `26` joints. The values you specify here depend on the shape of the virtual hand mesh and the defaults are approximated for the virtual hand model shipped with the Unreal Engine VRTemplate.
## RootBoneRotationCorrection ## RootBoneRotationCorrection
Binary file not shown.
Binary file not shown.
+1 -12
View File
@@ -45,7 +45,6 @@ Deploy:
- "5.7" - "5.7"
- "5.6" - "5.6"
- "5.5" - "5.5"
- "5.4"
Exclusions: Exclusions:
- .git - .git
@@ -200,11 +199,6 @@ Rocket:
Linux: "/opt/UE55" Linux: "/opt/UE55"
Windows: "C:\\OPT\\UE_5.5" Windows: "C:\\OPT\\UE_5.5"
- Branch: "5.4"
Path:
Linux: "/opt/UE54"
Windows: "C:\\OPT\\UE_5.4"
NDKPaths: NDKPaths:
- Branch: "5.7" - Branch: "5.7"
@@ -222,11 +216,6 @@ Rocket:
Linux: "/opt/android/sdk/ndk/25.1.8937393" Linux: "/opt/android/sdk/ndk/25.1.8937393"
Windows: "%LOCALAPPDATA%\\Android\\Sdk\\ndk\\25.1.8937393" Windows: "%LOCALAPPDATA%\\Android\\Sdk\\ndk\\25.1.8937393"
- Branch: "5.4"
Path:
Linux: "/opt/android/sdk/ndk/25.1.8937393"
Windows: "%LOCALAPPDATA%\\Android\\Sdk\\ndk\\25.1.8937393"
Rsync: Rsync:
@@ -246,4 +235,4 @@ Rsync:
DestinationDirectory: DestinationDirectory:
Linux: /tmp/SenseGlove-Unreal/Stage Linux: /tmp/SenseGlove-Unreal/Stage
Windows: "%TEMP%\\SenseGlove-Unreal\\Stage" Windows: "%TEMP%\\SenseGlove-Unreal\\Stage"
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"FileVersion": 3, "FileVersion": 3,
"Version": 1, "Version": 1,
"VersionName": "2.7.1", "VersionName": "2.8.0",
"FriendlyName": "SenseGlove", "FriendlyName": "SenseGlove",
"Description": "Integrating the SenseGlove haptic controller into Unreal Engine", "Description": "Integrating the SenseGlove haptic controller into Unreal Engine",
"Category": "Virtual Reality", "Category": "Virtual Reality",
@@ -7,7 +7,7 @@
* *
* (The MIT License) * (The MIT License)
* *
* Copyright (c) 2020 - 2025 SenseGlove * Copyright (c) 2020 - 2026 SenseGlove
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -7,7 +7,7 @@
* *
* (The MIT License) * (The MIT License)
* *
* Copyright (c) 2020 - 2025 SenseGlove * Copyright (c) 2020 - 2026 SenseGlove
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -7,7 +7,7 @@
* *
* (The MIT License) * (The MIT License)
* *
* Copyright (c) 2020 - 2025 SenseGlove * Copyright (c) 2020 - 2026 SenseGlove
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -7,7 +7,7 @@
* *
* (The MIT License) * (The MIT License)
* *
* Copyright (c) 2020 - 2025 SenseGlove * Copyright (c) 2020 - 2026 SenseGlove
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -107,7 +107,6 @@ void FSGVirtualHandAnimInstanceProxy::PreEvaluateAnimation(UAnimInstance* InAnim
return; return;
} }
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
HandTrackingState = VirtualHand->GetHandTrackingState(); HandTrackingState = VirtualHand->GetHandTrackingState();
if (!HandTrackingState.bValid) if (!HandTrackingState.bValid)
{ {
@@ -115,26 +114,11 @@ void FSGVirtualHandAnimInstanceProxy::PreEvaluateAnimation(UAnimInstance* InAnim
} }
ensureAlwaysMsgf(HandTrackingState.HandKeyLocations.Num() == HandTrackingState.HandKeyRotations.Num(), ensureAlwaysMsgf(HandTrackingState.HandKeyLocations.Num() == HandTrackingState.HandKeyRotations.Num(),
TEXT("Mismatched hand tracking state locations and rotations!")); TEXT("Mismatched hand tracking state locations and rotations!"));
#else /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
MotionControllerData = VirtualHand->GetMotionControllerData();
if (!MotionControllerData.bValid)
{
return;
}
ensureAlwaysMsgf(MotionControllerData.HandKeyPositions.Num() == MotionControllerData.HandKeyRotations.Num(),
TEXT("Mismatched motion controller data positions and rotations!"));
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
BoneNamesMap.Empty(); BoneNamesMap.Empty();
for (TArray<FQuat>::SizeType HandKeyIndex = 0; for (TArray<FQuat>::SizeType HandKeyIndex = 0;
HandKeyIndex < HandKeyIndex < HandTrackingState.HandKeyRotations.Num();
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 ++HandKeyIndex)
HandTrackingState.HandKeyRotations.Num()
#else /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
MotionControllerData.HandKeyRotations.Num()
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
; ++HandKeyIndex)
{ {
const FName& BoneName{VirtualHand->GetHandBoneName(HandKeyIndex)}; const FName& BoneName{VirtualHand->GetHandBoneName(HandKeyIndex)};
BoneNamesMap.Add(HandKeyIndex, BoneName); BoneNamesMap.Add(HandKeyIndex, BoneName);
@@ -152,17 +136,10 @@ bool FSGVirtualHandAnimInstanceProxy::Evaluate(FPoseContext& Output)
return FAnimInstanceProxy::Evaluate(Output); return FAnimInstanceProxy::Evaluate(Output);
} }
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
if (!HandTrackingState.bValid) if (!HandTrackingState.bValid)
{ {
return FAnimInstanceProxy::Evaluate(Output); return FAnimInstanceProxy::Evaluate(Output);
} }
#else /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
if (!MotionControllerData.bValid)
{
return FAnimInstanceProxy::Evaluate(Output);
}
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
FComponentSpacePoseContext PoseContext{Output.AnimInstanceProxy}; FComponentSpacePoseContext PoseContext{Output.AnimInstanceProxy};
PoseContext.Pose.InitPose(Output.Pose); PoseContext.Pose.InitPose(Output.Pose);
@@ -187,15 +164,9 @@ bool FSGVirtualHandAnimInstanceProxy::Evaluate(FPoseContext& Output)
BoneReference.BoneIndex = BoneIndex; BoneReference.BoneIndex = BoneIndex;
BoneReference.bUseSkeletonIndex = true; BoneReference.bUseSkeletonIndex = true;
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
const FVector& HandKeyLocation{HandTrackingState.HandKeyLocations[HandKeyIndex]}; const FVector& HandKeyLocation{HandTrackingState.HandKeyLocations[HandKeyIndex]};
const FQuat& HandKeyRotation{HandTrackingState.HandKeyRotations[HandKeyIndex]}; const FQuat& HandKeyRotation{HandTrackingState.HandKeyRotations[HandKeyIndex]};
const FTransform HandKeyTransform{HandKeyRotation, HandKeyLocation}; const FTransform HandKeyTransform{HandKeyRotation, HandKeyLocation};
#else /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
const FVector& HandKeyPosition{MotionControllerData.HandKeyPositions[HandKeyIndex]};
const FQuat& HandKeyRotation{MotionControllerData.HandKeyRotations[HandKeyIndex]};
const FTransform HandKeyTransform{HandKeyRotation, HandKeyPosition};
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
const FTransform BoneTransform{AnimationBoneCorrectionOffsetTransform * HandKeyTransform}; const FTransform BoneTransform{AnimationBoneCorrectionOffsetTransform * HandKeyTransform};
FRotator BoneRotation{BoneTransform.GetRotation().Rotator()}; FRotator BoneRotation{BoneTransform.GetRotation().Rotator()};
@@ -7,7 +7,7 @@
* *
* (The MIT License) * (The MIT License)
* *
* Copyright (c) 2020 - 2025 SenseGlove * Copyright (c) 2020 - 2026 SenseGlove
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -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,276 @@
/**
* @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/SGHapticsComponent.h"
#include "SGCore/SGCustomWaveform.h"
#include "SGCore/SGHapticGlove.h"
#include "SGCore/SGHandLayer.h"
#include "SGTracking/SGGloveTracker.h"
struct USGHapticsComponent::FImpl
{
/************************
* Static methods
************************/
/************************
* Owner object
************************/
USGHapticsComponent* Owner;
/************************
* Constructor / Destructor
************************/
explicit FImpl(USGHapticsComponent* InOwner);
~FImpl();
/************************
* Default copy constructor & copy assignment operator
************************/
FImpl(const FImpl& Rhs) = default;
FImpl& operator=(const FImpl& Rhs) = default;
/************************
* Methods
************************/
FORCEINLINE USGHapticGlove* GetGlove() const
{
const USGGloveTracker* GloveTracker{USGGloveTracker::GetInstance(Owner->GetOuter())};
USGHapticGlove* Glove{IsValid(GloveTracker) ? GloveTracker->GetGlove(Owner->IsRight()) : nullptr};
return Glove;
}
void AutoStopAllHaptics() const;
};
USGHapticsComponent::USGHapticsComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer),
Pimpl(TUniquePtr<FImpl, FImplDeleter>(new FImpl{this}, PimplDeleter))
{
bRight = true;
bAutoStopAllHaptics = true;
}
void USGHapticsComponent::SetRight(const bool bInRight)
{
bRight = bInRight;
if (bAutoStopAllHaptics)
{
Pimpl->AutoStopAllHaptics();
}
}
void USGHapticsComponent::UninitializeComponent()
{
Super::UninitializeComponent();
if (bAutoStopAllHaptics)
{
Pimpl->AutoStopAllHaptics();
}
}
void USGHapticsComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Super::EndPlay(EndPlayReason);
if (bAutoStopAllHaptics)
{
Pimpl->AutoStopAllHaptics();
}
}
void USGHapticsComponent::StopHaptics()
{
USGHapticGlove* Glove{Pimpl->GetGlove()};
if (IsValid(Glove) && Glove->IsConnected())
{
Glove->StopHaptics();
}
}
void USGHapticsComponent::StopVibrations()
{
USGHapticGlove* Glove{Pimpl->GetGlove()};
if (IsValid(Glove) && Glove->IsConnected())
{
Glove->StopVibrations();
}
}
bool USGHapticsComponent::SendHaptics()
{
USGHapticGlove* Glove{Pimpl->GetGlove()};
if (IsValid(Glove) && Glove->IsConnected())
{
return Glove->SendHaptics();
}
return false;
}
bool USGHapticsComponent::SupportsCustomWaveform(const ESGHapticLocation AtLocation) const
{
USGHapticGlove* Glove{Pimpl->GetGlove()};
if (IsValid(Glove) && Glove->IsConnected())
{
return Glove->SupportsCustomWaveform(AtLocation);
}
return false;
}
bool USGHapticsComponent::SendCustomWaveform(
USGCustomWaveform* OutWaveform, const ESGHapticLocation Location)
{
USGHapticGlove* Glove{Pimpl->GetGlove()};
if (IsValid(Glove) && Glove->IsConnected())
{
return Glove->SendCustomWaveform(OutWaveform, Location);
}
return false;
}
bool USGHapticsComponent::SendCustomWaveform(
const float Amplitude, const float Duration, const ESGHapticLocation Location)
{
USGHapticGlove* Glove{Pimpl->GetGlove()};
if (IsValid(Glove) && Glove->IsConnected())
{
USGCustomWaveform* Waveform{
USGCustomWaveform::NewCustomWaveform(GetOuter(), Amplitude, Duration)
};
return Glove->SendCustomWaveform(MoveTemp(Waveform), Location);
}
return false;
}
bool USGHapticsComponent::SendCustomWaveform(
const float Amplitude, const float Duration, const float Frequency, const ESGHapticLocation Location)
{
USGHapticGlove* Glove{Pimpl->GetGlove()};
if (IsValid(Glove) && Glove->IsConnected())
{
USGCustomWaveform* Waveform{
USGCustomWaveform::NewCustomWaveform(GetOuter(), Amplitude, Duration, Frequency)
};
return Glove->SendCustomWaveform(MoveTemp(Waveform), Location);
}
return false;
}
bool USGHapticsComponent::QueueForceFeedbackLevels(const TArray<float>& Levels01)
{
USGHapticGlove* Glove{Pimpl->GetGlove()};
if (IsValid(Glove) && Glove->IsConnected())
{
return Glove->QueueForceFeedbackLevels(Levels01);
}
return false;
}
bool USGHapticsComponent::QueueForceFeedbackLevel(const int32 Finger, const float Level01)
{
USGHapticGlove* Glove{Pimpl->GetGlove()};
if (IsValid(Glove) && Glove->IsConnected())
{
return Glove->QueueForceFeedbackLevel(Finger, Level01);
}
return false;
}
bool USGHapticsComponent::QueueVibroLevels(const TArray<float>& Levels01)
{
USGHapticGlove* Glove{Pimpl->GetGlove()};
if (IsValid(Glove) && Glove->IsConnected())
{
return Glove->QueueVibroLevels(Levels01);
}
return false;
}
bool USGHapticsComponent::QueueVibroLevel(const ESGHapticLocation Location, const float Level01)
{
USGHapticGlove* Glove{Pimpl->GetGlove()};
if (IsValid(Glove) && Glove->IsConnected())
{
return Glove->QueueVibroLevel(Location, Level01);
}
return false;
}
bool USGHapticsComponent::SupportsWristSqueeze() const
{
return FSGHandLayer::SupportsWristSqueeze(IsRight());
}
bool USGHapticsComponent::QueueWristSqueeze(const float SqueezeLevel01, const bool bSendImmediate)
{
return FSGHandLayer::QueueCommand_WristSqueeze(IsRight(), SqueezeLevel01, bSendImmediate);
}
USGHapticsComponent::FImpl::FImpl(USGHapticsComponent* InOwner)
: Owner(InOwner)
{
}
USGHapticsComponent::FImpl::~FImpl() = default;
void USGHapticsComponent::FImpl::AutoStopAllHaptics() const
{
USGHapticGlove* Glove{GetGlove()};
if (IsValid(Glove) && Glove->IsConnected() && Owner->bAutoStopAllHaptics)
{
Glove->StopHaptics();
}
}
void USGHapticsComponent::FImplDeleter::operator()(const FImpl* P) const
{
delete P;
}
@@ -7,7 +7,7 @@
* *
* (The MIT License) * (The MIT License)
* *
* Copyright (c) 2020 - 2025 SenseGlove * Copyright (c) 2020 - 2026 SenseGlove
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -7,7 +7,7 @@
* *
* (The MIT License) * (The MIT License)
* *
* Copyright (c) 2020 - 2025 SenseGlove * Copyright (c) 2020 - 2026 SenseGlove
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -47,7 +47,6 @@
#include "SenseGlove/Animation/SGVirtualHandAnimInstance.h" #include "SenseGlove/Animation/SGVirtualHandAnimInstance.h"
#include "SenseGlove/Components/SGWristTrackerComponent.h" #include "SenseGlove/Components/SGWristTrackerComponent.h"
#include "SGBuildHacks/SGPlatform.h"
#include "SGCore/SGHapticGlove.h" #include "SGCore/SGHapticGlove.h"
#include "SGDebug/SGDebugVirtualHand.h" #include "SGDebug/SGDebugVirtualHand.h"
#include "SGLog/SGLog.h" #include "SGLog/SGLog.h"
@@ -100,13 +99,7 @@ struct USGVirtualHandComponent::FImpl
ECollisionEnabled::Type CachedCollisionEnabled; ECollisionEnabled::Type CachedCollisionEnabled;
FCollisionResponseContainer CachedCollisionResponse; FCollisionResponseContainer CachedCollisionResponse;
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION < 7
FXRMotionControllerData MotionControllerData;
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION < 7 */
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
FXRHandTrackingState HandTrackingState; FXRHandTrackingState HandTrackingState;
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
/************************ /************************
* Owner object * Owner object
@@ -166,6 +159,8 @@ struct USGVirtualHandComponent::FImpl
void DrawDebugVirtualHand() const; void DrawDebugVirtualHand() const;
void TriggerHandVisibilityChangedEvent(bool bNewVisibility) const; void TriggerHandVisibilityChangedEvent(bool bNewVisibility) const;
void AutoStopAllHaptics() const;
}; };
FName USGVirtualHandComponent::FImpl::ParseVirtualHandSettingsOverridesPropertyName(const FName& PropertyName) FName USGVirtualHandComponent::FImpl::ParseVirtualHandSettingsOverridesPropertyName(const FName& PropertyName)
@@ -246,11 +241,7 @@ USGVirtualHandComponent::USGVirtualHandComponent(const FObjectInitializer& Objec
Super::SetCollisionResponseToChannel(ECC_Vehicle, ECR_Block); Super::SetCollisionResponseToChannel(ECC_Vehicle, ECR_Block);
Super::SetCollisionResponseToChannel(ECC_Destructible, ECR_Block); Super::SetCollisionResponseToChannel(ECC_Destructible, ECR_Block);
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
Super::SetAnimInstanceClass(USGVirtualHandAnimInstance::StaticClass()); Super::SetAnimInstanceClass(USGVirtualHandAnimInstance::StaticClass());
#else /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
Super::SetAnimClass(USGVirtualHandAnimInstance::StaticClass());
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
bRight = true; bRight = true;
@@ -267,6 +258,14 @@ void USGVirtualHandComponent::SetRight(const bool bInRight)
{ {
bRight = bInRight; bRight = bInRight;
const bool bAutoStopHaptics = GetVirtualHandSettings().HapticsSettings.bAutoStopAllHapticsOnEndPlay;
if (bAutoStopHaptics)
{
Pimpl->AutoStopAllHaptics();
}
Pimpl->UpdateXRData();
if (!Pimpl->IsUsingUserMesh()) if (!Pimpl->IsUsingUserMesh())
{ {
Pimpl->SetDefaultMesh(); Pimpl->SetDefaultMesh();
@@ -305,7 +304,6 @@ FSGVirtualHandSettings USGVirtualHandComponent::GetVirtualHandSettings() const
return VirtualHandSettings; return VirtualHandSettings;
} }
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
void USGVirtualHandComponent::SetAnimInstanceClass(UClass* NewClass) void USGVirtualHandComponent::SetAnimInstanceClass(UClass* NewClass)
{ {
ensureAlwaysMsgf((TIsDerivedFrom<decltype(NewClass), USGVirtualHandAnimInstance>::IsDerived), ensureAlwaysMsgf((TIsDerivedFrom<decltype(NewClass), USGVirtualHandAnimInstance>::IsDerived),
@@ -313,15 +311,6 @@ void USGVirtualHandComponent::SetAnimInstanceClass(UClass* NewClass)
Super::SetAnimInstanceClass(NewClass); Super::SetAnimInstanceClass(NewClass);
} }
#else /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
void USGVirtualHandComponent::SetAnimClass(UClass* NewClass)
{
ensureAlwaysMsgf((TIsDerivedFrom<decltype(NewClass), USGVirtualHandAnimInstance>::IsDerived),
TEXT("The AnimInstance class must be a subclass of USGVirtualHandAnimInstance!"));
Super::SetAnimClass(NewClass);
}
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
void USGVirtualHandComponent::PostInitProperties() void USGVirtualHandComponent::PostInitProperties()
{ {
@@ -372,24 +361,22 @@ void USGVirtualHandComponent::InitializeComponent()
{ {
Super::InitializeComponent(); Super::InitializeComponent();
Pimpl->UpdateXRData();
if (!Pimpl->IsUsingUserMesh()) if (!Pimpl->IsUsingUserMesh())
{ {
Pimpl->SetDefaultMesh(); Pimpl->SetDefaultMesh();
} }
Pimpl->UpdateXRData();
} }
void USGVirtualHandComponent::UninitializeComponent() void USGVirtualHandComponent::UninitializeComponent()
{ {
Super::UninitializeComponent(); Super::UninitializeComponent();
const USGGloveTracker* GloveTracker{USGGloveTracker::GetInstance(GetOuter())};
USGHapticGlove* Glove{IsValid(GloveTracker) ? GloveTracker->GetGlove(IsRight()) : nullptr};
const bool bAutoStopHaptics = GetVirtualHandSettings().HapticsSettings.bAutoStopAllHapticsOnEndPlay; const bool bAutoStopHaptics = GetVirtualHandSettings().HapticsSettings.bAutoStopAllHapticsOnEndPlay;
if (IsValid(Glove) && Glove->IsConnected() && bAutoStopHaptics) if (bAutoStopHaptics)
{ {
Glove->StopHaptics(); Pimpl->AutoStopAllHaptics();
} }
} }
@@ -404,12 +391,10 @@ void USGVirtualHandComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{ {
Super::EndPlay(EndPlayReason); Super::EndPlay(EndPlayReason);
const USGGloveTracker* GloveTracker{USGGloveTracker::GetInstance(GetOuter())};
USGHapticGlove* Glove{IsValid(GloveTracker) ? GloveTracker->GetGlove(IsRight()) : nullptr};
const bool bAutoStopHaptics = GetVirtualHandSettings().HapticsSettings.bAutoStopAllHapticsOnEndPlay; const bool bAutoStopHaptics = GetVirtualHandSettings().HapticsSettings.bAutoStopAllHapticsOnEndPlay;
if (IsValid(Glove) && Glove->IsConnected() && bAutoStopHaptics) if (bAutoStopHaptics)
{ {
Glove->StopHaptics(); Pimpl->AutoStopAllHaptics();
} }
} }
@@ -471,12 +456,10 @@ USGHapticGlove* USGVirtualHandComponent::GetConnectedGlove() const
return Glove; return Glove;
} }
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
const FXRHandTrackingState& USGVirtualHandComponent::GetHandTrackingState() const const FXRHandTrackingState& USGVirtualHandComponent::GetHandTrackingState() const
{ {
return Pimpl->HandTrackingState; return Pimpl->HandTrackingState;
} }
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
const TArray<FName>& USGVirtualHandComponent::GetHandBoneNames() const const TArray<FName>& USGVirtualHandComponent::GetHandBoneNames() const
{ {
@@ -749,36 +732,14 @@ void USGVirtualHandComponent::FImpl::UpdateXRData()
{ {
const EControllerHand Hand = Owner->IsRight() ? EControllerHand::Right : EControllerHand::Left; const EControllerHand Hand = Owner->IsRight() ? EControllerHand::Right : EControllerHand::Left;
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION < 7 FXRHandTrackingState State;
{ State.bValid = false;
FXRMotionControllerData Data; const bool bGotHandTrackingState =
Data.bValid = false; FSGXRTracker::GetHandTrackingState(
Owner, EXRSpaceType::UnrealWorldSpace, Hand, State);
(void) bGotHandTrackingState;
HandTrackingState = MoveTemp(State);
PRAGMA_DISABLE_DEPRECATION_WARNINGS
const bool bGotMotionControllerData =
FSGXRTracker::GetMotionControllerData(Owner, Hand, Data);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
(void)
bGotMotionControllerData;
MotionControllerData = MoveTemp(Data);
}
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION < 7 */
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
{
FXRHandTrackingState State;
State.bValid = false;
const bool bGotHandTrackingState =
FSGXRTracker::GetHandTrackingState(
Owner, EXRSpaceType::UnrealWorldSpace, Hand, State);
(void) bGotHandTrackingState;
HandTrackingState = MoveTemp(State);
}
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
} }
void USGVirtualHandComponent::FImpl::SetVisibility(const bool bInVisible) void USGVirtualHandComponent::FImpl::SetVisibility(const bool bInVisible)
@@ -799,14 +760,7 @@ void USGVirtualHandComponent::FImpl::SetVisibility(const bool bInVisible)
void USGVirtualHandComponent::FImpl::UpdateVisibility() void USGVirtualHandComponent::FImpl::UpdateVisibility()
{ {
const bool bHandDataAvailable = const bool bHandDataAvailable = HandTrackingState.bValid;
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
HandTrackingState.bValid
#else /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
MotionControllerData.bValid
&& MotionControllerData.DeviceVisualType == EXRVisualType::Hand
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
;
const bool bHandVisible = Owner->IsVisible(); const bool bHandVisible = Owner->IsVisible();
const bool bShouldBeVisibleWhenHandDataUnavailable = const bool bShouldBeVisibleWhenHandDataUnavailable =
Owner->GetVirtualHandSettings().bVisibleWhenHandDataUnavailable; Owner->GetVirtualHandSettings().bVisibleWhenHandDataUnavailable;
@@ -865,23 +819,12 @@ void USGVirtualHandComponent::FImpl::DrawDebugVirtualHand() const
return; return;
} }
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
if (!HandTrackingState.bValid) if (!HandTrackingState.bValid)
{ {
return; return;
} }
FSGDebugVirtualHand::Draw(World, HandTrackingState, Owner->GetVirtualHandSettings().DebuggingSettings); FSGDebugVirtualHand::Draw(World, HandTrackingState, Owner->GetVirtualHandSettings().DebuggingSettings);
#else /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
if (!MotionControllerData.bValid
|| MotionControllerData.DeviceVisualType == EXRVisualType::Hand)
{
return;
}
FSGDebugVirtualHand::Draw(World, MotionControllerData, Owner->GetVirtualHandSettings().DebuggingSettings);
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
} }
void USGVirtualHandComponent::FImpl::TriggerHandVisibilityChangedEvent(const bool bNewVisibility) const void USGVirtualHandComponent::FImpl::TriggerHandVisibilityChangedEvent(const bool bNewVisibility) const
@@ -890,6 +833,17 @@ void USGVirtualHandComponent::FImpl::TriggerHandVisibilityChangedEvent(const boo
Owner->OnHandVisibilityChanged(bNewVisibility); Owner->OnHandVisibilityChanged(bNewVisibility);
} }
void USGVirtualHandComponent::FImpl::AutoStopAllHaptics() const
{
const USGGloveTracker* GloveTracker{USGGloveTracker::GetInstance(Owner->GetOuter())};
USGHapticGlove* Glove{IsValid(GloveTracker) ? GloveTracker->GetGlove(Owner->IsRight()) : nullptr};
const bool bAutoStopHaptics = Owner->GetVirtualHandSettings().HapticsSettings.bAutoStopAllHapticsOnEndPlay;
if (IsValid(Glove) && Glove->IsConnected() && bAutoStopHaptics)
{
Glove->StopHaptics();
}
}
void USGVirtualHandComponent::FImplDeleter::operator()(const FImpl* P) const void USGVirtualHandComponent::FImplDeleter::operator()(const FImpl* P) const
{ {
delete P; delete P;
@@ -7,7 +7,7 @@
* *
* (The MIT License) * (The MIT License)
* *
* Copyright (c) 2020 - 2025 SenseGlove * Copyright (c) 2020 - 2026 SenseGlove
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
@@ -37,7 +37,6 @@
#include "IXRTrackingSystem.h" #include "IXRTrackingSystem.h"
#include "SGBuildHacks/SGPlatform.h"
#include "SGDebug/SGDebugGizmo.h" #include "SGDebug/SGDebugGizmo.h"
#include "SGLog/SGLog.h" #include "SGLog/SGLog.h"
#include "SGSettings/SGSettings.h" #include "SGSettings/SGSettings.h"
@@ -57,13 +56,7 @@ struct USGWristTrackerComponent::FImpl
bool bOverridePluginSettingsPropertyAlreadyModified; bool bOverridePluginSettingsPropertyAlreadyModified;
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION < 7
FXRMotionControllerData MotionControllerData;
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION < 7 */
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
FXRHandTrackingState HandTrackingState; FXRHandTrackingState HandTrackingState;
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
/************************ /************************
* Owner object * Owner object
@@ -146,7 +139,10 @@ FName USGWristTrackerComponent::FImpl::ParseWristTrackingSettingsOverridesProper
void USGWristTrackerComponent::SetRight(const bool bInRight) void USGWristTrackerComponent::SetRight(const bool bInRight)
{ {
bRight = bInRight; bRight = bInRight;
Pimpl->UpdateMotionSource(); Pimpl->UpdateMotionSource();
Pimpl->UpdateXRData();
Pimpl->UpdateWristTrackingData();
} }
void USGWristTrackerComponent::SetWristTrackingSettingsOverrides( void USGWristTrackerComponent::SetWristTrackingSettingsOverrides(
@@ -255,19 +251,10 @@ void USGWristTrackerComponent::EditorTick(
Pimpl->UpdateWristTrackingData(); Pimpl->UpdateWristTrackingData();
} }
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION < 7
const FXRMotionControllerData& USGWristTrackerComponent::GetMotionControllerData() const
{
return Pimpl->MotionControllerData;
}
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION < 7 */
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
const FXRHandTrackingState& USGWristTrackerComponent::GetHandTrackingState() const const FXRHandTrackingState& USGWristTrackerComponent::GetHandTrackingState() const
{ {
return Pimpl->HandTrackingState; return Pimpl->HandTrackingState;
} }
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
USGWristTrackerComponent::FImpl::FImpl(USGWristTrackerComponent* InOwner) USGWristTrackerComponent::FImpl::FImpl(USGWristTrackerComponent* InOwner)
: bOverridePluginSettingsPropertyAlreadyModified(false), : bOverridePluginSettingsPropertyAlreadyModified(false),
@@ -331,35 +318,14 @@ void USGWristTrackerComponent::FImpl::UpdateXRData()
{ {
const EControllerHand Hand = Owner->IsRight() ? EControllerHand::Right : EControllerHand::Left; const EControllerHand Hand = Owner->IsRight() ? EControllerHand::Right : EControllerHand::Left;
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION < 7 FXRHandTrackingState State;
{ State.bValid = false;
FXRMotionControllerData Data; const bool bGotHandTrackingState =
Data.bValid = false; FSGXRTracker::GetHandTrackingState(
Owner, EXRSpaceType::UnrealWorldSpace, Hand, State);
(void) bGotHandTrackingState;
PRAGMA_DISABLE_DEPRECATION_WARNINGS HandTrackingState = MoveTemp(State);
const bool bGotMotionControllerData =
FSGXRTracker::GetMotionControllerData(Owner, Hand, Data);
PRAGMA_ENABLE_DEPRECATION_WARNINGS
(void)
bGotMotionControllerData;
MotionControllerData = MoveTemp(Data);
}
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION < 7 */
#if ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5
{
FXRHandTrackingState State;
State.bValid = false;
const bool bGotHandTrackingState =
FSGXRTracker::GetHandTrackingState(
Owner, EXRSpaceType::UnrealWorldSpace, Hand, State);
(void) bGotHandTrackingState;
HandTrackingState = MoveTemp(State);
}
#endif /* ENGINE_MAJOR_VERSION >= 5 && ENGINE_MINOR_VERSION >= 5 */
} }
void USGWristTrackerComponent::FImpl::UpdateWristTrackingData() const void USGWristTrackerComponent::FImpl::UpdateWristTrackingData() const
@@ -7,7 +7,7 @@
* *
* (The MIT License) * (The MIT License)
* *
* Copyright (c) 2020 - 2025 SenseGlove * Copyright (c) 2020 - 2026 SenseGlove
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal

Some files were not shown because too many files have changed in this diff Show More