Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
551e625c1f
|
||
|
|
287a81356d
|
||
|
|
02690badc6
|
||
|
|
ba4441655f
|
||
|
|
9b361f5ef9
|
||
|
|
22210f05f0
|
||
|
|
baa2998c8d
|
||
|
|
4b2654a296
|
||
|
|
3393a31e5f
|
||
|
|
3d274c1734
|
||
|
|
eb8a8b197b
|
||
|
|
f3f5893fd8
|
@@ -38,8 +38,6 @@ Resources/**/* filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# LFS: everything under Source/ThirdParty/
|
||||
Source/ThirdParty/**/* filter=lfs diff=lfs merge=lfs -text
|
||||
# Except
|
||||
Source/ThirdParty/**/*.Build.cs filter= diff= merge= text
|
||||
|
||||
# LFS: everything under Handbook/dist/
|
||||
Handbook/dist/**/* filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -50,9 +48,3 @@ Handbook/dist/**/* filter=lfs diff=lfs merge=lfs -text
|
||||
# LFS: sgum-packager Linux and Windows binaries
|
||||
Packager/sgum-packager filter=lfs diff=lfs merge=lfs -text
|
||||
Packager/sgum-packager.exe filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# LFS: sgfakeshm-connect-client abd sgfakeshm-core-client Linux and Windows binaries
|
||||
Tuning/sgfakeshm-connect-client filter=lfs diff=lfs merge=lfs -text
|
||||
Tuning/sgfakeshm-connect-client.exe filter=lfs diff=lfs merge=lfs -text
|
||||
Tuning/sgfakeshm-core-client filter=lfs diff=lfs merge=lfs -text
|
||||
Tuning/sgfakeshm-core-client.exe filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -1,5 +1,4 @@
|
||||
[FilterPlugin]
|
||||
/Config/FilterPlugin.ini
|
||||
/Documentation/the-senseglove-unreal-engine-handbook.epub
|
||||
/Documentation/the-senseglove-unreal-engine-handbook.pdf
|
||||
/Documentation/the-senseglove-unreal-engine-handbook.zip
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# (The MIT License)
|
||||
#
|
||||
# Copyright (c) 2020 - 2026 SenseGlove
|
||||
# Copyright (c) 2020 - 2024 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
|
||||
|
||||
+93
-120
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# (The MIT License)
|
||||
#
|
||||
# Copyright (c) 2020 - 2026 SenseGlove
|
||||
# Copyright (c) 2020 - 2024 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
|
||||
@@ -51,19 +51,17 @@ SG_PLUGIN_FILE := $(SG_PLUGIN_DIR)/SenseGlove.uplugin
|
||||
|
||||
SG_DOCUMENTATION_URL := https://unreal.docs.senseglove.com
|
||||
SG_DOCUMENTATION_DIR := $(SG_PLUGIN_DIR)/Documentation
|
||||
SG_DOCUMENTATION_EPUB_DIR := $(SG_DOCUMENTATION_DIR)/epub
|
||||
SG_DOCUMENTATION_HTML_DIR_NAME := html
|
||||
SG_DOCUMENTATION_HTML_DIR := $(SG_DOCUMENTATION_DIR)/$(SG_DOCUMENTATION_HTML_DIR_NAME)
|
||||
SG_DOCUMENTATION_PDF_DIR := $(SG_DOCUMENTATION_DIR)/pdf-outline
|
||||
SG_DOCUMENTATION_PDF_FILE := $(SG_DOCUMENTATION_PDF_DIR)/output.pdf
|
||||
SG_DOCUMENTATION_EPUB_DIR := $(SG_DOCUMENTATION_DIR)/epub
|
||||
SG_DOCUMENTATION_EPUB_FILE := $(SG_DOCUMENTATION_EPUB_DIR)/$(SG_PROJECT_NAME).epub
|
||||
SG_DOCUMENTATION_PACK_DIR_NAME := pack
|
||||
SG_DOCUMENTATION_PACK_DIR := $(SG_DOCUMENTATION_DIR)/$(SG_DOCUMENTATION_PACK_DIR_NAME)
|
||||
|
||||
SG_HANDBOOK_PACKED_HTML_DIR := $(SG_DOCUMENTATION_PACK_DIR)/$(SG_HANDBOOK_ESCAPED_NAME)
|
||||
SG_HANDBOOK_PACKED_HTML_FILE := $(SG_DOCUMENTATION_PACK_DIR)/$(SG_HANDBOOK_ESCAPED_NAME).zip
|
||||
SG_HANDBOOK_PACKED_PDF_FILE := $(SG_DOCUMENTATION_PACK_DIR)/$(SG_HANDBOOK_ESCAPED_NAME).pdf
|
||||
SG_HANDBOOK_PACKED_EPUB_FILE := $(SG_DOCUMENTATION_PACK_DIR)/$(SG_HANDBOOK_ESCAPED_NAME).epub
|
||||
|
||||
SG_BROWSER_BINARY_PATH_LINUX := /opt/brave.com/brave/brave
|
||||
SG_BROWSER_BINARY_PATH_WINDOWS := C:\Program Files\BraveSoftware\Brave-Browser\Application\brave.exe
|
||||
@@ -119,12 +117,6 @@ 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_PLATFORM_SUPPORT_MATRIX_FILE := $(SG_PLUGIN_DIR)/PLATFORM_SUPPORT_MATRIX.md
|
||||
|
||||
################################################################################
|
||||
# Rust/Cargo Settings
|
||||
################################################################################
|
||||
|
||||
RUST_TOOLCHAIN_VERSION := 1.96.1
|
||||
|
||||
################################################################################
|
||||
# Version and Build Information
|
||||
################################################################################
|
||||
@@ -232,7 +224,6 @@ SG_MDBOOK_HTML_SITE_URL := $(SG_HANDBOOK_REVISION)
|
||||
SG_HANDBOOK_REVISION_URL := $(SG_DOCUMENTATION_URL)/$(SG_HANDBOOK_REVISION)
|
||||
SG_HANDBOOK_RELEASE_NAME := $(shell echo $(SG_PROJECT_NAME)-$(SG_HANDBOOK_REVISION) | tr '[:upper:]' '[:lower:]' | sed 's/ /-/g')
|
||||
SG_HANDBOOK_PDF_URL := $(SG_HANDBOOK_REVISION_URL)/$(SG_HANDBOOK_RELEASE_NAME).pdf
|
||||
SG_HANDBOOK_EPUB_URL := $(SG_HANDBOOK_REVISION_URL)/$(SG_HANDBOOK_RELEASE_NAME).epub
|
||||
endif # ($(strip $(SG_HANDBOOK_REVISION)),mismatched)
|
||||
|
||||
################################################################################
|
||||
@@ -292,7 +283,6 @@ SG_HANDBOOK_DEPLOY_TEMP_DIR := $(shell mktemp -d -t $(SG_HANDBOOK_DEPLOY_TEMP_DI
|
||||
$(info Temporary directory has been set up at '$(SG_HANDBOOK_DEPLOY_TEMP_DIR)')
|
||||
|
||||
SG_HANDBOOK_DEPLOY_GENERATED_PDF_FILE := $(SG_HANDBOOK_DEPLOY_TEMP_DIR)/$(SG_HANDBOOK_RELEASE_NAME).pdf
|
||||
SG_HANDBOOK_DEPLOY_GENERATED_EPUB_FILE := $(SG_HANDBOOK_DEPLOY_TEMP_DIR)/$(SG_HANDBOOK_RELEASE_NAME).epub
|
||||
|
||||
SG_HANDBOOK_DEPLOY_REVISION_LIST_FILE := $(SG_HANDBOOK_DEPLOY_TEMP_DIR)/revisions.txt
|
||||
|
||||
@@ -342,7 +332,6 @@ info:
|
||||
@echo " - Handbook Revision : $(SG_HANDBOOK_REVISION)"
|
||||
@echo " - Handbook Revision URL : $(SG_HANDBOOK_REVISION_URL)"
|
||||
@echo " - Handbook PDF URL : $(SG_HANDBOOK_PDF_URL)"
|
||||
@echo " - Handbook ePub URL : $(SG_HANDBOOK_EPUB_URL)"
|
||||
@echo " - Git Branch : $(SG_GIT_BRANCH)"
|
||||
@echo " - Git Tag : $(SG_GIT_TAG)"
|
||||
@echo " - Git Commit : $(SG_GIT_COMMIT)"
|
||||
@@ -371,18 +360,16 @@ clean:
|
||||
.PHONY: install-build-deps
|
||||
install-build-deps:
|
||||
@echo "Installing build dependencies if they have not already been installed..."
|
||||
@rustup install $(RUST_TOOLCHAIN_VERSION)
|
||||
@rustup override set $(RUST_TOOLCHAIN_VERSION)
|
||||
@cargo install mdbook --version "0.5.4" --locked
|
||||
@cargo install xq --vers "^0.5" --locked
|
||||
@cargo install mdbook-pdf --vers "0.1.13" --locked
|
||||
@cargo install mdbook --vers "^0.4" --locked
|
||||
@cargo install xq --vers "^0.4" --locked
|
||||
@cargo install mdbook-alerts --vers "^0.6" --locked
|
||||
@cargo install mdbook-pdf --vers "^0.1" --locked
|
||||
ifeq ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
@echo "Installing mdbook-pdf-outline..."
|
||||
@python -m venv $(VIRTUAL_ENV) \
|
||||
&& . $(SG_LINUX_PYTHON_VENV_BIN_DIR)/activate \
|
||||
&& pip install mdbook-pdf-outline 2>/dev/null
|
||||
endif # ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
@cargo install mdbook-epub --vers "0.5.4" --locked
|
||||
|
||||
################################################################################
|
||||
# Make Targets - mdBook
|
||||
@@ -469,7 +456,6 @@ _mdbook_serve:
|
||||
SG_HANDBOOK_REVISION='$(SG_HANDBOOK_REVISION)' \
|
||||
SG_HANDBOOK_REVISION_URL='$(SG_HANDBOOK_REVISION_URL)' \
|
||||
SG_HANDBOOK_PDF_URL='$(SG_HANDBOOK_PDF_URL)' \
|
||||
SG_HANDBOOK_EPUB_URL='$(SG_HANDBOOK_EPUB_URL)' \
|
||||
SG_GIT_BRANCH='$(SG_GIT_BRANCH)' \
|
||||
SG_GIT_TAG='$(SG_GIT_TAG)' \
|
||||
SG_GIT_COMMIT='$(SG_GIT_COMMIT)' \
|
||||
@@ -502,14 +488,13 @@ _mdbook_build:
|
||||
SG_HANDBOOK_REVISION='$(SG_HANDBOOK_REVISION)' \
|
||||
SG_HANDBOOK_REVISION_URL='$(SG_HANDBOOK_REVISION_URL)' \
|
||||
SG_HANDBOOK_PDF_URL='$(SG_HANDBOOK_PDF_URL)' \
|
||||
SG_HANDBOOK_EPUB_URL='$(SG_HANDBOOK_EPUB_URL)' \
|
||||
SG_GIT_BRANCH='$(SG_GIT_BRANCH)' \
|
||||
SG_GIT_TAG='$(SG_GIT_TAG)' \
|
||||
SG_GIT_COMMIT='$(SG_GIT_COMMIT)' \
|
||||
SG_GIT_COMMIT_NUMS_SINCE_TAG='$(SG_GIT_COMMIT_NUMS_SINCE_TAG)' \
|
||||
SG_GIT_TREE_STATE='$(SG_GIT_TREE_STATE)' \
|
||||
SG_GIT_IS_SHALLOW_CLONE='$(SG_GIT_IS_SHALLOW_CLONE)' \
|
||||
SG_GIT_LATEST_REMOTE_TAG='$(SG_GIT_LATEST_REMOTE_TAG)' \
|
||||
SG_GIT_TREE_STATE='$(SG_GIT_TREE_STATE)' \
|
||||
SG_GIT_VERSION='$(SG_GIT_VERSION)' \
|
||||
SG_GIT_VERSION_MAJOR='$(SG_GIT_VERSION_MAJOR)' \
|
||||
SG_GIT_VERSION_MINOR='$(SG_GIT_VERSION_MINOR)' \
|
||||
@@ -534,30 +519,24 @@ _pack:
|
||||
@mkdir -p "$(SG_DOCUMENTATION_PACK_DIR)"
|
||||
@echo "Moving '$(SG_DOCUMENTATION_HTML_DIR)' to '$(SG_HANDBOOK_PACKED_HTML_DIR)'..."
|
||||
ifeq ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
@mv "$(SG_DOCUMENTATION_HTML_DIR)" "$(SG_HANDBOOK_PACKED_HTML_DIR)"
|
||||
@mv $(SG_DOCUMENTATION_HTML_DIR) $(SG_HANDBOOK_PACKED_HTML_DIR)
|
||||
else # ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
@powershell -Command "Move-Item '$(SG_DOCUMENTATION_HTML_DIR)' '$(SG_HANDBOOK_PACKED_HTML_DIR)'"
|
||||
endif # ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
@echo "Compressing '$(SG_HANDBOOK_PACKED_HTML_DIR)' to '$(SG_HANDBOOK_PACKED_HTML_FILE)'..."
|
||||
ifeq ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
@cd "$(SG_DOCUMENTATION_PACK_DIR)" \
|
||||
@cd $(SG_DOCUMENTATION_PACK_DIR) \
|
||||
&& zip $(SG_HANDBOOK_PACKED_HTML_FILE) -r $(SG_HANDBOOK_ESCAPED_NAME)
|
||||
else # ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
@cd "$(SG_DOCUMENTATION_PACK_DIR)" \
|
||||
@cd $(SG_DOCUMENTATION_PACK_DIR) \
|
||||
&& powershell Compress-Archive $(SG_HANDBOOK_ESCAPED_NAME) $(SG_HANDBOOK_PACKED_HTML_FILE)
|
||||
endif # ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
@echo "Moving '$(SG_DOCUMENTATION_PDF_FILE)' to '$(SG_HANDBOOK_PACKED_PDF_FILE)'..."
|
||||
ifeq ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
@mv "$(SG_DOCUMENTATION_PDF_FILE)" "$(SG_HANDBOOK_PACKED_PDF_FILE)"
|
||||
@mv $(SG_DOCUMENTATION_PDF_FILE) $(SG_HANDBOOK_PACKED_PDF_FILE)
|
||||
else # ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
@powershell -Command "Move-Item '$(SG_DOCUMENTATION_PDF_FILE)' '$(SG_HANDBOOK_PACKED_PDF_FILE)'"
|
||||
endif # ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
@echo "Moving '$(SG_DOCUMENTATION_EPUB_FILE)' to '$(SG_HANDBOOK_PACKED_EPUB_FILE)'..."
|
||||
ifeq ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
@mv "$(SG_DOCUMENTATION_EPUB_FILE)" "$(SG_HANDBOOK_PACKED_EPUB_FILE)"
|
||||
else # ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
@powershell -Command "Move-Item '$(SG_DOCUMENTATION_EPUB_FILE)' '$(SG_HANDBOOK_PACKED_EPUB_FILE)'"
|
||||
endif # ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
|
||||
.PHONY: _deploy
|
||||
_deploy:
|
||||
@@ -573,7 +552,7 @@ ifeq ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
--delete $(SG_DOCUMENTATION_HTML_DIR)/ \
|
||||
$(SG_HANDBOOK_SSH_USER)@$(SG_HANDBOOK_SSH_HOST):$(SG_HANDBOOK_SSH_REMOTE_DIR)/$(SG_HANDBOOK_REVISION)
|
||||
else # ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
@cd "$(SG_DOCUMENTATION_DIR)" \
|
||||
@cd $(SG_DOCUMENTATION_DIR) \
|
||||
&& "$(SG_RSYNC_BINARY_PATH_WINDOWS)" \
|
||||
-avze "\"$(SG_RSYNC_SSH_BINARY_PATH_WINDOWS)\" -p $(SG_HANDBOOK_SSH_PORT)" \
|
||||
--perms \
|
||||
@@ -582,19 +561,13 @@ else # ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
$(SG_HANDBOOK_SSH_USER)@$(SG_HANDBOOK_SSH_HOST):$(SG_HANDBOOK_SSH_REMOTE_DIR)/$(SG_HANDBOOK_REVISION)
|
||||
endif # ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
@echo "Copying '$(SG_DOCUMENTATION_PDF_FILE)' to '$(SG_HANDBOOK_DEPLOY_GENERATED_PDF_FILE)'..."
|
||||
@cp -v "$(SG_DOCUMENTATION_PDF_FILE)" "$(SG_HANDBOOK_DEPLOY_GENERATED_PDF_FILE)"
|
||||
@cp -v $(SG_DOCUMENTATION_PDF_FILE) $(SG_HANDBOOK_DEPLOY_GENERATED_PDF_FILE)
|
||||
@echo "Uploading '$(SG_HANDBOOK_DEPLOY_GENERATED_PDF_FILE)' to '$(SG_HANDBOOK_SSH_USER)@$(SG_HANDBOOK_SSH_HOST):$(SG_HANDBOOK_SSH_REMOTE_DIR)/$(SG_HANDBOOK_REVISION)'..."
|
||||
@scp \
|
||||
-P $(SG_HANDBOOK_SSH_PORT) \
|
||||
"$(SG_HANDBOOK_DEPLOY_GENERATED_PDF_FILE)" \
|
||||
$(SG_HANDBOOK_SSH_USER)@$(SG_HANDBOOK_SSH_HOST):$(SG_HANDBOOK_SSH_REMOTE_DIR)/$(SG_HANDBOOK_REVISION)
|
||||
@echo "Copying '$(SG_DOCUMENTATION_EPUB_FILE)' to '$(SG_HANDBOOK_DEPLOY_GENERATED_EPUB_FILE)'..."
|
||||
@cp -v "$(SG_DOCUMENTATION_EPUB_FILE)" "$(SG_HANDBOOK_DEPLOY_GENERATED_EPUB_FILE)"
|
||||
@echo "Uploading '$(SG_HANDBOOK_DEPLOY_GENERATED_EPUB_FILE)' to '$(SG_HANDBOOK_SSH_USER)@$(SG_HANDBOOK_SSH_HOST):$(SG_HANDBOOK_SSH_REMOTE_DIR)/$(SG_HANDBOOK_REVISION)'..."
|
||||
@scp \
|
||||
-P $(SG_HANDBOOK_SSH_PORT) \
|
||||
"$(SG_HANDBOOK_DEPLOY_GENERATED_EPUB_FILE)" \
|
||||
$(SG_HANDBOOK_DEPLOY_GENERATED_PDF_FILE) \
|
||||
$(SG_HANDBOOK_SSH_USER)@$(SG_HANDBOOK_SSH_HOST):$(SG_HANDBOOK_SSH_REMOTE_DIR)/$(SG_HANDBOOK_REVISION)
|
||||
@cp -v $(SG_DOCUMENTATION_PDF_FILE) $(SG_HANDBOOK_DEPLOY_GENERATED_PDF_FILE)
|
||||
@echo "Retrieving the list of deployed handbook revisions from '$(SG_HANDBOOK_SSH_USER)@$(SG_HANDBOOK_SSH_HOST):$(SG_HANDBOOK_SSH_PORT)/$(SG_HANDBOOK_SSH_REMOTE_DIR)'..."
|
||||
@ssh \
|
||||
-p $(SG_HANDBOOK_SSH_PORT) $(SG_HANDBOOK_SSH_USER)@$(SG_HANDBOOK_SSH_HOST) \
|
||||
@@ -607,96 +580,96 @@ endif # ($(SG_BUILD_PLATFORM),$(SG_BUILD_PLATFORM_LINUX))
|
||||
grep -i 'next' $(SG_HANDBOOK_DEPLOY_REVISION_LIST_FILE); \
|
||||
grep -vi 'next' $(SG_HANDBOOK_DEPLOY_REVISION_LIST_FILE) \
|
||||
| sort -r; \
|
||||
} > "$(SG_HANDBOOK_DEPLOY_REVISION_LIST_FILE).tmp"
|
||||
@mv "$(SG_HANDBOOK_DEPLOY_REVISION_LIST_FILE).tmp" "$(SG_HANDBOOK_DEPLOY_REVISION_LIST_FILE)"
|
||||
} > $(SG_HANDBOOK_DEPLOY_REVISION_LIST_FILE).tmp
|
||||
@mv $(SG_HANDBOOK_DEPLOY_REVISION_LIST_FILE).tmp $(SG_HANDBOOK_DEPLOY_REVISION_LIST_FILE)
|
||||
@echo "Revisions list saved to '$(SG_HANDBOOK_DEPLOY_REVISION_LIST_FILE)'!"
|
||||
@echo "Current revisions:"
|
||||
@sed 's/^/ - /' "$(SG_HANDBOOK_DEPLOY_REVISION_LIST_FILE)"
|
||||
@sed 's/^/ - /' $(SG_HANDBOOK_DEPLOY_REVISION_LIST_FILE)
|
||||
@echo "Generating '$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)'..."
|
||||
@echo "<html>" > "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "<head>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "<title>Welcome to $(SG_PROJECT_NAME)</title>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "<link rel='icon' href='favicon.svg'>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "<link rel='shortcut icon' href='favicon.svg'>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "<style>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "body {" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "font-family: Arial, sans-serif;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "background-color: #003070;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "color: #fff;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "margin: 0;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "padding: 0;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "}" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo ".container {" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "padding: 2.0em;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "}" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo ".logo-container {" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "text-align: center;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "}" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo ".logo-container img {" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "max-width: 100%;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "height: auto;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "}" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo ".title-container {" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "text-align: center;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "}" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo ".revisions-container {" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "display: table;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "width: 300px;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "margin: 0 auto;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "padding: 20px;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "}" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo ".row {" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "display: table-row;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "}" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo ".cell {" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "display: table-cell;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "background-color: #004890;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "border-radius: 5px;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "margin: 10px;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "padding: 20px;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "text-align: center;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "transition: transform 0.2s;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "}" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo ".cell:hover {" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "transform: scale(1.05);" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "}" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo ".cell a {" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "display: block;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "height: 100%;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "width: 100%;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "text-decoration: none;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "color: #fff;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "font-weight: bold;" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "}" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "</style>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "</head>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "<body>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "<div class='container'>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "<html>" > $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "<head>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "<title>Welcome to $(SG_PROJECT_NAME)</title>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "<link rel='icon' href='favicon.svg'>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "<link rel='shortcut icon' href='favicon.svg'>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "<style>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "body {" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "font-family: Arial, sans-serif;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "background-color: #003070;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "color: #fff;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "margin: 0;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "padding: 0;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "}" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo ".container {" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "padding: 2.0em;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "}" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo ".logo-container {" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "text-align: center;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "}" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo ".logo-container img {" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "max-width: 100%;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "height: auto;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "}" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo ".title-container {" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "text-align: center;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "}" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo ".revisions-container {" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "display: table;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "width: 300px;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "margin: 0 auto;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "padding: 20px;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "}" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo ".row {" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "display: table-row;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "}" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo ".cell {" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "display: table-cell;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "background-color: #004890;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "border-radius: 5px;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "margin: 10px;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "padding: 20px;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "text-align: center;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "transition: transform 0.2s;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "}" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo ".cell:hover {" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "transform: scale(1.05);" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "}" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo ".cell a {" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "display: block;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "height: 100%;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "width: 100%;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "text-decoration: none;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "color: #fff;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "font-weight: bold;" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "}" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "</style>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "</head>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "<body>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "<div class='container'>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "<div class='logo-container'>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "<img src="favicon.svg" alt='SenseGlove' />" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "</div>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "<div class='title-container'>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "<h1>Welcome to the SenseGlove Unreal Engine Handbook</h1>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "<h3>Please choose a revision to continue!</h3>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "<h5>* Please note that the next revision is the documentation for the unstable next release of the plugin and subject to change!</h5>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "</div>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "<div class='revisions-container'>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "<img src="favicon.svg" alt='SenseGlove' />" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "</div>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "<div class='title-container'>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "<h1>Welcome to the SenseGlove Unreal Engine Handbook</h1>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "<h3>Please choose a revision to continue!</h3>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "<h5>* Please note that the next revision is the documentation for the unstable next release of the plugin and subject to change!</h5>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "</div>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "<div class='revisions-container'>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@while read dir; \
|
||||
do \
|
||||
echo "<div class='row'><div class='cell'><a href=\"$$dir\">$$dir</a></div></div>"; \
|
||||
done < $(SG_HANDBOOK_DEPLOY_REVISION_LIST_FILE) >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "</div>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "</div>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "</body>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
@echo "</html>" >> "$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)"
|
||||
done < $(SG_HANDBOOK_DEPLOY_REVISION_LIST_FILE) >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "</div>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "</div>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "</body>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "</html>" >> $(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)
|
||||
@echo "Uploading '$(SG_HANDBOOK_FAVICON_FILE)' to '$(SG_HANDBOOK_SSH_USER)@$(SG_HANDBOOK_SSH_HOST):$(SG_HANDBOOK_SSH_REMOTE_DIR)'"
|
||||
@scp \
|
||||
-P $(SG_HANDBOOK_SSH_PORT) \
|
||||
"$(SG_HANDBOOK_FAVICON_FILE)" \
|
||||
$(SG_HANDBOOK_FAVICON_FILE) \
|
||||
$(SG_HANDBOOK_SSH_USER)@$(SG_HANDBOOK_SSH_HOST):$(SG_HANDBOOK_SSH_REMOTE_DIR)
|
||||
@echo "Uploading '$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)' to '$(SG_HANDBOOK_SSH_USER)@$(SG_HANDBOOK_SSH_HOST):$(SG_HANDBOOK_SSH_REMOTE_DIR)'"
|
||||
@scp \
|
||||
-P $(SG_HANDBOOK_SSH_PORT) \
|
||||
"$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE)" \
|
||||
$(SG_HANDBOOK_DEPLOY_GENERATED_HOME_INDEX_FILE) \
|
||||
$(SG_HANDBOOK_SSH_USER)@$(SG_HANDBOOK_SSH_HOST):$(SG_HANDBOOK_SSH_REMOTE_DIR)
|
||||
@@ -5,7 +5,7 @@
|
||||
#
|
||||
# (The MIT License)
|
||||
#
|
||||
# Copyright (c) 2020 - 2026 SenseGlove
|
||||
# Copyright (c) 2020 - 2024 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
|
||||
@@ -33,8 +33,8 @@ fi
|
||||
|
||||
readonly JSON_INPUT=$(cat)
|
||||
|
||||
readonly CONTEXT=$(printf '%s' "${JSON_INPUT}" | xq '.[0]')
|
||||
readonly BOOK=$(printf '%s' "${JSON_INPUT}" | xq '.[1]')
|
||||
readonly CONTEXT=$(echo "${JSON_INPUT}" | xq '.[0]')
|
||||
readonly BOOK=$(echo "${JSON_INPUT}" | xq '.[1]')
|
||||
|
||||
escape_string() {
|
||||
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
|
||||
@@ -43,7 +43,6 @@ escape_string() {
|
||||
SG_PROJECT_NAME_ESCAPED=$(escape_string "${SG_PROJECT_NAME}")
|
||||
SG_HANDBOOK_REVISION_ESCAPED=$(escape_string "${SG_HANDBOOK_REVISION}")
|
||||
SG_HANDBOOK_REVISION_URL_ESCAPED=$(escape_string "${SG_HANDBOOK_REVISION_URL}")
|
||||
SG_HANDBOOK_EPUB_URL_ESCAPED=$(escape_string "${SG_HANDBOOK_EPUB_URL}")
|
||||
SG_HANDBOOK_PDF_URL_ESCAPED=$(escape_string "${SG_HANDBOOK_PDF_URL}")
|
||||
SG_GIT_BRANCH_ESCAPED=$(escape_string "${SG_GIT_BRANCH}")
|
||||
SG_GIT_TAG_ESCAPED=$(escape_string "${SG_GIT_TAG}")
|
||||
@@ -63,15 +62,14 @@ SG_PLUGIN_VERSION_PATCH_ESCAPED=$(escape_string "${SG_PLUGIN_VERSION_PATCH}")
|
||||
SG_BUILD_HOST_ESCAPED=$(escape_string "${SG_BUILD_HOST}")
|
||||
SG_BUILD_TIME_ESCAPED=$(escape_string "${SG_BUILD_TIME}")
|
||||
|
||||
PARSED_BOOK=$(printf '%s' "${BOOK}" | xq "
|
||||
.items[] |= (
|
||||
PARSED_BOOK=$(echo "${BOOK}" | xq "
|
||||
.sections[] |= (
|
||||
if has(\"Chapter\") then
|
||||
.Chapter.content |=
|
||||
walk(if type == \"string\" then
|
||||
. | gsub(\"{{ #sg-project-name }}\"; \"${SG_PROJECT_NAME_ESCAPED}\") |
|
||||
gsub(\"{{ #sg-handbook-revision }}\"; \"${SG_HANDBOOK_REVISION_ESCAPED}\") |
|
||||
gsub(\"{{ #sg-handbook-revision-url }}\"; \"${SG_HANDBOOK_REVISION_URL_ESCAPED}\") |
|
||||
gsub(\"{{ #sg-handbook-epub-url }}\"; \"${SG_HANDBOOK_EPUB_URL_ESCAPED}\") |
|
||||
gsub(\"{{ #sg-handbook-pdf-url }}\"; \"${SG_HANDBOOK_PDF_URL_ESCAPED}\") |
|
||||
gsub(\"{{ #sg-git-branch }}\"; \"${SG_GIT_BRANCH_ESCAPED}\") |
|
||||
gsub(\"{{ #sg-git-tag }}\"; \"${SG_GIT_TAG_ESCAPED}\") |
|
||||
@@ -103,4 +101,4 @@ PARSED_BOOK=$(printf '%s' "${BOOK}" | xq "
|
||||
#echo "BOOK: ${BOOK}" >&2
|
||||
#echo "PARSED_BOOK: ${PARSED_BOOK}" >&2
|
||||
|
||||
printf '%s' "${PARSED_BOOK}"
|
||||
echo "${PARSED_BOOK}" | xq '.'
|
||||
|
||||
+6
-8
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# (The MIT License)
|
||||
#
|
||||
# Copyright (c) 2020 - 2026 SenseGlove
|
||||
# Copyright (c) 2020 - 2024 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
|
||||
@@ -28,6 +28,7 @@
|
||||
authors = ["Mamadou Babaei"]
|
||||
description = "Welcome to the SenseGlove Unreal Engine documentation!"
|
||||
language = "en"
|
||||
multilingual = false
|
||||
src = "src"
|
||||
text-direction = "ltr"
|
||||
title = "The SenseGlove Unreal Engine Handbook"
|
||||
@@ -42,23 +43,20 @@ use-default-preprocessors = true
|
||||
|
||||
[preprocessor.links]
|
||||
|
||||
[preprocessor.alerts]
|
||||
|
||||
[preprocessor.senseglove]
|
||||
after = [ "index", "links" ]
|
||||
command = "sh ./bin/sg-preprocessor.sh"
|
||||
|
||||
[output.epub]
|
||||
no-section-label = false
|
||||
curly-quotes = true
|
||||
epub-version = 3
|
||||
footnote-backrefs = true
|
||||
|
||||
[output.html]
|
||||
additional-css = ["styles/custom.css"]
|
||||
additional-js = []
|
||||
cname = "unreal.docs.senseglove.com"
|
||||
copy-fonts = true
|
||||
default-theme = "navy"
|
||||
git-repository-url = "https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal"
|
||||
git-repository-icon = "fab-git"
|
||||
git-repository-icon = "fa-git"
|
||||
input-404 = "404.md"
|
||||
mathjax-support = false
|
||||
no-section-label = false
|
||||
|
||||
@@ -4,7 +4,7 @@ Welcome to the documentation for the SenseGlove Unreal Engine Plugin (a.k.a. The
|
||||
|
||||
This handbook is an ongoing effort and a work in progress to document the SenseGlove Unreal Engine Plugin. Feel free to visit this handbook on a regular basis.
|
||||
|
||||
Due to superior formatting and frequent updates, we recommend [the online version of the handbook]({{ #sg-handbook-revision-url }}); nonetheless, it's also available in [PDF]({{ #sg-handbook-pdf-url }}) and [ePub]({{ #sg-handbook-epub-url }}) formats as well.
|
||||
Due to superior formatting and frequent updates, we recommend [the online version of the handbook]({{ #sg-handbook-revision-url }}); nonetheless, it's available in [PDF format]({{ #sg-handbook-pdf-url }}) as well.
|
||||
|
||||
> [!TIP]
|
||||
> Feel free to check out [the SenseGlove Unreal Engine Plugin landing page on Fab](https://www.fab.com/listings/62bcdd2a-0bb1-4f34-b7b8-c5e7c9313099) as well.
|
||||
|
||||
+8
-35
@@ -15,21 +15,11 @@ Welcom to the SenseGlove Unreal Engine Handbook!
|
||||
- [Via Microsoft Azure DevOps Repositories](getting-started/installation/microsoft-azure-devops-repositories.md)
|
||||
- [Enabling and Verifying the Plugin Version](getting-started/plugin-verify-version-enable/README.md)
|
||||
- [SenseCom](getting-started/sensecom/README.md)
|
||||
- [Bluetooth Low Energy](getting-started/sensecom/bluetooth-low-energy/README.md)
|
||||
- [SenseCom on Android](getting-started/sensecom/bluetooth-low-energy/android.md)
|
||||
- [SenseCom on GNU/Linux](getting-started/sensecom/bluetooth-low-energy/gnu-linux.md)
|
||||
- [SenseCom on Microsoft Windows](getting-started/sensecom/bluetooth-low-energy/microsoft-windows.md)
|
||||
- [Bluetooth Serial](getting-started/sensecom/bluetooth-serial/README.md)
|
||||
- [SenseCom on Android](getting-started/sensecom/bluetooth-serial/android.md)
|
||||
- [SenseCom on GNU/Linux](getting-started/sensecom/bluetooth-serial/gnu-linux/README.md)
|
||||
- [Connect to Nova gloves using Blueman Bluetooth Manager](getting-started/sensecom/bluetooth-serial/gnu-linux/blueman-bluetooth-manager.md)
|
||||
- [Connect to Nova gloves using Command-line](getting-started/sensecom/bluetooth-serial/gnu-linux/command-line.md)
|
||||
- [SenseCom on Microsoft Windows](getting-started/sensecom/bluetooth-serial/microsoft-windows.md)
|
||||
- [FakeShm Module Tuning](getting-started/sensecom/fakeshm-tuning/README.md)
|
||||
- [SenseCom on GNU/Linux](getting-started/sensecom/gnu-linux/README.md)
|
||||
- [Connect to Nova gloves using Blueman Bluetooth Manager](getting-started/sensecom/gnu-linux/blueman-bluetooth-manager.md)
|
||||
- [Connect to Nova gloves using Command-line](getting-started/sensecom/gnu-linux/command-line.md)
|
||||
- [SenseCom on Microsoft Windows](getting-started/sensecom/microsoft-windows.md)
|
||||
- [Enabling XR_EXT_hand_tracking on VR Headsets](getting-started/enabling-xr-ext-hand-tracking-vr-headsets/README.md)
|
||||
- [PCVR Mode](getting-started/enabling-xr-ext-hand-tracking-vr-headsets/pcvr-mode/README.md)
|
||||
- [Standalone Mode](getting-started/enabling-xr-ext-hand-tracking-vr-headsets/standalone-mode/README.md)
|
||||
- [Third-Party Tutorials](getting-started/enabling-xr-ext-hand-tracking-vr-headsets/third-party-tutorials/README.md)
|
||||
- [Setup SenseGlove Default Classes](getting-started/setup-senseglove-default-classes/README.md)
|
||||
- [SGGameModeBase](getting-started/setup-senseglove-default-classes/sggamemodebase.md)
|
||||
- [SGPawn](getting-started/setup-senseglove-default-classes/sgpawn.md)
|
||||
@@ -66,24 +56,19 @@ Welcom to the SenseGlove Unreal Engine Handbook!
|
||||
|
||||
- [SenseGlove Console Commands](misc/console-commands/README.md)
|
||||
- [Deploying to Android (Standalone)](misc/android-standalone-deployment/README.md)
|
||||
- [Third-Party Tutorials](misc/android-standalone-deployment/third-party-tutorials/README.md)
|
||||
- [Upgrade Guide](misc/upgrade-guide/README.md)
|
||||
- [Optimizing for Higher FPS](misc/optimize-higher-fps/README.md)
|
||||
- [Third-Party Tutorials](misc/optimize-higher-fps/third-party-tutorials/README.md)
|
||||
|
||||
# 🛠️ Advanced Topics
|
||||
|
||||
- [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)
|
||||
- [Consuming FXRHandTrackingState](advanced-topics/openxr/consuming-fxrhandtrackingstate/README.md)
|
||||
- [Blueprint](advanced-topics/openxr/consuming-fxrhandtrackingstate/blueprint.md)
|
||||
- [C++](advanced-topics/openxr/consuming-fxrhandtrackingstate/cpp.md)
|
||||
- [Third-Party Integrations](advanced-topics/openxr/third-party-integrations/README.md)
|
||||
- [Third-Party Tutorials](advanced-topics/openxr/third-party-tutorials/README.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)
|
||||
|
||||
# 🔌 Low-Level API
|
||||
|
||||
@@ -103,20 +88,8 @@ Welcom to the SenseGlove Unreal Engine Handbook!
|
||||
- [SenseGlove Unreal Engine Plugin License](license/senseglove-unreal-engine-plugin.md)
|
||||
- [SenseGlove Unreal Engine Handbook License](license/senseglove-unreal-engine-handbook.md)
|
||||
- [Third-Party Licenses](license/third-party.md)
|
||||
- [Argh Frustration-free Command-line Processing Library License](license/argh-command-line-processing-library.md)
|
||||
- [SenseGlove SDK License](license/senseglove-sdk.md)
|
||||
- [SGBLE and SGBLExx Rust Dependencies Licenses](license/sgble-sgblexx-rust-dependencies.md)
|
||||
- [Boost C++ Libraries License](license/boost-cpp-libraries.md)
|
||||
- [c4core C++ Library Licence](license/c4core-library.md)
|
||||
- [c4project CMake Scripts Licence](license/c4project-cmake-scripts.md)
|
||||
- [Debug Break Library Licence](license/debugbreak-library.md)
|
||||
- [fast_float Number Parsing Library](license/fast-float-number-parsing-library.md)
|
||||
- [FlatBuffers Memory Efficient Serialization Library Licence](license/flatbuffers-serialization-library.md)
|
||||
- [{fmt} Formatting Library License](license/fmt-formatting-library.md)
|
||||
- [FTXUI Functional Terminal (X) User Interface Library](license/ftxui-functional-tui-library.md)
|
||||
- [Loguru Logging Library License](license/loguru-logging-library.md)
|
||||
- [nanomsg-next-generation Lightweight Brokerless Messaging Library License](license/nng-messaging-library.md)
|
||||
- [Rapid YAML Parsing and Emitting Library](license/rapidyaml-parsing-emitting-library.md)
|
||||
- [Boost C++ Libraries](license/boost-cpp-libraries.md)
|
||||
- [Serial Communication Library License](license/serial-communication-library.md)
|
||||
|
||||
-----------
|
||||
|
||||
@@ -1,156 +1,35 @@
|
||||
# OpenXR
|
||||
|
||||
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).
|
||||
The SenseGlove Unreal Engine Plugin has provided OpenXR-compatible hand tracking by implementing `XR_EXT_hand_tracking` since `v2.1.0`.
|
||||
|
||||
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` 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:
|
||||
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.
|
||||
|
||||
- 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.
|
||||
- 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,
|
||||
> [!NOTE]
|
||||
> In order to retrieve the latest `FXRMotionControllerState` available, The
|
||||
> 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]
|
||||
> 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 `SGHapticGlove::GetWristLocation()` variants,
|
||||
> 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
|
||||
> }
|
||||
> ```
|
||||
> 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.
|
||||
|
||||
In the next sections we'll see:
|
||||
|
||||
- [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 `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).
|
||||
|
||||
- [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.
|
||||
- [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).
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
# Consuming FXRHandTrackingState
|
||||
|
||||
> [!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.
|
||||
> [!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 `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:
|
||||
|
||||
```cpp
|
||||
USTRUCT(BlueprintType)
|
||||
@@ -119,7 +119,7 @@ In order to draw and animate a virtual hand in real-time whether the data is com
|
||||
|
||||
Both `HandKeyLocations` 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/release/Engine/Source/Runtime/HeadMountedDisplay/Public/HeadMountedDisplayTypes.h) as follows:
|
||||
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/release/Engine/Source/Runtime/HeadMountedDisplay/Public/HeadMountedDisplayTypes.h)` as follows:
|
||||
|
||||
```cpp
|
||||
/**
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
# Consuming FXRHandTrackingState in Blueprint
|
||||
|
||||
Before continuing this section, please ensure you've studied the [Consuming FXRHandTrackingState](./) section, first.
|
||||
> [!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.
|
||||
|
||||
> [!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.
|
||||
Before continuing this section, please ensure you've studied the [Consuming FXRHandTrackingState](./) section, first.
|
||||
|
||||
## Drawing and Animating Virtual Hands
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
# Consuming FXRHandTrackingState in C++
|
||||
|
||||
Before continuing this section, please ensure you've first studied the [Consuming FXRHandTrackingState](./) section.
|
||||
> [!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.
|
||||
|
||||
> [!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.
|
||||
Before continuing this section, please ensure you've first studied the [Consuming FXRHandTrackingState](./) section.
|
||||
|
||||
## Drawing and Animating Virtual Hands
|
||||
|
||||
@@ -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.
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
# 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/release/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:
|
||||
|
||||

|
||||
|
||||
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/release/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:
|
||||
|
||||

|
||||
|
||||
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.
|
||||
@@ -0,0 +1,65 @@
|
||||
# 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.
|
||||
|
||||

|
||||
|
||||
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`.
|
||||
|
||||

|
||||
|
||||
6. Inside this function's event graph add a `Get Motion Controller Data` node from `SenseGlove > Tracking > XR Tracker > Get Motion Controller Data`.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
11. Well, now the full implementation for the `Draw Hand` function insde the `VRPawn` should look something like this:
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,266 @@
|
||||
# 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.
|
||||
|
||||

|
||||
|
||||
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...`.
|
||||
|
||||

|
||||
|
||||
5. Choose the Unreal Engine's `APawn` class as the parent class for the new C++ pawn class.
|
||||
|
||||

|
||||
|
||||
6. Name the new pawn 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.
|
||||
|
||||

|
||||
|
||||
9. In the new `Reparent blueprint` window choose `DebugPawn` as the new parent.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
BIN
Binary file not shown.
@@ -1,265 +0,0 @@
|
||||
# Third-Party OpenXR Integrations
|
||||
|
||||
The **SenseGlove Unreal Engine Plugin** registers itself as an `OpenXRHandTracking` provider, making it a fully compatible, drop-in replacement for Epic’s own **OpenXRHandTracking** plugin in Unreal Engine. This allows it to integrate seamlessly with any third-party system or plugin that can consume OpenXR hand-tracking data.
|
||||
|
||||
One notable example is the open-source, MIT-licensed [VR Expansion Plugin (VRE)](#vr-expansion-plugin).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> As explained in the
|
||||
> [Third-Party Tutorials: Consuming OpenXR Hand-Tracking Data](../third-party-tutorials/)
|
||||
> section, it’s entirely possible to build your own custom hand interaction
|
||||
> system without relying on
|
||||
> [SGPawn](../../../getting-started/setup-senseglove-default-classes/sgpawn.html)
|
||||
> or any third-party OpenXR-compatible interaction plugin altogether.
|
||||
>
|
||||
> If your project requires finer-grained control than what these solutions
|
||||
> offer, the tutorials in that section will guide you through understanding the
|
||||
> OpenXR hand-tracking data format in Unreal Engine and help you implement a
|
||||
> fully tailored interaction system from the ground up in a few hours.
|
||||
|
||||
As the SenseGlove Unreal Engine Plugin is fully OpenXR-compliant, it provides OpenXR hand-tracking data in the expected format and takes over as the active provider within Unreal. If your existing interaction system (e.g. VRE plugin) already uses OpenXR hand-tracking, SenseGlove will function as a direct tracking source instead of a real hand.
|
||||
|
||||
> [!NOTE]
|
||||
> Since most hand-tracking systems are not capable of haptics feedback,
|
||||
> integrating SenseGlove's haptic feedback requires a small amount of additional
|
||||
> effort.
|
||||
>
|
||||
> The SenseGlove API is fully exposed to Unreal Engine via C++ and Blueprint,
|
||||
> so triggering haptic feedback is as simple as calling a function.
|
||||
> For more information, refer to the [Blueprint Changes](#blueprint-changes)
|
||||
> section below.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you're using a third-party OpenXR hand interaction system, configuring the
|
||||
> [Wrist-Tracker Hardware Settings](../../../getting-started/setup-wrist-tracking-hardware/)
|
||||
> will likely have no effect, and your hand offsets may appear at the incorrect
|
||||
> location in the scene.
|
||||
>
|
||||
> This is because those settings are only recognized by SenseGlove’s native
|
||||
> actors and components such as `SGPawn`, `SGWristTrackerComponent`, etc.
|
||||
> Most third-party plugins are unaware of these settings. As a result, you'll
|
||||
> need to figure out how to manually apply the appropriate offsets within your
|
||||
> chosen OpenXR hand interaction system.
|
||||
>
|
||||
> For example, the VRE plugin provides similar configurations in their plugin's
|
||||
> settings section. For more information refer to the
|
||||
> [Changing Wrist-Tracker Offsets](#changing-wrist-tracker-offsets) section
|
||||
> below.
|
||||
|
||||
## Comparison of Supported OpenXR 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 |
|
||||
|
||||
## VR Expansion Plugin
|
||||
|
||||
The [**VR Expansion Plugin (VRE)**](https://vreue4.com/) is a robust, community-driven plugin for Unreal Engine that focuses on advanced VR interaction and gameplay mechanics. It is open-source (MIT licensed), actively maintained, and has received support from Epic via the [MegaGrants program](https://www.unrealengine.com/en-US/megagrants).
|
||||
|
||||
Designed to extend Unreal’s capabilities for virtual reality, VRE offers a modular set of tools covering:
|
||||
|
||||
- Multiplayer and networking
|
||||
- Locomotion systems
|
||||
- Object gripping and interaction
|
||||
- Custom movement and physics handling
|
||||
|
||||
The plugin is particularly useful for teams building sophisticated VR experiences. While it's beginner-friendly to an extent, its depth and flexibility are **best suited for intermediate to advanced Unreal Engine developers**. Whether you're prototyping with built-in features or extracting specific systems for your own framework, VRE offers a rich foundation for VR development.
|
||||
|
||||
> [!NOTE]
|
||||
> For support and assistance with the VRE plugin, you can join
|
||||
> [its active and welcoming Discord community](https://discord.gg/P4V7TY8BQy),
|
||||
> known for being responsive and supportive.
|
||||
|
||||
### SGVRETemplate Demo Scene
|
||||
|
||||
To showcase how SenseGlove can be integrated with OpenXR-compatible third-party interaction systems, SenseGlove provides a ready-to-use [VR Expansion Plugin Integration Demo for Unreal Engine 5.4](https://dev.azure.com/SenseGlove/_git/SGVRETemplate).
|
||||
|
||||
This repository includes UE `5.4`–compatible versions of both the **SenseGlove** and **VR Expansion** plugins, with all necessary setup and configuration already in place. Simply download the project and it should run out of the box, allowing you to explore the integration without additional setup.
|
||||
|
||||
> [!NOTE]
|
||||
> SenseGlove provides this demo to demonstrate the potential for integrating
|
||||
> with third-party OpenXR-based hand interaction systems.
|
||||
> Please note that the **VR Expansion Plugin** is a third-party solution, and as
|
||||
> such, **we do not offer official support for it**.
|
||||
>
|
||||
> For help with the VRE plugin, refer to its documentation at
|
||||
> [**vreue4.com**](https://vreue4.com/) and consider joining the
|
||||
> [**official VRE Discord community**](https://discord.gg/P4V7TY8BQy),
|
||||
> which is active, supportive, and very responsive.
|
||||
|
||||
### SGVRETemplate Modifications
|
||||
|
||||
The [**SGVRETemplate**](https://dev.azure.com/SenseGlove/_git/SGVRETemplate) is built on top of the [VR Expansion Plugin Example Template](https://github.com/mordentral/VRExpPluginExample). However, since the original template is not directly compatible with SenseGlove, several adjustments were necessary.
|
||||
|
||||
In addition, a few known issues with OpenXR support in the VR Expansion Plugin for Unreal Engine `5.4` required us to modify the plugin itself to ensure smooth integration.
|
||||
|
||||
Below is an overview of the key modifications made to both the project template and this version of the VRE plugin.
|
||||
|
||||
#### Blueprint Changes
|
||||
|
||||
- **Content/VRE/Core/Character/BP_VRCharacter**: Four functions were added: `SendVibration`, `SendFFB`, `SendSqueeze`, and `ResetHaptics`. These functions retrieve the glove instance and send the appropriate haptic command to it. In the `OnPossessed` event, `Load Controller by Name` was added along with a string uproperty `Tracking Offset`, which is used to load the correct tracking offsets based on the selected profile.
|
||||
|
||||
> [!NOTE]
|
||||
> If you'd like to implement your own haptic functions, the most convenient
|
||||
> approach is to
|
||||
> [safely acquire a glove instance](../../../advanced-topics/safe-glove-access-blueprint/).
|
||||
> Once you have the glove instance, applying haptic feedback is as simple as
|
||||
> calling the appropriate function.
|
||||
>
|
||||
> SenseGlove supports three types of haptics: Vibrations, Force-feedback, and
|
||||
> Wrist-squeeze.
|
||||
>
|
||||
> - Using `Send Custom Waveform`, you can send vibrations to the glove instance.
|
||||
> - Using `Queue Command Force Feedback Levels`, you can send force-feedback.
|
||||
> - Using `Queue Command Wrist Squeeze`, you can send a wrist-squeeze command to
|
||||
> the glove.
|
||||
>
|
||||
> Each of these functions can be called directly on the glove instance to
|
||||
> trigger the desired haptic feedback.
|
||||
|
||||
- **Content/VRE/Core/GraspingHands/GraspingHandManny**: In the `SetupFingerAnimations` function, replace the hardcoded check for `HandType == Left` with a string comparison: convert the enum to a string and check if it contains `"Left"`. This allows compatibility with alternative tracking sources such as `"Left Foot"`.
|
||||
|
||||
#### C++ Changes
|
||||
|
||||
- **Plugins/VRExpansionPlugin/Source/VRExpansionPlugin/Public/Grippables/HandSocketComponent.h**: The following line was added as a public `UPROPERTY` in the header file:
|
||||
|
||||
```cpp
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Hand Animation")
|
||||
float HandAnimationProgress = 0.0f;
|
||||
```
|
||||
|
||||
- **Plugins/VRExpansionPlugin/Source/VRExpansionPlugin/Private/Grippables/HandSocketComponent.cpp**: In the function `bool UHandSocketComponent::GetBlendedPoseSnapShot(FPoseSnapshot& PoseSnapShot, USkeletalMeshComponent* TargetMesh, bool bSkipRootBone, bool bFlipHand)`, the `TrackLocation` calculation was modified from:
|
||||
|
||||
|
||||
```cpp
|
||||
if (TrackIndex != INDEX_NONE && (!bSkipRootBone || TrackIndex != 0))
|
||||
{
|
||||
double TrackLocation = 0.0f;
|
||||
HandTargetAnimation->GetBoneTransform(LocalTransform, FSkeletonPoseBoneIndex(TrackMap[TrackIndex].BoneTreeIndex), TrackLocation, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
```
|
||||
|
||||
To:
|
||||
|
||||
```cpp
|
||||
if (TrackIndex != INDEX_NONE && (!bSkipRootBone || TrackIndex != 0))
|
||||
{
|
||||
double TrackLocation = HandTargetAnimation->GetPlayLength() * HandAnimationProgress;
|
||||
HandTargetAnimation->GetBoneTransform(LocalTransform, FSkeletonPoseBoneIndex(TrackMap[TrackIndex].BoneTreeIndex), TrackLocation, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
```
|
||||
|
||||
- **Plugins/VRExpansionPlugin/Source/VRExpansionPlugin/Private/GripMotionControllerComponent.cpp**: In the function `void UGripMotionControllerComponent::GetCurrentProfileTransform(bool bBindToNoticationDelegate)`, the following logic was updated from:
|
||||
|
||||
```cpp
|
||||
if (HandType == EControllerHand::Left || HandType == EControllerHand::AnyHand || !VRSettings->bUseSeperateHandTransforms)
|
||||
{
|
||||
NewControllerProfileTransform = VRSettings->CurrentControllerProfileTransform;
|
||||
}
|
||||
else if (HandType == EControllerHand::Right)
|
||||
{
|
||||
NewControllerProfileTransform = VRSettings->CurrentControllerProfileTransformRight;
|
||||
}
|
||||
```
|
||||
|
||||
To:
|
||||
|
||||
```cpp
|
||||
if (UEnum::GetDisplayValueAsText(HandType).ToString().Contains("Left") || HandType == EControllerHand::AnyHand || !VRSettings->bUseSeperateHandTransforms)
|
||||
{
|
||||
NewControllerProfileTransform = VRSettings->CurrentControllerProfileTransform;
|
||||
}
|
||||
else if (UEnum::GetDisplayValueAsText(HandType).ToString().Contains("Right"))
|
||||
{
|
||||
NewControllerProfileTransform = VRSettings->CurrentControllerProfileTransformRight;
|
||||
}
|
||||
```
|
||||
|
||||
The following function was also updated; from:
|
||||
|
||||
```cpp
|
||||
void UGripMotionControllerComponent::GetHandType(EControllerHand& Hand)
|
||||
{
|
||||
if (!IMotionController::GetHandEnumForSourceName(MotionSource, Hand))
|
||||
{
|
||||
// Check if the palm motion source extension is being used
|
||||
// I assume eventually epic will handle this case
|
||||
if (MotionSource.Compare(FName(TEXT("RightPalm"))) == 0 || MotionSource.Compare(FName(TEXT("RightWrist"))) == 0)
|
||||
{
|
||||
Hand = EControllerHand::Right;
|
||||
}
|
||||
// Could skip this and default to left now but would rather check
|
||||
else if (MotionSource.Compare(FName(TEXT("LeftPalm"))) == 0 || MotionSource.Compare(FName(TEXT("LeftWrist"))) == 0)
|
||||
{
|
||||
Hand = EControllerHand::Left;
|
||||
}
|
||||
else
|
||||
{
|
||||
Hand = EControllerHand::Left;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To:
|
||||
|
||||
```cpp
|
||||
void UGripMotionControllerComponent::GetHandType(EControllerHand& Hand)
|
||||
{
|
||||
if (!IMotionController::GetHandEnumForSourceName(MotionSource, Hand))
|
||||
{
|
||||
// Check if the palm motion source extension is being used
|
||||
// I assume eventually epic will handle this case
|
||||
if (MotionSource.Compare(FName(TEXT("RightPalm"))) == 0 || MotionSource.Compare(FName(TEXT("RightWrist"))) == 0 || MotionSource.ToString().Contains("Right"))
|
||||
{
|
||||
Hand = EControllerHand::Right;
|
||||
}
|
||||
// Could skip this and default to left now but would rather check
|
||||
else if (MotionSource.Compare(FName(TEXT("LeftPalm"))) == 0 || MotionSource.Compare(FName(TEXT("LeftWrist"))) == 0 || MotionSource.ToString().Contains("Left"))
|
||||
{
|
||||
Hand = EControllerHand::Left;
|
||||
}
|
||||
else
|
||||
{
|
||||
Hand = EControllerHand::Left;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Changing Wrist-Tracker Offsets
|
||||
|
||||
If you are using wrist-tracking hardware supported by the SenseGlove plugin, you can change the offsets inside `BP_VRCharacter` using the uproperty `Tracking Offset` typing or copying any of the following, depending on your hardware:
|
||||
|
||||
- **SenseGlove_Quest3**: The wrist-tracking controller profile for for Meta Quest3.
|
||||
|
||||
- **SenseGlove_ViveWristTrackers**: The wrist-tracking controller profile for HTC VIVE wrist trackers.
|
||||
|
||||
#### Changing Motion Source
|
||||
|
||||
In `BP_VRCharacter`, you can change the wrist-tracking motion source for each hand. This is required depending on which tracker you are using.
|
||||
|
||||
#### Adding More Gestures
|
||||
|
||||
In the `GraspingHandManny` Blueprint, we’ve created a simple function called `SaveHandPose`. If you press the `Space Bar` while the game is running, it will save the current pose of the corresponding hand. The pose is stored in a gestures database located under `Content/SenseGlove` with the default name `NewHandPose`. You should rename the pose to something meaningful when you intend to use it.
|
||||
|
||||
It’s helpful to add an Event Dispatcher to the `GraspingHandManny` Blueprint, which is triggered in the Event Graph by the `On New Gesture Detected` event from the `OpenXRHandPose` component. This system is index-based rather than name-based, so keep that in mind when adding more dispatchers. By default, we’ve included examples for `Teleport`, `Grab`, `Release`, and `Use`.
|
||||
|
||||
#### Video Summary
|
||||
|
||||
This short video provides an overview of some of the key changes and modifications behind the SGVRETemplate demo scene, mentioned above.
|
||||
|
||||
<div style="margin: 0 auto; width: 560px;">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/6uTu4T10mjU" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
### SGVRETemplate Demo Calibration Scene
|
||||
|
||||
The SGVRETemplate includes a basic Calibration Scene located at `Content/SenseGlove/Maps/Calibration`. Inside this level, you’ll find a copy of `Content/SenseGlove/Blueprints/Calibration/BP_Calibrator` Blueprint responsible for transitioning to your desired target map after the calibration process is complete. You can configure the target map directly within this Blueprint by adjusting the `Level to Load` uproperty.
|
||||
@@ -1,34 +0,0 @@
|
||||
# Third-Party Tutorials: Consuming OpenXR Hand-Tracking Data
|
||||
|
||||
## Introduction to Virtual Reality, OpenXR Hand-Tracking, and Gesture Detection in Unreal Engine
|
||||
|
||||
In this tutorial, you'll learn how to get the OpenXR Hand-Tracking Data from the Unreal Engine API and how to consume it to draw virtual hand models using cubes (as hand joints). It will also dive into gesture recognition by implementing a simple pinch gesture recognition.
|
||||
|
||||
In the first part, it will focus on UE `4.26` to `5.4` API. And, in the second part, you'll learn how to update the project to work with `5.5`.
|
||||
|
||||
<div style="margin: 0 auto; width: 560px;">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/67v-sEhidvM" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
## Procedural Virtual Hand Mesh Animation Using OpenXR Hand-Tracking Data
|
||||
|
||||
Building on the [Introduction to Virtual Reality, OpenXR Hand-Tracking, and Gesture Recognition in Unreal Engine tutorial](#introduction-to-virtual-reality-openxr-hand-tracking-and-gesture-detection-in-unreal-engine), this slightly more advanced tutorial will dive deeper into the following topics:
|
||||
|
||||
- Transitioning seamlessly between motion controller and hand-tracking modes in Unreal Engine.
|
||||
- Adding custom debugging gizmos to improve development and testing workflows.
|
||||
- Visualizing debug virtual hands by incorporating the custom gizmos.
|
||||
- Animating virtual hand meshes with OpenXR hand-tracking data, moving beyond basic joint representation with cubes.
|
||||
- Re-using and adapting the gesture recognition code from the introductory tutorial to integrate with the new animated virtual hand meshes.
|
||||
This guide will help you take your VR projects to the next level with polished and practical implementations.
|
||||
|
||||
**Part 1**:
|
||||
|
||||
<div style="margin: 0 auto; width: 560px;">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/TPEA1GJr_kU" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
**Part 2**:
|
||||
|
||||
<div style="margin: 0 auto; width: 560px;">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/xEnuephuNmw" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
@@ -1,41 +0,0 @@
|
||||
# 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 Epic’s **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 Engine’s `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.
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
# 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:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Blueprint Properties
|
||||
|
||||
`SGHandTrackerComponent` exposes the following properties through the `Details` panel in Unreal’s Blueprint Editor:
|
||||
|
||||

|
||||
|
||||
- `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.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 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:
|
||||
|
||||

|
||||
|
||||
## GetHandTrackingState
|
||||
|
||||
The most important function accessible via `SGHandTrackerComponent` is `GetHandTrackingState()`:
|
||||
|
||||

|
||||
|
||||
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/).
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
-574
@@ -1,574 +0,0 @@
|
||||
# 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:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Blueprint Properties
|
||||
|
||||
`SGHapticsComponent` exposes the following properties through the `Details` panel in Unreal’s Blueprint Editor:
|
||||
|
||||

|
||||
|
||||
- `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:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
**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
|
||||
|
||||

|
||||
|
||||
**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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
**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.
|
||||
|
||||

|
||||
|
||||
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 vibration’s 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.
|
||||
|
||||

|
||||
|
||||
**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.
|
||||
|
||||

|
||||
|
||||
**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.
|
||||
|
||||

|
||||
|
||||
**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.
|
||||
|
||||

|
||||
|
||||
**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**.
|
||||
|
||||

|
||||
|
||||
**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.
|
||||
|
||||

|
||||
|
||||
**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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
-594
@@ -1,594 +0,0 @@
|
||||
# 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`
|
||||
|
||||

|
||||
|
||||
> [!IMPORTANT]
|
||||
> The current implementation of `SGPawn` relies on `3` grab colliders and `5`
|
||||
> touch colliders for grab and touch detection.
|
||||
|
||||

|
||||
|
||||
### 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:
|
||||
|
||||

|
||||
|
||||
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 hand’s 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 thumb’s 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 finger’s grab collider. If `null`, the index finger is not overlapping any grabbable actor.
|
||||
- `ActorMiddleCanGrab`: The actor currently overlapping with the middle finger’s 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:
|
||||
|
||||

|
||||
|
||||
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 thumb’s 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 index’s touch collider. If `null`, the index is not overlapping any touchable actor.
|
||||
- `ActorMiddleTouching`: The actor currently overlapping with the middle’s touch collider. If `null`, the middle is not overlapping any touchable actor.
|
||||
- `ActorRingTouching`: The actor currently overlapping with the ring’s touch collider. If `null`, the ring is not overlapping any touchable actor.
|
||||
- `ActorPinkyTouching`: The actor currently overlapping with the pinky’s 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 finger’s 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:
|
||||
|
||||

|
||||
|
||||
### 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:
|
||||
|
||||

|
||||
|
||||
### 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:
|
||||
|
||||

|
||||
|
||||
### 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:
|
||||
|
||||

|
||||
|
||||
## 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:
|
||||
|
||||

|
||||
|
||||

|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+178
-706
File diff suppressed because it is too large
Load Diff
@@ -62,142 +62,78 @@
|
||||
└── ThirdParty (3rd-party dependencies)
|
||||
│
|
||||
├── android (.jar file Java libraries for Android)
|
||||
│ │
|
||||
│ ├── debug
|
||||
│ │
|
||||
│ └── release
|
||||
│
|
||||
├── include (header files)
|
||||
│ │
|
||||
│ └── SenseGlove
|
||||
│ │
|
||||
│ ├── BLE (SGBLE headers)
|
||||
│ │
|
||||
│ ├── Common (SGCommon headers)
|
||||
│ │
|
||||
│ ├── Connect (SGConnect headers)
|
||||
│ │
|
||||
│ ├── Core (SGCore headers)
|
||||
│ │
|
||||
│ └── Log (SGLog headers)
|
||||
│
|
||||
├── lib (platform-specific pre-built binary dependencies)
|
||||
│ ├── boost
|
||||
│ │
|
||||
│ ├── android
|
||||
│ ├── SenseGlove
|
||||
│ │ │
|
||||
│ │ └── r25b (Android NDK r25b dependencies for UE 5.1+)
|
||||
│ │ │
|
||||
│ │ ├── aarch64 (64-bit ARM variant of Android)
|
||||
│ │ │ │
|
||||
│ │ │ ├── debug
|
||||
│ │ │ │
|
||||
│ │ │ └── release
|
||||
│ │ │
|
||||
│ │ └── x86-64 (64-bit x86-64 variant of Android)
|
||||
│ │ │
|
||||
│ │ ├── debug
|
||||
│ │ │
|
||||
│ │ └── release
|
||||
│ │ ├── Connect (SGConnect headers)
|
||||
│ │ │
|
||||
│ │ └── Core (SGCoreCpp headers)
|
||||
│ │
|
||||
│ ├── linux
|
||||
│ │ │
|
||||
│ │ ├── rustc (GNU/Linux binary dependencies built with Rust)
|
||||
│ │ │ │
|
||||
│ │ │ ├── aarch64 (dependencies targeting GNU/Linux AArch64 architecture)
|
||||
│ │ │ │ │
|
||||
│ │ │ │ ├── debug
|
||||
│ │ │ │ │
|
||||
│ │ │ │ └── release
|
||||
│ │ │ │
|
||||
│ │ │ └── x86-64 (dependencies targeting GNU/Linux x86-64 architecture)
|
||||
│ │ │ │
|
||||
│ │ │ ├── debug
|
||||
│ │ │ │
|
||||
│ │ │ └── release
|
||||
│ │ │
|
||||
│ │ ├── v22 (5.4 GNU/Linux dependencies)
|
||||
│ │ │ │
|
||||
│ │ │ ├── aarch64 (dependencies targeting GNU/Linux AArch64 architecture)
|
||||
│ │ │ │ │
|
||||
│ │ │ │ ├── debug
|
||||
│ │ │ │ │
|
||||
│ │ │ │ └── release
|
||||
│ │ │ │
|
||||
│ │ │ └── x86-64 (dependencies targeting GNU/Linux x86-64 architecture)
|
||||
│ │ │ │
|
||||
│ │ │ ├── debug
|
||||
│ │ │ │
|
||||
│ │ │ └── release
|
||||
│ │ │
|
||||
│ │ ├── v23 (UE 5.5 GNU/Linux dependencies)
|
||||
│ │ │ │
|
||||
│ │ │ ├── aarch64 (dependencies targeting GNU/Linux AArch64 architecture)
|
||||
│ │ │ │ │
|
||||
│ │ │ │ ├── debug
|
||||
│ │ │ │ │
|
||||
│ │ │ │ └── release
|
||||
│ │ │ │
|
||||
│ │ │ └── x86-64 (dependencies targeting GNU/Linux x86-64 architecture)
|
||||
│ │ │ │
|
||||
│ │ │ ├── debug
|
||||
│ │ │ │
|
||||
│ │ │ └── release
|
||||
│ │ │
|
||||
│ │ └── v25 (UE 5.6 GNU/Linux dependencies)
|
||||
│ │ │
|
||||
│ │ ├── aarch64 (dependencies targeting GNU/Linux AArch64 architecture)
|
||||
│ │ │ │
|
||||
│ │ │ ├── debug
|
||||
│ │ │ │
|
||||
│ │ │ └── release
|
||||
│ │ │
|
||||
│ │ └── x86-64 (dependencies targeting GNU/Linux x86-64 architecture)
|
||||
│ │ │
|
||||
│ │ ├── debug
|
||||
│ │ │
|
||||
│ │ └── release
|
||||
│ │
|
||||
│ └── windows
|
||||
│ │
|
||||
│ ├── v143-1438 (Microsoft Visual Studio 2022 dependencies for UE 5.5 and 5.6)
|
||||
│ │ │
|
||||
│ │ └── x86-64 (dependencies targeting Microsoft Windows x86-64 architecture)
|
||||
│ │ │
|
||||
│ │ ├── debug
|
||||
│ │ │
|
||||
│ │ └── release
|
||||
│ │
|
||||
│ ├── v143-1444 (Microsoft Visual Studio 2022 dependencies for UE 5.7 and 5.8)
|
||||
│ │ │
|
||||
│ │ └── x86-64 (dependencies targeting Microsoft Windows x86-64 architecture)
|
||||
│ │ │
|
||||
│ │ ├── debug
|
||||
│ │ │
|
||||
│ │ └── release
|
||||
│ │
|
||||
│ ├── v145-1450 (Microsoft Visual Studio 2026 dependencies for UE 5.8)
|
||||
│ │ │
|
||||
│ │ └── x86-64 (dependencies targeting Microsoft Windows x86-64 architecture)
|
||||
│ │ │
|
||||
│ │ ├── debug
|
||||
│ │ │
|
||||
│ │ └── release
|
||||
│ │
|
||||
│ └── rustc (Microsoft Windows binary dependencies built with Rust)
|
||||
│ │
|
||||
│ └── x86-64 (dependencies targeting Microsoft Windows x86-64 architecture)
|
||||
│ │
|
||||
│ ├── debug
|
||||
│ │
|
||||
│ └── release
|
||||
│ └── serial
|
||||
│
|
||||
├── SGBleThirdPartyLibs (Third-party module providing SGBLE headers and libraries)
|
||||
│
|
||||
├── SGConnectThirdPartyHeaders (Third-party module providing SGConnect headers)
|
||||
│
|
||||
├── SGConnectThirdPartyLibs (Third-party module providing SGConnect headers and libraries)
|
||||
│
|
||||
├── SGCoreThirdPartyLibs (Third-party module providing SGCore headers and libraries)
|
||||
│
|
||||
└── SGSerialThirdPartyLibs (Third-party module providing github.com/wjwwood/serial headers and libraries)
|
||||
└── lib (platform-specific pre-built binary dependencies)
|
||||
│
|
||||
├── android
|
||||
│ │
|
||||
│ └── r25b (Android NDK r25b dependencies for UE 5.1+)
|
||||
│ │
|
||||
│ ├── arm64 (64-bit ARM variant of Android)
|
||||
│ │ │
|
||||
│ │ ├── debug
|
||||
│ │ │
|
||||
│ │ └── release
|
||||
│ │
|
||||
│ └── x64 (64-bit x86-64 variant of Android)
|
||||
│ │
|
||||
│ ├── debug
|
||||
│ │
|
||||
│ └── release
|
||||
│
|
||||
├── linux
|
||||
│ │
|
||||
│ ├── v21 (UE 5.2 Linux dependencies)
|
||||
│ │ │
|
||||
│ │ ├── aarch64 (dependencies targeting AArch64 Linux architecture)
|
||||
│ │ │ │
|
||||
│ │ │ ├── debug
|
||||
│ │ │ │
|
||||
│ │ │ └── release
|
||||
│ │ │
|
||||
│ │ └── x86-64 (dependencies targeting x86-64 Linux architecture)
|
||||
│ │ │
|
||||
│ │ ├── debug
|
||||
│ │ │
|
||||
│ │ └── release
|
||||
│ │
|
||||
│ └── v22 (UE 5.3 and 5.4 Linux dependencies)
|
||||
│ │
|
||||
│ ├── aarch64 (dependencies targeting AArch64 Linux architecture)
|
||||
│ │ │
|
||||
│ │ ├── debug
|
||||
│ │ │
|
||||
│ │ └── release
|
||||
│ │
|
||||
│ └── x86-64 (dependencies targeting x86-64 Linux architecture)
|
||||
│ │
|
||||
│ ├── debug
|
||||
│ │
|
||||
│ └── release
|
||||
│
|
||||
└── win64
|
||||
│
|
||||
├── msvc142 (Microsoft Visual Studio 2019 dependencies)
|
||||
│ │
|
||||
│ ├── debug
|
||||
│ │
|
||||
│ └── release
|
||||
│
|
||||
└── msvc143 (Microsoft Visual Studio 2022 dependencies)
|
||||
│
|
||||
├── debug
|
||||
│
|
||||
└── release
|
||||
```
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
# Extra Resources
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
## 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 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 Blueprint API Project (only compatible with early `v1.x.x` releases)](https://dev.azure.com/SenseGlove/_git/SenseGlove-Unreal-SGPlaygroundBP)
|
||||
|
||||
### Developer Spotlight Demos
|
||||
|
||||
- OpenXR Hand Interactions System From Scratch (Pure Blueprint)
|
||||
- [Source Code Repository](https://gitlab.com/senseglove-unreal-engine-public/senseglove-unreal-openxr-hand-from-scratch-bp-demo)
|
||||
- [Video Documentation: Building OpenXR Hand Interactions from Scratch in Unreal Engine](https://youtu.be/J18c99vl6dE)
|
||||
|
||||
### Third-Party OpenXR Integration Demos
|
||||
|
||||
- [A VRExpansionPlugin Integration Demo for UE `5.4`](https://dev.azure.com/SenseGlove/_git/SGVRETemplate)
|
||||
- [Documentation](../advanced-topics/openxr/third-party-integrations/)
|
||||
- [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)
|
||||
- [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)
|
||||
|
||||
## Tutorials
|
||||
|
||||
@@ -36,21 +25,3 @@ There are various resources available for older versions of the SenseGlove Unrea
|
||||
- [Setting up VIVE Focus 3 & VIVE Wrist Trackers in Unreal Engine](https://youtu.be/SGmQevkzsY4)
|
||||
- [SGBasicDemo: setup throwing objects and physics settings for the real and virtual hands](https://youtu.be/K9Qr_LqgTcY)
|
||||
- [SGBasicDemo v2: upgrading your projects to the SenseGlove Unreal Engine Plugin v2.0.0](https://youtu.be/VbWfoep-Hsg)
|
||||
|
||||
## Third-Party Tutorials
|
||||
|
||||
### OpenXR Tutorials
|
||||
|
||||
- [Introduction to Virtual Reality, OpenXR Hand-Tracking, and Gesture Detection in Unreal Engine](https://youtu.be/67v-sEhidvM)
|
||||
- [Procedural Virtual Hand Mesh Animation Using OpenXR Hand-Tracking Data - Part 1](https://youtu.be/TPEA1GJr_kU)
|
||||
- [Procedural Virtual Hand Mesh Animation Using OpenXR Hand-Tracking Data - Part 2](https://youtu.be/xEnuephuNmw)
|
||||
- [Unreal Engine OpenXR Hand-Tracking on Android with Meta XR (Quest 3S/3/Pro/2) and HTC VIVE OpenXR (Focus Vision/XR Elite/Focus 3) Plugins](https://youtu.be/BWd3MwtfTJE)
|
||||
|
||||
### Android (Meta Quest / HTC VIVE) Tutorials
|
||||
|
||||
- [Build & Deploy Unreal Engine 5.5 Projects APK to Android & Meta Quest 3S/3/Pro/2 in Standalone Mode](https://youtu.be/EClbEbNcl4k)
|
||||
- [Unreal Engine OpenXR Hand-Tracking on Android with Meta XR (Quest 3S/3/Pro/2) and HTC VIVE OpenXR (Focus Vision/XR Elite/Focus 3) Plugins](https://youtu.be/BWd3MwtfTJE)
|
||||
|
||||
### VR Optimization
|
||||
|
||||
- [Optimizing Unreal Engine VR Projects for Higher Framerates (Meta Quest, HTC VIVE, FFR, ETFR, NVIDIA DLSS, AMD FSR, and Intel XeSS Tips Included!)](https://youtu.be/67CZqMrAOns)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
## Planned Features Completion Status
|
||||
|
||||
### Implemented as of v2.9.x
|
||||
### Implemented as of v2.2.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 Blueprint.
|
||||
- [X] DK 1 Support.
|
||||
@@ -14,10 +13,9 @@
|
||||
- [X] Support for GNU/Linux x64 as a deployment platform.
|
||||
- [X] Support for GNU/Linux AArch64 as a deployment platform.
|
||||
- [X] Support for Android as a deployment platform.
|
||||
- [X] Support for Oculus Quest 2, Oculus Quest Pro, Oculus Quest 3, and Oculus Quest 3s.
|
||||
- [X] Support for HTC VIVE Pro, HTC VIVE Focus 3, HTC XR Elite, and HTC VIVE Focus Vision.
|
||||
- [X] Support for Oculus Quest 2 and Oculus Quest Pro.
|
||||
- [X] Support for HTC VIVE Pro and HTC VIVE Focus 3.
|
||||
- [X] Support for HTC VIVE Trackers and HTC VIVE Wrist Trackers.
|
||||
- [X] Support for both Bluetooth Serial and Bluetooth Low Energy.
|
||||
- [X] On-device calibration for Android without the need for SenseCom.
|
||||
- [X] Haptic feedback including force feedback, buzz, and thumper commands.
|
||||
- [X] A customizable Grab component that could be added to any actor.
|
||||
@@ -40,12 +38,8 @@
|
||||
- [X] The SenseGlove grab/touch sockets one-click-setup ability on any Epic-compliant virtual hand mesh from within the Unreal Editor's Content Browser, Skeleton Editor, or Skeletal Mesh Editor.
|
||||
- [X] A flexible virtual hand animation system that can take the mesh bone's transforms into account for a more reliable hand animation.
|
||||
- [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] `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.10.x release
|
||||
### Upcoming features planned for the v2.3.x release
|
||||
|
||||
### Planned features long-term
|
||||
|
||||
|
||||
@@ -1,25 +1,22 @@
|
||||
## Platform Support Matrix
|
||||
|
||||
| | **Windows (MSVC 2017)** | **Windows (MSVC 2019)** | **Windows (MSVC 2022)** | **Windows (MSVC 2026)** | **Linux x86-64 (Native Toolchain)** | **Linux AArch64 (Native Toolchain)** | **Meta Quest Standalone (Android NDK)** | **HTC VIVE Standalone (Android NDK)** | **Fab** | **Azure DevOps Version** |
|
||||
|:--------:|:-----------------------:|:-----------------------:|:-----------------------:|:-----------------------:|:-----------------------------------:|:------------------------------------:|:---------------------------------------:|---------------------------------------|----------|--------------------------|
|
||||
| **5.8** | ❌ | ❌ | ✅ v2.9.x | ✅ v2.9.x | ✅ v2.9.x | ✅ v2.9.x | ✅ v2.9.x (r25b) | ❓ | ✅ v2.9.2 | ✅ v2.9.2 |
|
||||
| **5.7** | ❌ | ❌ | ✅ v2.9.x | ✅ v2.9.x | ✅ v2.9.x | ✅ v2.9.x | ✅ v2.9.x (r25b) | ❓ | ✅ v2.9.2 | ✅ v2.9.2 |
|
||||
| **5.6** | ❌ | ❌ | ✅ v2.9.x | ❌ | ✅ v2.9.x | ✅ v2.9.x | ✅ v2.9.x (r25b) | ❓ | ✅ v2.9.2 | ✅ v2.9.2 |
|
||||
| **5.5** | ❌ | ❌ | ⚠️ v2.9.x | ❌ | ⚠️ v2.9.x | ⚠️ v2.9.x | ⚠️ v2.9.x (r25b) | ❓ | ⚠️ v2.9.2 | ⚠️ v2.9.2 |
|
||||
| **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.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.0** | ❌ | ⚠️ v1.6.x | ⚠️ v1.6.x | ❌ | ⚠️ v1.6.x | ⚠️ v1.6.x | ⚠️ v1.6.x (r21e) | ❓ | ⚠️ v1.6.1 | ⚠️ v1.6.1 |
|
||||
| **4.27** | ⚠️ v1.4.x | ⚠️ v1.4.x | ⚠️ v1.4.x | ❌ | ⚠️ v1.4.x | ⚠️ v1.4.x | ⚠️ v1.4.x (r21e) | ❓ | ⚠️ v1.3.1 | ⚠️ v1.4.3 |
|
||||
| **4.26** | ⚠️ v1.0.x | ⚠️ v1.0.x | ❌ | ❌ | ⚠️ v1.0.x | ❌ | ❌ | ❌ | ❌ | ⚠️ v1.0.4 |
|
||||
| **4.25** | ⚠️ v1.0.x | ⚠️ v1.0.x | ❌ | | ⚠️ v1.0.x | ❌ | ❌ | ❌ | ❌ | ⚠️ v1.0.4 |
|
||||
| **4.24** | ⚠️ v1.0.x | ⚠️ v1.0.x | ❌ | | ⚠️ v1.0.x | ❌ | ❌ | ❌ | ❌ | ⚠️ v1.0.4 |
|
||||
| **4.23** | ⚠️ v1.0.x | ⚠️ v1.0.x | ❌ | | ⚠️ v1.0.x | ❌ | ❌ | ❌ | ❌ | ⚠️ v1.0.4 |
|
||||
| **4.22** | ⚠️ v1.0.x | ⚠️ v1.0.x | ❌ | | ⚠️ v1.0.x | ❌ | ❌ | ❌ | ❌ | ⚠️ v1.0.4 |
|
||||
| | **Windows (MSVC 2017)** | **Windows (MSVC 2019)** | **Windows (MSVC 2022)** | **Linux x86-64 (Native Toolchain)** | **Linux AArch64 (Native Toolchain)** | **Meta Quest Stand-alone (Android NDK)** | **HTC VIVE Stand-alone (Android NDK)** | **Fab** | **Azure DevOps Version** |
|
||||
|:--------:|:-----------------------:|:-----------------------:|:-----------------------:|:-----------------------------------:|:------------------------------------:|:----------------------------------------:|:--------------------------------------:|:--------:|:------------------------:|
|
||||
| **5.5** | ❌ | ❌ | ✅ v2.2.x | ✅ v2.2.x | ✅ v2.2.x | ✅ v2.2.x (r25b) | ❓ | ❌ | ✅ v2.2.1 |
|
||||
| **5.4** | ❌ | ❌ | ✅ v2.2.x | ✅ v2.2.x | ✅ v2.2.x | ✅ v2.2.x (r25b) | ❓ | ✅ v2.2.1 | ✅ v2.2.1 |
|
||||
| **5.3** | ❌ | ✅ v2.2.x | ✅ v2.2.x | ✅ v2.2.x | ✅ v2.2.x | ✅ v2.2.x (r25b) | ❓ | ✅ v2.2.1 | ✅ v2.2.1 |
|
||||
| **5.2** | ❌ | ✅ v2.2.x | ✅ v2.2.x | ✅ v2.2.x | ✅ v2.2.x | ✅ v2.2.x (r25b) | ❓ | ✅ v2.2.1 | ✅ v2.2.1 |
|
||||
| **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.0** | ❌ | ⚠️ v1.6.x | ⚠️ v1.6.x | ⚠️ v1.6.x | ⚠️ v1.6.x | ⚠️ v1.6.x (r21e) | ❓ | ⚠️ v1.6.1 | ⚠️ v1.6.1 |
|
||||
| **4.27** | ⚠️ v1.4.x | ⚠️ v1.4.x | ⚠️ v1.4.x | ⚠️ v1.4.x | ⚠️ v1.4.x | ⚠️ v1.4.x (r21e) | ❓ | ⚠️ v1.3.1 | ⚠️ v1.4.3 |
|
||||
| **4.26** | ⚠️ v1.0.x | ⚠️ v1.0.x | ❌ | ⚠️ v1.0.x | ❌ | ❌ | ❌ | ❌ | ⚠️ v1.0.4 |
|
||||
| **4.25** | ⚠️ v1.0.x | ⚠️ v1.0.x | ❌ | ⚠️ v1.0.x | ❌ | ❌ | ❌ | ❌ | ⚠️ v1.0.4 |
|
||||
| **4.24** | ⚠️ v1.0.x | ⚠️ v1.0.x | ❌ | ⚠️ v1.0.x | ❌ | ❌ | ❌ | ❌ | ⚠️ v1.0.4 |
|
||||
| **4.23** | ⚠️ v1.0.x | ⚠️ v1.0.x | ❌ | ⚠️ v1.0.x | ❌ | ❌ | ❌ | ❌ | ⚠️ v1.0.4 |
|
||||
| **4.22** | ⚠️ v1.0.x | ⚠️ v1.0.x | ❌ | ⚠️ v1.0.x | ❌ | ❌ | ❌ | ❌ | ⚠️ v1.0.4 |
|
||||
|
||||
* <code>✅</code> Supported
|
||||
* <code>⚠️</code> End-of-life (EOL) release that is not supported anymore and might be lacking features
|
||||
* <code>⚠️</code> Not supported by the latest release and might be lacking features
|
||||
* <code>❌</code> Not supported at all
|
||||
* <code>❓</code> Unknown or untested
|
||||
|
||||
@@ -29,5 +26,3 @@ Remarks:
|
||||
* Oculus and VIVE support is only provided through the recommended Android NDK versions by Epic Games.
|
||||
* [wjwwood/serial](https://github.com/wjwwood/serial) requires Android NDK API Level `28+` in order to be built successfully.
|
||||
* All third-party libraries target Android NDK API Level `29`, thus any project relying on the plug-in should be build with the same NDK API Level.
|
||||
* Meta Quest PCVR-mode support is only provided through the [Epic OpenXR plugin](../getting-started/enabling-xr-ext-hand-tracking-vr-headsets/pcvr-mode/#meta-quest). Furthermore, the Standalone-mode support is also provided through the [Epic OpenXR plugin](..//getting-started/enabling-xr-ext-hand-tracking-vr-headsets/standalone-mode/#meta-quest) only. Please note that enabling the Meta XR plugin will result in crashes or unexpected behavior. Meta XR plugin compatibility is being worked on at the moment and might be supported in the future.
|
||||
* While HTC VIVE PCVR-mode support is only provided through the [Epic OpenXR plugin](../getting-started/enabling-xr-ext-hand-tracking-vr-headsets/pcvr-mode/#htc-vive), the standalone-mode support is only provided through the [official HTC `ViveOpenXR` plugin](../getting-started/enabling-xr-ext-hand-tracking-vr-headsets/standalone-mode/#htc-vive) and no other plugin is supported.
|
||||
|
||||
@@ -1,22 +1,41 @@
|
||||
# Enabling XR_EXT_hand_tracking OpenXR Extension on VR Headsets
|
||||
# Enabling XR_EXT_hand_tracking OpenXR extension on VR Headsets
|
||||
|
||||
> [!IMPORTANT]
|
||||
> 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. Without this OpenXR extension the plugin won't output
|
||||
> any glove data.
|
||||
> Starting from version `v2.1.0`, 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`](../../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.
|
||||
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 from your PC to your VR headset, to enabling `XR_EXT_hand_tracking` support, might require additional settings depending on the vendor.
|
||||
|
||||
## PCVR Mode
|
||||
For Meta Quest headsets, enable the `Developer runtime features` under the `Settings > Beta` section:
|
||||
|
||||
For instructions on [how to setup `XR_EXT_hand_tracking` support in PCVR mode](pcvr-mode/) please refer to the relevant section.
|
||||

|
||||
|
||||
## Standalone Mode
|
||||
> [!CAUTION]
|
||||
> Streaming to Meta Quest headsets from SteamVR is no longer supported because
|
||||
> the migration to OpenXR has caused controller offsets for Meta Quest HMDs to
|
||||
> break on SteamVR. One possible reason is that
|
||||
> [SteamVR lists `XR_FB_hand_tracking` as an unsupported feature](https://steamcommunity.com/app/250820/discussions/8/3121550424355682585/).
|
||||
> Further investigation is needed to identify the exact underlying cause.
|
||||
|
||||
For instructions on [how to setup `XR_EXT_hand_tracking` support in standalone mode on Android](standalone-mode/) please refer to the relevant section.
|
||||
For VIVE headsets relying on VIVE Business Streaming, ensure the Hand Tracking settings under `Input` are enabled:
|
||||
|
||||
## Third-Party Tutorials
|
||||

|
||||
|
||||
As a part of [this OpenXR comprehensive tutorial series](third-party-tutorials/), you will learn how to enable the developer runtime features, set up the OpenXR runtime, and the `XR_EXT_hand_tracking` support in PCVR mode. Furthermore, it will show you how to enable hand-tracking on Android (standalone mode) using the Meta XR and VIVE OpenXR plugins.
|
||||
> [!NOTE]
|
||||
> Tracking and accessing `FXRMotionControllerData` output from SenseGlove
|
||||
> 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
|
||||
> fallback option when no glove is connected to your PC.
|
||||
|
||||
As mentioned in the `v2.1.0` release changelog, enabling the Meta XR plugin—and potentially the VIVE OpenXR plugin—alongside the SenseGlove Unreal Engine Plugin in the same project will disrupt the OpenXR functionality provided by the SenseGlove Plugin, rendering it unusable.
|
||||
|
||||
> [!CAUTION]
|
||||
> As noted in the
|
||||
> [v2.1.0 release changelog](../../appendix/changelog.html), since this release
|
||||
> enabling the Meta XR plugin, —and potentially the VIVE OpenXR plugin—
|
||||
> alongside the SenseGlove Unreal Engine Plugin in the same project will disrupt
|
||||
> the OpenXR functionality provided by the SenseGlove Unreal Engine Plugin,
|
||||
> rendering it unusable.
|
||||
>
|
||||
> `Although the SenseGlove OpenXR implementation is fully compatible with the IOpenXRHMD interface and the FOpenXRHMD XRTrackingSystem, it is not compatible with the FOculusXRHMD backend provided by the Meta XR plugin. The same issue likely applies to the VIVE OpenXR plugin. So, if these plugins are enabled in your project, the SenseGlove OpenXR will not function as intended, effectively breaking the plugin's functionality. It seems these plugins are necessary in order to make the fallback to the hand-tracking feature work on Android. While we may add support and compatibility with Meta XR and VIVE OpenXR plugins in the future, for the time being, if your project requires these plugins, we advise continuing with the v2.0.x release of the SenseGlove Unreal Engine plugin until this issue is addressed.`
|
||||
|
||||
BIN
Binary file not shown.
-77
@@ -1,77 +0,0 @@
|
||||
# Enabling XR_EXT_hand_tracking OpenXR Extension on VR Headsets in PCVR Mode
|
||||
|
||||
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
|
||||
|
||||
Regardless of the type or vendor of the HMD you have in mind for development or deployment purposes, the `OpenXR` plugin is required as a prerequisite. Also, ensure the `OpenXRHandTracking` is disabled as it conflicts with the SenseGlove Unreal Engine Plugin since both implement the same `XR_EXT_hand_tracking` OpenXR extension.
|
||||
|
||||
Though [enabling the SenseGlove Unreal Engine Plugin](../../plugin-verify-version-enable/) should enable the `OpenXR` plugin automatically, it is recommended to ensure this plugin is enabled, and most importantly `OpenXRHandTracking` is disabled, by navigating to `Edit > Plugins` in the Unreal Editor menus.
|
||||
|
||||

|
||||
|
||||
The `OpenXRHandTracking` plugin implements the `XR_EXT_hand_tracking` OpenXR extension.
|
||||
|
||||
## Meta Quest
|
||||
|
||||
To set up `XR_EXT_hand_tracking` support on Meta Quest HMDs in PCVR mode, depending on your project requirements (e.g. whether you rely on the `Meta XR` plugin or not), additional setup steps are required.
|
||||
|
||||
## Meta Quest Link App
|
||||
|
||||
For Meta Quest headsets, enable the `Developer runtime features` under the `Settings > Beta` section inside the Meta Quest Link app:
|
||||
|
||||

|
||||
|
||||
> [!IMPORTANT]
|
||||
> Enabling `Developer runtime features` in the Meta Quest Link requires a
|
||||
> [Meta Developer Account](https://developers.facebook.com/docs/development/register/).
|
||||
> If you are not signed in using a Meta Developer Account, this option won't
|
||||
> be shown to you inside Meta Quest Link.
|
||||
|
||||
> [!CAUTION]
|
||||
> Streaming to Meta Quest headsets from SteamVR is no longer supported because
|
||||
> the migration to OpenXR has caused controller offsets for Meta Quest HMDs to
|
||||
> break on SteamVR. One possible reason is that
|
||||
> [SteamVR lists `XR_FB_hand_tracking` as an unsupported feature](https://steamcommunity.com/app/250820/discussions/8/3121550424355682585/).
|
||||
> Further investigation is needed to identify the exact underlying cause.
|
||||
|
||||
### Meta XR Plugin
|
||||
|
||||
> [!CAUTION]
|
||||
> Please note that enabling the `Meta XR` plugin alongside the SenseGlove plugin
|
||||
> will result in crashes or unexpected behavior. `Meta XR` plugin compatibility
|
||||
> is being worked on and might be supported in the future.
|
||||
|
||||
## HTC VIVE
|
||||
|
||||
To set up `XR_EXT_hand_tracking` support on HTC VIVE HMDs in PCVR mode, additional plugins or configuration steps are required.
|
||||
|
||||
### OpenXRViveTracker Plugin
|
||||
|
||||
To enable VIVE Trackers support ensure the `OpenXRViveTracker` plugin is enabled by navigating to `Edit > Plugins` in the Unreal Editor menus. This plugin should be enabled, or wrist tracking won't function on VIVE devices at all.
|
||||
|
||||

|
||||
|
||||
The `OpenXRViveTracker` plugin implements the `XR_HTCX_vive_tracker_interaction` OpenXR extension which is necessary to use VIVE Trackers or to [emulate the VIVE Wrist Trackers as VIVE Trackers on Windows](#vive-business-streaming-app).
|
||||
|
||||
### VIVE Business Streaming App
|
||||
|
||||
For VIVE headsets relying on the VIVE Business Streaming application, ensure the Hand Tracking settings under `Input` are enabled and `XR_HTCX_vive_tracker_interaction` is enabled for VIVE Wrist Trackers by enabling `Emulate VIVE Wrist Tracker as VIVE Tracker`:
|
||||
|
||||

|
||||
|
||||
> [!NOTE]
|
||||
> Tracking and accessing `FXRHandTrackingState` output from SenseGlove
|
||||
> 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
|
||||
> fallback option when no glove is connected to your PC.
|
||||
|
||||
### SteamVR App
|
||||
|
||||
After enabling the [OpenXRViveTracker plugin](#openxrvivetracker-plugin) and [enabling `Emulate VIVE Wrist Tracker as VIVE Tracker` for VIVE HMDs relying on the VIVE Business Streaming](#vive-business-streaming-app), you need to perform one final setup in the SteamVR app for the SenseGlove Unreal Engine Plugin to be able to retrieve the correct wrist-tracking offsets. Once you have paired your VIVE Trackers or VIVE Wrist Trackers, navigate to `SteamVR Settings > Controllers > MANAGE TRACKERS` and make sure your left tracker is set to `LEFT FOOT` and the right tracker is set to `RIGHT FOOT`:
|
||||
|
||||

|
||||
|
||||
## SenseGlove Wrist Tracking Settings
|
||||
|
||||
Once you have set up everything, it's time to adjust the [SenseGlove Wrist Tracking Settings](../../setup-wrist-tracking-hardware/) inside the project-wide plugin's settings. For detailed information, please visit [the Wrist Tracking Hardware and HMD auto-detection configuration section](../../../plugin-configuration/plugin-settings/tracking/wrist-tracking/) as well.
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
-113
@@ -1,113 +0,0 @@
|
||||
# Enabling XR_EXT_hand_tracking OpenXR Extension on VR Headsets in Standalone Mode
|
||||
|
||||
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
|
||||
|
||||
Regardless of the type or vendor of the HMD you have in mind for development or deployment purposes, the `OpenXR` plugin is required as a prerequisite. Also, ensure the `OpenXRHandTracking` is disabled as it conflicts with the SenseGlove Unreal Engine Plugin since both implement the same `XR_EXT_hand_tracking` OpenXR extension.
|
||||
|
||||
Though [enabling the SenseGlove Unreal Engine Plugin](../../plugin-verify-version-enable/) should enable the `OpenXR` plugin automatically, it is recommended to ensure this plugin is enabled, and most importantly `OpenXRHandTracking` is disabled, by navigating to `Edit > Plugins` in the Unreal Editor menus.
|
||||
|
||||

|
||||
|
||||
The `OpenXRHandTracking` plugin implements the `XR_EXT_hand_tracking` OpenXR extension.
|
||||
|
||||
## Meta Quest
|
||||
|
||||
To set up `XR_EXT_hand_tracking` support on Meta Quest HMDs in Standalone mode, depending on your project requirements (e.g. whether you rely on the `Meta XR` plugin or not), additional setup steps are required.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Although, the SenseGlove plugin does not require the `Meta XR` plugin to
|
||||
> function, and relying solely on the `OpenXR` and `OpenXRHandTracking` plugins
|
||||
> would suffice for functional glove data retrieval using OpenXR,
|
||||
> [hand-tracking as a fallback mechanism](../../../plugin-configuration/plugin-settings/tracking/#bfallbacktohandtrackingifnoglovedetected)
|
||||
> won't work on Android without the `Meta XR` plugin availability.
|
||||
|
||||
### Meta XR Plugin
|
||||
|
||||
> [!CAUTION]
|
||||
> Please note that enabling the `Meta XR` plugin alongside the SenseGlove plugin
|
||||
> will result in crashes or unexpected behavior. `Meta XR` plugin compatibility
|
||||
> is being worked on and might be supported in the future.
|
||||
|
||||
## HTC VIVE
|
||||
|
||||
To set up `XR_EXT_hand_tracking` support on HTC VIVE HMDs in Standalone mode, additional plugins or configuration steps are required.
|
||||
|
||||
> [!CAUTION]
|
||||
> The SenseGlove Unreal Engine Plugin `v2.7.x` is the last release series to
|
||||
> support Unreal Engine `5.4`, and its support will be removed in future minor
|
||||
> or major releases. This is important to keep in mind if your target
|
||||
> development and deployment platform is HTC VIVE in Standalone Mode.
|
||||
> Unfortunately, HTC has not released any updates to their HTC ViveOpenXR
|
||||
> plugin since December 6, 2024. Their
|
||||
> [latest release](https://developer.vive.com/resources/openxr/unreal/unreal-download/latest/)
|
||||
> [[1]](https://web.archive.org/web/20251113165658/https://developer.vive.com/resources/openxr/unreal/unreal-download/latest/)
|
||||
> [[2]](https://archive.ph/DlrLr), ViveOpenXR Plugin `v2.5.0`, supports only
|
||||
> Unreal Engine `5.3` and `5.4`.
|
||||
> [HTC VIVE PCVR Mode](../pcvr-mode/index.html#htc-vive) is unaffected and will
|
||||
> remain fully functional because, on Microsoft Windows, it is supported via the
|
||||
> [OpenXRViveTracker Plugin](../pcvr-mode/index.html#openxrvivetracker-plugin),
|
||||
> which is bundled with Unreal Engine and officially maintained by Epic Games.
|
||||
> If you still intend to target HTC in Standalone Mode, you are welcome to
|
||||
> continue using the latest SenseGlove Unreal Engine Plugin `v2.7.x`, which will
|
||||
> retain HTC Standalone Mode support. However, please keep in mind that once
|
||||
> newer versions of the SenseGlove Unreal Engine Plugin are released and UE
|
||||
> `5.4` is no longer supported, the latest release of the plugin supporting UE
|
||||
> `5.4` will not receive new features, hardware support, or bug fixes. If at any
|
||||
> point in the future HTC releases a new version of their ViveOpenXR plugin that
|
||||
> supports any Unreal Engine version we actively support,+
|
||||
> [in accordance with our support policy and Platform Support Matrix](../../../appendix/platform-support-matrix.html),
|
||||
> we will make every reasonable effort to reintroduce HTC Standalone Mode support.
|
||||
|
||||
### OpenXRViveTracker Plugin
|
||||
|
||||
Unlike the [PCVR-mode on Windows](../pcvr-mode/), the `OpenXRViveTracker` plugin is not required on Android since it only provides an implementation of the `XR_HTCX_vive_tracker_interaction` OpenXR extension which is necessary when we use VIVE Trackers on Windows or we [emulate the VIVE Wrist Trackers as VIVE Trackers on Windows](../pcvr-mode/#vive-business-streaming-app). Instead, we require the `XR_HTCX_vive_wrist_tracker_interaction` OpenXR extension to be able to use VIVE Wrist Trackers on Android, which is provided by the [`ViveOpenXR` plugin](#viveopenxr-plugin). So, you can safely ignore enabling this plugin for Android standalone deployments.
|
||||
|
||||
### ViveOpenXR Plugin
|
||||
|
||||
To enable VIVE Wrist Trackers support ensure the `ViveOpenXR` plugin is enabled by navigating to `Edit > Plugins` in the Unreal Editor menus. This plugin should be enabled, or wrist tracking won't function on VIVE devices at all.
|
||||
|
||||

|
||||
|
||||
The `ViveOpenXR` plugin implements the `XR_HTCX_vive_wrist_tracker_interaction` OpenXR extension.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Without the `ViveOpenXR` plugin deploying an immersive 3D VR application to
|
||||
> your HTC VIVE in Standalone mode won't be possible, whether you enable
|
||||
> `bStartInVR`, or not. Without it your app will be deployed and recognized as
|
||||
> a 2D app.
|
||||
|
||||
By default, the `ViveOpenXR` plugin settings located in `Edit > Project Settings > Plugins > Vive OpenXR` look something like these:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
The following settings control the availability of hand-tracking when the `ViveOpenXR` plugin is enabled:
|
||||
|
||||
- **Enable Hand Interaction**: This enables the hand interactions with the OpenXR hand interaction extension `XR_HTC_hand_interaction`. Changing this setting will prompt you to restart the engine to apply the new settings. This setting should be enabled.
|
||||
|
||||

|
||||
|
||||
- **Use HTC Hand Interaction**: This selects which OpenXR hand interaction extension to use. If enabled, `XR_HTC_hand_interaction` will be used, effectively breaking SenseGlove glove and hand-tracking data output. If disabled, `XR_EXT_hand_interaction` will be used, which is compatible with the SenseGlove Unreal Engine Plugin. Changing this setting will prompt you to restart the engine to apply the new settings. This setting should be disabled.
|
||||
|
||||
The following settings control the availability of wrist-tracking when the `ViveOpenXR` plugin is enabled:
|
||||
|
||||
- **Enable Wrist Tracker**: This option controls the `XR_HTC_vive_wrist_tracker_interaction` OpenXR extension. If enabled, in turn, it enables the use of HTC Wrist Tracker interaction profiles in OpenXR. Changing this setting will prompt you to restart the engine to apply the new settings.
|
||||
|
||||

|
||||
|
||||
- **Enable Ultimate Tracker (Beta)**: This option controls the `XR_HTC_path_enumeration` and `XR_HTC_vive_xr_tracker_interaction` OpenXR extensions. If enabled, in turn, they enable the use of HTC Xr Tracker interaction profiles in OpenXR. Changing this setting will prompt you to restart the engine to apply the new settings.
|
||||
|
||||
- **Enable Ultimate Tracker Pogo Pin Inputs (Beta)**: Enables or disables the input options for Unreal's Enhanced Input System. Changing this setting will prompt you to restart the engine to apply the new settings.
|
||||
|
||||
The following setup demonstrates a functional immersive 3D VR application with the minimum `ViveOpenXR` required features enabled to make it compatible with the SenseGlove Unreal Engine Plugin:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## SenseGlove Wrist Tracking Settings
|
||||
|
||||
Once you have set up everything, it's time to adjust the [SenseGlove Wrist Tracking Settings](../../setup-wrist-tracking-hardware/) inside the project-wide plugin's settings. For detailed information, please visit [the Wrist Tracking Hardware and HMD auto-detection configuration section](../../../plugin-configuration/plugin-settings/tracking/wrist-tracking/) as well.
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
-17
@@ -1,17 +0,0 @@
|
||||
# Third-Party Tutorials: XR_EXT_hand_tracking Setup
|
||||
|
||||
## Introduction to Virtual Reality, OpenXR Hand-Tracking, and Gesture Detection in Unreal Engine
|
||||
|
||||
A part of this comprehensive tutorial will guide you through setting up the OpenXR runtime and enabling developer runtime features:
|
||||
|
||||
<div style="margin: 0 auto; width: 560px;">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/67v-sEhidvM" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
## Unreal Engine OpenXR Hand-Tracking on Android with Meta XR (Quest 3S/3/Pro/2) and HTC VIVE OpenXR (Focus Vision/XR Elite/Focus 3) Plugins
|
||||
|
||||
A part of this comprehensive tutorial will guide you through setting up the Meta XR and VIVE OpenXR plugins for Android standalone-mode deployment:
|
||||
|
||||
<div style="margin: 0 auto; width: 560px;">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/BWd3MwtfTJE" frameborder="0" allowfullscreen></iframe>
|
||||
</div>
|
||||
LFS
Executable
BIN
Binary file not shown.
@@ -2,14 +2,6 @@
|
||||
|
||||
SenseCom (short for SenseGlove Communications) is a background program that runs alongside your Unreal Engine application. Its primary function is to discover, and connect to SenseGlove devices on your system, exchanging data with them, much like a "SteamVR for Haptic Gloves." The SenseGlove Unreal Engine Plugin relies on SenseCom to communicate with any SenseGlove hardware.
|
||||
|
||||
Communication between your application and the physical gloves are possible via either [Bluetooth Low Energy (a.k.a. Bluetooth LE, colloquially BLE, formerly marketed as Bluetooth Smart)](bluetooth-low-energy/), or [Bluetooth Serial (a.k.a. BT Serial)](bluetooth-serial/) depending on the type and model of your glove, or the firmware version.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Some glove models support firmware upgrades from a Bluetooth Serial firmware
|
||||
> to a BLE-compatible firmware version. For more information, refer to the
|
||||
> [relevant documentation here](https://senseglove.gitlab.io/SenseGloveDocs/nova-2.html?highlight=firmware#updating-your-nova-2-0-firmware),
|
||||
> as this topic is beyond the scope of this handbook.
|
||||
|
||||
> [!NOTE]
|
||||
> SenseCom is required only for communication on Windows or Linux. For
|
||||
> standalone Android devices, the communication functionality is embedded
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# SenseCom (Bluetooth Low Energy)
|
||||
|
||||
Up to SenseCom `v1.7.x`, the only supported Bluetooth protocol for communication was Bluetooth Serial. However, starting with the `v1.8.x` series, SenseCom introduced support for Bluetooth Low Energy, which is now the preferred method of communication.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If SenseCom fails to recognize your gloves with Bluetooth Low Energy firmware,
|
||||
> it may be because the `Legacy Connections` option is enabled. In that case
|
||||
> SenseCom is only able to discover gloves with a Bluetooth Serial firmware.
|
||||
> Enabling this option should allow SenseCom to discover and connect to your
|
||||
> glove.
|
||||
>
|
||||
> 
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Some glove models support firmware upgrades from a Bluetooth Serial firmware
|
||||
> to a BLE-compatible firmware version. For more information, refer to the
|
||||
> [relevant documentation here](https://senseglove.gitlab.io/SenseGloveDocs/nova-2.html?highlight=firmware#updating-your-nova-2-0-firmware),
|
||||
> as this topic is beyond the scope of this handbook.
|
||||
@@ -1,10 +0,0 @@
|
||||
# SenseCom on Android (Bluetooth Low Energy)
|
||||
|
||||
Unlike PCVR-mode on Windows or Linux, there's no separate SenseCom application available for Standalone-mode on Android; instead, the communication functionality is integrated into your application.
|
||||
|
||||
As a result, in Standalone-mode, unlike PCVR-mode where BLE gloves do not require pairing at the operating system level and connections are managed by SenseCom, you need to pair your desired gloves through your operating system's Bluetooth settings before launching any applications that rely on [the SenseGlove Unreal Engine Plugin](https://www.fab.com/listings/62bcdd2a-0bb1-4f34-b7b8-c5e7c9313099). These instructions vary depending on the vendor and model of your Head-Mounted Display device. Please refer to the official documentation for detailed instructions:
|
||||
|
||||
- [Meta Quest: Connect a compatible Bluetooth device to Meta Quest headsets](https://www.meta.com/en-gb/help/quest/3263186127339632/)
|
||||
- [HTC VIVE: Pairing Bluetooth devices](https://www.vive.com/eu/support/vive-xr/category_howto/pairing-bluetooth-devices.html)
|
||||
|
||||
If you are using a different kind of HMD, ensure you consult the vendor-specific instructions to properly pair your gloves with your HMD of choice in Standalone-mode.
|
||||
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user