library_godot_webxr.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. /*************************************************************************/
  2. /* library_godot_webxr.js */
  3. /*************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /*************************************************************************/
  8. /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
  9. /* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
  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'],
  32. $GodotWebXR: {
  33. gl: null,
  34. texture_ids: [null, null],
  35. textures: [null, null],
  36. session: null,
  37. space: null,
  38. frame: null,
  39. pose: null,
  40. // Monkey-patch the requestAnimationFrame() used by Emscripten for the main
  41. // loop, so that we can swap it out for XRSession.requestAnimationFrame()
  42. // when an XR session is started.
  43. orig_requestAnimationFrame: null,
  44. requestAnimationFrame: (callback) => {
  45. if (GodotWebXR.session && GodotWebXR.space) {
  46. const onFrame = function (time, frame) {
  47. GodotWebXR.frame = frame;
  48. GodotWebXR.pose = frame.getViewerPose(GodotWebXR.space);
  49. callback(time);
  50. GodotWebXR.frame = null;
  51. GodotWebXR.pose = null;
  52. };
  53. GodotWebXR.session.requestAnimationFrame(onFrame);
  54. } else {
  55. GodotWebXR.orig_requestAnimationFrame(callback);
  56. }
  57. },
  58. monkeyPatchRequestAnimationFrame: (enable) => {
  59. if (GodotWebXR.orig_requestAnimationFrame === null) {
  60. GodotWebXR.orig_requestAnimationFrame = Browser.requestAnimationFrame;
  61. }
  62. Browser.requestAnimationFrame = enable
  63. ? GodotWebXR.requestAnimationFrame : GodotWebXR.orig_requestAnimationFrame;
  64. },
  65. pauseResumeMainLoop: () => {
  66. // Once both GodotWebXR.session and GodotWebXR.space are set or
  67. // unset, our monkey-patched requestAnimationFrame() should be
  68. // enabled or disabled. When using the WebXR API Emulator, this
  69. // gets picked up automatically, however, in the Oculus Browser
  70. // on the Quest, we need to pause and resume the main loop.
  71. Browser.pauseAsyncCallbacks();
  72. Browser.mainLoop.pause();
  73. window.setTimeout(function () {
  74. Browser.resumeAsyncCallbacks();
  75. Browser.mainLoop.resume();
  76. }, 0);
  77. },
  78. // Some custom WebGL code for blitting our eye textures to the
  79. // framebuffer we get from WebXR.
  80. shaderProgram: null,
  81. programInfo: null,
  82. buffer: null,
  83. // Vertex shader source.
  84. vsSource: `
  85. const vec2 scale = vec2(0.5, 0.5);
  86. attribute vec4 aVertexPosition;
  87. varying highp vec2 vTextureCoord;
  88. void main () {
  89. gl_Position = aVertexPosition;
  90. vTextureCoord = aVertexPosition.xy * scale + scale;
  91. }
  92. `,
  93. // Fragment shader source.
  94. fsSource: `
  95. varying highp vec2 vTextureCoord;
  96. uniform sampler2D uSampler;
  97. void main() {
  98. gl_FragColor = texture2D(uSampler, vTextureCoord);
  99. }
  100. `,
  101. initShaderProgram: (gl, vsSource, fsSource) => {
  102. const vertexShader = GodotWebXR.loadShader(gl, gl.VERTEX_SHADER, vsSource);
  103. const fragmentShader = GodotWebXR.loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
  104. const shaderProgram = gl.createProgram();
  105. gl.attachShader(shaderProgram, vertexShader);
  106. gl.attachShader(shaderProgram, fragmentShader);
  107. gl.linkProgram(shaderProgram);
  108. if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
  109. GodotRuntime.error(`Unable to initialize the shader program: ${gl.getProgramInfoLog(shaderProgram)}`);
  110. return null;
  111. }
  112. return shaderProgram;
  113. },
  114. loadShader: (gl, type, source) => {
  115. const shader = gl.createShader(type);
  116. gl.shaderSource(shader, source);
  117. gl.compileShader(shader);
  118. if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
  119. GodotRuntime.error(`An error occurred compiling the shader: ${gl.getShaderInfoLog(shader)}`);
  120. gl.deleteShader(shader);
  121. return null;
  122. }
  123. return shader;
  124. },
  125. initBuffer: (gl) => {
  126. const positionBuffer = gl.createBuffer();
  127. gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  128. const positions = [
  129. -1.0, -1.0,
  130. 1.0, -1.0,
  131. -1.0, 1.0,
  132. 1.0, 1.0,
  133. ];
  134. gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
  135. return positionBuffer;
  136. },
  137. blitTexture: (gl, texture) => {
  138. if (GodotWebXR.shaderProgram === null) {
  139. GodotWebXR.shaderProgram = GodotWebXR.initShaderProgram(gl, GodotWebXR.vsSource, GodotWebXR.fsSource);
  140. GodotWebXR.programInfo = {
  141. program: GodotWebXR.shaderProgram,
  142. attribLocations: {
  143. vertexPosition: gl.getAttribLocation(GodotWebXR.shaderProgram, 'aVertexPosition'),
  144. },
  145. uniformLocations: {
  146. uSampler: gl.getUniformLocation(GodotWebXR.shaderProgram, 'uSampler'),
  147. },
  148. };
  149. GodotWebXR.buffer = GodotWebXR.initBuffer(gl);
  150. }
  151. const orig_program = gl.getParameter(gl.CURRENT_PROGRAM);
  152. gl.useProgram(GodotWebXR.shaderProgram);
  153. gl.bindBuffer(gl.ARRAY_BUFFER, GodotWebXR.buffer);
  154. gl.vertexAttribPointer(GodotWebXR.programInfo.attribLocations.vertexPosition, 2, gl.FLOAT, false, 0, 0);
  155. gl.enableVertexAttribArray(GodotWebXR.programInfo.attribLocations.vertexPosition);
  156. gl.activeTexture(gl.TEXTURE0);
  157. gl.bindTexture(gl.TEXTURE_2D, texture);
  158. gl.uniform1i(GodotWebXR.programInfo.uniformLocations.uSampler, 0);
  159. gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  160. // Restore state.
  161. gl.bindTexture(gl.TEXTURE_2D, null);
  162. gl.disableVertexAttribArray(GodotWebXR.programInfo.attribLocations.vertexPosition);
  163. gl.bindBuffer(gl.ARRAY_BUFFER, null);
  164. gl.useProgram(orig_program);
  165. },
  166. // Holds the controllers list between function calls.
  167. controllers: [],
  168. // Updates controllers array, where the left hand (or sole tracker) is
  169. // the first element, and the right hand is the second element, and any
  170. // others placed at the 3rd position and up.
  171. sampleControllers: () => {
  172. if (!GodotWebXR.session || !GodotWebXR.frame) {
  173. return;
  174. }
  175. let other_index = 2;
  176. const controllers = [];
  177. GodotWebXR.session.inputSources.forEach((input_source) => {
  178. if (input_source.targetRayMode === 'tracked-pointer') {
  179. if (input_source.handedness === 'right') {
  180. controllers[1] = input_source;
  181. } else if (input_source.handedness === 'left' || !controllers[0]) {
  182. controllers[0] = input_source;
  183. }
  184. } else {
  185. controllers[other_index++] = input_source;
  186. }
  187. });
  188. GodotWebXR.controllers = controllers;
  189. },
  190. getControllerId: (input_source) => GodotWebXR.controllers.indexOf(input_source),
  191. },
  192. godot_webxr_is_supported__proxy: 'sync',
  193. godot_webxr_is_supported__sig: 'i',
  194. godot_webxr_is_supported: function () {
  195. return !!navigator.xr;
  196. },
  197. godot_webxr_is_session_supported__proxy: 'sync',
  198. godot_webxr_is_session_supported__sig: 'vii',
  199. godot_webxr_is_session_supported: function (p_session_mode, p_callback) {
  200. const session_mode = GodotRuntime.parseString(p_session_mode);
  201. const cb = GodotRuntime.get_func(p_callback);
  202. if (navigator.xr) {
  203. navigator.xr.isSessionSupported(session_mode).then(function (supported) {
  204. const c_str = GodotRuntime.allocString(session_mode);
  205. cb(c_str, supported ? 1 : 0);
  206. GodotRuntime.free(c_str);
  207. });
  208. } else {
  209. const c_str = GodotRuntime.allocString(session_mode);
  210. cb(c_str, 0);
  211. GodotRuntime.free(c_str);
  212. }
  213. },
  214. godot_webxr_initialize__deps: ['emscripten_webgl_get_current_context'],
  215. godot_webxr_initialize__proxy: 'sync',
  216. godot_webxr_initialize__sig: 'viiiiiiiiii',
  217. 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_controller_changed, p_on_input_event, p_on_simple_event) {
  218. GodotWebXR.monkeyPatchRequestAnimationFrame(true);
  219. const session_mode = GodotRuntime.parseString(p_session_mode);
  220. const required_features = GodotRuntime.parseString(p_required_features).split(',').map((s) => s.trim()).filter((s) => s !== '');
  221. const optional_features = GodotRuntime.parseString(p_optional_features).split(',').map((s) => s.trim()).filter((s) => s !== '');
  222. const requested_reference_space_types = GodotRuntime.parseString(p_requested_reference_spaces).split(',').map((s) => s.trim());
  223. const onstarted = GodotRuntime.get_func(p_on_session_started);
  224. const onended = GodotRuntime.get_func(p_on_session_ended);
  225. const onfailed = GodotRuntime.get_func(p_on_session_failed);
  226. const oncontroller = GodotRuntime.get_func(p_on_controller_changed);
  227. const oninputevent = GodotRuntime.get_func(p_on_input_event);
  228. const onsimpleevent = GodotRuntime.get_func(p_on_simple_event);
  229. const session_init = {};
  230. if (required_features.length > 0) {
  231. session_init['requiredFeatures'] = required_features;
  232. }
  233. if (optional_features.length > 0) {
  234. session_init['optionalFeatures'] = optional_features;
  235. }
  236. navigator.xr.requestSession(session_mode, session_init).then(function (session) {
  237. GodotWebXR.session = session;
  238. session.addEventListener('end', function (evt) {
  239. onended();
  240. });
  241. session.addEventListener('inputsourceschange', function (evt) {
  242. let controller_changed = false;
  243. [evt.added, evt.removed].forEach((lst) => {
  244. lst.forEach((input_source) => {
  245. if (input_source.targetRayMode === 'tracked-pointer') {
  246. controller_changed = true;
  247. }
  248. });
  249. });
  250. if (controller_changed) {
  251. oncontroller();
  252. }
  253. });
  254. ['selectstart', 'select', 'selectend', 'squeezestart', 'squeeze', 'squeezeend'].forEach((input_event) => {
  255. session.addEventListener(input_event, function (evt) {
  256. const c_str = GodotRuntime.allocString(input_event);
  257. oninputevent(c_str, GodotWebXR.getControllerId(evt.inputSource));
  258. GodotRuntime.free(c_str);
  259. });
  260. });
  261. session.addEventListener('visibilitychange', function (evt) {
  262. const c_str = GodotRuntime.allocString('visibility_state_changed');
  263. onsimpleevent(c_str);
  264. GodotRuntime.free(c_str);
  265. });
  266. const gl_context_handle = _emscripten_webgl_get_current_context(); // eslint-disable-line no-undef
  267. const gl = GL.getContext(gl_context_handle).GLctx;
  268. GodotWebXR.gl = gl;
  269. gl.makeXRCompatible().then(function () {
  270. session.updateRenderState({
  271. baseLayer: new XRWebGLLayer(session, gl),
  272. });
  273. function onReferenceSpaceSuccess(reference_space, reference_space_type) {
  274. GodotWebXR.space = reference_space;
  275. // Using reference_space.addEventListener() crashes when
  276. // using the polyfill with the WebXR Emulator extension,
  277. // so we set the event property instead.
  278. reference_space.onreset = function (evt) {
  279. const c_str = GodotRuntime.allocString('reference_space_reset');
  280. onsimpleevent(c_str);
  281. GodotRuntime.free(c_str);
  282. };
  283. // Now that both GodotWebXR.session and GodotWebXR.space are
  284. // set, we need to pause and resume the main loop for the XR
  285. // main loop to kick in.
  286. GodotWebXR.pauseResumeMainLoop();
  287. // Call in setTimeout() so that errors in the onstarted()
  288. // callback don't bubble up here and cause Godot to try the
  289. // next reference space.
  290. window.setTimeout(function () {
  291. const c_str = GodotRuntime.allocString(reference_space_type);
  292. onstarted(c_str);
  293. GodotRuntime.free(c_str);
  294. }, 0);
  295. }
  296. function requestReferenceSpace() {
  297. const reference_space_type = requested_reference_space_types.shift();
  298. session.requestReferenceSpace(reference_space_type)
  299. .then((refSpace) => {
  300. onReferenceSpaceSuccess(refSpace, reference_space_type);
  301. })
  302. .catch(() => {
  303. if (requested_reference_space_types.length === 0) {
  304. const c_str = GodotRuntime.allocString('Unable to get any of the requested reference space types');
  305. onfailed(c_str);
  306. GodotRuntime.free(c_str);
  307. } else {
  308. requestReferenceSpace();
  309. }
  310. });
  311. }
  312. requestReferenceSpace();
  313. }).catch(function (error) {
  314. const c_str = GodotRuntime.allocString(`Unable to make WebGL context compatible with WebXR: ${error}`);
  315. onfailed(c_str);
  316. GodotRuntime.free(c_str);
  317. });
  318. }).catch(function (error) {
  319. const c_str = GodotRuntime.allocString(`Unable to start session: ${error}`);
  320. onfailed(c_str);
  321. GodotRuntime.free(c_str);
  322. });
  323. },
  324. godot_webxr_uninitialize__proxy: 'sync',
  325. godot_webxr_uninitialize__sig: 'v',
  326. godot_webxr_uninitialize: function () {
  327. if (GodotWebXR.session) {
  328. GodotWebXR.session.end()
  329. // Prevent exception when session has already ended.
  330. .catch((e) => { });
  331. }
  332. // Clean-up the textures we allocated for each view.
  333. const gl = GodotWebXR.gl;
  334. for (let i = 0; i < GodotWebXR.textures.length; i++) {
  335. const texture = GodotWebXR.textures[i];
  336. if (texture !== null) {
  337. gl.deleteTexture(texture);
  338. }
  339. GodotWebXR.textures[i] = null;
  340. GodotWebXR.texture_ids[i] = null;
  341. }
  342. GodotWebXR.session = null;
  343. GodotWebXR.space = null;
  344. GodotWebXR.frame = null;
  345. GodotWebXR.pose = null;
  346. // Disable the monkey-patched window.requestAnimationFrame() and
  347. // pause/restart the main loop to activate it on all platforms.
  348. GodotWebXR.monkeyPatchRequestAnimationFrame(false);
  349. GodotWebXR.pauseResumeMainLoop();
  350. },
  351. godot_webxr_get_render_targetsize__proxy: 'sync',
  352. godot_webxr_get_render_targetsize__sig: 'i',
  353. godot_webxr_get_render_targetsize: function () {
  354. if (!GodotWebXR.session || !GodotWebXR.pose) {
  355. return 0;
  356. }
  357. const glLayer = GodotWebXR.session.renderState.baseLayer;
  358. const view = GodotWebXR.pose.views[0];
  359. const viewport = glLayer.getViewport(view);
  360. const buf = GodotRuntime.malloc(2 * 4);
  361. GodotRuntime.setHeapValue(buf + 0, viewport.width, 'i32');
  362. GodotRuntime.setHeapValue(buf + 4, viewport.height, 'i32');
  363. return buf;
  364. },
  365. godot_webxr_get_transform_for_eye__proxy: 'sync',
  366. godot_webxr_get_transform_for_eye__sig: 'ii',
  367. godot_webxr_get_transform_for_eye: function (p_eye) {
  368. if (!GodotWebXR.session || !GodotWebXR.pose) {
  369. return 0;
  370. }
  371. const views = GodotWebXR.pose.views;
  372. let matrix;
  373. if (p_eye === 0) {
  374. matrix = GodotWebXR.pose.transform.matrix;
  375. } else {
  376. matrix = views[p_eye - 1].transform.matrix;
  377. }
  378. const buf = GodotRuntime.malloc(16 * 4);
  379. for (let i = 0; i < 16; i++) {
  380. GodotRuntime.setHeapValue(buf + (i * 4), matrix[i], 'float');
  381. }
  382. return buf;
  383. },
  384. godot_webxr_get_projection_for_eye__proxy: 'sync',
  385. godot_webxr_get_projection_for_eye__sig: 'ii',
  386. godot_webxr_get_projection_for_eye: function (p_eye) {
  387. if (!GodotWebXR.session || !GodotWebXR.pose) {
  388. return 0;
  389. }
  390. const view_index = (p_eye === 2 /* ARVRInterface::EYE_RIGHT */) ? 1 : 0;
  391. const matrix = GodotWebXR.pose.views[view_index].projectionMatrix;
  392. const buf = GodotRuntime.malloc(16 * 4);
  393. for (let i = 0; i < 16; i++) {
  394. GodotRuntime.setHeapValue(buf + (i * 4), matrix[i], 'float');
  395. }
  396. return buf;
  397. },
  398. godot_webxr_get_external_texture_for_eye__proxy: 'sync',
  399. godot_webxr_get_external_texture_for_eye__sig: 'ii',
  400. godot_webxr_get_external_texture_for_eye: function (p_eye) {
  401. if (!GodotWebXR.session || !GodotWebXR.pose) {
  402. return 0;
  403. }
  404. const view_index = (p_eye === 2 /* ARVRInterface::EYE_RIGHT */) ? 1 : 0;
  405. if (GodotWebXR.texture_ids[view_index]) {
  406. return GodotWebXR.texture_ids[view_index];
  407. }
  408. const glLayer = GodotWebXR.session.renderState.baseLayer;
  409. const view = GodotWebXR.pose.views[view_index];
  410. const viewport = glLayer.getViewport(view);
  411. const gl = GodotWebXR.gl;
  412. const texture = gl.createTexture();
  413. gl.bindTexture(gl.TEXTURE_2D, texture);
  414. gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, viewport.width, viewport.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
  415. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  416. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  417. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  418. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  419. gl.bindTexture(gl.TEXTURE_2D, null);
  420. const texture_id = GL.getNewId(GL.textures);
  421. GL.textures[texture_id] = texture;
  422. GodotWebXR.textures[view_index] = texture;
  423. GodotWebXR.texture_ids[view_index] = texture_id;
  424. return texture_id;
  425. },
  426. godot_webxr_commit_for_eye__proxy: 'sync',
  427. godot_webxr_commit_for_eye__sig: 'vi',
  428. godot_webxr_commit_for_eye: function (p_eye) {
  429. if (!GodotWebXR.session || !GodotWebXR.pose) {
  430. return;
  431. }
  432. const view_index = (p_eye === 2 /* ARVRInterface::EYE_RIGHT */) ? 1 : 0;
  433. const glLayer = GodotWebXR.session.renderState.baseLayer;
  434. const view = GodotWebXR.pose.views[view_index];
  435. const viewport = glLayer.getViewport(view);
  436. const gl = GodotWebXR.gl;
  437. const orig_framebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
  438. const orig_viewport = gl.getParameter(gl.VIEWPORT);
  439. // Bind to WebXR's framebuffer.
  440. gl.bindFramebuffer(gl.FRAMEBUFFER, glLayer.framebuffer);
  441. gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
  442. GodotWebXR.blitTexture(gl, GodotWebXR.textures[view_index]);
  443. // Restore state.
  444. gl.bindFramebuffer(gl.FRAMEBUFFER, orig_framebuffer);
  445. gl.viewport(orig_viewport[0], orig_viewport[1], orig_viewport[2], orig_viewport[3]);
  446. },
  447. godot_webxr_sample_controller_data__proxy: 'sync',
  448. godot_webxr_sample_controller_data__sig: 'v',
  449. godot_webxr_sample_controller_data: function () {
  450. GodotWebXR.sampleControllers();
  451. },
  452. godot_webxr_get_controller_count__proxy: 'sync',
  453. godot_webxr_get_controller_count__sig: 'i',
  454. godot_webxr_get_controller_count: function () {
  455. if (!GodotWebXR.session || !GodotWebXR.frame) {
  456. return 0;
  457. }
  458. return GodotWebXR.controllers.length;
  459. },
  460. godot_webxr_is_controller_connected__proxy: 'sync',
  461. godot_webxr_is_controller_connected__sig: 'ii',
  462. godot_webxr_is_controller_connected: function (p_controller) {
  463. if (!GodotWebXR.session || !GodotWebXR.frame) {
  464. return false;
  465. }
  466. return !!GodotWebXR.controllers[p_controller];
  467. },
  468. godot_webxr_get_controller_transform__proxy: 'sync',
  469. godot_webxr_get_controller_transform__sig: 'ii',
  470. godot_webxr_get_controller_transform: function (p_controller) {
  471. if (!GodotWebXR.session || !GodotWebXR.frame) {
  472. return 0;
  473. }
  474. const controller = GodotWebXR.controllers[p_controller];
  475. if (!controller) {
  476. return 0;
  477. }
  478. const frame = GodotWebXR.frame;
  479. const space = GodotWebXR.space;
  480. const pose = frame.getPose(controller.targetRaySpace, space);
  481. if (!pose) {
  482. // This can mean that the controller lost tracking.
  483. return 0;
  484. }
  485. const matrix = pose.transform.matrix;
  486. const buf = GodotRuntime.malloc(16 * 4);
  487. for (let i = 0; i < 16; i++) {
  488. GodotRuntime.setHeapValue(buf + (i * 4), matrix[i], 'float');
  489. }
  490. return buf;
  491. },
  492. godot_webxr_get_controller_buttons__proxy: 'sync',
  493. godot_webxr_get_controller_buttons__sig: 'ii',
  494. godot_webxr_get_controller_buttons: function (p_controller) {
  495. if (GodotWebXR.controllers.length === 0) {
  496. return 0;
  497. }
  498. const controller = GodotWebXR.controllers[p_controller];
  499. if (!controller || !controller.gamepad) {
  500. return 0;
  501. }
  502. const button_count = controller.gamepad.buttons.length;
  503. const buf = GodotRuntime.malloc((button_count + 1) * 4);
  504. GodotRuntime.setHeapValue(buf, button_count, 'i32');
  505. for (let i = 0; i < button_count; i++) {
  506. GodotRuntime.setHeapValue(buf + 4 + (i * 4), controller.gamepad.buttons[i].value, 'float');
  507. }
  508. return buf;
  509. },
  510. godot_webxr_get_controller_axes__proxy: 'sync',
  511. godot_webxr_get_controller_axes__sig: 'ii',
  512. godot_webxr_get_controller_axes: function (p_controller) {
  513. if (GodotWebXR.controllers.length === 0) {
  514. return 0;
  515. }
  516. const controller = GodotWebXR.controllers[p_controller];
  517. if (!controller || !controller.gamepad) {
  518. return 0;
  519. }
  520. const axes_count = controller.gamepad.axes.length;
  521. const buf = GodotRuntime.malloc((axes_count + 1) * 4);
  522. GodotRuntime.setHeapValue(buf, axes_count, 'i32');
  523. for (let i = 0; i < axes_count; i++) {
  524. GodotRuntime.setHeapValue(buf + 4 + (i * 4), controller.gamepad.axes[i], 'float');
  525. }
  526. return buf;
  527. },
  528. godot_webxr_get_visibility_state__proxy: 'sync',
  529. godot_webxr_get_visibility_state__sig: 'i',
  530. godot_webxr_get_visibility_state: function () {
  531. if (!GodotWebXR.session || !GodotWebXR.session.visibilityState) {
  532. return 0;
  533. }
  534. return GodotRuntime.allocString(GodotWebXR.session.visibilityState);
  535. },
  536. godot_webxr_get_bounds_geometry__proxy: 'sync',
  537. godot_webxr_get_bounds_geometry__sig: 'i',
  538. godot_webxr_get_bounds_geometry: function () {
  539. if (!GodotWebXR.space || !GodotWebXR.space.boundsGeometry) {
  540. return 0;
  541. }
  542. const point_count = GodotWebXR.space.boundsGeometry.length;
  543. if (point_count === 0) {
  544. return 0;
  545. }
  546. const buf = GodotRuntime.malloc(((point_count * 3) + 1) * 4);
  547. GodotRuntime.setHeapValue(buf, point_count, 'i32');
  548. for (let i = 0; i < point_count; i++) {
  549. const point = GodotWebXR.space.boundsGeometry[i];
  550. GodotRuntime.setHeapValue(buf + ((i * 3) + 1) * 4, point.x, 'float');
  551. GodotRuntime.setHeapValue(buf + ((i * 3) + 2) * 4, point.y, 'float');
  552. GodotRuntime.setHeapValue(buf + ((i * 3) + 3) * 4, point.z, 'float');
  553. }
  554. return buf;
  555. },
  556. };
  557. autoAddDeps(GodotWebXR, '$GodotWebXR');
  558. mergeInto(LibraryManager.library, GodotWebXR);