Source: extern/screen.js

  1. import register from "../util/register.js";
  2. import m_cam_fact from "../intern/camera.js";
  3. import m_cont_fact from "../intern/container.js";
  4. import m_cfg_fact from "../intern/config.js";
  5. import m_hud_fact from "../intern/hud.js";
  6. import m_input_fact from "../intern/input.js";
  7. import m_main_fact from "../intern/main.js";
  8. import m_obj_util_fact from "../intern/obj_util.js";
  9. import m_print_fact from "../intern/print.js";
  10. import m_scs_fact from "../intern/scenes.js";
  11. import * as m_vec4 from "../libs/gl_matrix/vec4.js";
  12. /**
  13. * Screen API
  14. * The API is used to manage content on the screen: fullscreen, HMD, HUD.
  15. * @module screen
  16. */
  17. function Screen(ns, exports) {
  18. var m_cam = m_cam_fact(ns);
  19. var m_cont = m_cont_fact(ns);
  20. var m_cfg = m_cfg_fact(ns);
  21. var m_hud = m_hud_fact(ns);
  22. var m_input = m_input_fact(ns);
  23. var m_main = m_main_fact(ns);
  24. var m_obj_util = m_obj_util_fact(ns);
  25. var m_print = m_print_fact(ns);
  26. var m_scs = m_scs_fact(ns);
  27. var _vec4_tmp = m_vec4.create();
  28. var _vec4_tmp2 = m_vec4.create();
  29. var _splited_screen = false;
  30. var _exit_cb = function() {};
  31. var _vr_present_change_cb = function() {};
  32. var cfg_dbg = m_cfg.debug_subs;
  33. // TODO: fix API doc of draw_mixer_strip function. Add information about id,
  34. // is_active, slot, params, active_param, mute, solo params.
  35. /**
  36. * Draw the mixer strip.
  37. * Used by mixer addon.
  38. * @method module:screen.draw_mixer_strip
  39. */
  40. exports.draw_mixer_strip = m_hud.draw_mixer_strip;
  41. /**
  42. * Plot the array.
  43. * @method module:screen.plot_array
  44. * @param {string} header Plot header
  45. * @param {number} slot Slot number
  46. * @param {Float32Array} arr Array
  47. * @param {number} arg_min Minimum plot argument value
  48. * @param {number} arg_max Maximum plot argument value
  49. * @param {number} val_min Minimum plot value
  50. * @param {number} val_max Maximum plot value
  51. */
  52. exports.plot_array = m_hud.plot_array;
  53. exports.request_fullscreen = request_fullscreen;
  54. /**
  55. * Request fullscreen mode, i.e. presenting element entire to a screen.
  56. * It is better to use
  57. * {@link module:screen.request_fullscreen_hmd|request_fullscreen_hmd} for
  58. * presenting VR content on head-mounted display.
  59. * Security issues: execute by user event.
  60. * @method module:screen.request_fullscreen
  61. * @param {HTMLElement} [elem=Canvas container element] HTML element.
  62. * @param {FullscreenEnabledCallback} [enabled_cb] Enabled callback.
  63. * @param {FullscreenDisabledCallback} [disabled_cb] Disabled callback.
  64. */
  65. function request_fullscreen(elem, enabled_cb, disabled_cb) {
  66. elem = elem || m_cont.get_container();
  67. enabled_cb = enabled_cb || function() {};
  68. disabled_cb = disabled_cb || function() {};
  69. function on_fullscreen_change() {
  70. if (document.fullscreenElement === elem ||
  71. document.webkitFullscreenElement === elem ||
  72. document.mozFullScreenElement === elem ||
  73. document.webkitIsFullScreen ||
  74. document.msFullscreenElement === elem) {
  75. //m_print.log("Fullscreen enabled");
  76. enabled_cb();
  77. } else {
  78. document.removeEventListener("fullscreenchange",
  79. on_fullscreen_change, false);
  80. document.removeEventListener("webkitfullscreenchange",
  81. on_fullscreen_change, false);
  82. document.removeEventListener("mozfullscreenchange",
  83. on_fullscreen_change, false);
  84. document.removeEventListener("MSFullscreenChange",
  85. on_fullscreen_change, false);
  86. //m_print.log("Fullscreen disabled");
  87. disabled_cb();
  88. }
  89. }
  90. document.addEventListener("fullscreenchange", on_fullscreen_change, false);
  91. document.addEventListener("webkitfullscreenchange", on_fullscreen_change, false);
  92. document.addEventListener("mozfullscreenchange", on_fullscreen_change, false);
  93. document.addEventListener("MSFullscreenChange", on_fullscreen_change, false);
  94. elem.requestFullScreen = elem.requestFullScreen ||
  95. elem.webkitRequestFullScreen || elem.mozRequestFullScreen
  96. || elem.msRequestFullscreen;
  97. if (elem.requestFullScreen)
  98. elem.requestFullScreen();
  99. else
  100. m_print.error("B4W App: request fullscreen method is not supported");
  101. }
  102. exports.exit_fullscreen = exit_fullscreen;
  103. /**
  104. * Exit fullscreen mode.
  105. * @method module:screen.exit_fullscreen
  106. */
  107. function exit_fullscreen() {
  108. var exit_fs = document.exitFullscreen || document.webkitExitFullscreen ||
  109. document.mozCancelFullScreen || document.msExitFullscreen;
  110. if (typeof exit_fs != "function")
  111. m_print.error("B4W App: exit fullscreen method is not supported");
  112. exit_fs.apply(document);
  113. }
  114. /**
  115. * Check whether fullscreen mode is available.
  116. * @method module:screen.check_fullscreen
  117. * @returns {boolean} Result of the check.
  118. */
  119. exports.check_fullscreen = m_input.check_fullscreen;
  120. /**
  121. * Request HMD fullscreen mode. Use the function for represent VR content
  122. * on a head-mounted display.
  123. * The function requires using of a browser supporting WebVR API
  124. * or a mobile browser supporting Fullscreen API.
  125. * Security issues: execute by user event.
  126. * @method module:screen.request_fullscreen_hmd
  127. * @param {HTMLElement} [element=Canvas container element] HTML element.
  128. * @param {GenericCallback} [enabled_cb] The callback will be called right
  129. * after switching HMD fullscreen mode on.
  130. * @param {GenericCallback} [disabled_cb] The callback will be called right
  131. * after switching HMD fullscreen mode off.
  132. * @example
  133. * var m_input = require("input");
  134. * var m_screen = require("screen");
  135. * m_input.add_click_listener(document.body, function() {
  136. * m_screen.request_fullscreen_hmd();
  137. * });
  138. */
  139. exports.request_fullscreen_hmd = function(element, enabled_cb, disabled_cb) {
  140. if (!m_cfg.get("stereo") == "HMD" &&
  141. !(m_cfg.get("stereo") == "NONE" && m_cfg.get("is_mobile_device")))
  142. return;
  143. var hmd_device = m_input.get_device_by_type_element(m_input.DEVICE_HMD);
  144. if (hmd_device &&
  145. m_input.get_value_param(hmd_device, m_input.HMD_WEBVR_TYPE) & m_input.HMD_WEBVR1) {
  146. var webvr_display = hmd_device.webvr_display;
  147. if (webvr_display && !webvr_display.isPresenting) {
  148. enabled_cb = enabled_cb || function() {};
  149. disabled_cb = disabled_cb || function() {};
  150. _vr_present_change_cb = function() {
  151. if (webvr_display.isPresenting)
  152. enabled_cb();
  153. else {
  154. window.removeEventListener('vrdisplaypresentchange', _vr_present_change_cb);
  155. disabled_cb();
  156. }
  157. }
  158. window.addEventListener('vrdisplaypresentchange', _vr_present_change_cb, false);
  159. var capabilities = webvr_display.capabilities;
  160. if (!capabilities.canPresent)
  161. m_print.error("HMD fullscreen request failed.");
  162. else {
  163. var canvas = m_cont.get_canvas();
  164. webvr_display.requestPresent([{source: canvas}]).then(function () {
  165. // TODO: add some logic
  166. }, function () {
  167. m_print.error("HMD fullscreen request failed.");
  168. });
  169. }
  170. }
  171. } else
  172. request_fullscreen(element, enabled_cb, disabled_cb);
  173. }
  174. /**
  175. * Exit HMD fullscreen mode.
  176. * @method module:screen.exit_fullscreen_hmd
  177. */
  178. exports.exit_fullscreen_hmd = function() {
  179. if (!m_cfg.get("stereo") == "HMD" &&
  180. !(m_cfg.get("stereo") == "NONE" && m_cfg.get("is_mobile_device")))
  181. return;
  182. var hmd_device = m_input.get_device_by_type_element(m_input.DEVICE_HMD);
  183. if (hmd_device &&
  184. m_input.get_value_param(hmd_device, m_input.HMD_WEBVR_TYPE) & m_input.HMD_WEBVR1) {
  185. var webvr_display = hmd_device.webvr_display;
  186. if (webvr_display && webvr_display.isPresenting) {
  187. webvr_display.exitPresent();
  188. m_cont.resize_to_container(true);
  189. }
  190. } else
  191. exit_fullscreen();
  192. }
  193. /**
  194. * Check whether HMD fullscreen mode is available.
  195. * @method module:screen.check_fullscreen_hmd
  196. * @returns {boolean} The result of the checking.
  197. */
  198. exports.check_fullscreen_hmd = function() {
  199. var hmd_device = m_input.get_device_by_type_element(m_input.DEVICE_HMD);
  200. var is_webvr_1 = m_input.get_value_param(hmd_device, m_input.HMD_WEBVR_TYPE) & m_input.HMD_WEBVR1;
  201. if (hmd_device &&
  202. (is_webvr_1 && hmd_device.webvr_display || !is_webvr_1 && m_input.check_fullscreen()))
  203. return true;
  204. return false;
  205. }
  206. exports.request_split_screen = request_split_screen;
  207. /**
  208. * Request "split screen" mode, which represent rendering two eyes suitable
  209. * for head-mounted displays.
  210. * The function requires setting the "stereo" config to "HMD"
  211. * {see @link module:config}, using a browser supporting WebVR API
  212. * or a mobile browser.
  213. * @method module:screen.request_split_screen
  214. * @param {GenericCallback} [enter_cb] The callback will be called right
  215. * after turning split screen mode on.
  216. * @param {GenericCallback} [exit_cb] The callback will be called right
  217. * after turning split screen mode off.
  218. * @example
  219. * var m_app = require("app");
  220. * var m_screen = require("screen");
  221. * exports.init = function() {
  222. * m_app.init({
  223. * // . . .
  224. * stereo: "HMD"
  225. * });
  226. * }
  227. *
  228. * m_screen.request_split_screen();
  229. */
  230. function request_split_screen(enter_cb, exit_cb) {
  231. var hmd_device = m_input.get_device_by_type_element(m_input.DEVICE_HMD);
  232. if (!hmd_device)
  233. return false;
  234. if (_splited_screen)
  235. return true;
  236. // _exit_cb will be called in the exit_split_screen function.
  237. _exit_cb = exit_cb;
  238. if (m_scs.check_active()) {
  239. var camobj = m_scs.get_camera(m_scs.get_active());
  240. if (!camobj)
  241. return;
  242. } else
  243. return;
  244. if (hmd_device.registered) {
  245. enable_split_screen(camobj);
  246. if (enter_cb)
  247. enter_cb();
  248. } else {
  249. m_input.request_register_device(hmd_device);
  250. hmd_device.registered_cb = function() {
  251. enable_split_screen(camobj);
  252. if (enter_cb)
  253. enter_cb();
  254. }
  255. }
  256. }
  257. function enable_split_screen(camobj) {
  258. var hmd_device = m_input.get_device_by_type_element(m_input.DEVICE_HMD);
  259. if (!hmd_device)
  260. return false;
  261. if (_splited_screen)
  262. return true;
  263. var hmd_type = m_input.get_value_param(hmd_device, m_input.HMD_WEBVR_TYPE);
  264. if (hmd_type & m_input.HMD_WEBVR1_1) {
  265. var webvr_display = hmd_device.webvr_display;
  266. // TODO: check code below
  267. // There was strange behavior Samsung Internet on GearVR
  268. var left_eye = webvr_display.getEyeParameters("left");
  269. var right_eye = webvr_display.getEyeParameters("right");
  270. m_cont.resize(
  271. Math.max(left_eye.renderWidth, right_eye.renderWidth) * 2,
  272. Math.max(left_eye.renderHeight, right_eye.renderHeight), false);
  273. // TODO: uncomment until fieldOfView is not in WebVR 1.*
  274. // var hmd_left_proj_mat = m_input.get_vector_param(hmd_device,
  275. // m_input.HMD_PROJ_LEFT, _mat4_tmp);
  276. // var hmd_right_proj_mat = m_input.get_vector_param(hmd_device,
  277. // m_input.HMD_PROJ_RIGHT, _mat4_tmp2);
  278. // m_cam.set_hmd_proj_mat(camobj, hmd_left_proj_mat, hmd_right_proj_mat);
  279. } else {
  280. // non-WebVR, pre-WebVR 1.1
  281. // TODO: remove magic numbers
  282. var canvas = m_cont.get_canvas();
  283. var actual_width = Math.max(2 * canvas.height, canvas.width);
  284. var actual_height = actual_width / 2;
  285. m_cont.resize(actual_width, actual_height, false);
  286. }
  287. // TODO: remove HMD FOV
  288. // HMD FOV deprecated. Use projection matrixes instead.
  289. var hmd_left_fov = m_input.get_vector_param(hmd_device,
  290. m_input.HMD_FOV_LEFT, _vec4_tmp);
  291. var hmd_right_fov = m_input.get_vector_param(hmd_device,
  292. m_input.HMD_FOV_RIGHT, _vec4_tmp2);
  293. m_cam.set_hmd_fov(camobj, hmd_left_fov, hmd_right_fov,
  294. !Boolean(hmd_type & m_input.HMD_WEBVR1_1));
  295. var hmd_params = {};
  296. hmd_params.base_line_factor = 0.5;
  297. hmd_params.inter_lens_factor = 0.5;
  298. hmd_params.enable_hmd_stereo = true;
  299. if (hmd_type & (m_input.HMD_NON_WEBVR | m_input.HMD_WEBVR_MOBILE |
  300. m_input.HMD_WEBVR_DESKTOP)) {
  301. hmd_params.distortion_coefs = [
  302. hmd_device.distortion_coefs[0],
  303. hmd_device.distortion_coefs[1]
  304. ];
  305. hmd_params.chromatic_aberration_coefs = [
  306. hmd_device.chromatic_aberration_coefs[0],
  307. hmd_device.chromatic_aberration_coefs[1],
  308. hmd_device.chromatic_aberration_coefs[2],
  309. hmd_device.chromatic_aberration_coefs[3]
  310. ];
  311. if (!hmd_device.webvr_hmd_device)
  312. if (hmd_device.base_line_dist && hmd_device.height_dist && hmd_device.bevel_size)
  313. hmd_params.base_line_factor = (hmd_device.base_line_dist - hmd_device.bevel_size) /
  314. hmd_device.height_dist;
  315. else if (!hmd_device.bevel_size)
  316. hmd_params.base_line_factor = hmd_device.base_line_dist / hmd_device.height_dist;
  317. if (hmd_device.inter_lens_dist && hmd_device.width_dist && !hmd_device.webvr_hmd_device)
  318. hmd_params.inter_lens_factor = hmd_device.inter_lens_dist /
  319. hmd_device.width_dist;
  320. }
  321. // NOTE: prevent crash in case of difference of slink's dimensions.
  322. // For example: "debug" slink ~ 0.5 X 1.0,
  323. // "origin" slink ~ distortion_scale * 0.5 X distortion_scale * 1.0
  324. if (!cfg_dbg.enabled)
  325. m_scs.multiply_size_mult(0.5, 1.0);
  326. m_scs.set_hmd_params(hmd_params);
  327. var eye_distance = m_input.get_value_param(hmd_device, m_input.HMD_EYE_DISTANCE);
  328. if (eye_distance) {
  329. var active_scene = m_scs.get_active();
  330. var cam_scene_data = m_obj_util.get_scene_data(camobj, active_scene);
  331. var cameras = cam_scene_data.cameras;
  332. m_cam.set_eye_distance(cameras, eye_distance);
  333. }
  334. m_scs.render_both_eyes();
  335. // m_input.reset_device(hmd_device);
  336. _splited_screen = true;
  337. return true;
  338. }
  339. /**
  340. * Exit "split screen" mode.
  341. * @method module:screen.exit_split_screen
  342. * @returns {boolean} "Split screen" mode is disabled.
  343. */
  344. exports.exit_split_screen = function() {
  345. var hmd_device = m_input.get_device_by_type_element(m_input.DEVICE_HMD)
  346. if (!_splited_screen || !hmd_device || !hmd_device.registered)
  347. return false;
  348. // set up non-vr mode
  349. var hmd_params = {};
  350. hmd_params.enable_hmd_stereo = false;
  351. m_scs.set_hmd_params(hmd_params);
  352. _splited_screen = false;
  353. if (_exit_cb)
  354. _exit_cb();
  355. m_scs.render_one_eye();
  356. if (!cfg_dbg.enabled)
  357. m_scs.multiply_size_mult(2.0, 1.0);
  358. // resize screen to canvas resolution (non-vr mode)
  359. m_cont.resize_to_container(true);
  360. return true;
  361. }
  362. /**
  363. * Take a screenshot and download as screenshot.png image.
  364. * @method module:screen.shot
  365. * @param {string} [format="image/png"] The MIME image format ("image/png",
  366. * "image/jpeg", "image/webp" and so on)
  367. * @param {number} [quality=1.0] Number between 0 and 1 for types: "image/jpeg",
  368. * "image/webp"
  369. * @example
  370. * var m_screen = require("screen");
  371. * m_screen.shot();
  372. */
  373. exports.shot = function(format, quality) {
  374. format = format || "image/png";
  375. quality = quality || 1.0;
  376. var cb = function(url) {
  377. var file_name = "screenshot." + format.split("/")[1];
  378. if (navigator.msSaveOrOpenBlob) {
  379. navigator.msSaveOrOpenBlob(url, file_name);
  380. } else {
  381. var a = window.document.createElement("a");
  382. document.body.appendChild(a);
  383. a.style.display = "none";
  384. a.href = url;
  385. a.download = "screenshot." + format.split("/")[1];
  386. a.click();
  387. document.body.removeChild(a);
  388. }
  389. }
  390. m_main.canvas_data_url(cb, format, quality, true);
  391. }
  392. }
  393. var screen_factory = register("screen", Screen);
  394. export default screen_factory;