Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion modules/visionos_xr/doc_classes/VisionOSXRInterface.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
[/codeblock]
Do not initialize the XR interface in the [method Node._process] method, as this will initialize it at some unspecified point in the middle of the frame rendering, possibly causing incorrect visionOS API usage.
Enable the hand tracking module with the [member ProjectSettings.xr/visionos/enable_hand_tracking] project setting. It will populate the [code]"/user/hand_tracker/left"[/code] and [code]"/user/hand_tracker/right"[/code] XRHandTrackers.
Enable the spatial controller module using the [member ProjectSettings.xr/visionos/enable_controller_tracking] project setting. It will populate the [code]"left_hand"[/code] and [code]"right_hand"[/code] reserved XRController trackers.
The [code]"left_hand"[/code] and [code]"right_hand"[/code] reserved [XRControllerTracker]s are always registered. They are driven by spatial controllers, hand gestures, and the system pinch (spatial events).
Enable the spatial controller module using the [member ProjectSettings.xr/visionos/enable_controller_tracking] project setting. Connected accessories then drive the [code]"left_hand"[/code] and [code]"right_hand"[/code] trackers.
[b]Spatial events:[/b] while the system pinch (look and pinch) is active, it drives [code]trigger_click[/code] and the [code]default[/code] pose of the pinching hand's [code]"left_hand"[/code] or [code]"right_hand"[/code] tracker, overriding hand gestures and accessories for that hand. The gaze selection ray at the start of each pinch is published as the [code]default[/code] pose of the [code]"/user/eyes_ext"[/code] [XRControllerTracker].
[b]Hand gestures:[/b] ARKit does not report gestures directly, so pinch and grasp are derived from joint positions. The [code]/user/hand_tracker/left[/code] and [code]/user/hand_tracker/right[/code] [XRHandTracker]s always carry [code]pinch[/code], [code]pinch_click[/code], [code]grasp[/code], and [code]grasp_click[/code] inputs. Those gestures are also mirrored onto the [code]left_hand[/code] and [code]right_hand[/code] [XRControllerTracker]s as [code]trigger[/code]/[code]trigger_click[/code] and [code]grip[/code]/[code]grip_click[/code], so gameplay built on [XRController3D] works without a physical accessory. A hand that has a spatial controller attached is driven by that controller instead.
[b]Passthrough:[/b] [member XRInterface.environment_blend_mode] is a portable view of [member immersion_style]. Full immersion reports [constant XRInterface.XR_ENV_BLEND_MODE_OPAQUE], while mixed and progressive immersion report [constant XRInterface.XR_ENV_BLEND_MODE_ALPHA_BLEND]. Setting it changes the immersion style to match. Whenever passthrough is visible the root viewport is switched to a transparent background, because CompositorServices composites passthrough where alpha is [code]0[/code].
[b]Scene understanding:[/b] enable plane detection, scene reconstruction, or world anchors with the [code]xr/visionos/scene_understanding/*[/code] project settings. Detected geometry is exposed through [VisionOSPlaneTracker] and [VisionOSMeshTracker], and anchors through [VisionOSAnchorTracker] and the [VisionOSSpatialAnchorCapability] singleton. Plane detection and scene reconstruction require world-sensing authorization; ordinary local world anchors do not. Anchor requests can also enable anchor observation lazily after native initialization, without enabling scanning.
Expand Down
10 changes: 7 additions & 3 deletions modules/visionos_xr/visionos_controller_tracking.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@

#include "visionos_definitions.h"

#include "servers/xr/xr_controller_tracker.h"

#ifdef __OBJC__
#define Key GodotKey
#import <CoreHaptics/CoreHaptics.h>
Expand Down Expand Up @@ -62,8 +64,8 @@ struct VisionOSControllerTracking {
ar_accessory_t right_controller_accessory = nullptr;

// Controller state
Ref<XRControllerTracker> left_controller_tracker;
Ref<XRControllerTracker> right_controller_tracker;
VisionOSSharedController *left_shared_controller = nullptr;
VisionOSSharedController *right_shared_controller = nullptr;
GCController *left_gc_controller = nullptr;
GCController *right_gc_controller = nullptr;
CHHapticEngine *left_haptic_engine = nullptr;
Expand All @@ -74,7 +76,9 @@ struct VisionOSControllerTracking {
id controller_disconnect_observer = nullptr;
uint64_t generation = 0;

void initialize(XRServer *p_xr_server, VisionOSXRInterface *p_xr_interface);
void initialize(XRServer *p_xr_server, VisionOSXRInterface *p_xr_interface,
VisionOSSharedController &p_left_hand,
VisionOSSharedController &p_right_hand);
void uninitialize(XRServer *p_xr_server);

void init_for_controller(GCController *p_controller);
Expand Down
71 changes: 28 additions & 43 deletions modules/visionos_xr/visionos_controller_tracking.mm
Original file line number Diff line number Diff line change
Expand Up @@ -37,31 +37,24 @@

#include "platform/visionos/godot_compositor_services_renderer.h"

void VisionOSControllerTracking::initialize(XRServer *p_xr_server, VisionOSXRInterface *p_xr_interface) {
void VisionOSControllerTracking::initialize(XRServer *p_xr_server, VisionOSXRInterface *p_xr_interface,
VisionOSSharedController &p_left_hand,
VisionOSSharedController &p_right_hand) {
generation++;
accessories = ar_accessories_create();

xr_interface = p_xr_interface;

left_shared_controller = &p_left_hand;
right_shared_controller = &p_right_hand;

// Scan existing controllers
for (GCController *controller in GCController.controllers) {
init_for_controller(controller);
}

setup_controller_notifications();

left_controller_tracker.instantiate();
left_controller_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_LEFT);
left_controller_tracker->set_tracker_name("left_hand");
left_controller_tracker->set_tracker_desc("visionOS Left Controller");
p_xr_server->add_tracker(left_controller_tracker);

right_controller_tracker.instantiate();
right_controller_tracker->set_tracker_hand(XRPositionalTracker::TRACKER_HAND_RIGHT);
right_controller_tracker->set_tracker_name("right_hand");
right_controller_tracker->set_tracker_desc("visionOS Right Controller");
p_xr_server->add_tracker(right_controller_tracker);

ar_accessory_tracking_configuration_t accessory_tracking_configuration = ar_accessory_tracking_configuration_create();
ar_accessory_tracking_configuration_set_accessories(accessory_tracking_configuration, accessories);
accessory_tracking_provider = ar_accessory_tracking_provider_create(accessory_tracking_configuration);
Expand All @@ -73,16 +66,8 @@
accessory_tracking_provider = nullptr;
}

if (p_xr_server != nullptr) {
if (left_controller_tracker.is_valid()) {
p_xr_server->remove_tracker(left_controller_tracker);
left_controller_tracker.unref();
}
if (right_controller_tracker.is_valid()) {
p_xr_server->remove_tracker(right_controller_tracker);
right_controller_tracker.unref();
}
}
left_shared_controller = nullptr;
right_shared_controller = nullptr;

left_controller_anchor = nullptr;
right_controller_anchor = nullptr;
Expand Down Expand Up @@ -115,24 +100,24 @@
if (!ObjectDB::get_instance(owner_id)) {
return;
}
if (generation != request_generation || ![GCController.controllers containsObject:p_controller] || left_controller_tracker.is_null() || right_controller_tracker.is_null()) {
if (generation != request_generation || ![GCController.controllers containsObject:p_controller] || left_shared_controller == nullptr || right_shared_controller == nullptr) {
return;
}
ar_accessory_chirality_t chirality = ar_accessory_get_inherent_chirality(accessory);
if (chirality == ar_accessory_chirality_left) {
left_gc_controller = p_controller;
left_controller_accessory = accessory;
reset_controller_tracker_data(left_controller_tracker);
left_controller_tracker->set_tracker_profile("visionos_accessory");
reset_controller_tracker_data(left_shared_controller->tracker);
left_shared_controller->tracker->set_tracker_profile("visionos_accessory");
if (left_gc_controller != nullptr && left_gc_controller.haptics != nullptr) {
left_haptic_engine = [left_gc_controller.haptics createEngineWithLocality:GCHapticsLocalityDefault];
}
ar_accessories_add_accessory(accessories, accessory);
} else if (chirality == ar_accessory_chirality_right) {
right_gc_controller = p_controller;
right_controller_accessory = accessory;
reset_controller_tracker_data(right_controller_tracker);
right_controller_tracker->set_tracker_profile("visionos_accessory");
reset_controller_tracker_data(right_shared_controller->tracker);
right_shared_controller->tracker->set_tracker_profile("visionos_accessory");
if (right_gc_controller != nullptr && right_gc_controller.haptics != nullptr) {
right_haptic_engine = [right_gc_controller.haptics createEngineWithLocality:GCHapticsLocalityDefault];
}
Expand Down Expand Up @@ -184,8 +169,8 @@
left_controller_anchor = nullptr;
left_gc_controller = nullptr;
left_haptic_engine = nullptr;
reset_controller_tracker_data(left_controller_tracker);
left_controller_tracker->set_tracker_profile("");
reset_controller_tracker_data(left_shared_controller->tracker);
left_shared_controller->tracker->set_tracker_profile("");
update_accessories_list();
} else if (right_gc_controller != nullptr && right_gc_controller == p_controller) {
if (right_controller_accessory != nullptr) {
Expand All @@ -195,8 +180,8 @@
right_controller_anchor = nullptr;
right_gc_controller = nullptr;
right_haptic_engine = nullptr;
reset_controller_tracker_data(right_controller_tracker);
right_controller_tracker->set_tracker_profile("");
reset_controller_tracker_data(right_shared_controller->tracker);
right_shared_controller->tracker->set_tracker_profile("");
update_accessories_list();
}
}
Expand Down Expand Up @@ -350,24 +335,24 @@ void process_thumbstick(GCControllerLiveInput *input, Ref<XRControllerTracker> c
ar_accessory_t accessory = ar_accessory_anchor_get_accessory(accessory_anchor);
ar_accessory_chirality_t chirality = ar_accessory_get_inherent_chirality(accessory);

if (chirality == ar_accessory_chirality_left && left_gc_controller != nullptr && !left_found) {
if (chirality == ar_accessory_chirality_left && left_gc_controller != nullptr && !left_found && !left_shared_controller->controlled_by_spatial_event) {
left_controller_anchor = accessory_anchor;
update_controller_from_anchor(left_controller_tracker, left_controller_anchor, left_gc_controller);
update_controller_from_anchor(left_shared_controller->tracker, left_controller_anchor, left_gc_controller);
left_found = true;
} else if (chirality == ar_accessory_chirality_right && right_gc_controller != nullptr && !right_found) {
} else if (chirality == ar_accessory_chirality_right && right_gc_controller != nullptr && !right_found && !right_shared_controller->controlled_by_spatial_event) {
right_controller_anchor = accessory_anchor;
update_controller_from_anchor(right_controller_tracker, right_controller_anchor, right_gc_controller);
update_controller_from_anchor(right_shared_controller->tracker, right_controller_anchor, right_gc_controller);
right_found = true;
}
return true;
});
}
// Do not invalidate the other hand's optical mirror.
if (left_gc_controller != nullptr && !left_found) {
reset_controller_tracker_data(left_controller_tracker);
// Do not invalidate the other hand's optical mirror, nor a hand a spatial event drives.
if (left_gc_controller != nullptr && !left_found && !left_shared_controller->controlled_by_spatial_event) {
reset_controller_tracker_data(left_shared_controller->tracker);
}
if (right_gc_controller != nullptr && !right_found) {
reset_controller_tracker_data(right_controller_tracker);
if (right_gc_controller != nullptr && !right_found && !right_shared_controller->controlled_by_spatial_event) {
reset_controller_tracker_data(right_shared_controller->tracker);
}
}
}
Expand All @@ -376,10 +361,10 @@ void process_thumbstick(GCControllerLiveInput *input, Ref<XRControllerTracker> c
GCController *target_controller = nullptr;
CHHapticEngine *target_engine = nullptr;

if (p_tracker_name == left_controller_tracker->get_tracker_name()) {
if (left_shared_controller && left_shared_controller->tracker.is_valid() && p_tracker_name == left_shared_controller->tracker->get_tracker_name()) {
target_controller = left_gc_controller;
target_engine = left_haptic_engine;
} else if (p_tracker_name == right_controller_tracker->get_tracker_name()) {
} else if (right_shared_controller && right_shared_controller->tracker.is_valid() && p_tracker_name == right_shared_controller->tracker->get_tracker_name()) {
target_controller = right_gc_controller;
target_engine = right_haptic_engine;
}
Expand Down
13 changes: 13 additions & 0 deletions modules/visionos_xr/visionos_definitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,17 @@ enum class VisionOSAuthorizationStatus {
DENIED,
};

// Trackers used by VisionOSControllerTracking, VisionOSHandTracking and VisionOSSpatialEventTracking.
struct VisionOSSharedController {
Ref<XRControllerTracker> tracker;

// Spatial Events override controllers, when active.
bool controlled_by_spatial_event = false;

// Set when a spatial event releases `trigger_click` while the pinch derived
// from hand tracking is still held, so the slower optical release does not
// press the trigger a second time.
bool hand_trigger_released_by_spatial_event = false;
};

#endif // VISIONOS_ENABLED
16 changes: 5 additions & 11 deletions modules/visionos_xr/visionos_hand_tracking.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,24 +67,18 @@ struct VisionOSHandTracking {

GestureState gestures[HAND_MAX];

// Controller-style trackers driven by hand gestures, so XRController3D based
// gameplay works without a physical accessory. These are only created when
// accessory tracking is disabled; otherwise VisionOSControllerTracking already
// owns trackers with these names and gestures are mirrored into those instead.
Ref<XRControllerTracker> left_hand_controller_tracker;
Ref<XRControllerTracker> right_hand_controller_tracker;

void initialize(XRServer *xr_server, bool p_accessory_tracking_enabled);
void uninitialize(XRServer *xr_server);
void initialize(XRServer *xr_server);
void update_hand_trackers_from_arkit(CFTimeInterval trackable_anchor_time);
void reset_hand_tracker_data(Ref<XRHandTracker> hand_tracker);
void set_hand_tracker_data_from_arkit(Ref<XRHandTracker> hand_tracker, ar_hand_anchor_t hand_anchor);

// Gesture detection. `update_gestures` recomputes state and publishes it on the
// hand tracker; `publish_gestures` mirrors it onto a controller-style tracker.
// hand tracker; `publish_gestures` mirrors it onto the shared `left_hand` or
// `right_hand` controller tracker, so XRController3D based gameplay works
// without a physical accessory.
void update_gestures(HandIndex p_hand, const Ref<XRHandTracker> &p_hand_tracker);
void reset_gestures(HandIndex p_hand, const Ref<XRHandTracker> &p_hand_tracker);
void publish_gestures(HandIndex p_hand, const Ref<XRControllerTracker> &p_controller_tracker);
void publish_gestures(HandIndex p_hand, VisionOSSharedController &p_controller);
};

#endif // VISIONOS_ENABLED
Loading
Loading