Skip to content

Commit

Permalink
Added initial WebXR entry UI and export target.
Browse files Browse the repository at this point in the history
Added thread support to handle resource queue loading.

Added new StartXR scene to manage starting the OpenXR or WebXR instance. Fixed loading_screen so it doesn't produce NANs when the headset is off for a while.
  • Loading branch information
Malcolmnixon committed Jan 5, 2023
1 parent b2d0d30 commit ce0eb84
Show file tree
Hide file tree
Showing 7 changed files with 366 additions and 27 deletions.
21 changes: 9 additions & 12 deletions addons/godot-xr-tools/staging/loading_screen.gd
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,23 @@ func _process(delta):
if !_camera:
return

var camera_dir = _camera.global_transform.basis.z
# Get the camera direction (horizontal only)
var camera_dir := _camera.global_transform.basis.z
camera_dir.y = 0.0
camera_dir = camera_dir.normalized()

var loading_screen_dir = global_transform.basis.z
# Get the loading screen direction
var loading_screen_dir := global_transform.basis.z

# Calculate the rotation-axis to rotate the screen in front of the camera
var cross = loading_screen_dir.cross(camera_dir)
if cross.is_equal_approx(Vector3.ZERO):
# Get the angle
var angle := loading_screen_dir.signed_angle_to(camera_dir, Vector3.UP)
if angle == 0:
return

# Calculate the angle to rotate the screen in front of the camera
cross = cross.normalized()
var dot = loading_screen_dir.dot(camera_dir)
var angle = acos(dot)

# Do rotation based on the curve
global_transform.basis = global_transform.basis.rotated(
cross,
follow_speed.interpolate_baked(angle / PI) * delta
Vector3.UP * sign(angle),
follow_speed.interpolate_baked(abs(angle) / PI) * delta
).orthonormalized()


Expand Down
271 changes: 271 additions & 0 deletions addons/godot-xr-tools/xr/start_xr.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
tool
class_name XRToolsStartXR
extends Node


## XRTools Start XR Class
##
## This class supports both the OpenXR and WebXR interfaces, and handles
## the initialization of the interface as well as reporting when the user
## starts and ends the VR session.
##
## For OpenXR this class also supports passthrough on compatible devices such
## as the Meta Quest 1 and 2.


## This signal is emitted when XR becomes active. For OpenXR this corresponds
## with the 'openxr_focused_state' signal, and for WebXR this corresponds with
## the 'session_started' signal.
signal xr_started

## This signal is emitted when XR ends. For OpenXR this corresponds with the
## 'openxr_session_synchronized' state, and for WebXR this corresponds with the
## 'session_ended' signal.
signal xr_ended


## If true, the XR interface is automatically initialized
export var auto_initialize : bool = true

## If true, the XR passthrough is enabled (OpenXR only)
export var enable_passthrough : bool = false setget _set_enable_passthrough

## Physics rate multiplier compared to HMD frame rate
export var physics_rate_multiplier : float = 1


## Current XR interface
var xr_interface : ARVRInterface

## XR active flag
var xr_active : bool = false

# OpenXR configuration (of type OpenXRConfig.gdns)
var _openxr_configuration

# OpenXR enabled extensions
var _openxr_enabled_extensions : Array


# Handle auto-initialization when ready
func _ready() -> void:
if !Engine.editor_hint and auto_initialize:
initialize()


## Initialize the XR interface
func initialize() -> bool:
# Check for OpenXR interface
xr_interface = ARVRServer.find_interface('OpenXR')
if xr_interface:
_setup_for_openxr()
return true

# Check for WebXR interface
xr_interface = ARVRServer.find_interface('WebXR')
if xr_interface:
_setup_for_webxr()
return true

# No XR interface
xr_interface = null
print("No XR interface detected")
return false


# Check for configuration issues
func _get_configuration_warning():
if physics_rate_multiplier < 1.0:
return "Physics rate multiplier should be at least 1x the HMD rate"

return ""


# Perform OpenXR setup
func _setup_for_openxr() -> void:
print("OpenXR: Configuring interface")

# Load the OpenXR configuration resource
var openxr_config_res := load("res://addons/godot-openxr/config/OpenXRConfig.gdns")
if not openxr_config_res:
push_error("OpenXR: Unable to load OpenXRConfig.gdns")
return

