Source: extern/main.js

  1. import register from "../util/register.js";
  2. import m_anchors_fact from "../intern/anchors.js";
  3. import m_anim_fact from "../intern/animation.js";
  4. import m_assets_fact from "../intern/assets.js";
  5. import m_cfg_fact from "../intern/config.js";
  6. import m_compat_fact from "../intern/compat.js";
  7. import m_cont_fact from "../intern/container.js";
  8. import m_context_fact from "../intern/context.js";
  9. import m_ctl_fact from "../intern/controls.js";
  10. import m_data_fact from "../intern/data.js";
  11. import m_debug_check_fact from "../intern/debug/check.js";
  12. import m_debug_telemetry_fact from "../intern/debug/telemetry.js";
  13. import m_ext_fact from "../intern/extensions.js";
  14. import m_geom_fact from "../intern/geometry.js";
  15. import m_input_fact from "../intern/input.js";
  16. import m_hud_fact from "../intern/hud.js";
  17. import m_nla_fact from "../intern/nla.js";
  18. import m_main_fact from "../intern/main.js";
  19. import m_lnodes_fact from "../intern/logic_nodes.js";
  20. import m_obj_fact from "../intern/objects.js";
  21. import m_phy_fact from "../intern/physics.js";
  22. import m_print_fact from "../intern/print.js";
  23. import m_render_fact from "../intern/renderer.js";
  24. import m_scenes_fact from "../intern/scenes.js";
  25. import m_sfx_fact from "../intern/sfx.js";
  26. import m_shaders_fact from "../intern/shaders.js";
  27. import m_textures_fact from "../intern/textures.js";
  28. import m_time_fact from "../intern/time.js";
  29. import m_trans_fact from "../intern/transform.js";
  30. import * as m_util from "../intern/util.js";
  31. import {version_str, type, date_str} from "../intern/version.js";
  32. import m_particles_fact from "../intern/particles.js";
  33. /**
  34. * Main Blend4Web module.
  35. * Implements methods to initialize and change the global params of the engine.
  36. * @module main
  37. * @local LoopCallback
  38. * @local RenderCallback
  39. * @local FPSCallback
  40. */
  41. function Main(ns, exports) {
  42. /**
  43. * Loop callback.
  44. * @callback LoopCallback
  45. * @param {number} timeline Timeline
  46. * @param {number} delta Delta
  47. */
  48. /**
  49. * Rendering callback.
  50. * @callback RenderCallback
  51. * @param {number} delta Delta
  52. * @param {number} timeline Timeline
  53. */
  54. /**
  55. * FPS callback
  56. * @callback FPSCallback
  57. * @param {number} fps_avg Averaged rendering FPS.
  58. * @param {number} phy_fps_avg Averaged physics FPS.
  59. */
  60. var m_anchors = m_anchors_fact(ns);
  61. var m_anim = m_anim_fact(ns);
  62. var m_assets = m_assets_fact(ns);
  63. var m_cfg = m_cfg_fact(ns);
  64. var m_compat = m_compat_fact(ns);
  65. var m_cont = m_cont_fact(ns);
  66. var m_context = m_context_fact(ns);
  67. var m_ctl = m_ctl_fact(ns);
  68. var m_data = m_data_fact(ns);
  69. var m_debug_check = m_debug_check_fact(ns);
  70. var m_debug_telemetry = m_debug_telemetry_fact(ns);
  71. var m_ext = m_ext_fact(ns);
  72. var m_geom = m_geom_fact(ns);
  73. var m_input = m_input_fact(ns);
  74. var m_hud = m_hud_fact(ns);
  75. var m_nla = m_nla_fact(ns);
  76. var m_main = m_main_fact(ns);
  77. var m_lnodes = m_lnodes_fact(ns)
  78. var m_obj = m_obj_fact(ns);
  79. var m_phy = m_phy_fact(ns);
  80. var m_print = m_print_fact(ns);
  81. var m_render = m_render_fact(ns);
  82. var m_scenes = m_scenes_fact(ns);
  83. var m_sfx = m_sfx_fact(ns);
  84. var m_shaders = m_shaders_fact(ns);
  85. var m_textures = m_textures_fact(ns);
  86. var m_time = m_time_fact(ns);
  87. var m_trans = m_trans_fact(ns);
  88. var m_particles = m_particles_fact(ns);
  89. var cfg_ctx = m_cfg.context;
  90. var cfg_def = m_cfg.defaults;
  91. var _last_abs_time = 0;
  92. var _pause_time = 0;
  93. var _resume_time = 0;
  94. var _loop_cb = [];
  95. var _fps_callback = function() {};
  96. var _fps_counter = function() {};
  97. var _render_callback = function() {};
  98. var WEBGL_CTX_IDS = ["webgl", "experimental-webgl"];
  99. var WEBGL2_CTX_IDS = ["webgl2", "experimental-webgl2"];
  100. var _gl = null;
  101. /**
  102. * NOTE: According to the spec, this function takes only one param
  103. */
  104. var _requestAnimFrame = (function() {
  105. return window.requestAnimationFrame ||
  106. window.webkitRequestAnimationFrame ||
  107. window.mozRequestAnimationFrame ||
  108. window.oRequestAnimationFrame ||
  109. window.msRequestAnimationFrame ||
  110. function(callback) {return window.setTimeout(callback,
  111. 1000/cfg_def.max_fps);};
  112. })();
  113. // public enums
  114. /**
  115. * Create the WebGL context and initialize the engine.
  116. * @method module:main.init
  117. * @param {HTMLCanvasElement} elem_canvas_webgl Canvas element for WebGL
  118. * @param {HTMLCanvasElement} [elem_canvas_hud] Canvas element for HUD
  119. * @returns {WebGLRenderingContext|Null} WebGL context or null
  120. */
  121. exports.init = function(elem_canvas_webgl, elem_canvas_hud) {
  122. // NOTE: for debug purposes
  123. // works in chrome with --enable-memory-info --js-flags="--expose-gc"
  124. //window.setInterval(function() {window.gc();}, 1000);
  125. m_print.set_verbose(cfg_def.console_verbose);
  126. var ver_str = version_str() + " " + type() + " (" + date_str() + ")";
  127. m_print.log("%cINIT ENGINE", "color: #00a", ver_str);
  128. m_print.log("%cUSER AGENT:", "color: #00a", navigator.userAgent);
  129. // check gl context and performance.now()
  130. if (!window["WebGLRenderingContext"])
  131. return null;
  132. setup_clock();
  133. if (elem_canvas_hud) {
  134. m_hud.init(elem_canvas_hud);
  135. } else {
  136. // disable features which depend on HUD
  137. m_cfg.defaults.show_hud_debug_info = false;
  138. m_cfg.sfx.mix_mode = false;
  139. }
  140. m_compat.apply_context_alpha_hack();
  141. // allow WebGL 2 only in Chrome and Firefox
  142. if (!(m_compat.check_user_agent("Chrome") ||
  143. m_compat.check_user_agent("Firefox")))
  144. cfg_def.webgl2 = false;
  145. var gl = get_context(elem_canvas_webgl, cfg_def.webgl2);
  146. // fallback to WebGL 1
  147. if (!gl && cfg_def.webgl2) {
  148. cfg_def.webgl2 = false;
  149. gl = get_context(elem_canvas_webgl, false);
  150. }
  151. if (!gl)
  152. return null;
  153. m_print.log("%cINIT WEBGL " + (cfg_def.webgl2 ? "2" : "1"), "color: #00a");
  154. _gl = gl;
  155. init_context(elem_canvas_webgl, elem_canvas_hud, gl);
  156. m_cfg.apply_quality();
  157. m_compat.set_hardware_defaults(gl, true);
  158. m_cfg.set_paths();
  159. m_shaders.load_shaders();
  160. if (cfg_def.ie11_edge_touchscreen_hack)
  161. elem_canvas_webgl.style["touch-action"] = "none";
  162. m_print.log("%cSET PRECISION:", "color: #00a", cfg_def.precision);
  163. return gl;
  164. }
  165. function setup_clock() {
  166. if (!window.performance) {
  167. m_print.log("Apply performance workaround");
  168. window.performance = {};
  169. }
  170. var curr_time = Date.now();
  171. if (!window.performance.now) {
  172. m_print.log("Apply performance.now() workaround");
  173. //cfg_def.no_phy_interp_hack = true;
  174. window.performance.now = function() {
  175. return Date.now() - curr_time;
  176. }
  177. }
  178. m_time.set_timeline(0);
  179. }
  180. function get_context(canvas, init_webgl2) {
  181. var ctx = null;
  182. var ctx_ids = init_webgl2 ? WEBGL2_CTX_IDS : WEBGL_CTX_IDS;
  183. for (var i = 0; i < ctx_ids.length; i++) {
  184. var name = ctx_ids[i];
  185. try {
  186. ctx = canvas.getContext(name, cfg_ctx);
  187. } catch(e) {
  188. // nothing
  189. }
  190. if (ctx)
  191. break;
  192. }
  193. if (ctx)
  194. m_compat.detect_tegra_invalid_enum_issue(ctx);
  195. return ctx;
  196. }
  197. function init_context(canvas, canvas_hud, gl) {
  198. canvas.addEventListener("webglcontextlost",
  199. function(event) {
  200. event.preventDefault();
  201. m_print.error("WebGL context lost");
  202. // at least prevent freeze
  203. pause();
  204. }, false);
  205. m_ext.setup_context(gl);
  206. var rinfo = m_ext.get_renderer_info();
  207. if (rinfo)
  208. m_print.log("%cRENDERER INFO:", "color: #00a",
  209. gl.getParameter(rinfo.UNMASKED_VENDOR_WEBGL) + ", " +
  210. gl.getParameter(rinfo.UNMASKED_RENDERER_WEBGL));
  211. m_render.setup_context(gl);
  212. m_geom.setup_context(gl);
  213. m_textures.setup_context(gl);
  214. m_shaders.setup_context(gl);
  215. m_cont.setup_context(gl);
  216. m_context.setup(gl);
  217. m_debug_check.setup_context(gl);
  218. m_data.setup_canvas(canvas);
  219. m_cont.init(canvas, canvas_hud);
  220. m_scenes.setup_dim(canvas.width, canvas.height, 1);
  221. m_sfx.init();
  222. m_input.init();
  223. _fps_counter = init_fps_counter();
  224. loop();
  225. }
  226. /**
  227. * Set the callback for the FPS counter
  228. * @method module:main.set_fps_callback
  229. * @param {FPSCallback} fps_cb FPS callback
  230. */
  231. exports.set_fps_callback = function(fps_cb) {
  232. _fps_callback = fps_cb;
  233. }
  234. /**
  235. * Remove the callback for the FPS counter
  236. * @method module:main.clear_fps_callback
  237. */
  238. exports.clear_fps_callback = function() {
  239. _fps_callback = function() {};
  240. }
  241. /**
  242. * Set the rendering callback which is executed for every frame just before the
  243. * rendering. Only one callback is allowed.
  244. * @method module:main.set_render_callback
  245. * @param {RenderCallback} callback Render callback
  246. */
  247. exports.set_render_callback = function(callback) {
  248. set_render_callback(callback);
  249. }
  250. function set_render_callback(callback) {
  251. _render_callback = callback;
  252. }
  253. /**
  254. * Remove the rendering callback.
  255. * @method module:main.clear_render_callback
  256. */
  257. exports.clear_render_callback = function() {
  258. clear_render_callback();
  259. }
  260. function clear_render_callback() {
  261. _render_callback = function() {};
  262. }
  263. exports.pause = pause;
  264. /**
  265. * Pause the engine
  266. * @method module:main.pause
  267. */
  268. function pause() {
  269. if (is_paused())
  270. return;
  271. _pause_time = performance.now() / 1000;
  272. m_sfx.pause();
  273. m_phy.pause();
  274. m_textures.pause();
  275. m_anchors.pause();
  276. }
  277. /**
  278. * Resume the engine (after pausing)
  279. * @method module:main.resume
  280. */
  281. exports.resume = function() {
  282. if (!is_paused())
  283. return;
  284. _resume_time = performance.now() / 1000;
  285. m_sfx.resume();
  286. m_phy.resume();
  287. m_textures.play(true);
  288. m_anchors.resume();
  289. }
  290. /**
  291. * Check if the engine is paused
  292. * @method module:main.is_paused
  293. * @returns {boolean} Paused flag
  294. */
  295. exports.is_paused = is_paused;
  296. function is_paused() {
  297. return (_resume_time < _pause_time);
  298. }
  299. function loop() {
  300. var vr_display = cfg_def.stereo === "HMD" && m_input.get_webvr_display();
  301. if (vr_display)
  302. vr_display.requestAnimationFrame(loop);
  303. else
  304. _requestAnimFrame(loop);
  305. // float sec
  306. var abstime = performance.now() / 1000;
  307. if (!_last_abs_time)
  308. _last_abs_time = abstime;
  309. var delta = abstime - _last_abs_time;
  310. // do not render short frames
  311. if (delta < 1/cfg_def.max_fps)
  312. return;
  313. var timeline = m_time.get_timeline();
  314. for (var i = 0; i < _loop_cb.length; i++)
  315. _loop_cb[i](timeline, delta);
  316. if (!is_paused()) {
  317. // correct delta if resume occured since last frame
  318. if (_resume_time > _last_abs_time)
  319. delta -= (_resume_time - Math.max(_pause_time, _last_abs_time));
  320. m_time.set_delta(delta);
  321. timeline += delta;
  322. m_time.set_timeline(timeline);
  323. m_debug_telemetry.update();
  324. m_assets.update();
  325. m_data.update();
  326. frame(timeline, delta);
  327. _fps_counter(delta);
  328. }
  329. _last_abs_time = abstime;
  330. if (vr_display && vr_display.isPresenting)
  331. vr_display.submitFrame();
  332. }
  333. function frame(timeline, delta) {
  334. // possible unload between frames
  335. if (!m_data.is_primary_loaded())
  336. return;
  337. m_hud.reset();
  338. m_trans.update(delta);
  339. m_lnodes.update(timeline, delta)
  340. m_nla.update(timeline, delta);
  341. // sound
  342. m_sfx.update(timeline, delta);
  343. // animation
  344. if (delta)
  345. m_anim.update(delta);
  346. // possible unload in animation callbacks
  347. if (!m_data.is_primary_loaded())
  348. return;
  349. m_phy.update(timeline, delta);
  350. // possible unload in physics callbacks
  351. if (!m_data.is_primary_loaded())
  352. return;
  353. //inputs should be updated before controls
  354. m_input.update(timeline);
  355. // controls
  356. m_ctl.update(timeline, delta);
  357. // possible unload in controls callbacks
  358. if (!m_data.is_primary_loaded())
  359. return;
  360. // anchors
  361. m_anchors.update(false);
  362. // objects
  363. m_obj.update(timeline, delta);
  364. // particles
  365. m_particles.update();
  366. // user callback
  367. _render_callback(delta, timeline);
  368. // possible unload in render callback
  369. if (!m_data.is_primary_loaded())
  370. return;
  371. // rendering
  372. m_scenes.update(timeline, delta);
  373. // anchors
  374. m_anchors.update_visibility();
  375. m_main.frame();
  376. }
  377. function init_fps_counter() {
  378. var fps_avg = 60; // decent default value
  379. var fps_frame_counter = 0;
  380. var interval = cfg_def.fps_measurement_interval;
  381. var interval_cb = cfg_def.fps_callback_interval;
  382. var fps_counter = function(delta) {
  383. // NOTE: fixes issues when delta=0
  384. if (delta < 1/cfg_def.max_fps)
  385. return;
  386. fps_avg = m_util.smooth(1/delta, fps_avg, delta, interval);
  387. // stays zero for disabled physics/FPS calculation
  388. var phy_fps_avg = m_phy.get_fps();
  389. fps_frame_counter = (fps_frame_counter + 1) % interval_cb;
  390. if (fps_frame_counter == 0) {
  391. _fps_callback(Math.round(fps_avg), phy_fps_avg);
  392. }
  393. }
  394. return fps_counter;
  395. }
  396. /**
  397. * Reset the engine.
  398. * Unloads the scene and releases the engine's resources.
  399. * @method module:main.reset
  400. */
  401. exports.reset = function() {
  402. m_data.unload(0);
  403. m_context.reset();
  404. m_data.reset();
  405. m_ext.reset();
  406. m_render.reset();
  407. m_geom.reset();
  408. m_textures.reset_mod();
  409. m_shaders.reset();
  410. m_debug_check.reset();
  411. m_cont.reset();
  412. m_data.reset();
  413. m_cont.reset();
  414. m_time.reset();
  415. m_sfx.reset();
  416. _last_abs_time = 0;
  417. _pause_time = 0;
  418. _resume_time = 0;
  419. _fps_callback = function() {};
  420. _fps_counter = function() {};
  421. _render_callback = function() {};
  422. _loop_cb.length = 0;
  423. _gl = null;
  424. }
  425. /**
  426. * Register one-time callback to return DataURL of rendered canvas element.
  427. * @param {BlobURLCallback} callback BlobURL callback.
  428. * @param {string} [format="image/png"] The image format ("image/png", "image/jpeg",
  429. * "image/webp" and so on).
  430. * @param {number} [quality=1.0] Number between 0 and 1 for types: "image/jpeg",
  431. * "image/webp".
  432. * @param {boolean} [auto_revoke=true] Automatically revoke blob object.
  433. * If auto_revoke is false then application must revoke blob URL via the following call {@link https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL| URL.revokeObjectURL(blobURL)}.
  434. */
  435. exports.canvas_data_url = function(callback, format, quality, auto_revoke) {
  436. m_main.canvas_data_url(callback, format, quality, auto_revoke);
  437. }
  438. /**
  439. * Check using device.
  440. * @method module:main.detect_mobile
  441. * @returns {boolean} Checking result.
  442. */
  443. exports.detect_mobile = function() {
  444. return m_compat.detect_mobile();
  445. }
  446. /**
  447. * Append a callback to be executed every frame
  448. * (even if the rendering is paused). Its purpose is to perform actions
  449. * non-related to the actual rendering, e.g html/css manipulation.
  450. * This method allows registration of multiple callbacks.
  451. * @method module:main.append_loop_cb
  452. * @param {LoopCallback} callback Callback
  453. */
  454. exports.append_loop_cb = function(callback) {
  455. for (var i = 0; i < _loop_cb.length; i++)
  456. if (_loop_cb[i] == callback)
  457. return;
  458. _loop_cb.push(callback);
  459. }
  460. /**
  461. * Remove loop callback.
  462. * @method module:main.remove_loop_cb
  463. * @param {LoopCallback} callback Callback
  464. */
  465. exports.remove_loop_cb = function(callback) {
  466. for (var i = 0; i < _loop_cb.length; i++)
  467. if (_loop_cb[i] == callback) {
  468. _loop_cb.splice(i, 1);
  469. break;
  470. }
  471. }
  472. /**
  473. * Return renderer info.
  474. * @method module:main.get_renderer_info
  475. * @returns {RendererInfo|Null} Renderer info.
  476. */
  477. exports.get_renderer_info = function() {
  478. var rinfo = m_ext.get_renderer_info();
  479. if (!rinfo)
  480. return null;
  481. var vendor = _gl.getParameter(rinfo.UNMASKED_VENDOR_WEBGL);
  482. var renderer = _gl.getParameter(rinfo.UNMASKED_RENDERER_WEBGL);
  483. return {"vendor": vendor, "renderer": renderer};
  484. }
  485. }
  486. var main_factory = register("main", Main);
  487. export default main_factory;