Skip to content

Commit d256e73

Browse files
committed
Add OpenXR demo project showing off local reference space
1 parent b74261c commit d256e73

File tree

14 files changed

+580
-0
lines changed

14 files changed

+580
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Normalize EOL for all files that Git considers text files.
2+
* text=auto eol=lf
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Godot 4+ specific ignores
2+
.godot/
1.77 KB
Loading
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
[remap]
2+
3+
importer="texture"
4+
type="CompressedTexture2D"
5+
uid="uid://rek0t7kubpx4"
6+
path.s3tc="res://.godot/imported/pattern.png-cf6f03dfd1cdd4bc35da3414e912103d.s3tc.ctex"
7+
metadata={
8+
"imported_formats": ["s3tc_bptc"],
9+
"vram_texture": true
10+
}
11+
12+
[deps]
13+
14+
source_file="res://assets/pattern.png"
15+
dest_files=["res://.godot/imported/pattern.png-cf6f03dfd1cdd4bc35da3414e912103d.s3tc.ctex"]
16+
17+
[params]
18+
19+
compress/mode=2
20+
compress/high_quality=false
21+
compress/lossy_quality=0.7
22+
compress/hdr_compression=1
23+
compress/normal_map=0
24+
compress/channel_pack=0
25+
mipmaps/generate=true
26+
mipmaps/limit=-1
27+
roughness/mode=0
28+
roughness/src_normal=""
29+
process/fix_alpha_border=true
30+
process/premult_alpha=false
31+
process/normal_map_invert_y=false
32+
process/hdr_as_srgb=false
33+
process/hdr_clamp_exposure=false
34+
process/size_limit=0
35+
detect_3d/compress_to=0
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[remap]
2+
3+
importer="texture"
4+
type="CompressedTexture2D"
5+
uid="uid://d1s6lsinmhdj5"
6+
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
7+
metadata={
8+
"vram_texture": false
9+
}
10+
11+
[deps]
12+
13+
source_file="res://icon.svg"
14+
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
15+
16+
[params]
17+
18+
compress/mode=0
19+
compress/high_quality=false
20+
compress/lossy_quality=0.7
21+
compress/hdr_compression=1
22+
compress/normal_map=0
23+
compress/channel_pack=0
24+
mipmaps/generate=false
25+
mipmaps/limit=-1
26+
roughness/mode=0
27+
roughness/src_normal=""
28+
process/fix_alpha_border=true
29+
process/premult_alpha=false
30+
process/normal_map_invert_y=false
31+
process/hdr_as_srgb=false
32+
process/hdr_clamp_exposure=false
33+
process/size_limit=0
34+
detect_3d/compress_to=1
35+
svg/scale=1.0
36+
editor/scale_with_editor_scale=false
37+
editor/convert_colors_with_editor_theme=false
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
[gd_scene load_steps=11 format=3 uid="uid://db1q04xf4urua"]
2+
3+
[ext_resource type="Texture2D" uid="uid://rek0t7kubpx4" path="res://assets/pattern.png" id="1_sbpla"]
4+
[ext_resource type="PackedScene" uid="uid://dkdk37kpjui3q" path="res://vehicle.tscn" id="2_x0meh"]
5+
[ext_resource type="Script" path="res://start_vr.gd" id="3_d042y"]
6+
[ext_resource type="PackedScene" uid="uid://du5twm6cwhq6j" path="res://track.tscn" id="4_ddbiu"]
7+
8+
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_t0uks"]
9+
sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
10+
ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
11+
12+
[sub_resource type="Sky" id="Sky_f7epq"]
13+
sky_material = SubResource("ProceduralSkyMaterial_t0uks")
14+
15+
[sub_resource type="Environment" id="Environment_ctbfb"]
16+
background_mode = 2
17+
sky = SubResource("Sky_f7epq")
18+
tonemap_mode = 2
19+
20+
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_jptbt"]
21+
albedo_color = Color(0.211765, 0.717647, 0.160784, 1)
22+
albedo_texture = ExtResource("1_sbpla")
23+
uv1_scale = Vector3(100, 100, 100)
24+
25+
[sub_resource type="PlaneMesh" id="PlaneMesh_judwf"]
26+
size = Vector2(1000, 1000)
27+
subdivide_width = 10
28+
subdivide_depth = 10
29+
30+
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_k6vqu"]
31+
32+
[node name="Main" type="Node3D"]
33+
34+
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
35+
environment = SubResource("Environment_ctbfb")
36+
37+
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
38+
transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -0.500003, 0.749999, -0.43301, 0, 0, 0)
39+
40+
[node name="Floor" type="StaticBody3D" parent="."]
41+
42+
[node name="MeshInstance3D" type="MeshInstance3D" parent="Floor"]
43+
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.01, 0)
44+
material_override = SubResource("StandardMaterial3D_jptbt")
45+
mesh = SubResource("PlaneMesh_judwf")
46+
47+
[node name="CollisionShape3D" type="CollisionShape3D" parent="Floor"]
48+
shape = SubResource("WorldBoundaryShape3D_k6vqu")
49+
50+
[node name="Vehicle" parent="." instance=ExtResource("2_x0meh")]
51+
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -4.54837, 0.2, 0)
52+
53+
[node name="StartVR" type="Node3D" parent="."]
54+
script = ExtResource("3_d042y")
55+
maximum_refresh_rate = 144
56+
57+
[node name="Track" parent="." instance=ExtResource("4_ddbiu")]
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[gd_resource type="OpenXRActionMap" load_steps=7 format=3 uid="uid://b6o1742qdpkht"]
2+
3+
[sub_resource type="OpenXRAction" id="OpenXRAction_bxnsb"]
4+
resource_name = "default_pose"
5+
localized_name = "Default pose"
6+
action_type = 3
7+
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right")
8+
9+
[sub_resource type="OpenXRAction" id="OpenXRAction_e4g8i"]
10+
resource_name = "haptic"
11+
localized_name = "Haptic"
12+
action_type = 4
13+
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right")
14+
15+
[sub_resource type="OpenXRActionSet" id="OpenXRActionSet_ucgoy"]
16+
resource_name = "godot"
17+
localized_name = "Godot action set"
18+
actions = [SubResource("OpenXRAction_bxnsb"), SubResource("OpenXRAction_e4g8i")]
19+
20+
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_xbpfq"]
21+
action = SubResource("OpenXRAction_bxnsb")
22+
paths = PackedStringArray("/user/hand/left/input/aim/pose", "/user/hand/right/input/aim/pose")
23+
24+
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_7tlrk"]
25+
action = SubResource("OpenXRAction_e4g8i")
26+
paths = PackedStringArray("/user/hand/left/output/haptic", "/user/hand/right/output/haptic")
27+
28+
[sub_resource type="OpenXRInteractionProfile" id="OpenXRInteractionProfile_1xkvk"]
29+
interaction_profile_path = "/interaction_profiles/khr/simple_controller"
30+
bindings = [SubResource("OpenXRIPBinding_xbpfq"), SubResource("OpenXRIPBinding_7tlrk")]
31+
32+
[resource]
33+
action_sets = [SubResource("OpenXRActionSet_ucgoy")]
34+
interaction_profiles = [SubResource("OpenXRInteractionProfile_1xkvk")]
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
; Engine configuration file.
2+
; It's best edited using the editor UI and not directly,
3+
; since the parameters that go here are not all obvious.
4+
;
5+
; Format:
6+
; [section] ; section goes between []
7+
; param=value ; assign values to parameters
8+
9+
config_version=5
10+
11+
[application]
12+
13+
config/name="OpenXR Vehicle Movement"
14+
run/main_scene="res://main.tscn"
15+
config/features=PackedStringArray("4.1", "GL Compatibility")
16+
config/icon="res://icon.svg"
17+
18+
[input]
19+
20+
turn_left={
21+
"deadzone": 0.5,
22+
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"echo":false,"script":null)
23+
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":-1.0,"script":null)
24+
]
25+
}
26+
turn_right={
27+
"deadzone": 0.5,
28+
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"echo":false,"script":null)
29+
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":2,"axis_value":1.0,"script":null)
30+
]
31+
}
32+
accelerate={
33+
"deadzone": 0.5,
34+
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"echo":false,"script":null)
35+
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":5,"axis_value":1.0,"script":null)
36+
]
37+
}
38+
brake={
39+
"deadzone": 0.5,
40+
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null)
41+
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":4,"axis_value":1.0,"script":null)
42+
]
43+
}
44+
45+
[rendering]
46+
47+
renderer/rendering_method="gl_compatibility"
48+
renderer/rendering_method.mobile="gl_compatibility"
49+
50+
[xr]
51+
52+
openxr/enabled=true
53+
openxr/reference_space=0
54+
shaders/enabled=true
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
extends Node3D
2+
3+
signal focus_lost
4+
signal focus_gained
5+
signal pose_recentered
6+
7+
@export var maximum_refresh_rate : int = 90
8+
9+
var xr_interface : OpenXRInterface
10+
var xr_is_focussed = false
11+
12+
13+
# Called when the node enters the scene tree for the first time.
14+
func _ready():
15+
xr_interface = XRServer.find_interface("OpenXR")
16+
if xr_interface and xr_interface.is_initialized():
17+
print("OpenXR instantiated successfully.")
18+
var vp : Viewport = get_viewport()
19+
20+
# Enable XR on our viewport
21+
vp.use_xr = true
22+
23+
# Make sure v-sync is off, v-sync is handled by OpenXR
24+
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
25+
26+
# Connect the OpenXR events
27+
xr_interface.connect("session_begun", _on_openxr_session_begun)
28+
xr_interface.connect("session_visible", _on_openxr_visible_state)
29+
xr_interface.connect("session_focussed", _on_openxr_focused_state)
30+
xr_interface.connect("session_stopping", _on_openxr_stopping)
31+
xr_interface.connect("pose_recentered", _on_openxr_pose_recentered)
32+
else:
33+
# We couldn't start OpenXR.
34+
print("OpenXR not instantiated!")
35+
get_tree().quit()
36+
37+
38+
# Handle OpenXR session ready
39+
func _on_openxr_session_begun() -> void:
40+
# Get the reported refresh rate
41+
var current_refresh_rate = xr_interface.get_display_refresh_rate()
42+
if current_refresh_rate > 0:
43+
print("OpenXR: Refresh rate reported as ", str(current_refresh_rate))
44+
else:
45+
print("OpenXR: No refresh rate given by XR runtime")
46+
47+
# See if we have a better refresh rate available
48+
var new_rate = current_refresh_rate
49+
var available_rates : Array = xr_interface.get_available_display_refresh_rates()
50+
if available_rates.size() == 0:
51+
print("OpenXR: Target does not support refresh rate extension")
52+
elif available_rates.size() == 1:
53+
# Only one available, so use it
54+
new_rate = available_rates[0]
55+
else:
56+
for rate in available_rates:
57+
if rate > new_rate and rate <= maximum_refresh_rate:
58+
new_rate = rate
59+
60+
# Did we find a better rate?
61+
if current_refresh_rate != new_rate:
62+
print("OpenXR: Setting refresh rate to ", str(new_rate))
63+
xr_interface.set_display_refresh_rate(new_rate)
64+
current_refresh_rate = new_rate
65+
66+
# Now match our physics rate
67+
Engine.physics_ticks_per_second = current_refresh_rate
68+
69+
70+
# Handle OpenXR visible state
71+
func _on_openxr_visible_state() -> void:
72+
# We always pass this state at startup,
73+
# but the second time we get this it means our player took off their headset
74+
if xr_is_focussed:
75+
print("OpenXR lost focus")
76+
77+
xr_is_focussed = false
78+
79+
# pause our game
80+
process_mode = Node.PROCESS_MODE_DISABLED
81+
82+
emit_signal("focus_lost")
83+
84+
85+
# Handle OpenXR focused state
86+
func _on_openxr_focused_state() -> void:
87+
print("OpenXR gained focus")
88+
xr_is_focussed = true
89+
90+
# unpause our game
91+
process_mode = Node.PROCESS_MODE_INHERIT
92+
93+
emit_signal("focus_gained")
94+
95+
# Handle OpenXR stopping state
96+
func _on_openxr_stopping() -> void:
97+
# Our session is being stopped.
98+
print("OpenXR is stopping")
99+
100+
# Handle OpenXR pose recentered signal
101+
func _on_openxr_pose_recentered() -> void:
102+
# User recentered view, we have to react to this by recentering the view.
103+
# This is game implementation dependent.
104+
emit_signal("pose_recentered")

0 commit comments

Comments
 (0)