init
This commit is contained in:
344
addons/godot-xr-tools/xr/start_xr.gd
Normal file
344
addons/godot-xr-tools/xr/start_xr.gd
Normal file
@@ -0,0 +1,344 @@
|
||||
@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.
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
## This signal is emitted if XR fails to initialize.
|
||||
signal xr_failed_to_initialize
|
||||
|
||||
|
||||
## XR active flag
|
||||
static var _xr_active : bool = false
|
||||
|
||||
|
||||
## Optional viewport to control
|
||||
@export var viewport : Viewport
|
||||
|
||||
## Adjusts the pixel density on the rendering target
|
||||
@export var render_target_size_multiplier : float = 1.0
|
||||
|
||||
## If true, the XR passthrough is enabled (OpenXR only)
|
||||
@export var enable_passthrough : bool = false: set = _set_enable_passthrough
|
||||
|
||||
## Physics rate multiplier compared to HMD frame rate
|
||||
@export var physics_rate_multiplier : int = 1
|
||||
|
||||
## If non-zero, specifies the target refresh rate
|
||||
@export var target_refresh_rate : float = 0
|
||||
|
||||
|
||||
## Current XR interface
|
||||
var xr_interface : XRInterface
|
||||
|
||||
## XR frame rate
|
||||
var xr_frame_rate : float = 0
|
||||
|
||||
# Is a WebXR is_session_supported query running
|
||||
var _webxr_session_query : bool = false
|
||||
|
||||
|
||||
# Handle auto-initialization when ready
|
||||
func _ready() -> void:
|
||||
if !Engine.is_editor_hint():
|
||||
_initialize()
|
||||
|
||||
|
||||
## Initialize the XR interface
|
||||
func _initialize() -> bool:
|
||||
# Check for OpenXR interface
|
||||
xr_interface = XRServer.find_interface('OpenXR')
|
||||
if xr_interface:
|
||||
return _setup_for_openxr()
|
||||
|
||||
# Check for WebXR interface
|
||||
xr_interface = XRServer.find_interface('WebXR')
|
||||
if xr_interface:
|
||||
return _setup_for_webxr()
|
||||
|
||||
# No XR interface
|
||||
xr_interface = null
|
||||
print("No XR interface detected")
|
||||
xr_failed_to_initialize.emit()
|
||||
return false
|
||||
|
||||
|
||||
## End the XR experience
|
||||
func end_xr() -> void:
|
||||
# For WebXR drop the interactive experience and go back to the web page
|
||||
if xr_interface is WebXRInterface:
|
||||
# Uninitialize the WebXR interface
|
||||
xr_interface.uninitialize()
|
||||
return
|
||||
|
||||
# Terminate the application
|
||||
get_tree().quit()
|
||||
|
||||
|
||||
## Test if XR is active
|
||||
static func is_xr_active() -> bool:
|
||||
return _xr_active
|
||||
|
||||
|
||||
## Get the XR viewport
|
||||
func get_xr_viewport() -> Viewport:
|
||||
# Use the specified viewport if set
|
||||
if viewport:
|
||||
return viewport
|
||||
|
||||
# Use the default viewport
|
||||
return get_viewport()
|
||||
|
||||
|
||||
# Check for configuration issues
|
||||
func _get_configuration_warnings() -> PackedStringArray:
|
||||
var warnings := PackedStringArray()
|
||||
|
||||
if physics_rate_multiplier < 1:
|
||||
warnings.append("Physics rate multiplier should be at least 1x the HMD rate")
|
||||
|
||||
return warnings
|
||||
|
||||
|
||||
# Perform OpenXR setup
|
||||
func _setup_for_openxr() -> bool:
|
||||
print("OpenXR: Configuring interface")
|
||||
|
||||
# Set the render target size multiplier
|
||||
xr_interface.render_target_size_multiplier = render_target_size_multiplier
|
||||
|
||||
# Initialize the OpenXR interface
|
||||
if not xr_interface.is_initialized():
|
||||
print("OpenXR: Initializing interface")
|
||||
if not xr_interface.initialize():
|
||||
push_error("OpenXR: Failed to initialize")
|
||||
xr_failed_to_initialize.emit()
|
||||
return false
|
||||
|
||||
# Connect the OpenXR events
|
||||
xr_interface.connect("session_begun", _on_openxr_session_begun)
|
||||
xr_interface.connect("session_visible", _on_openxr_visible_state)
|
||||
xr_interface.connect("session_focussed", _on_openxr_focused_state)
|
||||
|
||||
# Check for passthrough
|
||||
if enable_passthrough and xr_interface.is_passthrough_supported():
|
||||
enable_passthrough = xr_interface.start_passthrough()
|
||||
|
||||
# Disable vsync
|
||||
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
|
||||
|
||||
# Switch the viewport to XR
|
||||
get_xr_viewport().transparent_bg = enable_passthrough
|
||||
get_xr_viewport().use_xr = true
|
||||
|
||||
# Report success
|
||||
return true
|
||||
|
||||
|
||||
# Handle OpenXR session ready
|
||||
func _on_openxr_session_begun() -> void:
|
||||
print("OpenXR: Session begun")
|
||||
|
||||
# Set the XR frame rate
|
||||
_set_xr_frame_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
|
||||
xr_ended.emit()
|
||||
|
||||
|
||||
# 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
|
||||
xr_started.emit()
|
||||
|
||||
|
||||
# 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:
|
||||
if enable_passthrough:
|
||||
# unset enable_passthrough if we can't start it.
|
||||
enable_passthrough = xr_interface.start_passthrough()
|
||||
else:
|
||||
xr_interface.stop_passthrough()
|
||||
|
||||
# Update transparent background
|
||||
get_xr_viewport().transparent_bg = enable_passthrough
|
||||
|
||||
|
||||
# Perform WebXR setup
|
||||
func _setup_for_webxr() -> bool:
|
||||
print("WebXR: Configuring interface")
|
||||
|
||||
# Connect the WebXR events
|
||||
xr_interface.connect("session_supported", _on_webxr_session_supported)
|
||||
xr_interface.connect("session_started", _on_webxr_session_started)
|
||||
xr_interface.connect("session_ended", _on_webxr_session_ended)
|
||||
xr_interface.connect("session_failed", _on_webxr_session_failed)
|
||||
|
||||
# If the viewport is already in XR mode then we are done.
|
||||
if get_xr_viewport().use_xr:
|
||||
return true
|
||||
|
||||
# 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.
|
||||
_webxr_session_query = true
|
||||
xr_interface.is_session_supported('immersive-ar' if enable_passthrough else 'immersive-vr')
|
||||
|
||||
# Report success
|
||||
return true
|
||||
|
||||
|
||||
# Handle WebXR session supported check
|
||||
func _on_webxr_session_supported(session_mode: String, supported: bool) -> void:
|
||||
# Skip if not running session-query
|
||||
if not _webxr_session_query:
|
||||
return
|
||||
|
||||
# Clear the query flag
|
||||
_webxr_session_query = false
|
||||
|
||||
# Report if not supported
|
||||
if not supported:
|
||||
OS.alert("Your web browser doesn't support " + session_mode + ". Sorry!")
|
||||
xr_failed_to_initialize.emit()
|
||||
return
|
||||
|
||||
# WebXR supported - show canvas on web browser to enter WebVR
|
||||
$EnterWebXR.visible = true
|
||||
|
||||
|
||||
# Called when the WebXR session has started successfully
|
||||
func _on_webxr_session_started() -> void:
|
||||
print("WebXR: Session started")
|
||||
|
||||
# Set the XR frame rate
|
||||
_set_xr_frame_rate()
|
||||
|
||||
# Hide the canvas and switch the viewport to XR
|
||||
$EnterWebXR.visible = false
|
||||
get_xr_viewport().transparent_bg = enable_passthrough
|
||||
get_xr_viewport().use_xr = true
|
||||
|
||||
# Report the XR starting
|
||||
_xr_active = true
|
||||
xr_started.emit()
|
||||
|
||||
|
||||
# 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_xr_viewport().transparent_bg = false
|
||||
get_xr_viewport().use_xr = false
|
||||
|
||||
# Report the XR ending
|
||||
_xr_active = false
|
||||
xr_ended.emit()
|
||||
|
||||
|
||||
# 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-ar' if enable_passthrough else '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'
|
||||
|
||||
# Add hand-tracking if enabled in the project settings
|
||||
if ProjectSettings.get_setting_with_override("xr/openxr/extensions/hand_tracking"):
|
||||
xr_interface.optional_features += ", hand-tracking"
|
||||
|
||||
# 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")
|
||||
|
||||
|
||||
# Set the XR frame rate to the configured value
|
||||
func _set_xr_frame_rate() -> void:
|
||||
# Get the reported refresh rate
|
||||
xr_frame_rate = xr_interface.get_display_refresh_rate()
|
||||
if xr_frame_rate > 0:
|
||||
print("StartXR: Refresh rate reported as ", str(xr_frame_rate))
|
||||
else:
|
||||
print("StartXR: No refresh rate given by XR runtime")
|
||||
|
||||
# Pick a desired refresh rate
|
||||
var desired_rate := target_refresh_rate if target_refresh_rate > 0 else xr_frame_rate
|
||||
var available_rates : Array = xr_interface.get_available_display_refresh_rates()
|
||||
if available_rates.size() == 0:
|
||||
print("StartXR: Target does not support refresh rate extension")
|
||||
elif available_rates.size() == 1:
|
||||
print("StartXR: Target supports only one refresh rate")
|
||||
elif desired_rate > 0:
|
||||
print("StartXR: Available refresh rates are ", str(available_rates))
|
||||
var rate = _find_closest(available_rates, desired_rate)
|
||||
if rate > 0:
|
||||
print("StartXR: Setting refresh rate to ", str(rate))
|
||||
xr_interface.set_display_refresh_rate(rate)
|
||||
xr_frame_rate = rate
|
||||
|
||||
# Pick a physics rate
|
||||
var active_rate := xr_frame_rate if xr_frame_rate > 0 else 144.0
|
||||
var physics_rate := int(round(active_rate * physics_rate_multiplier))
|
||||
print("StartXR: Setting physics rate to ", physics_rate)
|
||||
Engine.physics_ticks_per_second = physics_rate
|
||||
|
||||
|
||||
# Find the closest value in the array to the target
|
||||
func _find_closest(values : Array, target : float) -> float:
|
||||
# Return 0 if no values
|
||||
if values.size() == 0:
|
||||
return 0.0
|
||||
|
||||
# Find the closest value to the target
|
||||
var best : float = values.front()
|
||||
for v in values:
|
||||
if abs(target - v) < abs(target - best):
|
||||
best = v
|
||||
|
||||
# Return the best value
|
||||
return best
|
||||
Reference in New Issue
Block a user