Add OpenXR hand tracking demo

This commit is contained in:
Bastiaan Olij
2023-10-03 22:32:49 +11:00
parent 9a0c857131
commit 677dc46eeb
29 changed files with 2694 additions and 0 deletions

View File

@@ -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/

View File

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

View File

@@ -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"
}
]
}

View File

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

View File

@@ -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"
}
]
}

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="124" height="124" rx="14" fill="#363d52" stroke="#212532" stroke-width="4"/><g transform="scale(.101) translate(122 122)"><g fill="#fff"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 813 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H447l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c3 34 55 34 58 0v-86c-3-34-55-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></g></svg>

After

Width:  |  Height:  |  Size: 950 B

View File

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

View File

@@ -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"]

View File

@@ -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")

View File

@@ -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")

View File

@@ -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")]

View File

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

View File

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

View File

@@ -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"]

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 KiB

View File

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

View File

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

View File

@@ -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;
}
"

View File

@@ -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()