Skip to content

Commit

Permalink
Implement XR_FB_hand_tracking_aim extension wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
devloglogan committed Feb 19, 2024
1 parent d51b503 commit 69be122
Show file tree
Hide file tree
Showing 9 changed files with 539 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Merge GDExtension into a single implementation
- Upgrade Android, Gradle, Godot and Kotlin dependencies
- Add XR_FB_face_tracking support
- Add XR_FB_hand_tracking_aim support

## 2.0.3
- Migrate the export scripts from gdscript to C++ via gdextension
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/**************************************************************************/
/* openxr_fb_hand_tracking_aim_extension_wrapper.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT XR */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2022-present Godot XR contributors (see CONTRIBUTORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/

#include "extensions/openxr_fb_hand_tracking_aim_extension_wrapper.h"

#include <godot_cpp/classes/project_settings.hpp>
#include <godot_cpp/classes/xr_pose.hpp>

using namespace godot;

OpenXRFbHandTrackingAimExtensionWrapper *OpenXRFbHandTrackingAimExtensionWrapper::singleton = nullptr;

OpenXRFbHandTrackingAimExtensionWrapper *OpenXRFbHandTrackingAimExtensionWrapper::get_singleton() {
if (singleton == nullptr) {
singleton = memnew(OpenXRFbHandTrackingAimExtensionWrapper());
}
return singleton;
}

OpenXRFbHandTrackingAimExtensionWrapper::OpenXRFbHandTrackingAimExtensionWrapper() :
OpenXRExtensionWrapperExtension() {
ERR_FAIL_COND_MSG(singleton != nullptr, "An OpenXRFbHandTrackingAimExtensionWrapper singleton already exists.");

request_extensions[XR_FB_HAND_TRACKING_AIM_EXTENSION_NAME] = &fb_hand_tracking_aim_ext;
singleton = this;
}

OpenXRFbHandTrackingAimExtensionWrapper::~OpenXRFbHandTrackingAimExtensionWrapper() {
cleanup();
}

void OpenXRFbHandTrackingAimExtensionWrapper::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_enabled"), &OpenXRFbHandTrackingAimExtensionWrapper::is_enabled);
}

void OpenXRFbHandTrackingAimExtensionWrapper::cleanup() {
XRServer *xr_server = XRServer::get_singleton();

for (int i = 0; i < Hand::HAND_MAX; i++) {
if (xr_server != nullptr) {
xr_server->remove_tracker(trackers[i]);
}
trackers[i].unref();
}

fb_hand_tracking_aim_ext = false;
}

godot::Dictionary OpenXRFbHandTrackingAimExtensionWrapper::_get_requested_extensions() {
godot::Dictionary result;
for (auto ext : request_extensions) {
godot::String key = ext.first;
uint64_t value = reinterpret_cast<uint64_t>(ext.second);
result[key] = (godot::Variant)value;
}
return result;
}

PackedStringArray OpenXRFbHandTrackingAimExtensionWrapper::_get_suggested_tracker_names() {
PackedStringArray arr = PackedStringArray();
arr.push_back(TRACKER_NAME_LEFT);
arr.push_back(TRACKER_NAME_RIGHT);
return arr;
}

void OpenXRFbHandTrackingAimExtensionWrapper::_on_state_ready() {
is_project_setting_enabled = ProjectSettings::get_singleton()->get_setting_with_override("xr/openxr/extensions/hand_tracking_aim");
if (!is_project_setting_enabled) {
return;
}

XRServer *xr_server = XRServer::get_singleton();
if (xr_server == nullptr) {
return;
}

trackers[Hand::HAND_LEFT].instantiate();
trackers[Hand::HAND_LEFT]->set_tracker_type(XRServer::TRACKER_CONTROLLER);
trackers[Hand::HAND_LEFT]->set_tracker_name(TRACKER_NAME_LEFT);
trackers[Hand::HAND_LEFT]->set_tracker_desc("FB Aim tracker Left");
xr_server->add_tracker(trackers[Hand::HAND_LEFT]);

trackers[Hand::HAND_RIGHT].instantiate();
trackers[Hand::HAND_RIGHT]->set_tracker_type(XRServer::TRACKER_CONTROLLER);
trackers[Hand::HAND_RIGHT]->set_tracker_name(TRACKER_NAME_RIGHT);
trackers[Hand::HAND_RIGHT]->set_tracker_desc("FB Aim tracker Right");
xr_server->add_tracker(trackers[Hand::HAND_RIGHT]);
}

void OpenXRFbHandTrackingAimExtensionWrapper::_on_instance_destroyed() {
cleanup();
}

