diff --git a/xr/openxr_hand_tracking_demo/.gitignore b/xr/openxr_hand_tracking_demo/.gitignore new file mode 100644 index 00000000..62fc2214 --- /dev/null +++ b/xr/openxr_hand_tracking_demo/.gitignore @@ -0,0 +1,5 @@ +# Ignore our Android build folder, should be installed by user if needed +android/ + +# Ignore vendor plugin add on folder, should be installed by user if needed +addons/godotopenxrvendors/ diff --git a/xr/openxr_hand_tracking_demo/README.md b/xr/openxr_hand_tracking_demo/README.md new file mode 100644 index 00000000..718d9bb0 --- /dev/null +++ b/xr/openxr_hand_tracking_demo/README.md @@ -0,0 +1,159 @@ +# XR Hand Tracking Demo + +This is a demo showing OpenXRs hand tracking and controller tracking logic. + +Language: GDScript + +Renderer: Compatibility + +> Note: this demo requires Godot 4.3 or later + +## Screenshots + +![Screenshot](screenshots/hand_tracking_demo.png) + +## How does it work? + +Being able to see the players hands, and having those hands interact with elements in the environment are paramount to a good XR experience. + +In this demo we look at the off the shelf logic for displaying a hand model that is automated based on either controller input or through optical tracking of the players hands. +We also implement logic that allows interaction based on input from the action map that allows the user to pick up the blocks in this demo. + +The problem this poses to us is that there have been two schools of thought around what hand tracking actually means, +and depending on the XR runtime in use there may be gaps in functionality. + +### Hand tracking is only for optical tracking + +The first school of thought treats hand tracking as a separate system that purely focusses on optical hand tracking. +The hand tracking API in OpenXR, even if reported as supported, will only provide data if optical hand tracking is used. + +This means that when controllers are used, no data is available and you as a developer have to come up with your own +solution for displaying a hand mesh and animating it according to controller input. +Note that the current version of Godot XR Tools contains a full solution for this. + +Equally in this line of thought, the action map is only applicable to controller input. +You as a developer are responsible for implementing some means of gesture recognition when optical hand tracking is active. + +This becomes extra nightmarish when support for both controller tracking and optical hand tracking needs to be supported in a single application. + +### The unified approach + +The second school of thought ignores the differences between controller tracking and optical hand tracking +and treats them as two versions of the same. +Especially with controllers like the Valve Index, or with various data gloves that are treated as controllers, +there is no discernible difference here. + +The hand tracking API is mostly used for visualising the players hand with bone positions either being inferred +from controller input or matching the optical tracking. +For advanced gesture recognition you would still use this data however it is now accessible regardless of +the physical means in which this data is obtained. + +At the same time, in this school of thought the action map system is seen as the primary means to gain input +and is no longer restriced to input from controllers. The XR runtime is now responsible for recognising base +gestures such as pinching and pointing resulting in inputs that can be bound in the action map. + +OpenXR is moving towards this approach and this demo has been build in accordance with this however not all runtimes have been updated yet. + +SteamVR has followed this approach for a long time and works out of the box, however SteamVR treats everything as controllers resulting in some short comings when a Quest is used over Meta Link or Steam Link and optical hand tracking is used. + +Metas native Quest runtime on all versions of Quest now support OpenXRs "data source extension" which Godot enables when hand tracking is enabled. +However Meta does not yet support OpenXRs "hand interaction profile extension" which is required. + +Meta link is still trailing behind and does not support this brave new world **yet**. + +For other runtimes like Picos, HTC, Varjos, Magic Leaps, etc. may or may not yet support the required extensions. + +### Conclusion + +Due to the wildgrowth in capabilities in XR runtimes, +and there being no solid way to detect the full limitations of the platform you are currently on, +Godot XR Tools does not have support for the hand tracking API and purely relies on its own inferred hand positioning approach. + +However with more and more runtimes adopting these new extensions any solution that targets platforms with support, +it is becoming possible to rely on the hand tracking API. + +This demo project shows what that future looks like. + +## Hand tracking API + +As mentioned, the hand tracking API is at the center of visualising the users hand. +In Godot 4.3 we overhauled the system so the XR Interface needs to convert hand tracking data to the Godot humanoid skeleton hand bone layout. +This also means that this logic works both in WebXR, OpenXR and any other XR Interface that adds support for this feature. + +Hand tracking now also makes use of the new Skeleton Modifier logic in Godot 4.3 however +the skeleton is posed in the hands local space, while positioning is provided through a XRNode3D node. + +This split is applied because: + +* positioning is always within the local space of the XROrigin3D node +* there are many use cases where the positioning may be ignored or modified + +> Note that the trackers used for the hand tracking API are `/user/hand_tracker/left` and `/user/hand_tracker/right`. + +## (Half) body Tracking API + +Just an honerable mention of this, this is not part of this demo but Godot now also has support +for half and full body tracking that includes hand tracking. This functionality however is only +available on a limited number of XR runtimes. + +## Action map + +As mentioned, we're using the action map here for input however when optical hand tracking is used +we rely on OpenXRs hand interaction profile extension. Without support for this extension this demo +will not fully function. + +This can be solved by checking that no interaction profile has been bound to our XRController3D node, +and performing our own gesture detection. +As this would greatly increase the complexity of this demo and the expectation is that this extension +will soon see wide adoption, this is left out of the demo. + +> Some headsets will support the simple controller when hand tracking is enabled. +> The simple controller interaction profile doesn't support an input for grabbing. +> In this scenario you can grab the cubes using the pinch gesture +> (touch the tip of your thumb with the tip of our index finger). + +We are not using the default action map and instead have created an action map specific to this use case. + +There are only two actions needed for this example: +- `pose` is used to position the XRController3D nodes and mapped to the grip pose in most cases +- `pickup` is used as the input for picking up an object, and mapped accordingly. + +The pickup logic itself is split into two components: + +* `pickup_handler.gd/.tscn` is an Area3D node with logic that is added as a child to an XRController3D node and handles the logic for that hand to pick up objects in range. +* `pickup_able_body.gd` is a script that can be added to a RigidBody3D node to make it possible to pick up/drop that object. + +> Note that the trackers used by the action map are `left_hand` and `right_hand`. + +### MSFT Hand interaction extension + +Microsoft introduced a hand interaction extension that Godot now supports and is configured for this project. +Several other vendors such as Meta have added support for this extension as well. + +With this extension both grab gestures and pinch gestures are supported and you can thus pick up the blocks in this project by making a grab motion (making a fist). + +### HTC Hand interaction extension + +HTC introduced a hand interaction extension that Godot now support however this has not been implemented in this project. +This extension introduces two new trackers requiring you to change the trackers on the XRController3D node to make this work. + +## Local floor reference space + +A final notable element is that this demo uses the local floor reference space. + +With this reference space the XR runtime will center the player on the XROrigin3D node when the user triggers the recenter logic. +The startup behavior is different between different XR runtimes, Quest will attempt to remember where you recentered last, while SteamVR tends to reset this to default. +It can thus not be guaranteed the player is in the correct spot when the demo starts. +Hence the instructions suggesting the user recenters. + +## Running on PCVR + +This project can be run as normal for PCVR. Ensure that an OpenXR runtime has been installed. +This project has been tested with the Oculus client and SteamVR OpenXR runtimes. +Note that Godot currently can't run using the WMR OpenXR runtime. Install SteamVR with WMR support. + +## Running on standalone VR + +You must install the Android build templates and OpenXR loader plugin and configure an export template for your device. +Please follow [the instructions for deploying on Android in the manual](https://docs.godotengine.org/en/stable/tutorials/xr/deploying_to_android.html). + diff --git a/xr/openxr_hand_tracking_demo/assets/gltf/LeftHandHumanoid.bin b/xr/openxr_hand_tracking_demo/assets/gltf/LeftHandHumanoid.bin new file mode 100644 index 00000000..35c2f078 Binary files /dev/null and b/xr/openxr_hand_tracking_demo/assets/gltf/LeftHandHumanoid.bin differ diff --git a/xr/openxr_hand_tracking_demo/assets/gltf/LeftHandHumanoid.gltf b/xr/openxr_hand_tracking_demo/assets/gltf/LeftHandHumanoid.gltf new file mode 100644 index 00000000..1624d5b5 --- /dev/null +++ b/xr/openxr_hand_tracking_demo/assets/gltf/LeftHandHumanoid.gltf @@ -0,0 +1,739 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.0.44", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 26 + ] + } + ], + "nodes":[ + { + "name":"LeftThumbTip", + "rotation":[ + -0.001300050993449986, + -0.009501079097390175, + -0.028294457122683525, + 0.9995536208152771 + ], + "scale":[ + 1, + 0.9999999403953552, + 1.0000001192092896 + ], + "translation":[ + -2.7939677238464355e-09, + 0.030749443918466568, + -2.7939677238464355e-09 + ] + }, + { + "children":[ + 0 + ], + "name":"LeftThumbDistal", + "rotation":[ + -0.005640584509819746, + 0.011601205915212631, + 0.028976770117878914, + 0.9994968771934509 + ], + "scale":[ + 1, + 1.0000001192092896, + 1 + ], + "translation":[ + 4.6566128730773926e-09, + 0.04214790090918541, + 1.862645149230957e-09 + ] + }, + { + "children":[ + 1 + ], + "name":"LeftThumbProximal", + "rotation":[ + 0.0857066959142685, + -0.027135683223605156, + 0.020723098888993263, + 0.9957351684570312 + ], + "scale":[ + 1, + 1, + 0.9999998807907104 + ], + "translation":[ + -2.7939677238464355e-09, + 0.04491649195551872, + 4.656612873077393e-10 + ] + }, + { + "children":[ + 2 + ], + "name":"LeftThumbMetacarpal", + "rotation":[ + -0.09866384416818619, + 0.3619273900985718, + 0.3093259632587433, + 0.8738372325897217 + ], + "scale":[ + 0.9999999403953552, + 1, + 0.9999999403953552 + ], + "translation":[ + -0.019999975338578224, + 0.02717285417020321, + 0.009999998845160007 + ] + }, + { + "name":"LeftIndexTip", + "rotation":[ + 0.10513854771852493, + 0.002554001985117793, + -0.05383634194731712, + 0.9929959774017334 + ], + "translation":[ + -6.402842700481415e-10, + 0.02712245285511017, + -1.6589183360338211e-09 + ] + }, + { + "children":[ + 4 + ], + "name":"LeftIndexDistal", + "rotation":[ + 0.006294804625213146, + -0.014059717766940594, + 0.011407645419239998, + 0.9998162984848022 + ], + "scale":[ + 0.9999999403953552, + 0.9999998807907104, + 0.9999999403953552 + ], + "translation":[ + -8.731149137020111e-11, + 0.030106855556368828, + -1.862645149230957e-09 + ] + }, + { + "children":[ + 5 + ], + "name":"LeftIndexIntermediate", + "rotation":[ + 0.1859731674194336, + -0.02551458030939102, + -0.010402532294392586, + 0.982168436050415 + ], + "scale":[ + 1, + 0.9999999403953552, + 1.0000001192092896 + ], + "translation":[ + 1.1641532182693481e-09, + 0.03811633959412575, + 3.4924596548080444e-10 + ] + }, + { + "children":[ + 6 + ], + "name":"LeftIndexProximal", + "rotation":[ + -0.07441822439432144, + 0.002075796714052558, + 0.11123824119567871, + 0.9910013675689697 + ], + "translation":[ + 4.3306158659106586e-10, + 0.08036758750677109, + -2.1805135475005955e-10 + ] + }, + { + "children":[ + 7 + ], + "name":"LeftIndexMetacarpal", + "rotation":[ + -0.0252196304500103, + 2.1154794012545608e-05, + -0.0005887916195206344, + 0.9996817708015442 + ], + "scale":[ + 0.9999999403953552, + 1, + 1 + ], + "translation":[ + -0.022405147552490234, + 0.02717723324894905, + -0.010000002570450306 + ] + }, + { + "name":"LeftMiddleTip", + "rotation":[ + 0.1568938046693802, + 0.0025405443739145994, + 0.010807150974869728, + 0.987553060054779 + ], + "scale":[ + 1, + 1.0000001192092896, + 1 + ], + "translation":[ + 6.366462912410498e-10, + 0.028246993198990822, + 4.3537511373870075e-09 + ] + }, + { + "children":[ + 9 + ], + "name":"LeftMiddleDistal", + "rotation":[ + -0.03076975606381893, + -0.001283689751289785, + -0.011195655912160873, + 0.9994630217552185 + ], + "scale":[ + 0.9999998807907104, + 1.0000001192092896, + 0.9999998807907104 + ], + "translation":[ + 1.1095835361629725e-10, + 0.03174003213644028, + 1.0500116331968457e-09 + ] + }, + { + "children":[ + 10 + ], + "name":"LeftMiddleIntermediate", + "rotation":[ + 0.07791730761528015, + 0.005997773725539446, + 0.03916316106915474, + 0.996172308921814 + ], + "translation":[ + 2.473825588822365e-10, + 0.04500338435173035, + 4.145476850681007e-09 + ] + }, + { + "children":[ + 11 + ], + "name":"LeftMiddleProximal", + "rotation":[ + 0.05410468578338623, + 0.00044474273454397917, + -0.011977387592196465, + 0.9984633922576904 + ], + "scale":[ + 1, + 0.9999999403953552, + 1 + ], + "translation":[ + -1.8189894035458565e-10, + 0.08046326786279678, + -2.045908331638202e-09 + ] + }, + { + "children":[ + 12 + ], + "name":"LeftMiddleMetacarpal", + "rotation":[ + -0.049977608025074005, + 4.1883933590725064e-05, + -0.03585462272167206, + 0.9981065392494202 + ], + "scale":[ + 1, + 0.9999998807907104, + 1 + ], + "translation":[ + -0.0035275882109999657, + 0.02714575082063675, + -0.010000000707805157 + ] + }, + { + "name":"LeftRingTip", + "rotation":[ + 0.13524393737316132, + -0.008499672636389732, + 0.026296839118003845, + 0.9904268383979797 + ], + "scale":[ + 0.9999999403953552, + 1.0000001192092896, + 1.0000001192092896 + ], + "translation":[ + -3.245077095925808e-09, + 0.0329010896384716, + -1.7462298274040222e-09 + ] + }, + { + "children":[ + 14 + ], + "name":"LeftRingDistal", + "rotation":[ + -0.011438504792749882, + 0.013675450347363949, + -0.0019264306174591184, + 0.9998392462730408 + ], + "scale":[ + 1.0000001192092896, + 1.0000001192092896, + 1 + ], + "translation":[ + 3.2014213502407074e-10, + 0.027040131390094757, + -1.7462298274040222e-09 + ] + }, + { + "children":[ + 15 + ], + "name":"LeftRingIntermediate", + "rotation":[ + 0.09900747239589691, + 0.019192615523934364, + 0.014731669798493385, + 0.9947925209999084 + ], + "scale":[ + 1, + 0.9999999403953552, + 1 + ], + "translation":[ + -3.798049874603748e-09, + 0.04013120383024216, + -1.7898855730891228e-09 + ] + }, + { + "children":[ + 16 + ], + "name":"LeftRingProximal", + "rotation":[ + 0.001759529230184853, + -0.004247802309691906, + -0.05091992765665054, + 0.9986922144889832 + ], + "scale":[ + 0.9999999403953552, + 1, + 1 + ], + "translation":[ + 1.7316779121756554e-09, + 0.0739438533782959, + 8.585629984736443e-10 + ] + }, + { + "children":[ + 17 + ], + "name":"LeftRingMetacarpal", + "rotation":[ + -0.018085606396198273, + 1.561214230605401e-05, + -0.07119491696357727, + 0.9972984790802002 + ], + "scale":[ + 0.9999998807907104, + 0.9999999403953552, + 1 + ], + "translation":[ + 0.013099989853799343, + 0.027118019759655, + -0.010000001639127731 + ] + }, + { + "name":"LeftLittleTip", + "rotation":[ + 0.03595229610800743, + -0.020332874730229378, + 0.013369254767894745, + 0.9990572333335876 + ], + "scale":[ + 0.9999999403953552, + 0.9999997615814209, + 0.9999999403953552 + ], + "translation":[ + -5.820766091346741e-10, + 0.01958998665213585, + 8.381903171539307e-09 + ] + }, + { + "children":[ + 19 + ], + "name":"LeftLittleDistal", + "rotation":[ + 0.01741550862789154, + 0.014375297352671623, + -0.012779458425939083, + 0.9996633529663086 + ], + "scale":[ + 1, + 1, + 0.9999999403953552 + ], + "translation":[ + -1.04046193882823e-09, + 0.01730157621204853, + 3.14321368932724e-09 + ] + }, + { + "children":[ + 20 + ], + "name":"LeftLittleIntermediate", + "rotation":[ + 0.1067298874258995, + 0.03360109031200409, + 0.04032363370060921, + 0.9929016828536987 + ], + "scale":[ + 1, + 0.9999999403953552, + 0.9999999403953552 + ], + "translation":[ + 2.0718289306387305e-09, + 0.033123549073934555, + 0 + ] + }, + { + "children":[ + 21 + ], + "name":"LeftLittleProximal", + "rotation":[ + 0.050176676362752914, + -0.0007295551477000117, + -0.08933921158313751, + 0.9947363138198853 + ], + "scale":[ + 0.9999999403953552, + 1, + 1 + ], + "translation":[ + 3.3651303965598345e-10, + 0.06571119278669357, + -3.637978807091713e-10 + ] + }, + { + "children":[ + 22 + ], + "name":"LeftLittleMetacarpal", + "rotation":[ + -0.028447696939110756, + 2.468071943440009e-05, + -0.09176953136920929, + 0.9953738451004028 + ], + "scale":[ + 0.9999999403953552, + 0.9999999403953552, + 1 + ], + "translation":[ + 0.029999960213899612, + 0.027089649811387062, + 8.651588889740935e-10 + ] + }, + { + "children":[ + 3, + 8, + 13, + 18, + 23 + ], + "name":"LeftHand", + "rotation":[ + -0.4995875358581543, + 0.5004213452339172, + -0.4995783269405365, + 0.5004121661186218 + ], + "scale":[ + 0.9999999403953552, + 0.9999997615814209, + 0.9999999403953552 + ], + "translation":[ + 3.8642522071086205e-08, + -1.8697472114581615e-05, + 0.027175573632121086 + ] + }, + { + "mesh":0, + "name":"LeftHandHumanoidMesh", + "skin":0 + }, + { + "children":[ + 25, + 24 + ], + "name":"LeftHandHumanoid" + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Hand", + "pbrMetallicRoughness":{ + "baseColorTexture":{ + "index":0 + }, + "metallicFactor":0, + "roughnessFactor":0.5 + } + } + ], + "meshes":[ + { + "name":"LeftHandHumanoid", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2, + "JOINTS_0":3, + "WEIGHTS_0":4 + }, + "indices":5, + "material":0 + } + ] + } + ], + "textures":[ + { + "sampler":0, + "source":0 + } + ], + "images":[ + { + "mimeType":"image/png", + "name":"hand", + "uri":"hand.png" + } + ], + "skins":[ + { + "inverseBindMatrices":6, + "joints":[ + 24, + 3, + 2, + 1, + 0, + 8, + 7, + 6, + 5, + 4, + 13, + 12, + 11, + 10, + 9, + 18, + 17, + 16, + 15, + 14, + 23, + 22, + 21, + 20, + 19 + ], + "name":"LeftHandHumanoid" + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":603, + "max":[ + 0.03515051305294037, + 0.09001174569129944, + 0.02576880156993866 + ], + "min":[ + -0.029946302995085716, + -0.06838366389274597, + -0.18542321026325226 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":603, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":603, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5121, + "count":603, + "type":"VEC4" + }, + { + "bufferView":4, + "componentType":5126, + "count":603, + "type":"VEC4" + }, + { + "bufferView":5, + "componentType":5123, + "count":2814, + "type":"SCALAR" + }, + { + "bufferView":6, + "componentType":5126, + "count":25, + "type":"MAT4" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":7236, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":7236, + "byteOffset":7236, + "target":34962 + }, + { + "buffer":0, + "byteLength":4824, + "byteOffset":14472, + "target":34962 + }, + { + "buffer":0, + "byteLength":2412, + "byteOffset":19296, + "target":34962 + }, + { + "buffer":0, + "byteLength":9648, + "byteOffset":21708, + "target":34962 + }, + { + "buffer":0, + "byteLength":5628, + "byteOffset":31356, + "target":34963 + }, + { + "buffer":0, + "byteLength":1600, + "byteOffset":36984 + } + ], + "samplers":[ + { + "magFilter":9729, + "minFilter":9987 + } + ], + "buffers":[ + { + "byteLength":38584, + "uri":"LeftHandHumanoid.bin" + } + ] +} diff --git a/xr/openxr_hand_tracking_demo/assets/gltf/LeftHandHumanoid.gltf.import b/xr/openxr_hand_tracking_demo/assets/gltf/LeftHandHumanoid.gltf.import new file mode 100644 index 00000000..371ff307 --- /dev/null +++ b/xr/openxr_hand_tracking_demo/assets/gltf/LeftHandHumanoid.gltf.import @@ -0,0 +1,39 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://d22k0sp2hinew" +path="res://.godot/imported/LeftHandHumanoid.gltf-219698e659d03b53d9439b731eba1dad.scn" + +[deps] + +source_file="res://assets/gltf/LeftHandHumanoid.gltf" +dest_files=["res://.godot/imported/LeftHandHumanoid.gltf-219698e659d03b53d9439b731eba1dad.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +_subresources={} +fbx/importer=0 +fbx/allow_geometry_helper_nodes=false +fbx/embedded_image_handling=1 +gltf/naming_version=1 +gltf/embedded_image_handling=1 diff --git a/xr/openxr_hand_tracking_demo/assets/gltf/RightHandHumanoid.bin b/xr/openxr_hand_tracking_demo/assets/gltf/RightHandHumanoid.bin new file mode 100644 index 00000000..384d1179 Binary files /dev/null and b/xr/openxr_hand_tracking_demo/assets/gltf/RightHandHumanoid.bin differ diff --git a/xr/openxr_hand_tracking_demo/assets/gltf/RightHandHumanoid.gltf b/xr/openxr_hand_tracking_demo/assets/gltf/RightHandHumanoid.gltf new file mode 100644 index 00000000..76c690ff --- /dev/null +++ b/xr/openxr_hand_tracking_demo/assets/gltf/RightHandHumanoid.gltf @@ -0,0 +1,744 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.0.44", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 26 + ] + } + ], + "nodes":[ + { + "name":"RightThumbTip", + "rotation":[ + -0.0015842723660171032, + 0.01954343169927597, + 0.028279954567551613, + 0.9994077682495117 + ], + "scale":[ + 1, + 0.9999999403953552, + 1.0000001192092896 + ], + "translation":[ + 2.7939677238464355e-09, + 0.030749443918466568, + -2.7939677238464355e-09 + ] + }, + { + "children":[ + 0 + ], + "name":"RightThumbDistal", + "rotation":[ + -0.005640584509819746, + -0.011601205915212631, + -0.028976770117878914, + 0.9994968771934509 + ], + "scale":[ + 1, + 1.0000001192092896, + 1 + ], + "translation":[ + -4.6566128730773926e-09, + 0.04214790090918541, + 1.862645149230957e-09 + ] + }, + { + "children":[ + 1 + ], + "name":"RightThumbProximal", + "rotation":[ + 0.0857066959142685, + 0.027135683223605156, + -0.020723098888993263, + 0.9957351684570312 + ], + "scale":[ + 1, + 1, + 0.9999998807907104 + ], + "translation":[ + 2.7939677238464355e-09, + 0.04491649195551872, + 4.656612873077393e-10 + ] + }, + { + "children":[ + 2 + ], + "name":"RightThumbMetacarpal", + "rotation":[ + -0.09866384416818619, + -0.3619273900985718, + -0.3093259632587433, + 0.8738372325897217 + ], + "scale":[ + 0.9999999403953552, + 1, + 0.9999999403953552 + ], + "translation":[ + 0.019999975338578224, + 0.02717285417020321, + 0.009999998845160007 + ] + }, + { + "name":"RightIndexTip", + "rotation":[ + 0.10500194132328033, + -0.00039221683982759714, + 0.0540827140212059, + 0.9930002689361572 + ], + "scale":[ + 0.9999999403953552, + 1.0000001192092896, + 0.9999999403953552 + ], + "translation":[ + 9.89530235528946e-10, + 0.027121681720018387, + -1.3969838619232178e-09 + ] + }, + { + "children":[ + 4 + ], + "name":"RightIndexDistal", + "rotation":[ + 0.006300266366451979, + 0.014137149788439274, + -0.011408609338104725, + 0.9998151659965515 + ], + "scale":[ + 1, + 0.9999998807907104, + 1 + ], + "translation":[ + 8.731149137020111e-11, + 0.030106855556368828, + -1.862645149230957e-09 + ] + }, + { + "children":[ + 5 + ], + "name":"RightIndexIntermediate", + "rotation":[ + 0.1859731674194336, + 0.02551458030939102, + 0.010402532294392586, + 0.982168436050415 + ], + "scale":[ + 1, + 0.9999999403953552, + 1.0000001192092896 + ], + "translation":[ + -1.1641532182693481e-09, + 0.03811633959412575, + 3.4924596548080444e-10 + ] + }, + { + "children":[ + 6 + ], + "name":"RightIndexProximal", + "rotation":[ + -0.07441822439432144, + -0.002075796714052558, + -0.11123824119567871, + 0.9910013675689697 + ], + "translation":[ + -4.3306158659106586e-10, + 0.08036758750677109, + -2.1805135475005955e-10 + ] + }, + { + "children":[ + 7 + ], + "name":"RightIndexMetacarpal", + "rotation":[ + -0.0252196304500103, + -2.1154794012545608e-05, + 0.0005887916195206344, + 0.9996817708015442 + ], + "scale":[ + 0.9999999403953552, + 1, + 1 + ], + "translation":[ + 0.022405147552490234, + 0.02717723324894905, + -0.010000002570450306 + ] + }, + { + "name":"RightMiddleTip", + "rotation":[ + 0.1537046879529953, + 0.001326742465607822, + -0.011463530361652374, + 0.9880494475364685 + ], + "scale":[ + 0.9999999403953552, + 0.9999999403953552, + 1 + ], + "translation":[ + 1.0477378964424133e-09, + 0.028247453272342682, + 1.4370016288012266e-09 + ] + }, + { + "children":[ + 9 + ], + "name":"RightMiddleDistal", + "rotation":[ + -0.030728844925761223, + -0.0027318676002323627, + 0.01131731178611517, + 0.999459981918335 + ], + "scale":[ + 0.9999999403953552, + 1, + 0.9999999403953552 + ], + "translation":[ + 1.4370016288012266e-10, + 0.03174003213644028, + 1.0595613275654614e-09 + ] + }, + { + "children":[ + 10 + ], + "name":"RightMiddleIntermediate", + "rotation":[ + 0.07791730761528015, + -0.005997773725539446, + -0.03916317597031593, + 0.996172308921814 + ], + "translation":[ + -2.473825588822365e-10, + 0.04500338435173035, + 4.145476850681007e-09 + ] + }, + { + "children":[ + 11 + ], + "name":"RightMiddleProximal", + "rotation":[ + 0.05410468578338623, + -0.00044474273454397917, + 0.011977387592196465, + 0.9984633922576904 + ], + "scale":[ + 1, + 0.9999999403953552, + 1 + ], + "translation":[ + 1.8189894035458565e-10, + 0.08046326786279678, + -2.045908331638202e-09 + ] + }, + { + "children":[ + 12 + ], + "name":"RightMiddleMetacarpal", + "rotation":[ + -0.049977608025074005, + -4.1883933590725064e-05, + 0.03585462272167206, + 0.9981065392494202 + ], + "scale":[ + 1, + 0.9999998807907104, + 1 + ], + "translation":[ + 0.0035275882109999657, + 0.02714575082063675, + -0.010000000707805157 + ] + }, + { + "name":"RightRingTip", + "rotation":[ + 0.13524393737316132, + 0.008499672636389732, + -0.026296839118003845, + 0.9904268383979797 + ], + "scale":[ + 0.9999999403953552, + 1.0000001192092896, + 1.0000001192092896 + ], + "translation":[ + 3.245077095925808e-09, + 0.0329010896384716, + -1.7462298274040222e-09 + ] + }, + { + "children":[ + 14 + ], + "name":"RightRingDistal", + "rotation":[ + -0.011438504792749882, + -0.013675450347363949, + 0.0019264306174591184, + 0.9998392462730408 + ], + "scale":[ + 1.0000001192092896, + 1.0000001192092896, + 1 + ], + "translation":[ + -3.2014213502407074e-10, + 0.027040131390094757, + -1.7462298274040222e-09 + ] + }, + { + "children":[ + 15 + ], + "name":"RightRingIntermediate", + "rotation":[ + 0.09900747239589691, + -0.019192615523934364, + -0.014731669798493385, + 0.9947925209999084 + ], + "scale":[ + 1, + 0.9999999403953552, + 1 + ], + "translation":[ + 3.798049874603748e-09, + 0.04013120383024216, + -1.7898855730891228e-09 + ] + }, + { + "children":[ + 16 + ], + "name":"RightRingProximal", + "rotation":[ + 0.001759529230184853, + 0.004247802309691906, + 0.05091992765665054, + 0.9986922144889832 + ], + "scale":[ + 0.9999999403953552, + 1, + 1 + ], + "translation":[ + -1.7316779121756554e-09, + 0.0739438533782959, + 8.585629984736443e-10 + ] + }, + { + "children":[ + 17 + ], + "name":"RightRingMetacarpal", + "rotation":[ + -0.018085606396198273, + -1.561214230605401e-05, + 0.07119491696357727, + 0.9972984790802002 + ], + "scale":[ + 0.9999998807907104, + 0.9999999403953552, + 1 + ], + "translation":[ + -0.013099989853799343, + 0.027118019759655, + -0.010000001639127731 + ] + }, + { + "name":"RightLittleTip", + "rotation":[ + 0.03595229610800743, + 0.020332874730229378, + -0.013369254767894745, + 0.9990572333335876 + ], + "scale":[ + 0.9999999403953552, + 0.9999997615814209, + 0.9999999403953552 + ], + "translation":[ + 5.820766091346741e-10, + 0.01958998665213585, + 8.381903171539307e-09 + ] + }, + { + "children":[ + 19 + ], + "name":"RightLittleDistal", + "rotation":[ + 0.01741550862789154, + -0.014375297352671623, + 0.012779458425939083, + 0.9996633529663086 + ], + "scale":[ + 1, + 1, + 0.9999999403953552 + ], + "translation":[ + 1.04046193882823e-09, + 0.01730157621204853, + 3.14321368932724e-09 + ] + }, + { + "children":[ + 20 + ], + "name":"RightLittleIntermediate", + "rotation":[ + 0.1067298874258995, + -0.03360109031200409, + -0.04032363370060921, + 0.9929016828536987 + ], + "scale":[ + 1, + 0.9999999403953552, + 0.9999999403953552 + ], + "translation":[ + -2.0718289306387305e-09, + 0.033123549073934555, + 0 + ] + }, + { + "children":[ + 21 + ], + "name":"RightLittleProximal", + "rotation":[ + 0.050176676362752914, + 0.0007295551477000117, + 0.08933921158313751, + 0.9947363138198853 + ], + "scale":[ + 0.9999999403953552, + 1, + 1 + ], + "translation":[ + -3.3651303965598345e-10, + 0.06571119278669357, + -3.637978807091713e-10 + ] + }, + { + "children":[ + 22 + ], + "name":"RightLittleMetacarpal", + "rotation":[ + -0.028447696939110756, + -2.468071943440009e-05, + 0.09176953136920929, + 0.9953738451004028 + ], + "scale":[ + 0.9999999403953552, + 0.9999999403953552, + 1 + ], + "translation":[ + -0.029999960213899612, + 0.027089649811387062, + 8.651588889740935e-10 + ] + }, + { + "children":[ + 3, + 8, + 13, + 18, + 23 + ], + "name":"RightHand", + "rotation":[ + -0.4995875358581543, + -0.5004213452339172, + 0.4995783269405365, + 0.5004121661186218 + ], + "scale":[ + 0.9999999403953552, + 0.9999997615814209, + 0.9999999403953552 + ], + "translation":[ + -3.8642522071086205e-08, + -1.8697472114581615e-05, + 0.027175573632121086 + ] + }, + { + "mesh":0, + "name":"RightHandHumanoidMesh", + "skin":0 + }, + { + "children":[ + 25, + 24 + ], + "name":"RightHandHumanoid" + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Hand", + "pbrMetallicRoughness":{ + "baseColorTexture":{ + "index":0 + }, + "metallicFactor":0, + "roughnessFactor":0.5 + } + } + ], + "meshes":[ + { + "name":"RightHandHumanoid", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2, + "JOINTS_0":3, + "WEIGHTS_0":4 + }, + "indices":5, + "material":0 + } + ] + } + ], + "textures":[ + { + "sampler":0, + "source":0 + } + ], + "images":[ + { + "mimeType":"image/png", + "name":"hand", + "uri":"hand.png" + } + ], + "skins":[ + { + "inverseBindMatrices":6, + "joints":[ + 24, + 3, + 2, + 1, + 0, + 8, + 7, + 6, + 5, + 4, + 13, + 12, + 11, + 10, + 9, + 18, + 17, + 16, + 15, + 14, + 23, + 22, + 21, + 20, + 19 + ], + "name":"RightHandHumanoid" + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":603, + "max":[ + 0.02935725450515747, + 0.09001174569129944, + 0.025806419551372528 + ], + "min":[ + -0.035739559680223465, + -0.06838366389274597, + -0.18542321026325226 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":603, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":603, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5121, + "count":603, + "type":"VEC4" + }, + { + "bufferView":4, + "componentType":5126, + "count":603, + "type":"VEC4" + }, + { + "bufferView":5, + "componentType":5123, + "count":2814, + "type":"SCALAR" + }, + { + "bufferView":6, + "componentType":5126, + "count":25, + "type":"MAT4" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":7236, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":7236, + "byteOffset":7236, + "target":34962 + }, + { + "buffer":0, + "byteLength":4824, + "byteOffset":14472, + "target":34962 + }, + { + "buffer":0, + "byteLength":2412, + "byteOffset":19296, + "target":34962 + }, + { + "buffer":0, + "byteLength":9648, + "byteOffset":21708, + "target":34962 + }, + { + "buffer":0, + "byteLength":5628, + "byteOffset":31356, + "target":34963 + }, + { + "buffer":0, + "byteLength":1600, + "byteOffset":36984 + } + ], + "samplers":[ + { + "magFilter":9729, + "minFilter":9987 + } + ], + "buffers":[ + { + "byteLength":38584, + "uri":"RightHandHumanoid.bin" + } + ] +} diff --git a/xr/openxr_hand_tracking_demo/assets/gltf/RightHandHumanoid.gltf.import b/xr/openxr_hand_tracking_demo/assets/gltf/RightHandHumanoid.gltf.import new file mode 100644 index 00000000..4156b55c --- /dev/null +++ b/xr/openxr_hand_tracking_demo/assets/gltf/RightHandHumanoid.gltf.import @@ -0,0 +1,39 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://dlswhmq6s52gu" +path="res://.godot/imported/RightHandHumanoid.gltf-9b0df2ce88fbae3fc911bc06520ecfc9.scn" + +[deps] + +source_file="res://assets/gltf/RightHandHumanoid.gltf" +dest_files=["res://.godot/imported/RightHandHumanoid.gltf-9b0df2ce88fbae3fc911bc06520ecfc9.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +_subresources={} +fbx/importer=0 +fbx/allow_geometry_helper_nodes=false +fbx/embedded_image_handling=1 +gltf/naming_version=1 +gltf/embedded_image_handling=1 diff --git a/xr/openxr_hand_tracking_demo/assets/gltf/hand.png b/xr/openxr_hand_tracking_demo/assets/gltf/hand.png new file mode 100644 index 00000000..3c16e434 Binary files /dev/null and b/xr/openxr_hand_tracking_demo/assets/gltf/hand.png differ diff --git a/xr/openxr_hand_tracking_demo/assets/gltf/hand.png.import b/xr/openxr_hand_tracking_demo/assets/gltf/hand.png.import new file mode 100644 index 00000000..870add24 --- /dev/null +++ b/xr/openxr_hand_tracking_demo/assets/gltf/hand.png.import @@ -0,0 +1,36 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cblg6srneyvvs" +path.s3tc="res://.godot/imported/hand.png-a9641443c18f421b63decbe9f5ce13e4.s3tc.ctex" +path.etc2="res://.godot/imported/hand.png-a9641443c18f421b63decbe9f5ce13e4.etc2.ctex" +metadata={ +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/gltf/hand.png" +dest_files=["res://.godot/imported/hand.png-a9641443c18f421b63decbe9f5ce13e4.s3tc.ctex", "res://.godot/imported/hand.png-a9641443c18f421b63decbe9f5ce13e4.etc2.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/xr/openxr_hand_tracking_demo/assets/images/pattern.png b/xr/openxr_hand_tracking_demo/assets/images/pattern.png new file mode 100644 index 00000000..8bf420b0 Binary files /dev/null and b/xr/openxr_hand_tracking_demo/assets/images/pattern.png differ diff --git a/xr/openxr_hand_tracking_demo/assets/images/pattern.png.import b/xr/openxr_hand_tracking_demo/assets/images/pattern.png.import new file mode 100644 index 00000000..14489fc8 --- /dev/null +++ b/xr/openxr_hand_tracking_demo/assets/images/pattern.png.import @@ -0,0 +1,36 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b1waowk6l76ap" +path.s3tc="res://.godot/imported/pattern.png-4d94aeab07bbe0313c1636ed9890ceae.s3tc.ctex" +path.etc2="res://.godot/imported/pattern.png-4d94aeab07bbe0313c1636ed9890ceae.etc2.ctex" +metadata={ +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true +} + +[deps] + +source_file="res://assets/images/pattern.png" +dest_files=["res://.godot/imported/pattern.png-4d94aeab07bbe0313c1636ed9890ceae.s3tc.ctex", "res://.godot/imported/pattern.png-4d94aeab07bbe0313c1636ed9890ceae.etc2.ctex"] + +[params] + +compress/mode=2 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=0 diff --git a/xr/openxr_hand_tracking_demo/hand_info.gd b/xr/openxr_hand_tracking_demo/hand_info.gd new file mode 100644 index 00000000..03d3b5b0 --- /dev/null +++ b/xr/openxr_hand_tracking_demo/hand_info.gd @@ -0,0 +1,53 @@ +extends Node3D + +@export_enum("Left", "Right") var hand : int = 0 + + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + var text = "" + + if hand == 0: + text += "Left hand\n" + else: + text += "Right hand\n" + + var controller_tracker : XRPositionalTracker = XRServer.get_tracker("left_hand" if hand == 0 else "right_hand") + if controller_tracker: + var profile = controller_tracker.profile.replace("/interaction_profiles/", "").replace("/", " ") + text += "\nProfile: " + profile + "\n" + + var pose : XRPose = controller_tracker.get_pose("pose") + if pose: + if pose.tracking_confidence == XRPose.XR_TRACKING_CONFIDENCE_NONE: + text += "- No tracking data\n" + elif pose.tracking_confidence == XRPose.XR_TRACKING_CONFIDENCE_LOW: + text += "- Low confidence tracking data\n" + elif pose.tracking_confidence == XRPose.XR_TRACKING_CONFIDENCE_HIGH: + text += "- High confidence tracking data\n" + else: + text += "- Unknown tracking data %d \n" % [ pose.tracking_confidence ] + else: + text += "- No pose data\n" + else: + text += "\nNo controller tracker found!\n" + + var hand_tracker : XRHandTracker = XRServer.get_tracker("/user/hand_tracker/left" if hand == 0 else "/user/hand_tracker/right") + if hand_tracker: + text += "\nHand tracker found\n" + + if hand_tracker.has_tracking_data: + if hand_tracker.hand_tracking_source == XRHandTracker.HAND_TRACKING_SOURCE_UNKNOWN: + text += "- Source: unknown\n" + elif hand_tracker.hand_tracking_source == XRHandTracker.HAND_TRACKING_SOURCE_UNOBSTRUCTED: + text += "- Source: optical hand tracking\n" + elif hand_tracker.hand_tracking_source == XRHandTracker.HAND_TRACKING_SOURCE_CONTROLLER: + text += "- Source: inferred from controller\n" + else: + text += "- Source: %d\n" % [ hand_tracker.hand_tracking_source ] + else: + text += "- No tracking data\n" + else: + text += "\nNo hand tracker found!\n" + + $Info.text = text diff --git a/xr/openxr_hand_tracking_demo/hand_info.tscn b/xr/openxr_hand_tracking_demo/hand_info.tscn new file mode 100644 index 00000000..eda30ccd --- /dev/null +++ b/xr/openxr_hand_tracking_demo/hand_info.tscn @@ -0,0 +1,17 @@ +[gd_scene load_steps=2 format=3 uid="uid://dtabh705qyufu"] + +[ext_resource type="Script" path="res://hand_info.gd" id="1_kp65y"] + +[node name="HandInfo" type="Node3D"] +script = ExtResource("1_kp65y") + +[node name="Info" type="Label3D" parent="."] +pixel_size = 0.0015 +text = "Hand info +. +. +. +." +horizontal_alignment = 0 +vertical_alignment = 2 +autowrap_mode = 3 diff --git a/xr/openxr_hand_tracking_demo/icon.svg b/xr/openxr_hand_tracking_demo/icon.svg new file mode 100644 index 00000000..b370ceb7 --- /dev/null +++ b/xr/openxr_hand_tracking_demo/icon.svg @@ -0,0 +1 @@ + diff --git a/xr/openxr_hand_tracking_demo/icon.svg.import b/xr/openxr_hand_tracking_demo/icon.svg.import new file mode 100644 index 00000000..ce0b759f --- /dev/null +++ b/xr/openxr_hand_tracking_demo/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dtetda3s8m6po" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/xr/openxr_hand_tracking_demo/main.tscn b/xr/openxr_hand_tracking_demo/main.tscn new file mode 100644 index 00000000..d060375c --- /dev/null +++ b/xr/openxr_hand_tracking_demo/main.tscn @@ -0,0 +1,128 @@ +[gd_scene load_steps=14 format=3 uid="uid://br3bss6kac8pa"] + +[ext_resource type="PackedScene" uid="uid://d22k0sp2hinew" path="res://assets/gltf/LeftHandHumanoid.gltf" id="2_3hxem"] +[ext_resource type="Script" path="res://start_vr.gd" id="2_5rtkn"] +[ext_resource type="PackedScene" uid="uid://dlswhmq6s52gu" path="res://assets/gltf/RightHandHumanoid.gltf" id="3_oifi1"] +[ext_resource type="PackedScene" uid="uid://byif52d1xkl3u" path="res://pickup/pickup_handler.tscn" id="3_sg1io"] +[ext_resource type="Texture2D" uid="uid://b1waowk6l76ap" path="res://assets/images/pattern.png" id="4_3x0ea"] +[ext_resource type="PackedScene" uid="uid://dtabh705qyufu" path="res://hand_info.tscn" id="5_wlhtu"] +[ext_resource type="PackedScene" uid="uid://hanl00aqvu7u" path="res://objects/table.tscn" id="6_rfmma"] +[ext_resource type="PackedScene" uid="uid://cerkxyasq8t8b" path="res://objects/box.tscn" id="7_6sqt7"] + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_eyx45"] +sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) +ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1) + +[sub_resource type="Sky" id="Sky_tsis2"] +sky_material = SubResource("ProceduralSkyMaterial_eyx45") + +[sub_resource type="Environment" id="Environment_0xu52"] +background_mode = 2 +sky = SubResource("Sky_tsis2") +tonemap_mode = 2 + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_dxr1t"] +albedo_color = Color(0.187808, 0.607643, 0.279312, 1) +albedo_texture = ExtResource("4_3x0ea") +uv1_scale = Vector3(100, 100, 100) + +[sub_resource type="PlaneMesh" id="PlaneMesh_6hhse"] +material = SubResource("StandardMaterial3D_dxr1t") +size = Vector2(1000, 1000) +subdivide_width = 10 +subdivide_depth = 10 + +[node name="Main" type="Node3D"] + +[node name="StartVR" type="Node3D" parent="."] +script = ExtResource("2_5rtkn") + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_0xu52") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(0.397904, -0.794516, 0.458712, 0, 0.499998, 0.866026, -0.917427, -0.344595, 0.198951, 0, 5, 0) +directional_shadow_max_distance = 10.0 + +[node name="Floor" type="MeshInstance3D" parent="."] +mesh = SubResource("PlaneMesh_6hhse") + +[node name="RecenterInfo" type="Label3D" parent="Floor"] +transform = Transform3D(0.59873, 0, 0.800951, 0, 1, 0, -0.800951, 0, 0.59873, -1.76283, 1.50033, -0.380399) +pixel_size = 0.003 +text = "If you are not facing the table, +recenter your headset. + +On Quest, hold the Meta button +on your right controller +for several seconds + +In SteamVR, open the menu and +select recenter and follow +instructions" + +[node name="Table" parent="." instance=ExtResource("6_rfmma")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -0.5) + +[node name="Box01" parent="Table" instance=ExtResource("7_6sqt7")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.941084, 0) + +[node name="Box02" parent="Table" instance=ExtResource("7_6sqt7")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.13752, 0.941084, 0) + +[node name="Box03" parent="Table" instance=ExtResource("7_6sqt7")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.0746718, 1.06282, 0) + +[node name="LeftHandInfo" parent="Table" instance=ExtResource("5_wlhtu")] +transform = Transform3D(0.939693, -0.085635, 0.331126, 0, 0.968147, 0.25038, -0.34202, -0.23528, 0.909761, -0.713026, 0.8718, -0.309953) + +[node name="RightHandInfo" parent="Table" instance=ExtResource("5_wlhtu")] +transform = Transform3D(0.939693, 0.085635, -0.331126, 0, 0.968147, 0.25038, 0.34202, -0.23528, 0.909761, 0.278022, 0.8718, -0.381943) +hand = 1 + +[node name="XROrigin3D" type="XROrigin3D" parent="."] + +[node name="XRCamera3D" type="XRCamera3D" parent="XROrigin3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.7, 0) + +[node name="LeftHandController" type="XRController3D" parent="XROrigin3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 1, -0.5) +tracker = &"left_hand" +pose = &"pose" +show_when_tracked = true + +[node name="PickupHandler" parent="XROrigin3D/LeftHandController" instance=ExtResource("3_sg1io")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.05, 0, 0) +pickup_action = "pickup" + +[node name="RightHandController" type="XRController3D" parent="XROrigin3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 1, -0.5) +tracker = &"right_hand" +pose = &"pose" +show_when_tracked = true + +[node name="PickupHandler" parent="XROrigin3D/RightHandController" instance=ExtResource("3_sg1io")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.05, 0, 0) +pickup_action = "pickup" + +[node name="LeftHandMesh" type="XRNode3D" parent="XROrigin3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 1, -0.5) +tracker = &"/user/hand_tracker/left" +show_when_tracked = true + +[node name="LeftHandHumanoid2" parent="XROrigin3D/LeftHandMesh" instance=ExtResource("2_3hxem")] + +[node name="XRHandModifier3D" type="XRHandModifier3D" parent="XROrigin3D/LeftHandMesh/LeftHandHumanoid2/LeftHandHumanoid/Skeleton3D" index="1"] + +[node name="RightHandMesh" type="XRNode3D" parent="XROrigin3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 1, -0.5) +tracker = &"/user/hand_tracker/right" +show_when_tracked = true + +[node name="RightHandHumanoid2" parent="XROrigin3D/RightHandMesh" instance=ExtResource("3_oifi1")] + +[node name="XRHandModifier3D" type="XRHandModifier3D" parent="XROrigin3D/RightHandMesh/RightHandHumanoid2/RightHandHumanoid/Skeleton3D" index="1"] +hand_tracker = &"/user/hand_tracker/right" + +[editable path="XROrigin3D/LeftHandMesh/LeftHandHumanoid2"] +[editable path="XROrigin3D/RightHandMesh/RightHandHumanoid2"] diff --git a/xr/openxr_hand_tracking_demo/objects/box.tscn b/xr/openxr_hand_tracking_demo/objects/box.tscn new file mode 100644 index 00000000..39031d41 --- /dev/null +++ b/xr/openxr_hand_tracking_demo/objects/box.tscn @@ -0,0 +1,25 @@ +[gd_scene load_steps=6 format=3 uid="uid://cerkxyasq8t8b"] + +[ext_resource type="Script" path="res://pickup/pickup_able_body.gd" id="1_mxwa3"] +[ext_resource type="Texture2D" uid="uid://b1waowk6l76ap" path="res://assets/images/pattern.png" id="1_t4uiq"] + +[sub_resource type="BoxShape3D" id="BoxShape3D_jjy5v"] +margin = 0.001 +size = Vector3(0.1, 0.1, 0.1) + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ecfgs"] +albedo_texture = ExtResource("1_t4uiq") + +[sub_resource type="BoxMesh" id="BoxMesh_ajubj"] +material = SubResource("StandardMaterial3D_ecfgs") +size = Vector3(0.1, 0.1, 0.1) + +[node name="Box" type="RigidBody3D"] +script = ExtResource("1_mxwa3") + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +shape = SubResource("BoxShape3D_jjy5v") + +[node name="MeshInstance3D" type="MeshInstance3D" parent="."] +mesh = SubResource("BoxMesh_ajubj") +skeleton = NodePath("../CollisionShape3D") diff --git a/xr/openxr_hand_tracking_demo/objects/table.tscn b/xr/openxr_hand_tracking_demo/objects/table.tscn new file mode 100644 index 00000000..346a3e05 --- /dev/null +++ b/xr/openxr_hand_tracking_demo/objects/table.tscn @@ -0,0 +1,65 @@ +[gd_scene load_steps=8 format=3 uid="uid://hanl00aqvu7u"] + +[ext_resource type="Texture2D" uid="uid://b1waowk6l76ap" path="res://assets/images/pattern.png" id="1_h5hgt"] + +[sub_resource type="BoxShape3D" id="BoxShape3D_vtble"] +size = Vector3(1, 0.1, 0.5) + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_yh46n"] +albedo_color = Color(0.704983, 0.442908, 0.226976, 1) +albedo_texture = ExtResource("1_h5hgt") +uv1_scale = Vector3(3, 1, 1) + +[sub_resource type="BoxMesh" id="BoxMesh_ud4mc"] +material = SubResource("StandardMaterial3D_yh46n") +size = Vector3(1, 0.1, 0.5) + +[sub_resource type="BoxShape3D" id="BoxShape3D_dgs2r"] +size = Vector3(0.1, 0.7, 0.1) + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_gf8o1"] +albedo_color = Color(0.754123, 0.902538, 0.996894, 1) +albedo_texture = ExtResource("1_h5hgt") +uv1_scale = Vector3(1, 3, 1) + +[sub_resource type="BoxMesh" id="BoxMesh_l0vgn"] +material = SubResource("StandardMaterial3D_gf8o1") +size = Vector3(0.1, 0.7, 0.1) + +[node name="Table" type="StaticBody3D"] + +[node name="TableSurfaceShape" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.75, 0) +shape = SubResource("BoxShape3D_vtble") + +[node name="TableSurfaceMesh" type="MeshInstance3D" parent="TableSurfaceShape"] +mesh = SubResource("BoxMesh_ud4mc") +skeleton = NodePath("../..") + +[node name="TableLegShape01" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.45, 0.35, -0.2) +shape = SubResource("BoxShape3D_dgs2r") + +[node name="TableLegMesh" type="MeshInstance3D" parent="TableLegShape01"] +mesh = SubResource("BoxMesh_l0vgn") + +[node name="TableLegShape02" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.45, 0.35, -0.2) +shape = SubResource("BoxShape3D_dgs2r") + +[node name="TableLegMesh" type="MeshInstance3D" parent="TableLegShape02"] +mesh = SubResource("BoxMesh_l0vgn") + +[node name="TableLegShape03" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.45, 0.35, 0.2) +shape = SubResource("BoxShape3D_dgs2r") + +[node name="TableLegMesh" type="MeshInstance3D" parent="TableLegShape03"] +mesh = SubResource("BoxMesh_l0vgn") + +[node name="TableLegShape04" type="CollisionShape3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.45, 0.35, 0.2) +shape = SubResource("BoxShape3D_dgs2r") + +[node name="TableLegMesh" type="MeshInstance3D" parent="TableLegShape04"] +mesh = SubResource("BoxMesh_l0vgn") diff --git a/xr/openxr_hand_tracking_demo/openxr_action_map.tres b/xr/openxr_hand_tracking_demo/openxr_action_map.tres new file mode 100644 index 00000000..e3e388f9 --- /dev/null +++ b/xr/openxr_hand_tracking_demo/openxr_action_map.tres @@ -0,0 +1,99 @@ +[gd_resource type="OpenXRActionMap" load_steps=23 format=3 uid="uid://dydgx5ktpcmdl"] + +[sub_resource type="OpenXRAction" id="OpenXRAction_ywi2s"] +resource_name = "pose" +localized_name = "Pose" +action_type = 3 +toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") + +[sub_resource type="OpenXRAction" id="OpenXRAction_vayrd"] +resource_name = "haptic" +localized_name = "Haptic" +action_type = 4 +toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") + +[sub_resource type="OpenXRAction" id="OpenXRAction_h3dsb"] +resource_name = "pickup" +localized_name = "Pickup" +toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right") + +[sub_resource type="OpenXRActionSet" id="OpenXRActionSet_c2hwm"] +resource_name = "godot" +localized_name = "Godot action set" +actions = [SubResource("OpenXRAction_ywi2s"), SubResource("OpenXRAction_vayrd"), SubResource("OpenXRAction_h3dsb")] + +[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_r6sxc"] +action = SubResource("OpenXRAction_ywi2s") +paths = PackedStringArray("/user/hand/left/input/grip/pose", "/user/hand/right/input/grip/pose") + +[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_j0h30"] +action = SubResource("OpenXRAction_vayrd") +paths = PackedStringArray("/user/hand/left/output/haptic", "/user/hand/right/output/haptic") + +[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_govyu"] +action = SubResource("OpenXRAction_h3dsb") +paths = PackedStringArray("/user/hand/left/input/select/click", "/user/hand/right/input/select/click") + +[sub_resource type="OpenXRInteractionProfile" id="OpenXRInteractionProfile_643al"] +interaction_profile_path = "/interaction_profiles/khr/simple_controller" +bindings = [SubResource("OpenXRIPBinding_r6sxc"), SubResource("OpenXRIPBinding_j0h30"), SubResource("OpenXRIPBinding_govyu")] + +[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_fd646"] +action = SubResource("OpenXRAction_h3dsb") +paths = PackedStringArray("/user/hand/left/input/grasp_ext/value", "/user/hand/right/input/grasp_ext/value") + +[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_wkfos"] +action = SubResource("OpenXRAction_ywi2s") +paths = PackedStringArray("/user/hand/left/input/grip/pose", "/user/hand/right/input/grip/pose") + +[sub_resource type="OpenXRInteractionProfile" id="OpenXRInteractionProfile_nhhi0"] +interaction_profile_path = "/interaction_profiles/ext/hand_interaction_ext" +bindings = [SubResource("OpenXRIPBinding_fd646"), SubResource("OpenXRIPBinding_wkfos")] + +[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_gv55f"] +action = SubResource("OpenXRAction_ywi2s") +paths = PackedStringArray("/user/hand/left/input/grip/pose", "/user/hand/right/input/grip/pose") + +[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_mp0xr"] +action = SubResource("OpenXRAction_h3dsb") +paths = PackedStringArray("/user/hand/left/input/squeeze/value", "/user/hand/right/input/squeeze/value") + +[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_86te5"] +action = SubResource("OpenXRAction_vayrd") +paths = PackedStringArray("/user/hand/left/output/haptic", "/user/hand/right/output/haptic") + +[sub_resource type="OpenXRInteractionProfile" id="OpenXRInteractionProfile_wrsh4"] +interaction_profile_path = "/interaction_profiles/oculus/touch_controller" +bindings = [SubResource("OpenXRIPBinding_gv55f"), SubResource("OpenXRIPBinding_mp0xr"), SubResource("OpenXRIPBinding_86te5")] + +[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_n476d"] +action = SubResource("OpenXRAction_ywi2s") +paths = PackedStringArray("/user/hand/left/input/grip/pose", "/user/hand/right/input/grip/pose") + +[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_dh54r"] +action = SubResource("OpenXRAction_h3dsb") +paths = PackedStringArray("/user/hand/left/input/squeeze/value", "/user/hand/right/input/squeeze/value") + +[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_tc2nk"] +action = SubResource("OpenXRAction_vayrd") +paths = PackedStringArray("/user/hand/left/output/haptic", "/user/hand/right/output/haptic") + +[sub_resource type="OpenXRInteractionProfile" id="OpenXRInteractionProfile_lah6t"] +interaction_profile_path = "/interaction_profiles/valve/index_controller" +bindings = [SubResource("OpenXRIPBinding_n476d"), SubResource("OpenXRIPBinding_dh54r"), SubResource("OpenXRIPBinding_tc2nk")] + +[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_vhdol"] +action = SubResource("OpenXRAction_ywi2s") +paths = PackedStringArray("/user/hand/left/input/grip/pose", "/user/hand/right/input/grip/pose") + +[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_pwv0l"] +action = SubResource("OpenXRAction_h3dsb") +paths = PackedStringArray("/user/hand/left/input/squeeze/value", "/user/hand/right/input/squeeze/value") + +[sub_resource type="OpenXRInteractionProfile" id="OpenXRInteractionProfile_51rtw"] +interaction_profile_path = "/interaction_profiles/microsoft/hand_interaction" +bindings = [SubResource("OpenXRIPBinding_vhdol"), SubResource("OpenXRIPBinding_pwv0l")] + +[resource] +action_sets = [SubResource("OpenXRActionSet_c2hwm")] +interaction_profiles = [SubResource("OpenXRInteractionProfile_643al"), SubResource("OpenXRInteractionProfile_nhhi0"), SubResource("OpenXRInteractionProfile_wrsh4"), SubResource("OpenXRInteractionProfile_lah6t"), SubResource("OpenXRInteractionProfile_51rtw")] diff --git a/xr/openxr_hand_tracking_demo/pickup/pickup_able_body.gd b/xr/openxr_hand_tracking_demo/pickup/pickup_able_body.gd new file mode 100644 index 00000000..d302aadd --- /dev/null +++ b/xr/openxr_hand_tracking_demo/pickup/pickup_able_body.gd @@ -0,0 +1,111 @@ +extends RigidBody3D +class_name PickupAbleBody3D + + +var highlight_material : Material = preload("res://shaders/highlight_material.tres") +var picked_up_by : Area3D +var closest_areas : Array + +var original_parent : Node3D +var tween : Tween + +# Called when this object becomes the closest body in an area +func add_is_closest(area : Area3D) -> void: + if not closest_areas.has(area): + closest_areas.push_back(area) + + _update_highlight() + + +# Called when this object becomes the closest body in an area +func remove_is_closest(area : Area3D) -> void: + if closest_areas.has(area): + closest_areas.erase(area) + + _update_highlight() + + +# Returns whether we have been picked up. +func is_picked_up() -> bool: + # If we have a valid picked up by object, + # we've been picked up + if picked_up_by: + return true + + return false + + +# Pick this object up. +func pick_up(pick_up_by) -> void: + # Already picked up? Can't pick up twice. + if picked_up_by: + if picked_up_by == pick_up_by: + return + + let_go() + + # Remember some state we want to reapply on release. + original_parent = get_parent() + var current_transform = global_transform + + # Remove us from our old parent. + original_parent.remove_child(self) + + # Process our pickup. + picked_up_by = pick_up_by + picked_up_by.add_child(self) + global_transform = current_transform + freeze = true + + # Kill any existing tween and create a new one. + if tween: + tween.kill() + tween = create_tween() + + # Snap the object to this transform. + var snap_to : Transform3D + + # Add code here to determine snap position and orientation. + + # Now tween + tween.tween_property(self, "transform", snap_to, 0.1) + + +# Let this object go. +func let_go() -> void: + # Ignore if we haven't been picked up. + if not picked_up_by: + return + + # Cancel any ongoing tween + if tween: + tween.kill() + tween = null + + # Remember our current transform. + var current_transform = global_transform + + # Remove us from what picked us up. + picked_up_by.remove_child(self) + picked_up_by = null + + # Reset some state. + original_parent.add_child(self) + global_transform = current_transform + freeze = false + + +# Update our highlight to show that we can be picked up +func _update_highlight() -> void: + if not picked_up_by and not closest_areas.is_empty(): + # add highlight + for child in get_children(): + if child is MeshInstance3D: + var mesh_instance : MeshInstance3D = child + mesh_instance.material_overlay = highlight_material + else: + # remove highlight + for child in get_children(): + if child is MeshInstance3D: + var mesh_instance : MeshInstance3D = child + mesh_instance.material_overlay = null diff --git a/xr/openxr_hand_tracking_demo/pickup/pickup_handler.gd b/xr/openxr_hand_tracking_demo/pickup/pickup_handler.gd new file mode 100644 index 00000000..7017bdfe --- /dev/null +++ b/xr/openxr_hand_tracking_demo/pickup/pickup_handler.gd @@ -0,0 +1,118 @@ +@tool +extends Area3D +class_name PickupHandler3D + +# This area3D class detects all physics bodys based on +# PickupAbleBody3D within range and handles the logic +# for selecting the closest one and allowing pickup +# of that object. + +# Detect range specifies within what radius we detect +# objects we can pick up. +@export var detect_range : float = 0.3: + set(value): + detect_range = value + if is_inside_tree(): + _update_detect_range() + _update_closest_body() + +# Pickup Action specifies the action in the OpenXR +# action map that triggers our pickup function. +@export var pickup_action : String = "pickup" + +var closest_body : PickupAbleBody3D +var picked_up_body: PickupAbleBody3D +var was_pickup_pressed : bool = false + +# Update our detection range. +func _update_detect_range() -> void: + var shape : SphereShape3D = $CollisionShape3D.shape + if shape: + shape.radius = detect_range + + +# Update our closest body. +func _update_closest_body() -> void: + # Do not do this when we're in the editor. + if Engine.is_editor_hint(): + return + + # Do not check this if we've picked something up. + if picked_up_body: + if closest_body: + closest_body.remove_is_closest(self) + closest_body = null + + return + + # Find the body that is currently the closest. + var new_closest_body : PickupAbleBody3D + var closest_distance : float = 1000000.0 + + for body in get_overlapping_bodies(): + if body is PickupAbleBody3D and not body.is_picked_up(): + var distance_squared = (body.global_position - global_position).length_squared() + if distance_squared < closest_distance: + new_closest_body = body + closest_distance = distance_squared + + # Unchanged? Just exit + if closest_body == new_closest_body: + return + + # We had a closest body + if closest_body: + closest_body.remove_is_closest(self) + + closest_body = new_closest_body + if closest_body: + closest_body.add_is_closest(self) + + +# Get our controller that we are a child of +func _get_parent_controller() -> XRController3D: + var parent : Node = get_parent() + while parent: + if parent is XRController3D: + return parent + + parent = parent.get_parent() + + return null + + +# Called when the node enters the scene tree for the first time. +func _ready() -> void: + _update_detect_range() + _update_closest_body() + + +# Called every physics frame +func _physics_process(delta) -> void: + # As we move our hands we need to check if the closest body + # has changed. + _update_closest_body() + + # Check if our pickup action is true + var pickup_pressed = false + var controller : XRController3D = _get_parent_controller() + if controller: + # While OpenXR can return this as a boolean, there is a lot of + # difference in handling thresholds between platforms. + # So we implement our own logic here. + var pickup_value : float = controller.get_float(pickup_action) + var threshold : float = 0.4 if was_pickup_pressed else 0.6 + pickup_pressed = pickup_value > threshold + + # Do we need to let go? + if picked_up_body and not pickup_pressed: + picked_up_body.let_go() + picked_up_body = null + + # Do we need to pick something up + if not picked_up_body and not was_pickup_pressed and pickup_pressed and closest_body: + picked_up_body = closest_body + picked_up_body.pick_up(self) + + # Remember our state for the next frame + was_pickup_pressed = pickup_pressed diff --git a/xr/openxr_hand_tracking_demo/pickup/pickup_handler.tscn b/xr/openxr_hand_tracking_demo/pickup/pickup_handler.tscn new file mode 100644 index 00000000..cdf31041 --- /dev/null +++ b/xr/openxr_hand_tracking_demo/pickup/pickup_handler.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=3 format=3 uid="uid://byif52d1xkl3u"] + +[ext_resource type="Script" path="res://pickup/pickup_handler.gd" id="1_5qec3"] + +[sub_resource type="SphereShape3D" id="SphereShape3D_i5on0"] +resource_local_to_scene = true +margin = 0.001 +radius = 0.3 + +[node name="PickupHandler" type="Area3D"] +script = ExtResource("1_5qec3") +pickup_action = null + +[node name="CollisionShape3D" type="CollisionShape3D" parent="."] +shape = SubResource("SphereShape3D_i5on0") + +[connection signal="body_entered" from="." to="." method="_on_body_entered"] +[connection signal="body_exited" from="." to="." method="_on_body_exited"] diff --git a/xr/openxr_hand_tracking_demo/project.godot b/xr/openxr_hand_tracking_demo/project.godot new file mode 100644 index 00000000..1a18af26 --- /dev/null +++ b/xr/openxr_hand_tracking_demo/project.godot @@ -0,0 +1,39 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="openxr_hand_tracking_demo" +run/main_scene="res://main.tscn" +config/features=PackedStringArray("4.3", "GL Compatibility") +config/icon="res://icon.svg" + +[debug] + +settings/stdout/verbose_stdout=true + +[editor_plugins] + +enabled=PackedStringArray() + +[rendering] + +renderer/rendering_method="gl_compatibility" +renderer/rendering_method.mobile="gl_compatibility" +textures/vram_compression/import_etc2_astc=true + +[xr] + +openxr/enabled=true +openxr/reference_space=2 +openxr/foveation_level=2 +openxr/foveation_dynamic=true +openxr/extensions/hand_interaction_profile=true +shaders/enabled=true diff --git a/xr/openxr_hand_tracking_demo/screenshots/hand_tracking_demo.png b/xr/openxr_hand_tracking_demo/screenshots/hand_tracking_demo.png new file mode 100644 index 00000000..58aa3595 Binary files /dev/null and b/xr/openxr_hand_tracking_demo/screenshots/hand_tracking_demo.png differ diff --git a/xr/openxr_hand_tracking_demo/screenshots/hand_tracking_demo.png.import b/xr/openxr_hand_tracking_demo/screenshots/hand_tracking_demo.png.import new file mode 100644 index 00000000..f25b50fc --- /dev/null +++ b/xr/openxr_hand_tracking_demo/screenshots/hand_tracking_demo.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dvj2ofetki3oi" +path="res://.godot/imported/hand_tracking_demo.png-7ba3fe63e5b4cae9d220943b88a7ab14.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://screenshots/hand_tracking_demo.png" +dest_files=["res://.godot/imported/hand_tracking_demo.png-7ba3fe63e5b4cae9d220943b88a7ab14.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/xr/openxr_hand_tracking_demo/shaders/highlight_material.tres b/xr/openxr_hand_tracking_demo/shaders/highlight_material.tres new file mode 100644 index 00000000..636eb529 --- /dev/null +++ b/xr/openxr_hand_tracking_demo/shaders/highlight_material.tres @@ -0,0 +1,12 @@ +[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://bys74qhnreu01"] + +[ext_resource type="Shader" uid="uid://cs1jlvgrhd4ac" path="res://shaders/highlight_shader.tres" id="1_gvs70"] + +[resource] +render_priority = 0 +shader = ExtResource("1_gvs70") +shader_parameter/albedo = Color(0.727706, 0.726254, 0, 1) +shader_parameter/grow = 0.002 +shader_parameter/specular = 0.5 +shader_parameter/metallic = 0.0 +shader_parameter/roughness = 1.0 diff --git a/xr/openxr_hand_tracking_demo/shaders/highlight_shader.tres b/xr/openxr_hand_tracking_demo/shaders/highlight_shader.tres new file mode 100644 index 00000000..2bb4808c --- /dev/null +++ b/xr/openxr_hand_tracking_demo/shaders/highlight_shader.tres @@ -0,0 +1,28 @@ +[gd_resource type="Shader" format=3 uid="uid://cs1jlvgrhd4ac"] + +[resource] +code = "// NOTE: Shader automatically converted from Godot Engine 4.3.beta1's StandardMaterial3D. + +shader_type spatial; +render_mode blend_mix, depth_draw_opaque, cull_front, diffuse_burley, specular_schlick_ggx, unshaded; + +uniform vec4 albedo : source_color; +uniform float grow : hint_range(-16.0, 16.0, 0.001); + +uniform float specular : hint_range(0.0, 1.0, 0.01); +uniform float metallic : hint_range(0.0, 1.0, 0.01); +uniform float roughness : hint_range(0.0, 1.0); + +void vertex() { + // Standard grow along the normal will create seams + VERTEX += normalize(VERTEX) * grow; +} + +void fragment() { + ALBEDO = albedo.rgb; + + METALLIC = metallic; + SPECULAR = specular; + ROUGHNESS = roughness; +} +" diff --git a/xr/openxr_hand_tracking_demo/start_vr.gd b/xr/openxr_hand_tracking_demo/start_vr.gd new file mode 100644 index 00000000..c610b4c2 --- /dev/null +++ b/xr/openxr_hand_tracking_demo/start_vr.gd @@ -0,0 +1,112 @@ +extends Node3D + +signal focus_lost +signal focus_gained +signal pose_recentered + +@export var maximum_refresh_rate : int = 90 + +var xr_interface : OpenXRInterface +var xr_is_focused := false + + +func _ready() -> void: + xr_interface = XRServer.find_interface("OpenXR") + if xr_interface and xr_interface.is_initialized(): + print("OpenXR instantiated successfully.") + var vp : Viewport = get_viewport() + + # Enable XR on our viewport. + vp.use_xr = true + + # Make sure V-Sync is off, as V-Sync is handled by OpenXR. + DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED) + + # Enable variable rate shading. + if RenderingServer.get_rendering_device(): + vp.vrs_mode = Viewport.VRS_XR + elif int(ProjectSettings.get_setting("xr/openxr/foveation_level")) == 0: + push_warning("OpenXR: Recommend setting Foveation level to High in Project Settings") + + # Connect the OpenXR events. + xr_interface.session_begun.connect(_on_openxr_session_begun) + xr_interface.session_visible.connect(_on_openxr_visible_state) + xr_interface.session_focussed.connect(_on_openxr_focused_state) + xr_interface.session_stopping.connect(_on_openxr_stopping) + xr_interface.pose_recentered.connect(_on_openxr_pose_recentered) + else: + # We couldn't start OpenXR. + print("OpenXR not instantiated!") + get_tree().quit() + + +# Handle OpenXR session ready. +func _on_openxr_session_begun() -> void: + # Get the reported refresh rate. + var current_refresh_rate := xr_interface.get_display_refresh_rate() + if current_refresh_rate > 0: + print("OpenXR: Refresh rate reported as ", str(current_refresh_rate)) + else: + print("OpenXR: No refresh rate given by XR runtime") + + # See if we have a better refresh rate available. + var new_rate := current_refresh_rate + var available_rates: Array = xr_interface.get_available_display_refresh_rates() + if available_rates.is_empty(): + print("OpenXR: Target does not support refresh rate extension") + elif available_rates.size() == 1: + # Only one available, so use it. + new_rate = available_rates[0] + else: + for rate in available_rates: + if rate > new_rate and rate <= maximum_refresh_rate: + new_rate = rate + + # Did we find a better rate? + if current_refresh_rate != new_rate: + print("OpenXR: Setting refresh rate to ", str(new_rate)) + xr_interface.set_display_refresh_rate(new_rate) + current_refresh_rate = new_rate + + # Now match our physics rate. This is currently needed to avoid jittering, + # due to physics interpolation not being used. + Engine.physics_ticks_per_second = roundi(current_refresh_rate) + + +# Handle OpenXR visible state. +func _on_openxr_visible_state() -> void: + # We always pass this state at startup, + # but the second time we get this, it means our player took off their headset. + if xr_is_focused: + print("OpenXR lost focus") + + xr_is_focused = false + + # Pause our game. + process_mode = Node.PROCESS_MODE_DISABLED + + focus_lost.emit() + + +# Handle OpenXR focused state +func _on_openxr_focused_state() -> void: + print("OpenXR gained focus") + xr_is_focused = true + + # Unpause our game. + process_mode = Node.PROCESS_MODE_INHERIT + + focus_gained.emit() + + +# Handle OpenXR stopping state. +func _on_openxr_stopping() -> void: + # Our session is being stopped. + print("OpenXR is stopping") + + +# Handle OpenXR pose recentered signal. +func _on_openxr_pose_recentered() -> void: + # User recentered view, we have to react to this by recentering the view. + # This is game implementation dependent. + pose_recentered.emit()