initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled

This commit is contained in:
2025-09-16 20:46:46 -04:00
commit 9d30169a8d
13378 changed files with 7050105 additions and 0 deletions

12
modules/webxr/SCsub Normal file
View File

@@ -0,0 +1,12 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")
Import("env_modules")
if env["platform"] == "web":
env.AddJSLibraries(["native/library_godot_webxr.js"])
env.AddJSExterns(["native/webxr.externs.js"])
env_webxr = env_modules.Clone()
env_webxr.add_source_files(env.modules_sources, "*.cpp")

14
modules/webxr/config.py Normal file
View File

@@ -0,0 +1,14 @@
def can_build(env, platform):
return env["opengl3"] and not env["disable_xr"]
def configure(env):
pass
def get_doc_classes():
return ["WebXRInterface"]
def get_doc_path():
return "doc_classes"

View File

@@ -0,0 +1,298 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="WebXRInterface" inherits="XRInterface" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../doc/class.xsd">
<brief_description>
XR interface using WebXR.
</brief_description>
<description>
WebXR is an open standard that allows creating VR and AR applications that run in the web browser.
As such, this interface is only available when running in Web exports.
WebXR supports a wide range of devices, from the very capable (like Valve Index, HTC Vive, Oculus Rift and Quest) down to the much less capable (like Google Cardboard, Oculus Go, GearVR, or plain smartphones).
Since WebXR is based on JavaScript, it makes extensive use of callbacks, which means that [WebXRInterface] is forced to use signals, where other XR interfaces would instead use functions that return a result immediately. This makes [WebXRInterface] quite a bit more complicated to initialize than other XR interfaces.
Here's the minimum code required to start an immersive VR session:
[codeblock]
extends Node3D
var webxr_interface
var vr_supported = false
func _ready():
# We assume this node has a button as a child.
# This button is for the user to consent to entering immersive VR mode.
$Button.pressed.connect(self._on_button_pressed)
webxr_interface = XRServer.find_interface("WebXR")
if webxr_interface:
# WebXR uses a lot of asynchronous callbacks, so we connect to various
# signals in order to receive them.
webxr_interface.session_supported.connect(self._webxr_session_supported)
webxr_interface.session_started.connect(self._webxr_session_started)
webxr_interface.session_ended.connect(self._webxr_session_ended)
webxr_interface.session_failed.connect(self._webxr_session_failed)
# 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_interface.is_session_supported("immersive-vr")
func _webxr_session_supported(session_mode, supported):
if session_mode == 'immersive-vr':
vr_supported = supported
func _on_button_pressed():
if not vr_supported:
OS.alert("Your browser doesn't support VR")
return
# We want an immersive VR session, as opposed to AR ('immersive-ar') or a
# simple 3DoF viewer ('viewer').
webxr_interface.session_mode = 'immersive-vr'
# 'bounded-floor' is room scale, 'local-floor' is a standing or sitting
# experience (it puts you 1.6m above the ground if you have 3DoF headset),
# whereas as 'local' puts you down at the XROrigin.
# This list means it'll first try to request 'bounded-floor', then
# fallback on 'local-floor' and ultimately 'local', if nothing else is
# supported.
webxr_interface.requested_reference_space_types = 'bounded-floor, local-floor, local'
# In order to use 'local-floor' or 'bounded-floor' we must also
# mark the features as required or optional. By including 'hand-tracking'
# as an optional feature, it will be enabled if supported.
webxr_interface.required_features = 'local-floor'
webxr_interface.optional_features = 'bounded-floor, hand-tracking'
# This will return false if we're unable to even request the session,
# however, it can still fail asynchronously later in the process, so we
# only know if it's really succeeded or failed when our
# _webxr_session_started() or _webxr_session_failed() methods are called.
if not webxr_interface.initialize():
OS.alert("Failed to initialize")
return
func _webxr_session_started():
$Button.visible = false
# This tells Godot to start rendering to the headset.
get_viewport().use_xr = true
# This will be the reference space type you ultimately got, out of the
# types that you requested above. This is useful if you want the game to
# work a little differently in 'bounded-floor' versus 'local-floor'.
print("Reference space type: ", webxr_interface.reference_space_type)
# This will be the list of features that were successfully enabled
# (except on browsers that don't support this property).
print("Enabled features: ", webxr_interface.enabled_features)
func _webxr_session_ended():
$Button.visible = true
# If the user exits immersive mode, then we tell Godot to render to the web
# page again.
get_viewport().use_xr = false
func _webxr_session_failed(message):
OS.alert("Failed to initialize: " + message)
[/codeblock]
There are a couple ways to handle "controller" input:
- Using [XRController3D] nodes and their [signal XRController3D.button_pressed] and [signal XRController3D.button_released] signals. This is how controllers are typically handled in XR apps in Godot, however, this will only work with advanced VR controllers like the Oculus Touch or Index controllers, for example.
- Using the [signal select], [signal squeeze] and related signals. This method will work for both advanced VR controllers, and non-traditional input sources like a tap on the screen, a spoken voice command or a button press on the device itself.
You can use both methods to allow your game or app to support a wider or narrower set of devices and input methods, or to allow more advanced interactions with more advanced devices.
</description>
<tutorials>
<link title="How to make a VR game for WebXR with Godot 4">https://www.snopekgames.com/tutorial/2023/how-make-vr-game-webxr-godot-4</link>
</tutorials>
<methods>
<method name="get_available_display_refresh_rates" qualifiers="const">
<return type="Array" />
<description>
Returns display refresh rates supported by the current HMD. Only returned if this feature is supported by the web browser and after the interface has been initialized.
</description>
</method>
<method name="get_display_refresh_rate" qualifiers="const">
<return type="float" />
<description>
Returns the display refresh rate for the current HMD. Not supported on all HMDs and browsers. It may not report an accurate value until after using [method set_display_refresh_rate].
</description>
</method>
<method name="get_input_source_target_ray_mode" qualifiers="const">
<return type="int" enum="WebXRInterface.TargetRayMode" />
<param index="0" name="input_source_id" type="int" />
<description>
Returns the target ray mode for the given [param input_source_id].
This can help interpret the input coming from that input source. See [url=https://developer.mozilla.org/en-US/docs/Web/API/XRInputSource/targetRayMode]XRInputSource.targetRayMode[/url] for more information.
</description>
</method>
<method name="get_input_source_tracker" qualifiers="const">
<return type="XRControllerTracker" />
<param index="0" name="input_source_id" type="int" />
<description>
Gets an [XRControllerTracker] for the given [param input_source_id].
In the context of WebXR, an input source can be an advanced VR controller like the Oculus Touch or Index controllers, or even a tap on the screen, a spoken voice command or a button press on the device itself. When a non-traditional input source is used, interpret the position and orientation of the [XRPositionalTracker] as a ray pointing at the object the user wishes to interact with.
Use this method to get information about the input source that triggered one of these signals:
- [signal selectstart]
- [signal select]
- [signal selectend]
- [signal squeezestart]
- [signal squeeze]
- [signal squeezestart]
</description>
</method>
<method name="is_input_source_active" qualifiers="const">
<return type="bool" />
<param index="0" name="input_source_id" type="int" />
<description>
Returns [code]true[/code] if there is an active input source with the given [param input_source_id].
</description>
</method>
<method name="is_session_supported">
<return type="void" />
<param index="0" name="session_mode" type="String" />
<description>
Checks if the given [param session_mode] is supported by the user's browser.
Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRSessionMode]WebXR's XRSessionMode[/url], including: [code]"immersive-vr"[/code], [code]"immersive-ar"[/code], and [code]"inline"[/code].
This method returns nothing, instead it emits the [signal session_supported] signal with the result.
</description>
</method>
<method name="set_display_refresh_rate">
<return type="void" />
<param index="0" name="refresh_rate" type="float" />
<description>
Sets the display refresh rate for the current HMD. Not supported on all HMDs and browsers. It won't take effect right away until after [signal display_refresh_rate_changed] is emitted.
</description>
</method>
</methods>
<members>
<member name="enabled_features" type="String" setter="" getter="get_enabled_features">
A comma-separated list of features that were successfully enabled by [method XRInterface.initialize] when setting up the WebXR session.
This may include features requested by setting [member required_features] and [member optional_features], and will only be available after [signal session_started] has been emitted.
[b]Note:[/b] This may not be support by all web browsers, in which case it will be an empty string.
</member>
<member name="optional_features" type="String" setter="set_optional_features" getter="get_optional_features">
A comma-seperated list of optional features used by [method XRInterface.initialize] when setting up the WebXR session.
If a user's browser or device doesn't support one of the given features, initialization will continue, but you won't be able to use the requested feature.
This doesn't have any effect on the interface when already initialized.
See the MDN documentation on [url=https://developer.mozilla.org/en-US/docs/Web/API/XRSystem/requestSession#session_features]WebXR's session features[/url] for a list of possible values.
</member>
<member name="reference_space_type" type="String" setter="" getter="get_reference_space_type">
The reference space type (from the list of requested types set in the [member requested_reference_space_types] property), that was ultimately used by [method XRInterface.initialize] when setting up the WebXR session.
Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpaceType]WebXR's XRReferenceSpaceType[/url]. If you want to use a particular reference space type, it must be listed in either [member required_features] or [member optional_features].
</member>
<member name="requested_reference_space_types" type="String" setter="set_requested_reference_space_types" getter="get_requested_reference_space_types">
A comma-seperated list of reference space types used by [method XRInterface.initialize] when setting up the WebXR session.
The reference space types are requested in order, and the first one supported by the user's device or browser will be used. The [member reference_space_type] property contains the reference space type that was ultimately selected.
This doesn't have any effect on the interface when already initialized.
Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpaceType]WebXR's XRReferenceSpaceType[/url]. If you want to use a particular reference space type, it must be listed in either [member required_features] or [member optional_features].
</member>
<member name="required_features" type="String" setter="set_required_features" getter="get_required_features">
A comma-seperated list of required features used by [method XRInterface.initialize] when setting up the WebXR session.
If a user's browser or device doesn't support one of the given features, initialization will fail and [signal session_failed] will be emitted.
This doesn't have any effect on the interface when already initialized.
See the MDN documentation on [url=https://developer.mozilla.org/en-US/docs/Web/API/XRSystem/requestSession#session_features]WebXR's session features[/url] for a list of possible values.
</member>
<member name="session_mode" type="String" setter="set_session_mode" getter="get_session_mode">
The session mode used by [method XRInterface.initialize] when setting up the WebXR session.
This doesn't have any effect on the interface when already initialized.
Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRSessionMode]WebXR's XRSessionMode[/url], including: [code]"immersive-vr"[/code], [code]"immersive-ar"[/code], and [code]"inline"[/code].
</member>
<member name="visibility_state" type="String" setter="" getter="get_visibility_state">
Indicates if the WebXR session's imagery is visible to the user.
Possible values come from [url=https://developer.mozilla.org/en-US/docs/Web/API/XRVisibilityState]WebXR's XRVisibilityState[/url], including [code]"hidden"[/code], [code]"visible"[/code], and [code]"visible-blurred"[/code].
</member>
</members>
<signals>
<signal name="display_refresh_rate_changed">
<description>
Emitted after the display's refresh rate has changed.
</description>
</signal>
<signal name="reference_space_reset">
<description>
Emitted to indicate that the reference space has been reset or reconfigured.
When (or whether) this is emitted depends on the user's browser or device, but may include when the user has changed the dimensions of their play space (which you may be able to access via [method XRInterface.get_play_area]) or pressed/held a button to recenter their position.
See [url=https://developer.mozilla.org/en-US/docs/Web/API/XRReferenceSpace/reset_event]WebXR's XRReferenceSpace reset event[/url] for more information.
</description>
</signal>
<signal name="select">
<param index="0" name="input_source_id" type="int" />
<description>
Emitted after one of the input sources has finished its "primary action".
Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source.
</description>
</signal>
<signal name="selectend">
<param index="0" name="input_source_id" type="int" />
<description>
Emitted when one of the input sources has finished its "primary action".
Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source.
</description>
</signal>
<signal name="selectstart">
<param index="0" name="input_source_id" type="int" />
<description>
Emitted when one of the input source has started its "primary action".
Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source.
</description>
</signal>
<signal name="session_ended">
<description>
Emitted when the user ends the WebXR session (which can be done using UI from the browser or device).
At this point, you should do [code]get_viewport().use_xr = false[/code] to instruct Godot to resume rendering to the screen.
</description>
</signal>
<signal name="session_failed">
<param index="0" name="message" type="String" />
<description>
Emitted by [method XRInterface.initialize] if the session fails to start.
[param message] may optionally contain an error message from WebXR, or an empty string if no message is available.
</description>
</signal>
<signal name="session_started">
<description>
Emitted by [method XRInterface.initialize] if the session is successfully started.
At this point, it's safe to do [code]get_viewport().use_xr = true[/code] to instruct Godot to start rendering to the XR device.
</description>
</signal>
<signal name="session_supported">
<param index="0" name="session_mode" type="String" />
<param index="1" name="supported" type="bool" />
<description>
Emitted by [method is_session_supported] to indicate if the given [param session_mode] is supported or not.
</description>
</signal>
<signal name="squeeze">
<param index="0" name="input_source_id" type="int" />
<description>
Emitted after one of the input sources has finished its "primary squeeze action".
Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source.
</description>
</signal>
<signal name="squeezeend">
<param index="0" name="input_source_id" type="int" />
<description>
Emitted when one of the input sources has finished its "primary squeeze action".
Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source.
</description>
</signal>
<signal name="squeezestart">
<param index="0" name="input_source_id" type="int" />
<description>
Emitted when one of the input sources has started its "primary squeeze action".
Use [method get_input_source_tracker] and [method get_input_source_target_ray_mode] to get more information about the input source.
</description>
</signal>
<signal name="visibility_state_changed">
<description>
Emitted when [member visibility_state] has changed.
</description>
</signal>
</signals>
<constants>
<constant name="TARGET_RAY_MODE_UNKNOWN" value="0" enum="TargetRayMode">
We don't know the target ray mode.
</constant>
<constant name="TARGET_RAY_MODE_GAZE" value="1" enum="TargetRayMode">
Target ray originates at the viewer's eyes and points in the direction they are looking.
</constant>
<constant name="TARGET_RAY_MODE_TRACKED_POINTER" value="2" enum="TargetRayMode">
Target ray from a handheld pointer, most likely a VR touch controller.
</constant>
<constant name="TARGET_RAY_MODE_SCREEN" value="3" enum="TargetRayMode">
Target ray from touch screen, mouse or other tactile input device.
</constant>
</constants>
</class>

View File

@@ -0,0 +1,99 @@
/**************************************************************************/
/* godot_webxr.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
enum WebXRInputEvent {
WEBXR_INPUT_EVENT_SELECTSTART,
WEBXR_INPUT_EVENT_SELECTEND,
WEBXR_INPUT_EVENT_SQUEEZESTART,
WEBXR_INPUT_EVENT_SQUEEZEEND,
};
typedef void (*GodotWebXRSupportedCallback)(char *p_session_mode, int p_supported);
typedef void (*GodotWebXRStartedCallback)(char *p_reference_space_type, char *p_enabled_features, char *p_environment_blend_mode);
typedef void (*GodotWebXREndedCallback)();
typedef void (*GodotWebXRFailedCallback)(char *p_message);
typedef void (*GodotWebXRInputEventCallback)(int p_event_type, int p_input_source_id);
typedef void (*GodotWebXRSimpleEventCallback)(char *p_signal_name);
extern int godot_webxr_is_supported();
extern void godot_webxr_is_session_supported(const char *p_session_mode, GodotWebXRSupportedCallback p_callback);
extern void godot_webxr_initialize(
const char *p_session_mode,
const char *p_required_features,
const char *p_optional_features,
const char *p_requested_reference_space_types,
GodotWebXRStartedCallback p_on_session_started,
GodotWebXREndedCallback p_on_session_ended,
GodotWebXRFailedCallback p_on_session_failed,
GodotWebXRInputEventCallback p_on_input_event,
GodotWebXRSimpleEventCallback p_on_simple_event);
extern void godot_webxr_uninitialize();
extern int godot_webxr_get_view_count();
extern bool godot_webxr_get_render_target_size(int *r_size);
extern bool godot_webxr_get_transform_for_view(int p_view, float *r_transform);
extern bool godot_webxr_get_projection_for_view(int p_view, float *r_transform);
extern unsigned int godot_webxr_get_color_texture();
extern unsigned int godot_webxr_get_depth_texture();
extern unsigned int godot_webxr_get_velocity_texture();
extern bool godot_webxr_update_input_source(
int p_input_source_id,
float *r_target_pose,
int *r_target_ray_mode,
int *r_touch_index,
int *r_has_grip_pose,
float *r_grip_pose,
int *r_has_standard_mapping,
int *r_button_count,
float *r_buttons,
int *r_axes_count,
float *r_axes,
int *r_has_hand_data,
float *r_hand_joints,
float *r_hand_radii);
extern char *godot_webxr_get_visibility_state();
extern int godot_webxr_get_bounds_geometry(float **r_points);
extern float godot_webxr_get_frame_rate();
extern void godot_webxr_update_target_frame_rate(float p_frame_rate);
extern int godot_webxr_get_supported_frame_rates(float **r_frame_rates);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,689 @@
/**************************************************************************/
/* library_godot_webxr.js */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
const GodotWebXR = {
$GodotWebXR__deps: ['$MainLoop', '$GL', '$GodotRuntime', '$runtimeKeepalivePush', '$runtimeKeepalivePop'],
$GodotWebXR: {
gl: null,
session: null,
gl_binding: null,
layer: null,
space: null,
frame: null,
pose: null,
view_count: 1,
input_sources: new Array(16),
touches: new Array(5),
onsimpleevent: null,
// Monkey-patch the requestAnimationFrame() used by Emscripten for the main
// loop, so that we can swap it out for XRSession.requestAnimationFrame()
// when an XR session is started.
orig_requestAnimationFrame: null,
requestAnimationFrame: (callback) => {
if (GodotWebXR.session && GodotWebXR.space) {
const onFrame = function (time, frame) {
GodotWebXR.frame = frame;
GodotWebXR.pose = frame.getViewerPose(GodotWebXR.space);
callback(time);
GodotWebXR.frame = null;
GodotWebXR.pose = null;
};
GodotWebXR.session.requestAnimationFrame(onFrame);
} else {
GodotWebXR.orig_requestAnimationFrame(callback);
}
},
monkeyPatchRequestAnimationFrame: (enable) => {
if (GodotWebXR.orig_requestAnimationFrame === null) {
GodotWebXR.orig_requestAnimationFrame = MainLoop.requestAnimationFrame;
}
MainLoop.requestAnimationFrame = enable
? GodotWebXR.requestAnimationFrame
: GodotWebXR.orig_requestAnimationFrame;
},
pauseResumeMainLoop: () => {
// Once both GodotWebXR.session and GodotWebXR.space are set or
// unset, our monkey-patched requestAnimationFrame() should be
// enabled or disabled. When using the WebXR API Emulator, this
// gets picked up automatically, however, in the Oculus Browser
// on the Quest, we need to pause and resume the main loop.
MainLoop.pause();
runtimeKeepalivePush();
window.setTimeout(function () {
runtimeKeepalivePop();
MainLoop.resume();
}, 0);
},
getLayer: () => {
const new_view_count = (GodotWebXR.pose) ? GodotWebXR.pose.views.length : 1;
let layer = GodotWebXR.layer;
// If the view count hasn't changed since creating this layer, then
// we can simply return it.
if (layer && GodotWebXR.view_count === new_view_count) {
return layer;
}
if (!GodotWebXR.session || !GodotWebXR.gl_binding || !GodotWebXR.gl_binding.createProjectionLayer) {
return null;
}
const gl = GodotWebXR.gl;
layer = GodotWebXR.gl_binding.createProjectionLayer({
textureType: new_view_count > 1 ? 'texture-array' : 'texture',
colorFormat: gl.RGBA8,
depthFormat: gl.DEPTH_COMPONENT24,
});
GodotWebXR.session.updateRenderState({ layers: [layer] });
GodotWebXR.layer = layer;
GodotWebXR.view_count = new_view_count;
return layer;
},
getSubImage: () => {
if (!GodotWebXR.pose) {
return null;
}
const layer = GodotWebXR.getLayer();
if (layer === null) {
return null;
}
// Because we always use "texture-array" for multiview and "texture"
// when there is only 1 view, it should be safe to only grab the
// subimage for the first view.
return GodotWebXR.gl_binding.getViewSubImage(layer, GodotWebXR.pose.views[0]);
},
getTextureId: (texture) => {
if (texture.name !== undefined) {
return texture.name;
}
const id = GL.getNewId(GL.textures);
texture.name = id;
GL.textures[id] = texture;
return id;
},
addInputSource: (input_source) => {
let name = -1;
if (input_source.targetRayMode === 'tracked-pointer' && input_source.handedness === 'left') {
name = 0;
} else if (input_source.targetRayMode === 'tracked-pointer' && input_source.handedness === 'right') {
name = 1;
} else {
for (let i = 2; i < 16; i++) {
if (!GodotWebXR.input_sources[i]) {
name = i;
break;
}
}
}
if (name >= 0) {
GodotWebXR.input_sources[name] = input_source;
input_source.name = name;
// Find a free touch index for screen sources.
if (input_source.targetRayMode === 'screen') {
let touch_index = -1;
for (let i = 0; i < 5; i++) {
if (!GodotWebXR.touches[i]) {
touch_index = i;
break;
}
}
if (touch_index >= 0) {
GodotWebXR.touches[touch_index] = input_source;
input_source.touch_index = touch_index;
}
}
}
return name;
},
removeInputSource: (input_source) => {
if (input_source.name !== undefined) {
const name = input_source.name;
if (name >= 0 && name < 16) {
GodotWebXR.input_sources[name] = null;
}
if (input_source.touch_index !== undefined) {
const touch_index = input_source.touch_index;
if (touch_index >= 0 && touch_index < 5) {
GodotWebXR.touches[touch_index] = null;
}
}
return name;
}
return -1;
},
getInputSourceId: (input_source) => {
if (input_source !== undefined) {
return input_source.name;
}
return -1;
},
getTouchIndex: (input_source) => {
if (input_source.touch_index !== undefined) {
return input_source.touch_index;
}
return -1;
},
},
godot_webxr_is_supported__proxy: 'sync',
godot_webxr_is_supported__sig: 'i',
godot_webxr_is_supported: function () {
return !!navigator.xr;
},
godot_webxr_is_session_supported__proxy: 'sync',
godot_webxr_is_session_supported__sig: 'vii',
godot_webxr_is_session_supported: function (p_session_mode, p_callback) {
const session_mode = GodotRuntime.parseString(p_session_mode);
const cb = GodotRuntime.get_func(p_callback);
if (navigator.xr) {
navigator.xr.isSessionSupported(session_mode).then(function (supported) {
const c_str = GodotRuntime.allocString(session_mode);
cb(c_str, supported ? 1 : 0);
GodotRuntime.free(c_str);
});
} else {
const c_str = GodotRuntime.allocString(session_mode);
cb(c_str, 0);
GodotRuntime.free(c_str);
}
},
godot_webxr_initialize__deps: ['emscripten_webgl_get_current_context'],
godot_webxr_initialize__proxy: 'sync',
godot_webxr_initialize__sig: 'viiiiiiiii',
godot_webxr_initialize: function (p_session_mode, p_required_features, p_optional_features, p_requested_reference_spaces, p_on_session_started, p_on_session_ended, p_on_session_failed, p_on_input_event, p_on_simple_event) {
GodotWebXR.monkeyPatchRequestAnimationFrame(true);
const session_mode = GodotRuntime.parseString(p_session_mode);
const required_features = GodotRuntime.parseString(p_required_features).split(',').map((s) => s.trim()).filter((s) => s !== '');
const optional_features = GodotRuntime.parseString(p_optional_features).split(',').map((s) => s.trim()).filter((s) => s !== '');
const requested_reference_space_types = GodotRuntime.parseString(p_requested_reference_spaces).split(',').map((s) => s.trim());
const onstarted = GodotRuntime.get_func(p_on_session_started);
const onended = GodotRuntime.get_func(p_on_session_ended);
const onfailed = GodotRuntime.get_func(p_on_session_failed);
const oninputevent = GodotRuntime.get_func(p_on_input_event);
const onsimpleevent = GodotRuntime.get_func(p_on_simple_event);
const session_init = {};
if (required_features.length > 0) {
session_init['requiredFeatures'] = required_features;
}
if (optional_features.length > 0) {
session_init['optionalFeatures'] = optional_features;
}
navigator.xr.requestSession(session_mode, session_init).then(function (session) {
GodotWebXR.session = session;
session.addEventListener('end', function (evt) {
onended();
});
session.addEventListener('inputsourceschange', function (evt) {
evt.added.forEach(GodotWebXR.addInputSource);
evt.removed.forEach(GodotWebXR.removeInputSource);
});
['selectstart', 'selectend', 'squeezestart', 'squeezeend'].forEach((input_event, index) => {
session.addEventListener(input_event, function (evt) {
// Since this happens in-between normal frames, we need to
// grab the frame from the event in order to get poses for
// the input sources.
GodotWebXR.frame = evt.frame;
oninputevent(index, GodotWebXR.getInputSourceId(evt.inputSource));
GodotWebXR.frame = null;
});
});
session.addEventListener('visibilitychange', function (evt) {
const c_str = GodotRuntime.allocString('visibility_state_changed');
onsimpleevent(c_str);
GodotRuntime.free(c_str);
});
// Store onsimpleevent so we can use it later.
GodotWebXR.onsimpleevent = onsimpleevent;
const gl_context_handle = _emscripten_webgl_get_current_context();
const gl = GL.getContext(gl_context_handle).GLctx;
GodotWebXR.gl = gl;
gl.makeXRCompatible().then(function () {
const throwNoWebXRLayersError = () => {
throw new Error('This browser doesn\'t support WebXR Layers (which Godot requires) nor is the polyfill in use. If you are the developer of this application, please consider including the polyfill.');
};
try {
GodotWebXR.gl_binding = new XRWebGLBinding(session, gl);
} catch (error) {
// We'll end up here for browsers that don't have XRWebGLBinding at all, or if the browser does support WebXR Layers,
// but is using the WebXR polyfill, so calling native XRWebGLBinding with the polyfilled XRSession won't work.
throwNoWebXRLayersError();
}
if (!GodotWebXR.gl_binding.createProjectionLayer) {
// On other browsers, XRWebGLBinding exists and works, but it doesn't support creating projection layers (which is
// contrary to the spec, which says this MUST be supported) and so the polyfill is required.
throwNoWebXRLayersError();
}
// This will trigger the layer to get created.
const layer = GodotWebXR.getLayer();
if (!layer) {
throw new Error('Unable to create WebXR Layer.');
}
function onReferenceSpaceSuccess(reference_space, reference_space_type) {
GodotWebXR.space = reference_space;
// Using reference_space.addEventListener() crashes when
// using the polyfill with the WebXR Emulator extension,
// so we set the event property instead.
reference_space.onreset = function (evt) {
const c_str = GodotRuntime.allocString('reference_space_reset');
onsimpleevent(c_str);
GodotRuntime.free(c_str);
};
// Now that both GodotWebXR.session and GodotWebXR.space are
// set, we need to pause and resume the main loop for the XR
// main loop to kick in.
GodotWebXR.pauseResumeMainLoop();
// Call in setTimeout() so that errors in the onstarted()
// callback don't bubble up here and cause Godot to try the
// next reference space.
window.setTimeout(function () {
const reference_space_c_str = GodotRuntime.allocString(reference_space_type);
const enabled_features = 'enabledFeatures' in session ? Array.from(session.enabledFeatures) : [];
const enabled_features_c_str = GodotRuntime.allocString(enabled_features.join(','));
const environment_blend_mode = 'environmentBlendMode' in session ? session.environmentBlendMode : '';
const environment_blend_mode_c_str = GodotRuntime.allocString(environment_blend_mode);
onstarted(reference_space_c_str, enabled_features_c_str, environment_blend_mode_c_str);
GodotRuntime.free(reference_space_c_str);
GodotRuntime.free(enabled_features_c_str);
GodotRuntime.free(environment_blend_mode_c_str);
}, 0);
}
function requestReferenceSpace() {
const reference_space_type = requested_reference_space_types.shift();
session.requestReferenceSpace(reference_space_type)
.then((refSpace) => {
onReferenceSpaceSuccess(refSpace, reference_space_type);
})
.catch(() => {
if (requested_reference_space_types.length === 0) {
const c_str = GodotRuntime.allocString('Unable to get any of the requested reference space types');
onfailed(c_str);
GodotRuntime.free(c_str);
} else {
requestReferenceSpace();
}
});
}
requestReferenceSpace();
}).catch(function (error) {
const c_str = GodotRuntime.allocString(`Unable to make WebGL context compatible with WebXR: ${error}`);
onfailed(c_str);
GodotRuntime.free(c_str);
});
}).catch(function (error) {
const c_str = GodotRuntime.allocString(`Unable to start session: ${error}`);
onfailed(c_str);
GodotRuntime.free(c_str);
});
},
godot_webxr_uninitialize__proxy: 'sync',
godot_webxr_uninitialize__sig: 'v',
godot_webxr_uninitialize: function () {
if (GodotWebXR.session) {
GodotWebXR.session.end()
// Prevent exception when session has already ended.
.catch((e) => { });
}
GodotWebXR.session = null;
GodotWebXR.gl_binding = null;
GodotWebXR.layer = null;
GodotWebXR.space = null;
GodotWebXR.frame = null;
GodotWebXR.pose = null;
GodotWebXR.view_count = 1;
GodotWebXR.input_sources = new Array(16);
GodotWebXR.touches = new Array(5);
GodotWebXR.onsimpleevent = null;
// Disable the monkey-patched window.requestAnimationFrame() and
// pause/restart the main loop to activate it on all platforms.
GodotWebXR.monkeyPatchRequestAnimationFrame(false);
GodotWebXR.pauseResumeMainLoop();
},
godot_webxr_get_view_count__proxy: 'sync',
godot_webxr_get_view_count__sig: 'i',
godot_webxr_get_view_count: function () {
if (!GodotWebXR.session || !GodotWebXR.pose) {
return 1;
}
const view_count = GodotWebXR.pose.views.length;
return view_count > 0 ? view_count : 1;
},
godot_webxr_get_render_target_size__proxy: 'sync',
godot_webxr_get_render_target_size__sig: 'ii',
godot_webxr_get_render_target_size: function (r_size) {
const subimage = GodotWebXR.getSubImage();
if (subimage === null) {
return false;
}
GodotRuntime.setHeapValue(r_size + 0, subimage.viewport.width, 'i32');
GodotRuntime.setHeapValue(r_size + 4, subimage.viewport.height, 'i32');
return true;
},
godot_webxr_get_transform_for_view__proxy: 'sync',
godot_webxr_get_transform_for_view__sig: 'iii',
godot_webxr_get_transform_for_view: function (p_view, r_transform) {
if (!GodotWebXR.session || !GodotWebXR.pose) {
return false;
}
const views = GodotWebXR.pose.views;
let matrix;
if (p_view >= 0) {
matrix = views[p_view].transform.matrix;
} else {
// For -1 (or any other negative value) return the HMD transform.
matrix = GodotWebXR.pose.transform.matrix;
}
for (let i = 0; i < 16; i++) {
GodotRuntime.setHeapValue(r_transform + (i * 4), matrix[i], 'float');
}
return true;
},
godot_webxr_get_projection_for_view__proxy: 'sync',
godot_webxr_get_projection_for_view__sig: 'iii',
godot_webxr_get_projection_for_view: function (p_view, r_transform) {
if (!GodotWebXR.session || !GodotWebXR.pose) {
return false;
}
const matrix = GodotWebXR.pose.views[p_view].projectionMatrix;
for (let i = 0; i < 16; i++) {
GodotRuntime.setHeapValue(r_transform + (i * 4), matrix[i], 'float');
}
return true;
},
godot_webxr_get_color_texture__proxy: 'sync',
godot_webxr_get_color_texture__sig: 'i',
godot_webxr_get_color_texture: function () {
const subimage = GodotWebXR.getSubImage();
if (subimage === null) {
return 0;
}
return GodotWebXR.getTextureId(subimage.colorTexture);
},
godot_webxr_get_depth_texture__proxy: 'sync',
godot_webxr_get_depth_texture__sig: 'i',
godot_webxr_get_depth_texture: function () {
const subimage = GodotWebXR.getSubImage();
if (subimage === null) {
return 0;
}
if (!subimage.depthStencilTexture) {
return 0;
}
return GodotWebXR.getTextureId(subimage.depthStencilTexture);
},
godot_webxr_get_velocity_texture__proxy: 'sync',
godot_webxr_get_velocity_texture__sig: 'i',
godot_webxr_get_velocity_texture: function () {
const subimage = GodotWebXR.getSubImage();
if (subimage === null) {
return 0;
}
if (!subimage.motionVectorTexture) {
return 0;
}
return GodotWebXR.getTextureId(subimage.motionVectorTexture);
},
godot_webxr_update_input_source__proxy: 'sync',
godot_webxr_update_input_source__sig: 'iiiiiiiiiiiiiii',
godot_webxr_update_input_source: function (p_input_source_id, r_target_pose, r_target_ray_mode, r_touch_index, r_has_grip_pose, r_grip_pose, r_has_standard_mapping, r_button_count, r_buttons, r_axes_count, r_axes, r_has_hand_data, r_hand_joints, r_hand_radii) {
if (!GodotWebXR.session || !GodotWebXR.frame) {
return 0;
}
if (p_input_source_id < 0 || p_input_source_id >= GodotWebXR.input_sources.length || !GodotWebXR.input_sources[p_input_source_id]) {
return false;
}
const input_source = GodotWebXR.input_sources[p_input_source_id];
const frame = GodotWebXR.frame;
const space = GodotWebXR.space;
// Target pose.
const target_pose = frame.getPose(input_source.targetRaySpace, space);
if (!target_pose) {
// This can mean that the controller lost tracking.
return false;
}
const target_pose_matrix = target_pose.transform.matrix;
for (let i = 0; i < 16; i++) {
GodotRuntime.setHeapValue(r_target_pose + (i * 4), target_pose_matrix[i], 'float');
}
// Target ray mode.
let target_ray_mode = 0;
switch (input_source.targetRayMode) {
case 'gaze':
target_ray_mode = 1;
break;
case 'tracked-pointer':
target_ray_mode = 2;
break;
case 'screen':
target_ray_mode = 3;
break;
default:
}
GodotRuntime.setHeapValue(r_target_ray_mode, target_ray_mode, 'i32');
// Touch index.
GodotRuntime.setHeapValue(r_touch_index, GodotWebXR.getTouchIndex(input_source), 'i32');
// Grip pose.
let has_grip_pose = false;
if (input_source.gripSpace) {
const grip_pose = frame.getPose(input_source.gripSpace, space);
if (grip_pose) {
const grip_pose_matrix = grip_pose.transform.matrix;
for (let i = 0; i < 16; i++) {
GodotRuntime.setHeapValue(r_grip_pose + (i * 4), grip_pose_matrix[i], 'float');
}
has_grip_pose = true;
}
}
GodotRuntime.setHeapValue(r_has_grip_pose, has_grip_pose ? 1 : 0, 'i32');
// Gamepad data (mapping, buttons and axes).
let has_standard_mapping = false;
let button_count = 0;
let axes_count = 0;
if (input_source.gamepad) {
if (input_source.gamepad.mapping === 'xr-standard') {
has_standard_mapping = true;
}
button_count = Math.min(input_source.gamepad.buttons.length, 10);
for (let i = 0; i < button_count; i++) {
GodotRuntime.setHeapValue(r_buttons + (i * 4), input_source.gamepad.buttons[i].value, 'float');
}
axes_count = Math.min(input_source.gamepad.axes.length, 10);
for (let i = 0; i < axes_count; i++) {
GodotRuntime.setHeapValue(r_axes + (i * 4), input_source.gamepad.axes[i], 'float');
}
}
GodotRuntime.setHeapValue(r_has_standard_mapping, has_standard_mapping ? 1 : 0, 'i32');
GodotRuntime.setHeapValue(r_button_count, button_count, 'i32');
GodotRuntime.setHeapValue(r_axes_count, axes_count, 'i32');
// Hand tracking data.
let has_hand_data = false;
if (input_source.hand && r_hand_joints !== 0 && r_hand_radii !== 0) {
const hand_joint_array = new Float32Array(25 * 16);
const hand_radii_array = new Float32Array(25);
if (frame.fillPoses(input_source.hand.values(), space, hand_joint_array) && frame.fillJointRadii(input_source.hand.values(), hand_radii_array)) {
GodotRuntime.heapCopy(HEAPF32, hand_joint_array, r_hand_joints);
GodotRuntime.heapCopy(HEAPF32, hand_radii_array, r_hand_radii);
has_hand_data = true;
}
}
GodotRuntime.setHeapValue(r_has_hand_data, has_hand_data ? 1 : 0, 'i32');
return true;
},
godot_webxr_get_visibility_state__proxy: 'sync',
godot_webxr_get_visibility_state__sig: 'i',
godot_webxr_get_visibility_state: function () {
if (!GodotWebXR.session || !GodotWebXR.session.visibilityState) {
return 0;
}
return GodotRuntime.allocString(GodotWebXR.session.visibilityState);
},
godot_webxr_get_bounds_geometry__proxy: 'sync',
godot_webxr_get_bounds_geometry__sig: 'ii',
godot_webxr_get_bounds_geometry: function (r_points) {
if (!GodotWebXR.space || !GodotWebXR.space.boundsGeometry) {
return 0;
}
const point_count = GodotWebXR.space.boundsGeometry.length;
if (point_count === 0) {
return 0;
}
const buf = GodotRuntime.malloc(point_count * 3 * 4);
for (let i = 0; i < point_count; i++) {
const point = GodotWebXR.space.boundsGeometry[i];
GodotRuntime.setHeapValue(buf + ((i * 3) + 0) * 4, point.x, 'float');
GodotRuntime.setHeapValue(buf + ((i * 3) + 1) * 4, point.y, 'float');
GodotRuntime.setHeapValue(buf + ((i * 3) + 2) * 4, point.z, 'float');
}
GodotRuntime.setHeapValue(r_points, buf, 'i32');
return point_count;
},
godot_webxr_get_frame_rate__proxy: 'sync',
godot_webxr_get_frame_rate__sig: 'i',
godot_webxr_get_frame_rate: function () {
if (!GodotWebXR.session || GodotWebXR.session.frameRate === undefined) {
return 0;
}
return GodotWebXR.session.frameRate;
},
godot_webxr_update_target_frame_rate__proxy: 'sync',
godot_webxr_update_target_frame_rate__sig: 'vi',
godot_webxr_update_target_frame_rate: function (p_frame_rate) {
if (!GodotWebXR.session || GodotWebXR.session.updateTargetFrameRate === undefined) {
return;
}
GodotWebXR.session.updateTargetFrameRate(p_frame_rate).then(() => {
const c_str = GodotRuntime.allocString('display_refresh_rate_changed');
GodotWebXR.onsimpleevent(c_str);
GodotRuntime.free(c_str);
});
},
godot_webxr_get_supported_frame_rates__proxy: 'sync',
godot_webxr_get_supported_frame_rates__sig: 'ii',
godot_webxr_get_supported_frame_rates: function (r_frame_rates) {
if (!GodotWebXR.session || GodotWebXR.session.supportedFrameRates === undefined) {
return 0;
}
const frame_rate_count = GodotWebXR.session.supportedFrameRates.length;
if (frame_rate_count === 0) {
return 0;
}
const buf = GodotRuntime.malloc(frame_rate_count * 4);
for (let i = 0; i < frame_rate_count; i++) {
GodotRuntime.setHeapValue(buf + (i * 4), GodotWebXR.session.supportedFrameRates[i], 'float');
}
GodotRuntime.setHeapValue(r_frame_rates, buf, 'i32');
return frame_rate_count;
},
};
autoAddDeps(GodotWebXR, '$GodotWebXR');
mergeInto(LibraryManager.library, GodotWebXR);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
/**************************************************************************/
/* register_types.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "register_types.h"
#include "webxr_interface.h"
#include "webxr_interface_js.h"
#ifdef WEB_ENABLED
Ref<WebXRInterfaceJS> webxr;
#endif
void initialize_webxr_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
GDREGISTER_ABSTRACT_CLASS(WebXRInterface);
#ifdef WEB_ENABLED
if (XRServer::get_singleton()) {
webxr.instantiate();
XRServer::get_singleton()->add_interface(webxr);
}
#endif
}
void uninitialize_webxr_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
}
#ifdef WEB_ENABLED
if (webxr.is_valid()) {
// uninitialize our interface if it is initialized
if (webxr->is_initialized()) {
webxr->uninitialize();
}
// unregister our interface from the XR server
if (XRServer::get_singleton()) {
XRServer::get_singleton()->remove_interface(webxr);
}
// and release
webxr.unref();
}
#endif
}

View File

@@ -0,0 +1,36 @@
/**************************************************************************/
/* register_types.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "modules/register_module_types.h"
void initialize_webxr_module(ModuleInitializationLevel p_level);
void uninitialize_webxr_module(ModuleInitializationLevel p_level);

View File

@@ -0,0 +1,41 @@
/**************************************************************************/
/* webxr_interface.compat.inc */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef DISABLE_DEPRECATED
Ref<XRPositionalTracker> WebXRInterface::_get_input_source_tracker_bind_compat_90645(int p_input_source_id) const {
return get_input_source_tracker(p_input_source_id);
}
void WebXRInterface::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("get_input_source_tracker", "input_source_id"), &WebXRInterface::_get_input_source_tracker_bind_compat_90645);
}
#endif // DISABLE_DEPRECATED

