Skip to content

Commit 638df41

Browse files
authored
Extend HandPoseController from HandPoseDetector. (#20)
1 parent e4e755d commit 638df41

15 files changed

+134
-268
lines changed

README.md

+19-12
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,30 @@ Ensure the existing project is configured with XR hand tracking. The demo projec
4747
The addon files need to be copied to the `/addons/hand_pose_detector` folder of the Godot project.
4848

4949

50-
### Add Hand Pose Detectors
50+
### Add Hand Pose Controllers
5151

52-
Add Hand Pose Detector nodes into the scene - one for each hand.
52+
Add Hand Pose Controller nodes into the scene - one for each hand. These will detect hand poses and drive [XRController3D](https://docs.godotengine.org/en/latest/classes/class_xrcontroller3d.html) nodes.
5353

54-
![Add Hand Pose Detectors](/docs/add_hand_pose_detectors.png)
54+
![Add Hand Pose Controller](/docs/add_hand_pose_controllers.png)
5555

56-
Configure the hand pose detectors with the pose-set to detect, and the hand tracker to monitor.
56+
Add [XRController3D](https://docs.godotengine.org/en/latest/classes/class_xrcontroller3d.html) nodes for the virtual hand-driven controllers - one for each hand.
5757

58-
![Hand Pose Detector Settings](/docs/hand_pose_detector_settings.png)
58+
![Add XRController3D](/docs/add_hand_pose_virtual_controllers.png)
5959

60-
Connect the hand pose detector signals as desired.
60+
Configure the Hand Pose Controller nodes with:
61+
- The [XRControllerTracker](https://docs.godotengine.org/en/latest/classes/class_xrcontrollertracker.html) name for the virtual controller
62+
- The type of pose to drive (`Aim` is most widely supported)
63+
- The hand pose action map for actions to trigger on the virtual controllers
64+
- The [XRHandTracker](https://docs.godotengine.org/en/latest/classes/class_xrhandtracker.html) name for the hand
65+
- The set of hand-poses to detect
66+
67+
![Hand Pose Controller Settings](/docs/hand_pose_controller_settings.png)
68+
69+
Configure the [XRController3D](https://docs.godotengine.org/en/latest/classes/class_xrcontroller3d.html) Virtual Controller nodes with the name of the [XRControllerTracker](https://docs.godotengine.org/en/latest/classes/class_xrcontrollertracker.html) for each hand.
70+
71+
![Virtual Controller Settings](/docs/virtual_controller_settings.png)
72+
73+
If needed, connect the hand pose detector signals. The preferred approach is to generate actions using the hand pose action map, and then detect those actions as if generated by a standing XR controller.
6174

6275
![Hand Pose Detector Signals](/docs/hand_pose_detector_signals.png)
6376

@@ -119,12 +132,6 @@ The inspect scene provided in the demo project can be used to inspect the flexio
119132
![Inspect Scene](/docs/inspect_scene.png)
120133

121134

122-
## Pose Driven XR Controllers
123-
124-
The Hand Pose Detector addon includes a HandPoseController node which creates an XRControllerTracker capable of generating XR Input Actions in response to hand poses.
125-
126-
127-
128135
## Licensing
129136

130137
Code in this repository is licensed under the MIT license.

VERSIONS.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# 2.0.0 (in progress)
22
- Rename Action-Set to Action-Map to better match OpenXR naming
3+
- Modify HandPoseController to extend from HandPoseDetector (breaking change)
34

45
# 1.3.0
56
- Fine-tune aim pose

addons/hand_pose_detector/hand_pose_controller.gd

+47-44
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@tool
22
class_name HandPoseController
3-
extends Node
3+
extends HandPoseDetector
44

55

66
## Hand Pose Controller Node
@@ -57,75 +57,81 @@ const _POSE_TRANSFORMS_RIGHT : Array[Transform3D] = [
5757
]
5858

5959

60+
@export_group("Controller", "controller_")
61+
6062
## Name for the virtual controller tracker
61-
@export var tracker_name : String = "/user/hand_pose_controller/left"
63+
@export var controller_tracker_name : String = "/user/hand_pose_controller/left"
6264

6365
## Pose type
64-
@export var pose_type : PoseType = PoseType.SKELETON
66+
@export var controller_pose_type : PoseType = PoseType.SKELETON
6567

6668
## Hand poses generating boolean values
67-
@export var action_map : HandPoseActionMap
68-
69+
@export var controller_action_map : HandPoseActionMap
6970

70-
## Hand Pose Detector
71-
var pose_detector : HandPoseDetector
7271

7372
## Controller Tracker
74-
var tracker : XRControllerTracker
73+
var controller_tracker : XRControllerTracker
7574

7675

7776
# Called when the node enters the scene tree for the first time.
7877
func _ready() -> void:
78+
# Call the base
79+
super()
80+
7981
# Skip if in editor
8082
if Engine.is_editor_hint():
8183
set_process(false)
8284
return
8385

84-
# Get the pose detector
85-
pose_detector = get_parent() as HandPoseDetector
86-
if not pose_detector:
87-
set_process(false)
88-
return
86+
# If the hand-pose-set is not specified then construct one dynamically
87+
# from the controller action map.
88+
if not hand_pose_set and controller_action_map:
89+
hand_pose_set = HandPoseSet.new()
90+
for action in controller_action_map.actions:
91+
hand_pose_set.poses.append(action.pose)
8992

9093
# Subscribe to the detector events
91-
pose_detector.pose_started.connect(_pose_started)
92-
pose_detector.pose_ended.connect(_pose_ended)
94+
pose_started.connect(_pose_started)
95+
pose_ended.connect(_pose_ended)
9396

9497
# Create the controller tracker
95-
tracker = XRControllerTracker.new()
96-
tracker.name = tracker_name
97-
XRServer.add_tracker(tracker)
98+
controller_tracker = XRControllerTracker.new()
99+
controller_tracker.name = controller_tracker_name
100+
XRServer.add_tracker(controller_tracker)
98101

99102

100103
# Called every frame. 'delta' is the elapsed time since the previous frame.
101-
func _process(_delta: float) -> void:
104+
func _process(delta: float) -> void:
105+
# Call the base
106+
super(delta)
107+
102108
# Skip if no trackers
103-
if not pose_detector.tracker or not tracker:
109+
if not hand_tracker or not controller_tracker:
104110
return
105111

106112
# Get the hand tracker pose
107-
var pose := pose_detector.tracker.get_pose(&"default")
113+
var pose := hand_tracker.get_pose(&"default")
108114
if not pose:
109115
return
110116

111117
# Get the conversion transform
112-
var hand := pose_detector.tracker.hand
118+
var hand := hand_tracker.hand
113119

114120
# Get the conversion transform
115121
var conv_xform : Transform3D
116122
if hand == XRPositionalTracker.TrackerHand.TRACKER_HAND_LEFT:
117-
conv_xform = _POSE_TRANSFORMS_LEFT[pose_type]
123+
conv_xform = _POSE_TRANSFORMS_LEFT[controller_pose_type]
118124
else:
119-
conv_xform = _POSE_TRANSFORMS_RIGHT[pose_type]
125+
conv_xform = _POSE_TRANSFORMS_RIGHT[controller_pose_type]
120126

121127
# Apply conversion to pose components
122128
var pose_transform := pose.transform * conv_xform
123129
var pose_linear := pose.linear_velocity * conv_xform.basis
124130
var pose_angular := _rotate_angular_velocity(pose.angular_velocity, conv_xform.basis)
125131

126132
# Update the controller tracker pose
127-
tracker.hand = hand
128-
tracker.set_pose(
133+
controller_tracker.hand = hand
134+
controller_tracker.set_pose(
129135
pose.name,
130136
pose_transform,
131137
pose_linear,
@@ -135,23 +141,20 @@ func _process(_delta: float) -> void:
135141

136142
# Customize properties
137143
func _validate_property(property: Dictionary) -> void:
138-
if property.name == "tracker_name":
144+
if property.name == "controller_tracker_name":
139145
property.hint = PROPERTY_HINT_ENUM_SUGGESTION
140146
property.hint_string = "/user/hand_pose_controller/left,/user/hand_pose_controller/right"
147+
else:
148+
super(property)
141149

142150

143151
# Get configuration warnings
144152
func _get_configuration_warnings() -> PackedStringArray:
145-
var warnings := PackedStringArray()
146-
147-
# Verify tracker name is set
148-
if tracker_name == "":
149-
warnings.append("Tracker name not set")
153+
var warnings := super()
150154

151-
# Verify parent pose
152-
var parent_pose_detector := get_parent() as HandPoseDetector
153-
if not parent_pose_detector:
154-
warnings.append("Must be child of HandPoseDetector node")
155+
# Verify controller tracker name is set
156+
if controller_tracker_name == "":
157+
warnings.append("Controller racker name not set")
155158

156159
# Return the warnings
157160
return warnings
@@ -160,37 +163,37 @@ func _get_configuration_warnings() -> PackedStringArray:
160163
# Handle start of pose
161164
func _pose_started(p_name : String) -> void:
162165
# Skip if no tracker or action map
163-
if not tracker or not action_map:
166+
if not controller_tracker or not controller_action_map:
164167
return
165168

166169
# Find the action
167-
var action := action_map.get_action(p_name)
170+
var action := controller_action_map.get_action(p_name)
168171
if not action:
169172
return
170173

171174
# Set the input
172175
if action.action_type == HandPoseAction.ActionType.BOOL:
173-
tracker.set_input(action.action_name, true)
176+
controller_tracker.set_input(action.action_name, true)
174177
else:
175-
tracker.set_input(action.action_name, 1.0)
178+
controller_tracker.set_input(action.action_name, 1.0)
176179

177180

178181
# Handle end of pose
179182
func _pose_ended(p_name : String) -> void:
180183
# Skip if no tracker or action map
181-
if not tracker or not action_map:
184+
if not controller_tracker or not controller_action_map:
182185
return
183186

184187
# Find the action
185-
var action := action_map.get_action(p_name)
188+
var action := controller_action_map.get_action(p_name)
186189
if not action:
187190
return
188191

189192
# Set the input
190193
if action.action_type == HandPoseAction.ActionType.BOOL:
191-
tracker.set_input(action.action_name, false)
194+
controller_tracker.set_input(action.action_name, false)
192195
else:
193-
tracker.set_input(action.action_name, 0.0)
196+
controller_tracker.set_input(action.action_name, 0.0)
194197

195198

196199
# Returns an angular velocity rotated by the given basis matrix.

addons/hand_pose_detector/hand_pose_controller.tscn

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@
44

55
[node name="HandPoseController" type="Node"]
66
script = ExtResource("1_peu0g")
7-
tracker_name = null
8-
pose_name = null
7+
controller_pose_type = 1

addons/hand_pose_detector/hand_pose_detector.gd

+25-9
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,18 @@ signal pose_started(p_name : String)
1515
signal pose_ended(p_name : String)
1616

1717

18+
@export_group("Hand", "hand_")
19+
20+
## Name of the hand pose tracker
21+
@export var hand_tracker_name : String = "/user/hand_tracker/left"
22+
1823
## Current hand pose set
1924
@export var hand_pose_set : HandPoseSet
2025

21-
## Name of the hand pose tracker
22-
@export var tracker_name : String = "/user/hand_tracker/left"
2326

2427
## Current hand tracker
25-
var tracker : XRHandTracker
28+
var hand_tracker : XRHandTracker
29+
2630

2731
# Current hand pose data
2832
var _current_data : HandPoseData = HandPoseData.new()
@@ -42,7 +46,7 @@ var _new_hold : float = 0.0
4246

4347
# Customize the properties
4448
func _validate_property(property: Dictionary) -> void:
45-
if property.name == "tracker_name":
49+
if property.name == "hand_tracker_name":
4650
property.hint = PROPERTY_HINT_ENUM_SUGGESTION
4751
property.hint_string = "/user/hand_tracker/left,/user/hand_tracker/right"
4852

@@ -62,12 +66,12 @@ func _process(delta: float) -> void:
6266
return
6367

6468
# Skip if no tracker or hand pose set
65-
if not tracker or not hand_pose_set:
69+
if not hand_tracker or not hand_pose_set:
6670
return
6771

6872
# If the palm is not tracked then skip pose detection. Any current pose will
6973
# remain active until we see the hand again.
70-
var flags := tracker.get_hand_joint_flags(XRHandTracker.HAND_JOINT_PALM)
74+
var flags := hand_tracker.get_hand_joint_flags(XRHandTracker.HAND_JOINT_PALM)
7175
if (flags & XRHandTracker.HAND_JOINT_FLAG_POSITION_TRACKED) == 0:
7276
return;
7377
if (flags & XRHandTracker.HAND_JOINT_FLAG_ORIENTATION_TRACKED) == 0:
@@ -77,7 +81,7 @@ func _process(delta: float) -> void:
7781
var active_pos := _current_pose
7882

7983
# Find the pose
80-
_current_data.update(tracker)
84+
_current_data.update(hand_tracker)
8185
var pose := hand_pose_set.find_pose(_current_data)
8286

8387
# Manage current pose
@@ -122,7 +126,19 @@ func _process(delta: float) -> void:
122126
pose_started.emit(active_pos.pose_name)
123127

124128

129+
# Get configuration warnings
130+
func _get_configuration_warnings() -> PackedStringArray:
131+
var warnings := PackedStringArray()
132+
133+
# Verify hand tracker name is set
134+
if hand_tracker_name == "":
135+
warnings.append("Hand tracker name not set")
136+
137+
# Return the warnings
138+
return warnings
139+
140+
125141
# If the tracker changed then try to get the updated handle
126142
func _on_tracker_changed(p_name : StringName, _type) -> void:
127-
if p_name == tracker_name:
128-
tracker = XRServer.get_tracker(tracker_name)
143+
if p_name == hand_tracker_name:
144+
hand_tracker = XRServer.get_tracker(hand_tracker_name)

addons/hand_pose_detector/hand_pose_detector.tscn

-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,3 @@
44

55
[node name="HandPoseDetector" type="Node"]
66
script = ExtResource("1_rrxmu")
7-
tracker_name = "/user/hand_tracker/left"

assets/inspect/pose_info.gd

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ extends Label3D
44

55

66
## Hand pose to diagnose
7-
@export var pose : HandPose
7+
@export var pose : HandPose
88

99
## Name of the hand pose tracker
1010
@export var tracker_name : String = "/user/hand_tracker/left"

0 commit comments

Comments
 (0)