library_godot_webxr.js 23 KB

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