View File

@@ -0,0 +1,82 @@
/**************************************************************************/
/* webxr_interface.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "webxr_interface.h"
#include "webxr_interface.compat.inc"
void WebXRInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_session_supported", "session_mode"), &WebXRInterface::is_session_supported);
ClassDB::bind_method(D_METHOD("set_session_mode", "session_mode"), &WebXRInterface::set_session_mode);
ClassDB::bind_method(D_METHOD("get_session_mode"), &WebXRInterface::get_session_mode);
ClassDB::bind_method(D_METHOD("set_required_features", "required_features"), &WebXRInterface::set_required_features);
ClassDB::bind_method(D_METHOD("get_required_features"), &WebXRInterface::get_required_features);
ClassDB::bind_method(D_METHOD("set_optional_features", "optional_features"), &WebXRInterface::set_optional_features);
ClassDB::bind_method(D_METHOD("get_optional_features"), &WebXRInterface::get_optional_features);
ClassDB::bind_method(D_METHOD("get_reference_space_type"), &WebXRInterface::get_reference_space_type);
ClassDB::bind_method(D_METHOD("get_enabled_features"), &WebXRInterface::get_enabled_features);
ClassDB::bind_method(D_METHOD("set_requested_reference_space_types", "requested_reference_space_types"), &WebXRInterface::set_requested_reference_space_types);
ClassDB::bind_method(D_METHOD("get_requested_reference_space_types"), &WebXRInterface::get_requested_reference_space_types);
ClassDB::bind_method(D_METHOD("is_input_source_active", "input_source_id"), &WebXRInterface::is_input_source_active);
ClassDB::bind_method(D_METHOD("get_input_source_tracker", "input_source_id"), &WebXRInterface::get_input_source_tracker);
ClassDB::bind_method(D_METHOD("get_input_source_target_ray_mode", "input_source_id"), &WebXRInterface::get_input_source_target_ray_mode);
ClassDB::bind_method(D_METHOD("get_visibility_state"), &WebXRInterface::get_visibility_state);
ClassDB::bind_method(D_METHOD("get_display_refresh_rate"), &WebXRInterface::get_display_refresh_rate);
ClassDB::bind_method(D_METHOD("set_display_refresh_rate", "refresh_rate"), &WebXRInterface::set_display_refresh_rate);
ClassDB::bind_method(D_METHOD("get_available_display_refresh_rates"), &WebXRInterface::get_available_display_refresh_rates);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "session_mode", PROPERTY_HINT_NONE), "set_session_mode", "get_session_mode");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "required_features", PROPERTY_HINT_NONE), "set_required_features", "get_required_features");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "optional_features", PROPERTY_HINT_NONE), "set_optional_features", "get_optional_features");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "requested_reference_space_types", PROPERTY_HINT_NONE), "set_requested_reference_space_types", "get_requested_reference_space_types");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "reference_space_type", PROPERTY_HINT_NONE), "", "get_reference_space_type");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "enabled_features", PROPERTY_HINT_NONE), "", "get_enabled_features");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "visibility_state", PROPERTY_HINT_NONE), "", "get_visibility_state");
ADD_SIGNAL(MethodInfo("session_supported", PropertyInfo(Variant::STRING, "session_mode"), PropertyInfo(Variant::BOOL, "supported")));
ADD_SIGNAL(MethodInfo("session_started"));
ADD_SIGNAL(MethodInfo("session_ended"));
ADD_SIGNAL(MethodInfo("session_failed", PropertyInfo(Variant::STRING, "message")));
ADD_SIGNAL(MethodInfo("selectstart", PropertyInfo(Variant::INT, "input_source_id")));
ADD_SIGNAL(MethodInfo("select", PropertyInfo(Variant::INT, "input_source_id")));
ADD_SIGNAL(MethodInfo("selectend", PropertyInfo(Variant::INT, "input_source_id")));
ADD_SIGNAL(MethodInfo("squeezestart", PropertyInfo(Variant::INT, "input_source_id")));
ADD_SIGNAL(MethodInfo("squeeze", PropertyInfo(Variant::INT, "input_source_id")));
ADD_SIGNAL(MethodInfo("squeezeend", PropertyInfo(Variant::INT, "input_source_id")));
ADD_SIGNAL(MethodInfo("visibility_state_changed"));
ADD_SIGNAL(MethodInfo("reference_space_reset"));
ADD_SIGNAL(MethodInfo("display_refresh_rate_changed"));
BIND_ENUM_CONSTANT(TARGET_RAY_MODE_UNKNOWN);
BIND_ENUM_CONSTANT(TARGET_RAY_MODE_GAZE);
BIND_ENUM_CONSTANT(TARGET_RAY_MODE_TRACKED_POINTER);
BIND_ENUM_CONSTANT(TARGET_RAY_MODE_SCREEN);
}

View File

@@ -0,0 +1,79 @@
/**************************************************************************/
/* webxr_interface.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#include "servers/xr/xr_controller_tracker.h"
#include "servers/xr/xr_interface.h"
/**
The WebXR interface is a VR/AR interface that can be used on the web.
*/
class WebXRInterface : public XRInterface {
GDCLASS(WebXRInterface, XRInterface);
protected:
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
static void _bind_compatibility_methods();
Ref<XRPositionalTracker> _get_input_source_tracker_bind_compat_90645(int p_input_source_id) const;
#endif
public:
enum TargetRayMode {
TARGET_RAY_MODE_UNKNOWN,
TARGET_RAY_MODE_GAZE,
TARGET_RAY_MODE_TRACKED_POINTER,
TARGET_RAY_MODE_SCREEN,
};
virtual void is_session_supported(const String &p_session_mode) = 0;
virtual void set_session_mode(String p_session_mode) = 0;
virtual String get_session_mode() const = 0;
virtual void set_required_features(String p_required_features) = 0;
virtual String get_required_features() const = 0;
virtual void set_optional_features(String p_optional_features) = 0;
virtual String get_optional_features() const = 0;
virtual void set_requested_reference_space_types(String p_requested_reference_space_types) = 0;
virtual String get_requested_reference_space_types() const = 0;
virtual String get_reference_space_type() const = 0;
virtual String get_enabled_features() const = 0;
virtual bool is_input_source_active(int p_input_source_id) const = 0;
virtual Ref<XRControllerTracker> get_input_source_tracker(int p_input_source_id) const = 0;
virtual TargetRayMode get_input_source_target_ray_mode(int p_input_source_id) const = 0;
virtual String get_visibility_state() const = 0;
virtual float get_display_refresh_rate() const = 0;
virtual void set_display_refresh_rate(float p_refresh_rate) = 0;
virtual Array get_available_display_refresh_rates() const = 0;
};
VARIANT_ENUM_CAST(WebXRInterface::TargetRayMode);

