library_godot_webxr.js 23 KB

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