uint64_t OpenXRFbHandTrackingAimExtensionWrapper::_set_hand_joint_locations_and_get_next_pointer(int32_t p_hand_index, void *p_next_pointer) {
ERR_FAIL_INDEX_V_MSG(p_hand_index, Hand::HAND_MAX, reinterpret_cast<uint64_t>(p_next_pointer), vformat("Invalid hand index %d", p_hand_index));

if (!fb_hand_tracking_aim_ext) {
return reinterpret_cast<uint64_t>(p_next_pointer);
}

aim_state[p_hand_index].type = XR_TYPE_HAND_TRACKING_AIM_STATE_FB;
aim_state[p_hand_index].next = p_next_pointer;
aim_state[p_hand_index].status = 0;

return reinterpret_cast<uint64_t>(&aim_state[p_hand_index]);
}

void OpenXRFbHandTrackingAimExtensionWrapper::_on_process() {
if (!is_enabled() || !is_project_setting_enabled) {
return;
}

for (int i = 0; i < Hand::HAND_MAX; i++) {
if (!trackers[i].is_valid()) {
continue;
}

XrPosef aim_pose = aim_state[i].aimPose;
XrQuaternionf aim_quat = aim_pose.orientation;
XrVector3f aim_position = aim_pose.position;
Quaternion quat = Quaternion(aim_quat.x, aim_quat.y, aim_quat.z, aim_quat.w);
Vector3 origin = Vector3(aim_position.x, aim_position.y, aim_position.z);

Transform3D transform = Transform3D(quat, origin);
Vector3 linear_velocity = Vector3(0.0, 0.0, 0.0);
Vector3 angular_velocity = Vector3(0.0, 0.0, 0.0);
XRPose::TrackingConfidence confidence = XRPose::TrackingConfidence::XR_TRACKING_CONFIDENCE_LOW;

if (!(aim_state[i].status & XR_HAND_TRACKING_AIM_VALID_BIT_FB)) {
confidence = XRPose::TrackingConfidence::XR_TRACKING_CONFIDENCE_NONE;
} else if (aim_state[i].status & XR_HAND_TRACKING_AIM_COMPUTED_BIT_FB) {
confidence = XRPose::TrackingConfidence::XR_TRACKING_CONFIDENCE_HIGH;
}

trackers[i]->set_pose("default", transform, linear_velocity, angular_velocity, confidence);
trackers[i]->set_input("index_pinch", (bool)(aim_state[i].status & XR_HAND_TRACKING_AIM_INDEX_PINCHING_BIT_FB));
trackers[i]->set_input("middle_pinch", (bool)(aim_state[i].status & XR_HAND_TRACKING_AIM_MIDDLE_PINCHING_BIT_FB));
trackers[i]->set_input("ring_pinch", (bool)(aim_state[i].status & XR_HAND_TRACKING_AIM_RING_PINCHING_BIT_FB));
trackers[i]->set_input("little_pinch", (bool)(aim_state[i].status & XR_HAND_TRACKING_AIM_LITTLE_PINCHING_BIT_FB));
trackers[i]->set_input("index_pinch_strength", aim_state[i].pinchStrengthIndex);
trackers[i]->set_input("middle_pinch_strength", aim_state[i].pinchStrengthMiddle);
trackers[i]->set_input("ring_pinch_strength", aim_state[i].pinchStrengthRing);
trackers[i]->set_input("little_pinch_strength", aim_state[i].pinchStrengthLittle);
trackers[i]->set_input("system_gesture", (bool)(aim_state[i].status & XR_HAND_TRACKING_AIM_SYSTEM_GESTURE_BIT_FB));
trackers[i]->set_input("menu_gesture", (bool)(aim_state[i].status & XR_HAND_TRACKING_AIM_MENU_PRESSED_BIT_FB));
trackers[i]->set_input("dominant_hand", (bool)(aim_state[i].status & XR_HAND_TRACKING_AIM_DOMINANT_HAND_BIT_FB));
}
}