# Create the OpenXR configuration class
_openxr_configuration = openxr_config_res.new()

# Initialize the OpenXR interface
if not xr_interface.initialize():
push_error("OpenXR: Failed to initialize")
return

# Connect the OpenXR events
ARVRServer.connect("openxr_session_begun", self, "_on_openxr_session_begun")
ARVRServer.connect("openxr_session_synchronized", self, "_on_openxr_session_synchronized")
ARVRServer.connect("openxr_focused_state", self, "_on_openxr_focused_state")

# Read the OpenXR enabled extensions
_openxr_enabled_extensions = _openxr_configuration.get_enabled_extensions()

# Check for passthrough
if enable_passthrough and _openxr_is_passthrough_supported():
enable_passthrough = _openxr_start_passthrough()

# Switch the viewport to XR
get_viewport().arvr = true


# Handle OpenXR session ready
func _on_openxr_session_begun() -> void:
print("OpenXR: Session begun")

# Our interface will tell us whether we should keep our render buffer in linear color space
get_viewport().keep_3d_linear = _openxr_configuration.keep_3d_linear()

# increase our physics engine update speed
var refresh_rate : float = _openxr_configuration.get_refresh_rate()
if refresh_rate > 0:
# Report provided frame rare
print("OpenXR: HMD refresh rate is set to ", str(refresh_rate))
else:
# None provided, assume a standard rate
print("OpenXR: No refresh rate given by XR runtime")
refresh_rate = 144

# Pick a physics rate
var physics_rate := int(refresh_rate * physics_rate_multiplier + 0.5)
print("Setting physics rate to ", physics_rate)
Engine.iterations_per_second = physics_rate


# Handle OpenXR session synchronized
func _on_openxr_session_synchronized() -> void:
# Report the XR ending
if xr_active:
print("OpenXR: XR ended (session_synchronized)")
xr_active = false
emit_signal("xr_ended")


# Handle OpenXR focused state
func _on_openxr_focused_state() -> void:
# Report the XR starting
if not xr_active:
print("OpenXR: XR started (focused_state)")
xr_active = true
emit_signal("xr_started")


# Handle changes to the enable_passthrough property
func _set_enable_passthrough(p_new_value : bool) -> void:
# Save the new value
enable_passthrough = p_new_value

# Only actually start our passthrough if our interface has been instanced
# if not this will be delayed until initialise is successfully called.
if xr_interface and _openxr_configuration:
if enable_passthrough:
# unset enable_passthrough if we can't start it.
enable_passthrough = _openxr_start_passthrough()
else:
_openxr_stop_passthrough()


# Test if passthrough is supported
func _openxr_is_passthrough_supported() -> bool:
return _openxr_enabled_extensions.find("XR_FB_passthrough") >= 0


# Start OpenXR passthrough
func _openxr_start_passthrough() -> bool:
# Set viewport transparent background
get_viewport().transparent_bg = true

# Enable passthrough
return _openxr_configuration.start_passthrough()


# Stop OpenXR passthrough
func _openxr_stop_passthrough() -> void:
# Clear viewport transparent background
get_viewport().transparent_bg = false

# Disable passthrough
_openxr_configuration.stop_passthrough()


# Perform WebXR setup
func _setup_for_webxr() -> void:
print("WebXR: Configuring interface")

# Connect the WebXR events
xr_interface.connect("session_supported", self, "_on_webxr_session_supported")
xr_interface.connect("session_started", self, "_on_webxr_session_started")
xr_interface.connect("session_ended", self, "_on_webxr_session_ended")
xr_interface.connect("session_failed", self, "_on_webxr_session_failed")

# WebXR currently has no means of querying the refresh rate, so use
# something sufficiently high
Engine.iterations_per_second = 144

# This returns immediately - our _webxr_session_supported() method
# (which we connected to the "session_supported" signal above) will
# be called sometime later to let us know if it's supported or not.
xr_interface.is_session_supported("immersive-vr")


# Handle WebXR session supported check
func _on_webxr_session_supported(session_mode: String, supported: bool) -> void:
if session_mode == "immersive-vr":
if supported:
# WebXR supported - show canvas on web browser to enter WebVR
$EnterWebXR.visible = true
else:
OS.alert("Your web browser doesn't support VR. Sorry!")


