library_godot_webxr.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  1. /**************************************************************************/
  2. /* library_godot_webxr.js */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. const GodotWebXR = {
  31. $GodotWebXR__deps: ['$Browser', '$GL', '$GodotRuntime', '$runtimeKeepalivePush', '$runtimeKeepalivePop'],
  32. $GodotWebXR: {
  33. gl: null,
  34. session: null,
  35. gl_binding: null,
  36. layer: null,
  37. space: null,
  38. frame: null,
  39. pose: null,
  40. view_count: 1,
  41. input_sources: new Array(16),
  42. touches: new Array(5),
  43. onsimpleevent: null,
  44. // Monkey-patch the requestAnimationFrame() used by Emscripten for the main
  45. // loop, so that we can swap it out for XRSession.requestAnimationFrame()
  46. // when an XR session is started.
  47. orig_requestAnimationFrame: null,
  48. requestAnimationFrame: (callback) => {
  49. if (GodotWebXR.session && GodotWebXR.space) {
  50. const onFrame = function (time, frame) {
  51. GodotWebXR.frame = frame;
  52. GodotWebXR.pose = frame.getViewerPose(GodotWebXR.space);
  53. callback(time);
  54. GodotWebXR.frame = null;
  55. GodotWebXR.pose = null;
  56. };
  57. GodotWebXR.session.requestAnimationFrame(onFrame);
  58. } else {
  59. GodotWebXR.orig_requestAnimationFrame(callback);
  60. }
  61. },
  62. monkeyPatchRequestAnimationFrame: (enable) => {
  63. if (GodotWebXR.orig_requestAnimationFrame === null) {
  64. GodotWebXR.orig_requestAnimationFrame = Browser.requestAnimationFrame;
  65. }
  66. Browser.requestAnimationFrame = enable
  67. ? GodotWebXR.requestAnimationFrame : GodotWebXR.orig_requestAnimationFrame;
  68. },
  69. pauseResumeMainLoop: () => {
  70. // Once both GodotWebXR.session and GodotWebXR.space are set or
  71. // unset, our monkey-patched requestAnimationFrame() should be
  72. // enabled or disabled. When using the WebXR API Emulator, this
  73. // gets picked up automatically, however, in the Oculus Browser
  74. // on the Quest, we need to pause and resume the main loop.
  75. Browser.mainLoop.pause();
  76. runtimeKeepalivePush(); // eslint-disable-line no-undef
  77. window.setTimeout(function () {
  78. runtimeKeepalivePop(); // eslint-disable-line no-undef
  79. Browser.mainLoop.resume();
  80. }, 0);
  81. },
  82. getLayer: () => {
  83. const new_view_count = (GodotWebXR.pose) ? GodotWebXR.pose.views.length : 1;
  84. let layer = GodotWebXR.layer;
  85. // If the view count hasn't changed since creating this layer, then
  86. // we can simply return it.
  87. if (layer && GodotWebXR.view_count === new_view_count) {
  88. return layer;
  89. }
  90. if (!GodotWebXR.session || !GodotWebXR.gl_binding) {
  91. return null;
  92. }
  93. const gl = GodotWebXR.gl;
  94. layer = GodotWebXR.gl_binding.createProjectionLayer({
  95. textureType: new_view_count > 1 ? 'texture-array' : 'texture',
  96. colorFormat: gl.RGBA8,
  97. depthFormat: gl.DEPTH_COMPONENT24,
  98. });
  99. GodotWebXR.session.updateRenderState({ layers: [layer] });
  100. GodotWebXR.layer = layer;
  101. GodotWebXR.view_count = new_view_count;
  102. return layer;
  103. },
  104. getSubImage: () => {
  105. if (!GodotWebXR.pose) {
  106. return null;
  107. }
  108. const layer = GodotWebXR.getLayer();
  109. if (layer === null) {
  110. return null;
  111. }
  112. // Because we always use "texture-array" for multiview and "texture"
  113. // when there is only 1 view, it should be safe to only grab the
  114. // subimage for the first view.
  115. return GodotWebXR.gl_binding.getViewSubImage(layer, GodotWebXR.pose.views[0]);
  116. },
  117. getTextureId: (texture) => {
  118. if (texture.name !== undefined) {
  119. return texture.name;
  120. }
  121. const id = GL.getNewId(GL.textures);
  122. texture.name = id;
  123. GL.textures[id] = texture;
  124. return id;
  125. },
  126. addInputSource: (input_source) => {
  127. let name = -1;
  128. if (input_source.targetRayMode === 'tracked-pointer' && input_source.handedness === 'left') {
  129. name = 0;
  130. } else if (input_source.targetRayMode === 'tracked-pointer' && input_source.handedness === 'right') {
  131. name = 1;
  132. } else {
  133. for (let i = 2; i < 16; i++) {
  134. if (!GodotWebXR.input_sources[i]) {
  135. name = i;
  136. break;
  137. }
  138. }
  139. }
  140. if (name >= 0) {
  141. GodotWebXR.input_sources[name] = input_source;
  142. input_source.name = name;
  143. // Find a free touch index for screen sources.
  144. if (input_source.targetRayMode === 'screen') {
  145. let touch_index = -1;
  146. for (let i = 0; i < 5; i++) {
  147. if (!GodotWebXR.touches[i]) {
  148. touch_index = i;
  149. break;
  150. }
  151. }
  152. if (touch_index >= 0) {
  153. GodotWebXR.touches[touch_index] = input_source;
  154. input_source.touch_index = touch_index;
  155. }
  156. }
  157. }
  158. return name;
  159. },
  160. removeInputSource: (input_source) => {
  161. if (input_source.name !== undefined) {
  162. const name = input_source.name;
  163. if (name >= 0 && name < 16) {
  164. GodotWebXR.input_sources[name] = null;
  165. }
  166. if (input_source.touch_index !== undefined) {
  167. const touch_index = input_source.touch_index;
  168. if (touch_index >= 0 && touch_index < 5) {
  169. GodotWebXR.touches[touch_index] = null;
  170. }
  171. }
  172. return name;
  173. }
  174. return -1;
  175. },
  176. getInputSourceId: (input_source) => {
  177. if (input_source !== undefined) {
  178. return input_source.name;
  179. }
  180. return -1;
  181. },
  182. getTouchIndex: (input_source) => {
  183. if (input_source.touch_index !== undefined) {
  184. return input_source.touch_index;
  185. }
  186. return -1;
  187. },
  188. },
  189. godot_webxr_is_supported__proxy: 'sync',
  190. godot_webxr_is_supported__sig: 'i',
  191. godot_webxr_is_supported: function () {
  192. return !!navigator.xr;
  193. },
  194. godot_webxr_is_session_supported__proxy: 'sync',
  195. godot_webxr_is_session_supported__sig: 'vii',
  196. godot_webxr_is_session_supported: function (p_session_mode, p_callback) {
  197. const session_mode = GodotRuntime.parseString(p_session_mode);
  198. const cb = GodotRuntime.get_func(p_callback);
  199. if (navigator.xr) {
  200. navigator.xr.isSessionSupported(session_mode).then(function (supported) {
  201. const c_str = GodotRuntime.allocString(session_mode);
  202. cb(c_str, supported ? 1 : 0);
  203. GodotRuntime.free(c_str);
  204. });
  205. } else {
  206. const c_str = GodotRuntime.allocString(session_mode);
  207. cb(c_str, 0);
  208. GodotRuntime.free(c_str);
  209. }
  210. },
  211. godot_webxr_initialize__deps: ['emscripten_webgl_get_current_context'],
  212. godot_webxr_initialize__proxy: 'sync',
  213. godot_webxr_initialize__sig: 'viiiiiiiii',
  214. 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) {
  215. GodotWebXR.monkeyPatchRequestAnimationFrame(true);
  216. const session_mode = GodotRuntime.parseString(p_session_mode);
  217. const required_features = GodotRuntime.parseString(p_required_features).split(',').map((s) => s.trim()).filter((s) => s !== '');
  218. const optional_features = GodotRuntime.parseString(p_optional_features).split(',').map((s) => s.trim()).filter((s) => s !== '');
  219. const requested_reference_space_types = GodotRuntime.parseString(p_requested_reference_spaces).split(',').map((s) => s.trim());
  220. const onstarted = GodotRuntime.get_func(p_on_session_started);
  221. const onended = GodotRuntime.get_func(p_on_session_ended);
  222. const onfailed = GodotRuntime.get_func(p_on_session_failed);
  223. const oninputevent = GodotRuntime.get_func(p_on_input_event);
  224. const onsimpleevent = GodotRuntime.get_func(p_on_simple_event);
  225. const session_init = {};
  226. if (required_features.length > 0) {
  227. session_init['requiredFeatures'] = required_features;
  228. }
  229. if (optional_features.length > 0) {
  230. session_init['optionalFeatures'] = optional_features;
  231. }
  232. navigator.xr.requestSession(session_mode, session_init).then(function (session) {
  233. GodotWebXR.session = session;
  234. session.addEventListener('end', function (evt) {
  235. onended();
  236. });
  237. session.addEventListener('inputsourceschange', function (evt) {
  238. evt.added.forEach(GodotWebXR.addInputSource);
  239. evt.removed.forEach(GodotWebXR.removeInputSource);
  240. });
  241. ['selectstart', 'selectend', 'squeezestart', 'squeezeend'].forEach((input_event, index) => {
  242. session.addEventListener(input_event, function (evt) {
  243. // Since this happens in-between normal frames, we need to
  244. // grab the frame from the event in order to get poses for
  245. // the input sources.
  246. GodotWebXR.frame = evt.frame;
  247. oninputevent(index, GodotWebXR.getInputSourceId(evt.inputSource));
  248. GodotWebXR.frame = null;
  249. });
  250. });
  251. session.addEventListener('visibilitychange', function (evt) {
  252. const c_str = GodotRuntime.allocString('visibility_state_changed');
  253. onsimpleevent(c_str);
  254. GodotRuntime.free(c_str);
  255. });
  256. // Store onsimpleevent so we can use it later.
  257. GodotWebXR.onsimpleevent = onsimpleevent;
  258. const gl_context_handle = _emscripten_webgl_get_current_context(); // eslint-disable-line no-undef
  259. const gl = GL.getContext(gl_context_handle).GLctx;
  260. GodotWebXR.gl = gl;
  261. gl.makeXRCompatible().then(function () {
  262. GodotWebXR.gl_binding = new XRWebGLBinding(session, gl); // eslint-disable-line no-undef
  263. // This will trigger the layer to get created.
  264. GodotWebXR.getLayer();
  265. function onReferenceSpaceSuccess(reference_space, reference_space_type) {
  266. GodotWebXR.space = reference_space;
  267. // Using reference_space.addEventListener() crashes when
  268. // using the polyfill with the WebXR Emulator extension,
  269. // so we set the event property instead.
  270. reference_space.onreset = function (evt) {
  271. const c_str = GodotRuntime.allocString('reference_space_reset');
  272. onsimpleevent(c_str);
  273. GodotRuntime.free(c_str);
  274. };
  275. // Now that both GodotWebXR.session and GodotWebXR.space are
  276. // set, we need to pause and resume the main loop for the XR
  277. // main loop to kick in.
  278. GodotWebXR.pauseResumeMainLoop();
  279. // Call in setTimeout() so that errors in the onstarted()
  280. // callback don't bubble up here and cause Godot to try the
  281. // next reference space.
  282. window.setTimeout(function () {
  283. const c_str = GodotRuntime.allocString(reference_space_type);
  284. onstarted(c_str);
  285. GodotRuntime.free(c_str);
  286. }, 0);
  287. }
  288. function requestReferenceSpace() {
  289. const reference_space_type = requested_reference_space_types.shift();
  290. session.requestReferenceSpace(reference_space_type)
  291. .then((refSpace) => {
  292. onReferenceSpaceSuccess(refSpace, reference_space_type);
  293. })
  294. .catch(() => {
  295. if (requested_reference_space_types.length === 0) {
  296. const c_str = GodotRuntime.allocString('Unable to get any of the requested reference space types');
  297. onfailed(c_str);
  298. GodotRuntime.free(c_str);
  299. } else {
  300. requestReferenceSpace();
  301. }
  302. });
  303. }
  304. requestReferenceSpace();
  305. }).catch(function (error) {
  306. const c_str = GodotRuntime.allocString(`Unable to make WebGL context compatible with WebXR: ${error}`);
  307. onfailed(c_str);
  308. GodotRuntime.free(c_str);
  309. });
  310. }).catch(function (error) {
  311. const c_str = GodotRuntime.allocString(`Unable to start session: ${error}`);
  312. onfailed(c_str);
  313. GodotRuntime.free(c_str);
  314. });
  315. },
  316. godot_webxr_uninitialize__proxy: 'sync',
  317. godot_webxr_uninitialize__sig: 'v',
  318. godot_webxr_uninitialize: function () {
  319. if (GodotWebXR.session) {
  320. GodotWebXR.session.end()
  321. // Prevent exception when session has already ended.
  322. .catch((e) => { });
  323. }
  324. GodotWebXR.session = null;
  325. GodotWebXR.gl_binding = null;
  326. GodotWebXR.layer = null;
  327. GodotWebXR.space = null;
  328. GodotWebXR.frame = null;
  329. GodotWebXR.pose = null;
  330. GodotWebXR.view_count = 1;
  331. GodotWebXR.input_sources = new Array(16);
  332. GodotWebXR.touches = new Array(5);
  333. GodotWebXR.onsimpleevent = null;
  334. // Disable the monkey-patched window.requestAnimationFrame() and
  335. // pause/restart the main loop to activate it on all platforms.
  336. GodotWebXR.monkeyPatchRequestAnimationFrame(false);
  337. GodotWebXR.pauseResumeMainLoop();
  338. },
  339. godot_webxr_get_view_count__proxy: 'sync',
  340. godot_webxr_get_view_count__sig: 'i',
  341. godot_webxr_get_view_count: function () {
  342. if (!GodotWebXR.session || !GodotWebXR.pose) {
  343. return 1;
  344. }
  345. const view_count = GodotWebXR.pose.views.length;
  346. return view_count > 0 ? view_count : 1;
  347. },
  348. godot_webxr_get_render_target_size__proxy: 'sync',
  349. godot_webxr_get_render_target_size__sig: 'ii',
  350. godot_webxr_get_render_target_size: function (r_size) {
  351. const subimage = GodotWebXR.getSubImage();
  352. if (subimage === null) {
  353. return false;
  354. }
  355. GodotRuntime.setHeapValue(r_size + 0, subimage.viewport.width, 'i32');
  356. GodotRuntime.setHeapValue(r_size + 4, subimage.viewport.height, 'i32');
  357. return true;
  358. },
  359. godot_webxr_get_transform_for_view__proxy: 'sync',
  360. godot_webxr_get_transform_for_view__sig: 'iii',
  361. godot_webxr_get_transform_for_view: function (p_view, r_transform) {
  362. if (!GodotWebXR.session || !GodotWebXR.pose) {
  363. return false;
  364. }
  365. const views = GodotWebXR.pose.views;
  366. let matrix;
  367. if (p_view >= 0) {
  368. matrix = views[p_view].transform.matrix;
  369. } else {
  370. // For -1 (or any other negative value) return the HMD transform.
  371. matrix = GodotWebXR.pose.transform.matrix;
  372. }
  373. for (let i = 0; i < 16; i++) {
  374. GodotRuntime.setHeapValue(r_transform + (i * 4), matrix[i], 'float');
  375. }
  376. return true;
  377. },
  378. godot_webxr_get_projection_for_view__proxy: 'sync',
  379. godot_webxr_get_projection_for_view__sig: 'iii',
  380. godot_webxr_get_projection_for_view: function (p_view, r_transform) {
  381. if (!GodotWebXR.session || !GodotWebXR.pose) {
  382. return false;
  383. }
  384. const matrix = GodotWebXR.pose.views[p_view].projectionMatrix;
  385. for (let i = 0; i < 16; i++) {
  386. GodotRuntime.setHeapValue(r_transform + (i * 4), matrix[i], 'float');
  387. }
  388. return true;
  389. },
  390. godot_webxr_get_color_texture__proxy: 'sync',
  391. godot_webxr_get_color_texture__sig: 'i',
  392. godot_webxr_get_color_texture: function () {
  393. const subimage = GodotWebXR.getSubImage();
  394. if (subimage === null) {
  395. return 0;
  396. }
  397. return GodotWebXR.getTextureId(subimage.colorTexture);
  398. },
  399. godot_webxr_get_depth_texture__proxy: 'sync',
  400. godot_webxr_get_depth_texture__sig: 'i',
  401. godot_webxr_get_depth_texture: function () {
  402. const subimage = GodotWebXR.getSubImage();
  403. if (subimage === null) {
  404. return 0;
  405. }
  406. if (!subimage.depthStencilTexture) {
  407. return 0;
  408. }
  409. return GodotWebXR.getTextureId(subimage.depthStencilTexture);
  410. },
  411. godot_webxr_get_velocity_texture__proxy: 'sync',
  412. godot_webxr_get_velocity_texture__sig: 'i',
  413. godot_webxr_get_velocity_texture: function () {
  414. const subimage = GodotWebXR.getSubImage();
  415. if (subimage === null) {
  416. return 0;
  417. }
  418. if (!subimage.motionVectorTexture) {
  419. return 0;
  420. }
  421. return GodotWebXR.getTextureId(subimage.motionVectorTexture);
  422. },
  423. godot_webxr_update_input_source__proxy: 'sync',
  424. godot_webxr_update_input_source__sig: 'iiiiiiiiiiii',
  425. 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) {
  426. if (!GodotWebXR.session || !GodotWebXR.frame) {
  427. return 0;
  428. }
  429. if (p_input_source_id < 0 || p_input_source_id >= GodotWebXR.input_sources.length || !GodotWebXR.input_sources[p_input_source_id]) {
  430. return false;
  431. }
  432. const input_source = GodotWebXR.input_sources[p_input_source_id];
  433. const frame = GodotWebXR.frame;
  434. const space = GodotWebXR.space;
  435. // Target pose.
  436. const target_pose = frame.getPose(input_source.targetRaySpace, space);
  437. if (!target_pose) {
  438. // This can mean that the controller lost tracking.
  439. return false;
  440. }
  441. const target_pose_matrix = target_pose.transform.matrix;
  442. for (let i = 0; i < 16; i++) {
  443. GodotRuntime.setHeapValue(r_target_pose + (i * 4), target_pose_matrix[i], 'float');
  444. }
  445. // Target ray mode.
  446. let target_ray_mode = 0;
  447. switch (input_source.targetRayMode) {
  448. case 'gaze':
  449. target_ray_mode = 1;
  450. break;
  451. case 'tracked-pointer':
  452. target_ray_mode = 2;
  453. break;
  454. case 'screen':
  455. target_ray_mode = 3;
  456. break;
  457. default:
  458. }
  459. GodotRuntime.setHeapValue(r_target_ray_mode, target_ray_mode, 'i32');
  460. // Touch index.
  461. GodotRuntime.setHeapValue(r_touch_index, GodotWebXR.getTouchIndex(input_source), 'i32');
  462. // Grip pose.
  463. let has_grip_pose = false;
  464. if (input_source.gripSpace) {
  465. const grip_pose = frame.getPose(input_source.gripSpace, space);
  466. if (grip_pose) {
  467. const grip_pose_matrix = grip_pose.transform.matrix;
  468. for (let i = 0; i < 16; i++) {
  469. GodotRuntime.setHeapValue(r_grip_pose + (i * 4), grip_pose_matrix[i], 'float');
  470. }
  471. has_grip_pose = true;
  472. }
  473. }
  474. GodotRuntime.setHeapValue(r_has_grip_pose, has_grip_pose ? 1 : 0, 'i32');
  475. // Gamepad data (mapping, buttons and axes).
  476. let has_standard_mapping = false;
  477. let button_count = 0;
  478. let axes_count = 0;
  479. if (input_source.gamepad) {
  480. if (input_source.gamepad.mapping === 'xr-standard') {
  481. has_standard_mapping = true;
  482. }
  483. button_count = Math.min(input_source.gamepad.buttons.length, 10);
  484. for (let i = 0; i < button_count; i++) {
  485. GodotRuntime.setHeapValue(r_buttons + (i * 4), input_source.gamepad.buttons[i].value, 'float');
  486. }
  487. axes_count = Math.min(input_source.gamepad.axes.length, 10);
  488. for (let i = 0; i < axes_count; i++) {
  489. GodotRuntime.setHeapValue(r_axes + (i * 4), input_source.gamepad.axes[i], 'float');
  490. }
  491. }
  492. GodotRuntime.setHeapValue(r_has_standard_mapping, has_standard_mapping ? 1 : 0, 'i32');
  493. GodotRuntime.setHeapValue(r_button_count, button_count, 'i32');
  494. GodotRuntime.setHeapValue(r_axes_count, axes_count, 'i32');
  495. return true;
  496. },
  497. godot_webxr_get_visibility_state__proxy: 'sync',
  498. godot_webxr_get_visibility_state__sig: 'i',
  499. godot_webxr_get_visibility_state: function () {
  500. if (!GodotWebXR.session || !GodotWebXR.session.visibilityState) {
  501. return 0;
  502. }
  503. return GodotRuntime.allocString(GodotWebXR.session.visibilityState);
  504. },
  505. godot_webxr_get_bounds_geometry__proxy: 'sync',
  506. godot_webxr_get_bounds_geometry__sig: 'ii',
  507. godot_webxr_get_bounds_geometry: function (r_points) {
  508. if (!GodotWebXR.space || !GodotWebXR.space.boundsGeometry) {
  509. return 0;
  510. }
  511. const point_count = GodotWebXR.space.boundsGeometry.length;
  512. if (point_count === 0) {
  513. return 0;
  514. }
  515. const buf = GodotRuntime.malloc(point_count * 3 * 4);
  516. for (let i = 0; i < point_count; i++) {
  517. const point = GodotWebXR.space.boundsGeometry[i];
  518. GodotRuntime.setHeapValue(buf + ((i * 3) + 0) * 4, point.x, 'float');
  519. GodotRuntime.setHeapValue(buf + ((i * 3) + 1) * 4, point.y, 'float');
  520. GodotRuntime.setHeapValue(buf + ((i * 3) + 2) * 4, point.z, 'float');
  521. }
  522. GodotRuntime.setHeapValue(r_points, buf, 'i32');
  523. return point_count;
  524. },
  525. godot_webxr_get_frame_rate__proxy: 'sync',
  526. godot_webxr_get_frame_rate__sig: 'i',
  527. godot_webxr_get_frame_rate: function () {
  528. if (!GodotWebXR.session || GodotWebXR.session.frameRate === undefined) {
  529. return 0;
  530. }
  531. return GodotWebXR.session.frameRate;
  532. },
  533. godot_webxr_update_target_frame_rate__proxy: 'sync',
  534. godot_webxr_update_target_frame_rate__sig: 'vi',
  535. godot_webxr_update_target_frame_rate: function (p_frame_rate) {
  536. if (!GodotWebXR.session || GodotWebXR.session.updateTargetFrameRate === undefined) {
  537. return;
  538. }
  539. GodotWebXR.session.updateTargetFrameRate(p_frame_rate).then(() => {
  540. const c_str = GodotRuntime.allocString('display_refresh_rate_changed');
  541. GodotWebXR.onsimpleevent(c_str);
  542. GodotRuntime.free(c_str);
  543. });
  544. },
  545. godot_webxr_get_supported_frame_rates__proxy: 'sync',
  546. godot_webxr_get_supported_frame_rates__sig: 'ii',
  547. godot_webxr_get_supported_frame_rates: function (r_frame_rates) {
  548. if (!GodotWebXR.session || GodotWebXR.session.supportedFrameRates === undefined) {
  549. return 0;
  550. }
  551. const frame_rate_count = GodotWebXR.session.supportedFrameRates.length;
  552. if (frame_rate_count === 0) {
  553. return 0;
  554. }
  555. const buf = GodotRuntime.malloc(frame_rate_count * 4);
  556. for (let i = 0; i < frame_rate_count; i++) {
  557. GodotRuntime.setHeapValue(buf + (i * 4), GodotWebXR.session.supportedFrameRates[i], 'float');
  558. }
  559. GodotRuntime.setHeapValue(r_frame_rates, buf, 'i32');
  560. return frame_rate_count;
  561. },
  562. };
  563. autoAddDeps(GodotWebXR, '$GodotWebXR');
  564. mergeInto(LibraryManager.library, GodotWebXR);