Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added initial WebXR entry UI and export target. #312

Merged
merged 1 commit into from
Jan 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
14 changes: 14 additions & 0 deletions addons/godot-xr-tools/staging/staging.gd
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ signal scene_loaded(scene)
## New scene is now visible
signal scene_visible(scene)

## XR interaction started
signal xr_started

## XR interaction ended
signal xr_ended


## Main scene file
export (String, FILE, '*.tscn') var main_scene : String
Expand Down Expand Up @@ -259,3 +265,11 @@ func _on_exit_to_main_menu():

func _on_load_scene(p_scene_path : String):
load_scene(p_scene_path)


func _on_StartXR_xr_started():
emit_signal("xr_started")


func _on_StartXR_xr_ended():
emit_signal("xr_ended")
24 changes: 22 additions & 2 deletions addons/godot-xr-tools/staging/staging.tscn
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
[gd_scene load_steps=8 format=2]
[gd_scene load_steps=10 format=2]

[ext_resource path="res://addons/godot-xr-tools/misc/vr_common_shader_cache.tscn" type="PackedScene" id=1]
[ext_resource path="res://addons/godot-xr-tools/staging/staging.gd" type="Script" id=2]
[ext_resource path="res://addons/godot-xr-tools/staging/loading_screen.tscn" type="PackedScene" id=3]
[ext_resource path="res://addons/godot-xr-tools/staging/fade.gdshader" type="Shader" id=4]
[ext_resource path="res://addons/godot-xr-tools/xr/start_xr.tscn" type="PackedScene" id=5]

[sub_resource type="QuadMesh" id=4]
custom_aabb = AABB( -5000, -5000, -5000, 10000, 10000, 10000 )
Expand All @@ -29,9 +31,27 @@ material/0 = SubResource( 3 )
environment = SubResource( 2 )

[node name="LoadingScreen" parent="." instance=ExtResource( 3 )]
splash_screen = null
progress = 0.0

[node name="Scene" type="Spatial" parent="."]

[node name="Tween" type="Tween" parent="."]

[node name="ARVROrigin" type="ARVROrigin" parent="."]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should rename the nodes to XROrigin and XRCamera, might make things more recognizable between 3 and 4? Or am I over thinking it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It has benefits for compatibility, but it may also be a source of confusion as the developers will still have to use the ARVRxxxx names in code. If we choose to do this then I would probably make it a separate PR as we'd want to sweep through other files and comments.


[node name="ARVRCamera" type="ARVRCamera" parent="ARVROrigin"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.8, 0 )

[node name="VRCommonShaderCache" parent="ARVROrigin/ARVRCamera" instance=ExtResource( 1 )]

[node name="LeftHand" type="ARVRController" parent="ARVROrigin"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 1, -0.5 )

[node name="RightHand" type="ARVRController" parent="ARVROrigin"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 1, -0.5 )
controller_id = 2

[node name="StartXR" parent="." instance=ExtResource( 5 )]

[connection signal="xr_ended" from="StartXR" to="." method="_on_StartXR_xr_ended"]
[connection signal="xr_started" from="StartXR" to="." method="_on_StartXR_xr_started"]
279 changes: 279 additions & 0 deletions addons/godot-xr-tools/xr/start_xr.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
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 which occurs when the application
## starts receiving XR input, 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_visible_state' state which occurs when the application has lost
## XR input focus, 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 : int = 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:
return _setup_for_openxr()

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

# 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:
return "Physics rate multiplier should be at least 1x the HMD rate"

return ""


# Perform OpenXR setup
func _setup_for_openxr() -> bool:
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 false

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

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

# Connect the OpenXR events
ARVRServer.connect("openxr_session_begun", self, "_on_openxr_session_begun")
ARVRServer.connect("openxr_visible_state", self, "_on_openxr_visible_state")
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

# Report success
return 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(round(refresh_rate * physics_rate_multiplier))
print("Setting physics rate to ", physics_rate)
Engine.iterations_per_second = physics_rate


# Handle OpenXR visible state
func _on_openxr_visible_state() -> void:
# Report the XR ending
if xr_active:
print("OpenXR: XR ended (visible_state)")
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() -> bool:
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")

# Report success
return true


# 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 canvas and switch the viewport to non-XR
$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")
Loading