void OpenXRFbHandTrackingAimExtensionWrapper::add_project_setting() {
String p_name = "xr/openxr/extensions/hand_tracking_aim";
if (!ProjectSettings::get_singleton()->has_setting(p_name)) {
ProjectSettings::get_singleton()->set_setting(p_name, false);
}

ProjectSettings::get_singleton()->set_initial_value(p_name, false);
Dictionary property_info;
property_info["name"] = p_name;
property_info["type"] = Variant::Type::BOOL;
property_info["hint"] = PROPERTY_HINT_NONE;
ProjectSettings::get_singleton()->add_property_info(property_info);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**************************************************************************/
/* openxr_fb_hand_tracking_aim_extension_wrapper.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT XR */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2022-present Godot XR contributors (see CONTRIBUTORS.md) */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/

#ifndef OPENXR_FB_HAND_TRACKING_AIM_EXTENSION_WRAPPER_H
#define OPENXR_FB_HAND_TRACKING_AIM_EXTENSION_WRAPPER_H

#include <openxr/openxr.h>
#include <godot_cpp/classes/open_xr_extension_wrapper_extension.hpp>
#include <godot_cpp/classes/open_xr_interface.hpp>
#include <godot_cpp/classes/xr_positional_tracker.hpp>

#include <map>

using namespace godot;

// Wrapper for the set of Facebook XR hand tracking aim extension.
class OpenXRFbHandTrackingAimExtensionWrapper : public OpenXRExtensionWrapperExtension {
GDCLASS(OpenXRFbHandTrackingAimExtensionWrapper, OpenXRExtensionWrapperExtension);

public:
using Hand = OpenXRInterface::Hand;

godot::Dictionary _get_requested_extensions() override;

PackedStringArray _get_suggested_tracker_names() override;

void _on_state_ready() override;

void _on_instance_destroyed() override;

uint64_t _set_hand_joint_locations_and_get_next_pointer(int32_t p_hand_index, void *p_next_pointer) override;

bool is_enabled() {
return fb_hand_tracking_aim_ext;
}

void _on_process() override;

void add_project_setting();

static OpenXRFbHandTrackingAimExtensionWrapper *get_singleton();

OpenXRFbHandTrackingAimExtensionWrapper();
~OpenXRFbHandTrackingAimExtensionWrapper();

protected:
static void _bind_methods();

private:
std::map<godot::String, bool *> request_extensions;

void cleanup();

static OpenXRFbHandTrackingAimExtensionWrapper *singleton;

const String TRACKER_NAME_LEFT = "/user/fbhandaim/left";
const String TRACKER_NAME_RIGHT = "/user/fbhandaim/right";

bool fb_hand_tracking_aim_ext = false;
bool is_project_setting_enabled = false;

Ref<XRPositionalTracker> trackers[Hand::HAND_MAX];

XrHandTrackingAimStateFB aim_state[Hand::HAND_MAX];
};

#endif // OPENXR_FB_HAND_TRACKING_AIM_EXTENSION_WRAPPER_H
7 changes: 7 additions & 0 deletions common/src/main/cpp/register_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include "export/pico_export_plugin.h"

#include "extensions/openxr_fb_face_tracking_extension_wrapper.h"
#include "extensions/openxr_fb_hand_tracking_aim_extension_wrapper.h"
#include "extensions/openxr_fb_scene_capture_extension_wrapper.h"
#include "extensions/openxr_fb_scene_extension_wrapper.h"
#include "extensions/openxr_fb_spatial_entity_container_extension_wrapper.h"
Expand Down Expand Up @@ -72,6 +73,9 @@ void initialize_plugin_module(ModuleInitializationLevel p_level) {

ClassDB::register_class<OpenXRFbFaceTrackingExtensionWrapper>();
OpenXRFbFaceTrackingExtensionWrapper::get_singleton()->register_extension_wrapper();

ClassDB::register_class<OpenXRFbHandTrackingAimExtensionWrapper>();
OpenXRFbHandTrackingAimExtensionWrapper::get_singleton()->register_extension_wrapper();
} break;

case MODULE_INITIALIZATION_LEVEL_SERVERS:
Expand All @@ -84,6 +88,7 @@ void initialize_plugin_module(ModuleInitializationLevel p_level) {
Engine::get_singleton()->register_singleton("OpenXRFbSpatialEntityContainerExtensionWrapper", OpenXRFbSpatialEntityContainerExtensionWrapper::get_singleton());
Engine::get_singleton()->register_singleton("OpenXRFbSceneExtensionWrapper", OpenXRFbSceneExtensionWrapper::get_singleton());
Engine::get_singleton()->register_singleton("OpenXRFbFaceTrackingExtensionWrapper", OpenXRFbFaceTrackingExtensionWrapper::get_singleton());
Engine::get_singleton()->register_singleton("OpenXRFbHandTrackingAimExtensionWrapper", OpenXRFbHandTrackingAimExtensionWrapper::get_singleton());
} break;

case MODULE_INITIALIZATION_LEVEL_EDITOR: {
Expand All @@ -102,6 +107,8 @@ void initialize_plugin_module(ModuleInitializationLevel p_level) {

ClassDB::register_class<PicoEditorPlugin>();
EditorPlugins::add_by_type<PicoEditorPlugin>();

OpenXRFbHandTrackingAimExtensionWrapper::get_singleton()->add_project_setting();
} break;

case MODULE_INITIALIZATION_LEVEL_MAX:
Expand Down
2 changes: 1 addition & 1 deletion demo/addons/godotopenxrvendors/plugin.gdextension
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[configuration]

entry_symbol = "plugin_library_init"
compatibility_minimum = "4.2"
compatibility_minimum = "4.3"
android_aar_plugin = true

[libraries]
Expand Down
27 changes: 13 additions & 14 deletions demo/main.tscn
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
[gd_scene load_steps=16 format=3 uid="uid://cqsodpswgup8w"]
[gd_scene load_steps=15 format=3 uid="uid://cqsodpswgup8w"]

[ext_resource type="Script" path="res://main.gd" id="1_fsva1"]
[ext_resource type="Script" uid="uid://c7rq5m6t5pffi" path="res://main.gd" id="1_fsva1"]
[ext_resource type="PackedScene" uid="uid://c0uv4eu2yjm3b" path="res://viewport_2d_in_3d.tscn" id="2_7whgo"]
[ext_resource type="PackedScene" uid="uid://d4b4rllli6tqp" path="res://tablet_content.tscn" id="3_45w5g"]
[ext_resource type="Script" path="res://tracked_face.gd" id="4_fsql8"]
[ext_resource type="PackedScene" uid="uid://ikxieb2fyavg" path="res://assets/face/Face.gltf" id="4_wrwst"]
[ext_resource type="PackedScene" uid="uid://bwfyi8pgigune" path="res://xr_fb_hand_tracking_aim_demo.tscn" id="4_gy0yu"]

[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_0x6cv"]
sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
Expand Down Expand Up @@ -59,10 +58,10 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.352791, 0)

[node name="LeftHand" type="XRController3D" parent="XROrigin3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.460909, 0.388594, -0.241118)
tracker = &"left_hand"
pose = &"aim"
tracker = &"/user/fbhandaim/left"

[node name="LeftHandMesh" type="MeshInstance3D" parent="XROrigin3D/LeftHand"]
visible = false
mesh = SubResource("BoxMesh_3kt6b")

[node name="HandTablet" type="MeshInstance3D" parent="XROrigin3D/LeftHand/LeftHandMesh"]
Expand All @@ -79,9 +78,10 @@ unshaded = true

[node name="RightHand" type="XRController3D" parent="XROrigin3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.478861, 0.468292, -0.241097)
tracker = &"right_hand"
tracker = &"/user/fbhandaim/right"

[node name="RightHandMesh" type="MeshInstance3D" parent="XROrigin3D/RightHand"]
visible = false
mesh = SubResource("BoxMesh_ey3x4")

[node name="EyeGaze" type="XRController3D" parent="XROrigin3D"]
Expand All @@ -95,12 +95,11 @@ mesh = SubResource("SphereMesh_5gcab")
[node name="Floor" type="MeshInstance3D" parent="."]
mesh = SubResource("PlaneMesh_mjcgt")

[node name="TrackedFace" type="Node3D" parent="Floor"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.4, -1)
script = ExtResource("4_fsql8")

[node name="Face" parent="Floor/TrackedFace" instance=ExtResource("4_wrwst")]
[node name="XRFbHandTrackingAimDemo" parent="." instance=ExtResource("4_gy0yu")]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.8723, -1.31893)

[connection signal="button_pressed" from="XROrigin3D/LeftHand" to="." method="_on_left_hand_button_pressed"]

[editable path="Floor/TrackedFace/Face"]
[connection signal="button_pressed" from="XROrigin3D/LeftHand" to="XRFbHandTrackingAimDemo" method="_on_left_hand_button_pressed"]
[connection signal="button_released" from="XROrigin3D/LeftHand" to="XRFbHandTrackingAimDemo" method="_on_left_hand_button_released"]
[connection signal="button_pressed" from="XROrigin3D/RightHand" to="XRFbHandTrackingAimDemo" method="_on_right_hand_button_pressed"]
[connection signal="button_released" from="XROrigin3D/RightHand" to="XRFbHandTrackingAimDemo" method="_on_right_hand_button_released"]
1 change: 1 addition & 0 deletions demo/project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ textures/vram_compression/import_etc2_astc=true
openxr/enabled=true
openxr/extensions/eye_gaze_interaction=true
shaders/enabled=true
openxr/extensions/hand_tracking_aim=true
Loading

0 comments on commit 69be122

Please sign in to comment.