# Called when the WebXR session has started successfully
func _on_webxr_session_started() -> void:
print("WebXR: Session started")

# Hide the canvas and switch the viewport to XR
$EnterWebXR.visible = false
get_viewport().arvr = true

# Report the XR starting
xr_active = true
emit_signal("xr_started")


# Called when the user ends the immersive VR session
func _on_webxr_session_ended() -> void:
print("WebXR: Session ended")

# Show the
$EnterWebXR.visible = true
get_viewport().arvr = false

# Report the XR ending
xr_active = false
emit_signal("xr_ended")


# Called when the immersive VR session fails to start
func _on_webxr_session_failed(message: String) -> void:
OS.alert("Unable to enter VR: " + message)
$EnterWebXR.visible = true


# Handle the Enter VR button on the WebXR browser
func _on_enter_webxr_button_pressed() -> void:
# Configure the WebXR interface
xr_interface.session_mode = 'immersive-vr'
xr_interface.requested_reference_space_types = 'bounded-floor, local-floor, local'
xr_interface.required_features = 'local-floor'
xr_interface.optional_features = 'bounded-floor'
xr_interface.xr_standard_mapping = true

# Initialize the interface. This should trigger either _on_webxr_session_started
# or _on_webxr_session_failed
if not xr_interface.initialize():
OS.alert("Failed to initialize WebXR")
22 changes: 22 additions & 0 deletions addons/godot-xr-tools/xr/start_xr.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[gd_scene load_steps=2 format=2]

[ext_resource path="res://addons/godot-xr-tools/xr/start_xr.gd" type="Script" id=1]

[node name="StartXR" type="Node"]
script = ExtResource( 1 )

[node name="EnterWebXR" type="CanvasLayer" parent="."]
visible = false

[node name="EnterVRButton" type="Button" parent="EnterWebXR"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -132.0
margin_top = -52.5
margin_right = 132.0
margin_bottom = 52.5
text = "Enter VR"

[connection signal="pressed" from="EnterWebXR/EnterVRButton" to="." method="_on_enter_webxr_button_pressed"]
38 changes: 38 additions & 0 deletions export_presets.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,41 @@ permissions/write_sms=false
permissions/write_social_stream=false
permissions/write_sync_settings=false
permissions/write_user_dictionary=false

[preset.3]

name="WebXR"
platform="HTML5"
runnable=true
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter=""
export_path=""
script_export_mode=1
script_encryption_key=""

[preset.3.options]

custom_template/debug=""
custom_template/release=""
variant/export_type=1
vram_texture_compression/for_desktop=true
vram_texture_compression/for_mobile=true
html/export_icon=true
html/custom_html_shell=""
html/head_include="<script src=\"https://cdn.jsdelivr.net/npm/webxr-polyfill@latest/build/webxr-polyfill.min.js\"></script>
<script>
var polyfill = new WebXRPolyfill();
</script>"
html/canvas_resize_policy=2
html/focus_canvas_on_start=true
html/experimental_virtual_keyboard=false
progressive_web_app/enabled=false
progressive_web_app/offline_page=""
progressive_web_app/display=1
progressive_web_app/orientation=0
progressive_web_app/icon_144x144=""
progressive_web_app/icon_180x180=""
progressive_web_app/icon_512x512=""
progressive_web_app/background_color=Color( 0, 0, 0, 1 )
6 changes: 6 additions & 0 deletions project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,11 @@ _global_script_classes=[ {
"language": "GDScript",
"path": "res://addons/godot-xr-tools/staging/staging.gd"
}, {
"base": "Node",
"class": "XRToolsStartXR",
"language": "GDScript",
"path": "res://addons/godot-xr-tools/xr/start_xr.gd"
}, {
"base": "Reference",
"class": "XRToolsVelocityAverager",
"language": "GDScript",
Expand Down Expand Up @@ -347,6 +352,7 @@ _global_script_class_icons={
"XRToolsSceneBase": "",
"XRToolsSnapZone": "",
"XRToolsStaging": "",
"XRToolsStartXR": "",
"XRToolsVelocityAverager": "",
"XRToolsVelocityAveragerLinear": "",
"XRToolsVignette": "",
Expand Down
Loading

0 comments on commit ce0eb84

Please sign in to comment.