View File

@@ -0,0 +1,890 @@
/**************************************************************************/
/* webxr_interface_js.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "webxr_interface_js.h"
#ifdef WEB_ENABLED
#include "godot_webxr.h"
#include "core/input/input.h"
#include "core/os/os.h"
#include "drivers/gles3/storage/texture_storage.h"
#include "scene/main/scene_tree.h"
#include "scene/main/window.h"
#include "scene/scene_string_names.h"
#include "servers/rendering/renderer_compositor.h"
#include "servers/rendering/rendering_server_globals.h"
#include "servers/xr/xr_hand_tracker.h"
#include <emscripten.h>
#include <cstdlib>
void _emwebxr_on_session_supported(char *p_session_mode, int p_supported) {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
Ref<WebXRInterfaceJS> interface = xr_server->find_interface("WebXR");
ERR_FAIL_COND(interface.is_null());
String session_mode = String(p_session_mode);
interface->emit_signal(SNAME("session_supported"), session_mode, p_supported ? true : false);
}
void _emwebxr_on_session_started(char *p_reference_space_type, char *p_enabled_features, char *p_environment_blend_mode) {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
Ref<WebXRInterfaceJS> interface = xr_server->find_interface("WebXR");
ERR_FAIL_COND(interface.is_null());
String reference_space_type = String(p_reference_space_type);
interface->_set_reference_space_type(reference_space_type);
interface->_set_enabled_features(p_enabled_features);
interface->_set_environment_blend_mode(p_environment_blend_mode);
interface->emit_signal(SNAME("session_started"));
}
void _emwebxr_on_session_ended() {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
Ref<WebXRInterfaceJS> interface = xr_server->find_interface("WebXR");
ERR_FAIL_COND(interface.is_null());
interface->uninitialize();
interface->emit_signal(SNAME("session_ended"));
}
void _emwebxr_on_session_failed(char *p_message) {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
Ref<WebXRInterfaceJS> interface = xr_server->find_interface("WebXR");
ERR_FAIL_COND(interface.is_null());
interface->uninitialize();
String message = String(p_message);
interface->emit_signal(SNAME("session_failed"), message);
}
extern "C" EMSCRIPTEN_KEEPALIVE void _emwebxr_on_input_event(int p_event_type, int p_input_source_id) {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
Ref<WebXRInterfaceJS> interface = xr_server->find_interface("WebXR");
ERR_FAIL_COND(interface.is_null());
interface->_on_input_event(p_event_type, p_input_source_id);
}
extern "C" EMSCRIPTEN_KEEPALIVE void _emwebxr_on_simple_event(char *p_signal_name) {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
Ref<WebXRInterfaceJS> interface = xr_server->find_interface("WebXR");
ERR_FAIL_COND(interface.is_null());
StringName signal_name = StringName(p_signal_name);
interface->emit_signal(signal_name);
}
void WebXRInterfaceJS::is_session_supported(const String &p_session_mode) {
godot_webxr_is_session_supported(p_session_mode.utf8().get_data(), &_emwebxr_on_session_supported);
}
void WebXRInterfaceJS::set_session_mode(String p_session_mode) {
session_mode = p_session_mode;
}
String WebXRInterfaceJS::get_session_mode() const {
return session_mode;
}
void WebXRInterfaceJS::set_required_features(String p_required_features) {
required_features = p_required_features;
}
String WebXRInterfaceJS::get_required_features() const {
return required_features;
}
void WebXRInterfaceJS::set_optional_features(String p_optional_features) {
optional_features = p_optional_features;
}
String WebXRInterfaceJS::get_optional_features() const {
return optional_features;
}
void WebXRInterfaceJS::set_requested_reference_space_types(String p_requested_reference_space_types) {
requested_reference_space_types = p_requested_reference_space_types;
}
String WebXRInterfaceJS::get_requested_reference_space_types() const {
return requested_reference_space_types;
}
String WebXRInterfaceJS::get_reference_space_type() const {
return reference_space_type;
}
String WebXRInterfaceJS::get_enabled_features() const {
return enabled_features;
}
bool WebXRInterfaceJS::is_input_source_active(int p_input_source_id) const {
ERR_FAIL_INDEX_V(p_input_source_id, input_source_count, false);
return input_sources[p_input_source_id].active;
}
Ref<XRControllerTracker> WebXRInterfaceJS::get_input_source_tracker(int p_input_source_id) const {
ERR_FAIL_INDEX_V(p_input_source_id, input_source_count, Ref<XRControllerTracker>());
return input_sources[p_input_source_id].tracker;
}
WebXRInterface::TargetRayMode WebXRInterfaceJS::get_input_source_target_ray_mode(int p_input_source_id) const {
ERR_FAIL_INDEX_V(p_input_source_id, input_source_count, WebXRInterface::TARGET_RAY_MODE_UNKNOWN);
if (!input_sources[p_input_source_id].active) {
return WebXRInterface::TARGET_RAY_MODE_UNKNOWN;
}
return input_sources[p_input_source_id].target_ray_mode;
}
String WebXRInterfaceJS::get_visibility_state() const {
char *c_str = godot_webxr_get_visibility_state();
if (c_str) {
String visibility_state = String(c_str);
free(c_str);
return visibility_state;
}
return String();
}
PackedVector3Array WebXRInterfaceJS::get_play_area() const {
PackedVector3Array ret;
float *points;
int point_count = godot_webxr_get_bounds_geometry(&points);
if (point_count > 0) {
ret.resize(point_count);
for (int i = 0; i < point_count; i++) {
float *js_vector3 = points + (i * 3);
ret.set(i, Vector3(js_vector3[0], js_vector3[1], js_vector3[2]));
}
free(points);
}
return ret;
}
float WebXRInterfaceJS::get_display_refresh_rate() const {
return godot_webxr_get_frame_rate();
}
void WebXRInterfaceJS::set_display_refresh_rate(float p_refresh_rate) {
godot_webxr_update_target_frame_rate(p_refresh_rate);
}
Array WebXRInterfaceJS::get_available_display_refresh_rates() const {
Array ret;
float *rates;
int rate_count = godot_webxr_get_supported_frame_rates(&rates);
if (rate_count > 0) {
ret.resize(rate_count);
for (int i = 0; i < rate_count; i++) {
ret[i] = rates[i];
}
free(rates);
}
return ret;
}
Array WebXRInterfaceJS::get_supported_environment_blend_modes() {
Array blend_modes;
// The blend mode can't be changed, so return the current blend mode as the only supported one.
blend_modes.push_back(environment_blend_mode);
return blend_modes;
}
XRInterface::EnvironmentBlendMode WebXRInterfaceJS::get_environment_blend_mode() const {
return environment_blend_mode;
}
bool WebXRInterfaceJS::set_environment_blend_mode(EnvironmentBlendMode p_new_environment_blend_mode) {
if (environment_blend_mode == p_new_environment_blend_mode) {
// Environment blend mode can't be changed, but we'll consider it a success to set it
// to what it already is.
return true;
}
return false;
}
void WebXRInterfaceJS::_set_environment_blend_mode(String p_blend_mode_string) {
if (p_blend_mode_string == "opaque") {
environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_OPAQUE;
} else if (p_blend_mode_string == "additive") {
environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_ADDITIVE;
} else if (p_blend_mode_string == "alpha-blend") {
environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_ALPHA_BLEND;
} else {
// Not all browsers can give us this information, so as a fallback,
// we'll make some guesses about the blend mode.
if (session_mode == "immersive-ar") {
environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_ALPHA_BLEND;
} else {
environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_OPAQUE;
}
}
}
StringName WebXRInterfaceJS::get_name() const {
return "WebXR";
}
uint32_t WebXRInterfaceJS::get_capabilities() const {
return XRInterface::XR_STEREO | XRInterface::XR_MONO | XRInterface::XR_VR | XRInterface::XR_AR;
}
uint32_t WebXRInterfaceJS::get_view_count() {
return godot_webxr_get_view_count();
}
bool WebXRInterfaceJS::is_initialized() const {
return (initialized);
}
bool WebXRInterfaceJS::initialize() {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, false);
if (!initialized) {
if (!godot_webxr_is_supported()) {
emit_signal("session_failed", "WebXR is unsupported by this web browser.");
return false;
}
if (session_mode == "immersive-vr" && !GLES3::Config::get_singleton()->multiview_supported) {
emit_signal("session_failed", "Stereo rendering in Godot requires multiview, but this web browser doesn't support it.");
return false;
}
if (requested_reference_space_types.is_empty()) {
emit_signal("session_failed", "No reference spaces were requested.");
return false;
}
enabled_features.clear();
// We must create a tracker for our head.
head_transform.basis = Basis();
head_transform.origin = Vector3();
head_tracker.instantiate();
head_tracker->set_tracker_type(XRServer::TRACKER_HEAD);
head_tracker->set_tracker_name("head");
head_tracker->set_tracker_desc("Players head");
xr_server->add_tracker(head_tracker);
// Make this our primary interface.
xr_server->set_primary_interface(this);
// Clear render_targetsize to make sure it gets reset to the new size.
// Clearing in uninitialize() doesn't work because a frame can still be
// rendered after it's called, which will fill render_targetsize again.
render_targetsize.width = 0;
render_targetsize.height = 0;
initialized = true;
godot_webxr_initialize(
session_mode.utf8().get_data(),
required_features.utf8().get_data(),
optional_features.utf8().get_data(),
requested_reference_space_types.utf8().get_data(),
&_emwebxr_on_session_started,
&_emwebxr_on_session_ended,
&_emwebxr_on_session_failed,
&_emwebxr_on_input_event,
&_emwebxr_on_simple_event);
};
return true;
}
void WebXRInterfaceJS::uninitialize() {
if (initialized) {
XRServer *xr_server = XRServer::get_singleton();
if (xr_server != nullptr) {
if (head_tracker.is_valid()) {
xr_server->remove_tracker(head_tracker);
head_tracker.unref();
}
for (int i = 0; i < HAND_MAX; i++) {
if (hand_trackers[i].is_valid()) {
xr_server->remove_tracker(hand_trackers[i]);
hand_trackers[i].unref();
}
}
if (xr_server->get_primary_interface() == this) {
// no longer our primary interface
xr_server->set_primary_interface(nullptr);
}
}
godot_webxr_uninitialize();
GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
if (texture_storage != nullptr) {
for (KeyValue<unsigned int, RID> &E : texture_cache) {
// Forcibly mark as not part of a render target so we can free it.
GLES3::Texture *texture = texture_storage->get_texture(E.value);
texture->is_render_target = false;
texture_storage->texture_free(E.value);
}
}
texture_cache.clear();
reference_space_type.clear();
enabled_features.clear();
environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_OPAQUE;
initialized = false;
};
}
Dictionary WebXRInterfaceJS::get_system_info() {
Dictionary dict;
// TODO get actual information from WebXR to return here
dict[SNAME("XRRuntimeName")] = String("WebXR");
dict[SNAME("XRRuntimeVersion")] = String("");
return dict;
}
Transform3D WebXRInterfaceJS::_js_matrix_to_transform(float *p_js_matrix) {
Transform3D transform;
transform.basis.rows[0].x = p_js_matrix[0];
transform.basis.rows[1].x = p_js_matrix[1];
transform.basis.rows[2].x = p_js_matrix[2];
transform.basis.rows[0].y = p_js_matrix[4];
transform.basis.rows[1].y = p_js_matrix[5];
transform.basis.rows[2].y = p_js_matrix[6];
transform.basis.rows[0].z = p_js_matrix[8];
transform.basis.rows[1].z = p_js_matrix[9];
transform.basis.rows[2].z = p_js_matrix[10];
transform.origin.x = p_js_matrix[12];
transform.origin.y = p_js_matrix[13];
transform.origin.z = p_js_matrix[14];
return transform;
}
Size2 WebXRInterfaceJS::get_render_target_size() {
if (render_targetsize.width != 0 && render_targetsize.height != 0) {
return render_targetsize;
}
int js_size[2];
bool has_size = godot_webxr_get_render_target_size(js_size);
if (!initialized || !has_size) {
// As a temporary default (until WebXR is fully initialized), use the
// window size.
return DisplayServer::get_singleton()->window_get_size();
}
render_targetsize.width = (float)js_size[0];
render_targetsize.height = (float)js_size[1];
return render_targetsize;
}
Transform3D WebXRInterfaceJS::get_camera_transform() {
Transform3D camera_transform;
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, camera_transform);
if (initialized) {
double world_scale = xr_server->get_world_scale();
Transform3D _head_transform = head_transform;
_head_transform.origin *= world_scale;
camera_transform = (xr_server->get_reference_frame()) * _head_transform;
}
return camera_transform;
}
Transform3D WebXRInterfaceJS::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, p_cam_transform);
ERR_FAIL_COND_V(!initialized, p_cam_transform);
float js_matrix[16];
bool has_transform = godot_webxr_get_transform_for_view(p_view, js_matrix);
if (!has_transform) {
return p_cam_transform;
}
Transform3D transform_for_view = _js_matrix_to_transform(js_matrix);
double world_scale = xr_server->get_world_scale();
transform_for_view.origin *= world_scale;
return p_cam_transform * xr_server->get_reference_frame() * transform_for_view;
}
Projection WebXRInterfaceJS::get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) {
Projection view;
ERR_FAIL_COND_V(!initialized, view);
float js_matrix[16];
bool has_projection = godot_webxr_get_projection_for_view(p_view, js_matrix);
if (!has_projection) {
return view;
}
int k = 0;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
view.columns[i][j] = js_matrix[k++];
}
}
// Copied from godot_oculus_mobile's ovr_mobile_session.cpp
view.columns[2][2] = -(p_z_far + p_z_near) / (p_z_far - p_z_near);
view.columns[3][2] = -(2.0f * p_z_far * p_z_near) / (p_z_far - p_z_near);
return view;
}
bool WebXRInterfaceJS::pre_draw_viewport(RID p_render_target) {
GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
if (texture_storage == nullptr) {
return false;
}
// Cache the resources so we don't have to get them from JS twice.
color_texture = _get_color_texture();
depth_texture = _get_depth_texture();
// Per the WebXR spec, it returns "opaque textures" to us, which may be the
// same WebGLTexture object (which would be the same GLuint in C++) but
// represent a different underlying resource (probably the next texture in
// the XR device's swap chain). In order to render to this texture, we need
// to re-attach it to the FBO, otherwise we get an "incomplete FBO" error.
//
// See: https://immersive-web.github.io/layers/#xropaquetextures
//
// So, even if the color and depth textures have the same GLuint as the last
// frame, we need to re-attach them again.
texture_storage->render_target_set_reattach_textures(p_render_target, true);
return true;
}
Vector<BlitToScreen> WebXRInterfaceJS::post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) {
Vector<BlitToScreen> blit_to_screen;
GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
if (texture_storage == nullptr) {
return blit_to_screen;
}
texture_storage->render_target_set_reattach_textures(p_render_target, false);
return blit_to_screen;
}
RID WebXRInterfaceJS::_get_color_texture() {
unsigned int texture_id = godot_webxr_get_color_texture();
if (texture_id == 0) {
return RID();
}
return _get_texture(texture_id);
}
RID WebXRInterfaceJS::_get_depth_texture() {
unsigned int texture_id = godot_webxr_get_depth_texture();
if (texture_id == 0) {
return RID();
}
return _get_texture(texture_id);
}
RID WebXRInterfaceJS::_get_texture(unsigned int p_texture_id) {
RBMap<unsigned int, RID>::Element *cache = texture_cache.find(p_texture_id);
if (cache != nullptr) {
return cache->get();
}
GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
if (texture_storage == nullptr) {
return RID();
}
uint32_t view_count = godot_webxr_get_view_count();
Size2 texture_size = get_render_target_size();
RID texture = texture_storage->texture_create_from_native_handle(
view_count == 1 ? RS::TEXTURE_TYPE_2D : RS::TEXTURE_TYPE_LAYERED,
Image::FORMAT_RGBA8,
p_texture_id,
(int)texture_size.width,
(int)texture_size.height,
1,
view_count);
texture_cache.insert(p_texture_id, texture);
return texture;
}
RID WebXRInterfaceJS::get_color_texture() {
return color_texture;
}
RID WebXRInterfaceJS::get_depth_texture() {
return depth_texture;
}
RID WebXRInterfaceJS::get_velocity_texture() {
unsigned int texture_id = godot_webxr_get_velocity_texture();
if (texture_id == 0) {
return RID();
}
return _get_texture(texture_id);
}
void WebXRInterfaceJS::process() {
if (initialized) {
// Get the "head" position.
float js_matrix[16];
if (godot_webxr_get_transform_for_view(-1, js_matrix)) {
head_transform = _js_matrix_to_transform(js_matrix);
}
if (head_tracker.is_valid()) {
head_tracker->set_pose("default", head_transform, Vector3(), Vector3());
}
// Update all input sources.
for (int i = 0; i < input_source_count; i++) {
_update_input_source(i);
}
};
}
void WebXRInterfaceJS::_update_input_source(int p_input_source_id) {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL(xr_server);
InputSource &input_source = input_sources[p_input_source_id];
float target_pose[16];
int tmp_target_ray_mode;
int touch_index;
int has_grip_pose;
float grip_pose[16];
int has_standard_mapping;
int button_count;
float buttons[10];
int axes_count;
float axes[10];
int has_hand_data;
float hand_joints[WEBXR_HAND_JOINT_MAX * 16];
float hand_radii[WEBXR_HAND_JOINT_MAX];
input_source.active = godot_webxr_update_input_source(
p_input_source_id,
target_pose,
&tmp_target_ray_mode,
&touch_index,
&has_grip_pose,
grip_pose,
&has_standard_mapping,
&button_count,
buttons,
&axes_count,
axes,
&has_hand_data,
hand_joints,
hand_radii);
if (!input_source.active) {
if (input_source.tracker.is_valid()) {
xr_server->remove_tracker(input_source.tracker);
input_source.tracker.unref();
}
return;
}
input_source.target_ray_mode = (WebXRInterface::TargetRayMode)tmp_target_ray_mode;
input_source.touch_index = touch_index;
Ref<XRControllerTracker> &tracker = input_source.tracker;
if (tracker.is_null()) {
tracker.instantiate();
StringName tracker_name;
if (input_source.target_ray_mode == WebXRInterface::TargetRayMode::TARGET_RAY_MODE_SCREEN) {
tracker_name = touch_names[touch_index];
} else {
tracker_name = tracker_names[p_input_source_id];
}
// Input source id's 0 and 1 are always the left and right hands.
if (p_input_source_id < 2) {
tracker->set_tracker_name(tracker_name);
tracker->set_tracker_desc(p_input_source_id == 0 ? "Left hand controller" : "Right hand controller");
tracker->set_tracker_hand(p_input_source_id == 0 ? XRPositionalTracker::TRACKER_HAND_LEFT : XRPositionalTracker::TRACKER_HAND_RIGHT);
} else {
tracker->set_tracker_name(tracker_name);
tracker->set_tracker_desc(tracker_name);
}
xr_server->add_tracker(tracker);
}
Transform3D aim_transform = _js_matrix_to_transform(target_pose);
tracker->set_pose(SceneStringName(default_), aim_transform, Vector3(), Vector3());
tracker->set_pose(SNAME("aim"), aim_transform, Vector3(), Vector3());
if (has_grip_pose) {
tracker->set_pose(SNAME("grip"), _js_matrix_to_transform(grip_pose), Vector3(), Vector3());
}
for (int i = 0; i < button_count; i++) {
StringName button_name = has_standard_mapping ? standard_button_names[i] : unknown_button_names[i];
StringName button_pressure_name = has_standard_mapping ? standard_button_pressure_names[i] : unknown_button_pressure_names[i];
float value = buttons[i];
bool state = value > 0.0;
tracker->set_input(button_name, state);
tracker->set_input(button_pressure_name, value);
}
for (int i = 0; i < axes_count; i++) {
StringName axis_name = has_standard_mapping ? standard_axis_names[i] : unknown_axis_names[i];
float value = axes[i];
if (has_standard_mapping && (i == 1 || i == 3)) {
// Invert the Y-axis on thumbsticks and trackpads, in order to
// match OpenXR and other XR platform SDKs.
value = -value;
}
tracker->set_input(axis_name, value);
}
// Also create Vector2's for the thumbstick and trackpad when we have the
// standard mapping.
if (has_standard_mapping) {
if (axes_count >= 2) {
tracker->set_input(standard_vector_names[0], Vector2(axes[0], -axes[1]));
}
if (axes_count >= 4) {
tracker->set_input(standard_vector_names[1], Vector2(axes[2], -axes[3]));
}
}
if (input_source.target_ray_mode == WebXRInterface::TARGET_RAY_MODE_SCREEN) {
if (touch_index < 5 && axes_count >= 2) {
Vector2 joy_vector = Vector2(axes[0], axes[1]);
Vector2 position = _get_screen_position_from_joy_vector(joy_vector);
if (touches[touch_index].is_touching) {
Vector2 delta = position - touches[touch_index].position;
// If position has changed by at least 1 pixel, generate a drag event.
if (std::abs(delta.x) >= 1.0 || std::abs(delta.y) >= 1.0) {
Ref<InputEventScreenDrag> event;
event.instantiate();
event->set_index(touch_index);
event->set_position(position);
event->set_relative(delta);
event->set_relative_screen_position(delta);
Input::get_singleton()->parse_input_event(event);
}
}
touches[touch_index].position = position;
}
}
if (p_input_source_id < 2) {
Ref<XRHandTracker> hand_tracker = hand_trackers[p_input_source_id];
if (has_hand_data) {
// Transform orientations to match Godot Humanoid skeleton.
const Basis bone_adjustment(
Vector3(-1.0, 0.0, 0.0),
Vector3(0.0, 0.0, -1.0),
Vector3(0.0, -1.0, 0.0));
if (unlikely(hand_tracker.is_null())) {
hand_tracker.instantiate();
hand_tracker->set_tracker_hand(p_input_source_id == 0 ? XRPositionalTracker::TRACKER_HAND_LEFT : XRPositionalTracker::TRACKER_HAND_RIGHT);
hand_tracker->set_tracker_name(p_input_source_id == 0 ? "/user/hand_tracker/left" : "/user/hand_tracker/right");
// These flags always apply, since WebXR doesn't give us enough insight to be more fine grained.
BitField<XRHandTracker::HandJointFlags> joint_flags(XRHandTracker::HAND_JOINT_FLAG_POSITION_VALID | XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_VALID | XRHandTracker::HAND_JOINT_FLAG_POSITION_TRACKED | XRHandTracker::HAND_JOINT_FLAG_ORIENTATION_TRACKED);
for (int godot_joint = 0; godot_joint < XRHandTracker::HAND_JOINT_MAX; godot_joint++) {
hand_tracker->set_hand_joint_flags((XRHandTracker::HandJoint)godot_joint, joint_flags);
}
hand_trackers[p_input_source_id] = hand_tracker;
xr_server->add_tracker(hand_tracker);
}
hand_tracker->set_has_tracking_data(true);
for (int webxr_joint = 0; webxr_joint < WEBXR_HAND_JOINT_MAX; webxr_joint++) {
XRHandTracker::HandJoint godot_joint = (XRHandTracker::HandJoint)(webxr_joint + 1);
Transform3D joint_transform = _js_matrix_to_transform(hand_joints + (16 * webxr_joint));
joint_transform.basis *= bone_adjustment;
hand_tracker->set_hand_joint_transform(godot_joint, joint_transform);
hand_tracker->set_hand_joint_radius(godot_joint, hand_radii[webxr_joint]);
}
// WebXR doesn't have a palm joint, so we calculate it by finding the middle of the middle finger metacarpal bone.
{
// Start by getting the middle finger metacarpal joint.
// Note: 10 is the WebXR middle finger metacarpal joint.
Transform3D palm_transform = _js_matrix_to_transform(hand_joints + (10 * 16));
palm_transform.basis *= bone_adjustment;
// Get the middle finger phalanx position.
// Note: 11 is the WebXR middle finger phalanx proximal joint and 12 is the origin offset.
const float *phalanx_pos = hand_joints + (11 * 16) + 12;
Vector3 phalanx(phalanx_pos[0], phalanx_pos[1], phalanx_pos[2]);
// Offset the palm half-way towards the phalanx joint.
palm_transform.origin = (palm_transform.origin + phalanx) / 2.0;
// Set the palm joint and the pose.
hand_tracker->set_hand_joint_transform(XRHandTracker::HAND_JOINT_PALM, palm_transform);
hand_tracker->set_pose("default", palm_transform, Vector3(), Vector3());
}
} else if (hand_tracker.is_valid()) {
hand_tracker->set_has_tracking_data(false);
hand_tracker->invalidate_pose("default");
}
}
}
void WebXRInterfaceJS::_on_input_event(int p_event_type, int p_input_source_id) {
// Get the latest data for this input source. For transient input sources,
// we may not have any data at all yet!
_update_input_source(p_input_source_id);
if (p_event_type == WEBXR_INPUT_EVENT_SELECTSTART || p_event_type == WEBXR_INPUT_EVENT_SELECTEND) {
const InputSource &input_source = input_sources[p_input_source_id];
if (input_source.target_ray_mode == WebXRInterface::TARGET_RAY_MODE_SCREEN) {
int touch_index = input_source.touch_index;
if (touch_index >= 0 && touch_index < 5) {
touches[touch_index].is_touching = (p_event_type == WEBXR_INPUT_EVENT_SELECTSTART);
Ref<InputEventScreenTouch> event;
event.instantiate();
event->set_index(touch_index);
event->set_position(touches[touch_index].position);
event->set_pressed(p_event_type == WEBXR_INPUT_EVENT_SELECTSTART);
Input::get_singleton()->parse_input_event(event);
}
}
}
switch (p_event_type) {
case WEBXR_INPUT_EVENT_SELECTSTART:
emit_signal("selectstart", p_input_source_id);
break;
case WEBXR_INPUT_EVENT_SELECTEND:
emit_signal("selectend", p_input_source_id);
// Emit the 'select' event on our own (rather than intercepting the
// one from JavaScript) so that we don't have to needlessly call
// _update_input_source() a second time.
emit_signal("select", p_input_source_id);
break;
case WEBXR_INPUT_EVENT_SQUEEZESTART:
emit_signal("squeezestart", p_input_source_id);
break;
case WEBXR_INPUT_EVENT_SQUEEZEEND:
emit_signal("squeezeend", p_input_source_id);
// Again, we emit the 'squeeze' event on our own to avoid extra work.
emit_signal("squeeze", p_input_source_id);
break;
}
}
Vector2 WebXRInterfaceJS::_get_screen_position_from_joy_vector(const Vector2 &p_joy_vector) {
SceneTree *scene_tree = Object::cast_to<SceneTree>(OS::get_singleton()->get_main_loop());
if (!scene_tree) {
return Vector2();
}
Window *viewport = scene_tree->get_root();
Vector2 position_percentage((p_joy_vector.x + 1.0f) / 2.0f, ((p_joy_vector.y) + 1.0f) / 2.0f);
Vector2 position = (Size2)viewport->get_size() * position_percentage;
return position;
}
WebXRInterfaceJS::WebXRInterfaceJS() {
initialized = false;
session_mode = "inline";
requested_reference_space_types = "local";
}
WebXRInterfaceJS::~WebXRInterfaceJS() {
// and make sure we cleanup if we haven't already
if (initialized) {
uninitialize();
};
}
#endif // WEB_ENABLED

View File

@@ -0,0 +1,264 @@
/**************************************************************************/
/* webxr_interface_js.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#pragma once
#ifdef WEB_ENABLED
#include "servers/xr/xr_controller_tracker.h"
#include "servers/xr/xr_hand_tracker.h"
#include "webxr_interface.h"
/**
The WebXR interface is a VR/AR interface that can be used on the web.
*/
namespace GLES3 {
class TextureStorage;
}
class WebXRInterfaceJS : public WebXRInterface {
GDCLASS(WebXRInterfaceJS, WebXRInterface);
private:
bool initialized;
Ref<XRPositionalTracker> head_tracker;
Transform3D head_transform;
String session_mode;
String required_features;
String optional_features;
String requested_reference_space_types;
String reference_space_type;
String enabled_features;
XRInterface::EnvironmentBlendMode environment_blend_mode = XRInterface::XR_ENV_BLEND_MODE_OPAQUE;
Size2 render_targetsize;
RBMap<unsigned int, RID> texture_cache;
struct Touch {
bool is_touching = false;
Vector2 position;
} touches[5];
static constexpr uint8_t input_source_count = 16;
struct InputSource {
Ref<XRControllerTracker> tracker;
bool active = false;
TargetRayMode target_ray_mode;
int touch_index = -1;
} input_sources[input_source_count];
static const int WEBXR_HAND_JOINT_MAX = 25;
static const int HAND_MAX = 2;
Ref<XRHandTracker> hand_trackers[HAND_MAX];
RID color_texture;
RID depth_texture;
RID _get_color_texture();
RID _get_depth_texture();
RID _get_texture(unsigned int p_texture_id);
Transform3D _js_matrix_to_transform(float *p_js_matrix);
void _update_input_source(int p_input_source_id);
Vector2 _get_screen_position_from_joy_vector(const Vector2 &p_joy_vector);
public:
virtual void is_session_supported(const String &p_session_mode) override;
virtual void set_session_mode(String p_session_mode) override;
virtual String get_session_mode() const override;
virtual void set_required_features(String p_required_features) override;
virtual String get_required_features() const override;
virtual void set_optional_features(String p_optional_features) override;
virtual String get_optional_features() const override;
virtual void set_requested_reference_space_types(String p_requested_reference_space_types) override;
virtual String get_requested_reference_space_types() const override;
virtual String get_reference_space_type() const override;
virtual String get_enabled_features() const override;
virtual bool is_input_source_active(int p_input_source_id) const override;
virtual Ref<XRControllerTracker> get_input_source_tracker(int p_input_source_id) const override;
virtual TargetRayMode get_input_source_target_ray_mode(int p_input_source_id) const override;
virtual String get_visibility_state() const override;
virtual PackedVector3Array get_play_area() const override;
virtual float get_display_refresh_rate() const override;
virtual void set_display_refresh_rate(float p_refresh_rate) override;
virtual Array get_available_display_refresh_rates() const override;
virtual Array get_supported_environment_blend_modes() override;
virtual XRInterface::EnvironmentBlendMode get_environment_blend_mode() const override;
virtual bool set_environment_blend_mode(EnvironmentBlendMode p_new_environment_blend_mode) override;
virtual StringName get_name() const override;
virtual uint32_t get_capabilities() const override;
virtual bool is_initialized() const override;
virtual bool initialize() override;
virtual void uninitialize() override;
virtual Dictionary get_system_info() override;
virtual Size2 get_render_target_size() override;
virtual uint32_t get_view_count() override;
virtual Transform3D get_camera_transform() override;
virtual Transform3D get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) override;
virtual Projection get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) override;
virtual bool pre_draw_viewport(RID p_render_target) override;
virtual Vector<BlitToScreen> post_draw_viewport(RID p_render_target, const Rect2 &p_screen_rect) override;
virtual RID get_color_texture() override;
virtual RID get_depth_texture() override;
virtual RID get_velocity_texture() override;
virtual void process() override;
void _on_input_event(int p_event_type, int p_input_source_id);
// Internal setters used by callbacks from Emscripten.
inline void _set_reference_space_type(String p_reference_space_type) { reference_space_type = p_reference_space_type; }
inline void _set_enabled_features(String p_enabled_features) { enabled_features = p_enabled_features; }
void _set_environment_blend_mode(String p_blend_mode_string);
WebXRInterfaceJS();
~WebXRInterfaceJS();
private:
StringName tracker_names[16] = {
StringName("left_hand"),
StringName("right_hand"),
StringName("tracker_2"),
StringName("tracker_3"),
StringName("tracker_4"),
StringName("tracker_5"),
StringName("tracker_6"),
StringName("tracker_7"),
StringName("tracker_8"),
StringName("tracker_9"),
StringName("tracker_10"),
StringName("tracker_11"),
StringName("tracker_12"),
StringName("tracker_13"),
StringName("tracker_14"),
StringName("tracker_15"),
};
StringName touch_names[5] = {
StringName("touch_0"),
StringName("touch_1"),
StringName("touch_2"),
StringName("touch_3"),
StringName("touch_4"),
};
StringName standard_axis_names[10] = {
StringName("touchpad_x"),
StringName("touchpad_y"),
StringName("thumbstick_x"),
StringName("thumbstick_y"),
StringName("axis_4"),
StringName("axis_5"),
StringName("axis_6"),
StringName("axis_7"),
StringName("axis_8"),
StringName("axis_9"),
};
StringName standard_vector_names[2] = {
StringName("touchpad"),
StringName("thumbstick"),
};
StringName standard_button_names[10] = {
StringName("trigger_click"),
StringName("grip_click"),
StringName("touchpad_click"),
StringName("thumbstick_click"),
StringName("ax_button"),
StringName("by_button"),
StringName("button_6"),
StringName("button_7"),
StringName("button_8"),
StringName("button_9"),
};
StringName standard_button_pressure_names[10] = {
StringName("trigger"),
StringName("grip"),
StringName("touchpad_click_pressure"),
StringName("thumbstick_click_pressure"),
StringName("ax_button_pressure"),
StringName("by_button_pressure"),
StringName("button_pressure_6"),
StringName("button_pressure_7"),
StringName("button_pressure_8"),
StringName("button_pressure_9"),
};
StringName unknown_button_names[10] = {
StringName("button_0"),
StringName("button_1"),
StringName("button_2"),
StringName("button_3"),
StringName("button_4"),
StringName("button_5"),
StringName("button_6"),
StringName("button_7"),
StringName("button_8"),
StringName("button_9"),
};
StringName unknown_axis_names[10] = {
StringName("axis_0"),
StringName("axis_1"),
StringName("axis_2"),
StringName("axis_3"),
StringName("axis_4"),
StringName("axis_5"),
StringName("axis_6"),
StringName("axis_7"),
StringName("axis_8"),
StringName("axis_9"),
};
StringName unknown_button_pressure_names[10] = {
StringName("button_pressure_0"),
StringName("button_pressure_1"),
StringName("button_pressure_2"),
StringName("button_pressure_3"),
StringName("button_pressure_4"),
StringName("button_pressure_5"),
StringName("button_pressure_6"),
StringName("button_pressure_7"),
StringName("button_pressure_8"),
StringName("button_pressure_9"),
};
};
#endif // WEB_ENABLED