Source: addons/mouse.js

  1. import register from "../util/register.js";
  2. import m_cam_fact from "../extern/camera.js";
  3. import m_cont_fact from "../extern/container.js";
  4. import m_ctl_fact from "../extern/controls.js";
  5. import m_phy_fact from "../extern/physics.js";
  6. import m_print_fact from "../intern/print.js";
  7. import m_scs_fact from "../extern/scenes.js";
  8. import m_util_fact from "../extern/util.js";
  9. import m_main_fact from "../extern/main.js";
  10. /**
  11. * Pointer lock and mouse actions add-on.
  12. * Provides support for mouse pointer lock and low-level movement.
  13. * For more generic cases use {@link module:controls|sensor-based API}.
  14. * @see http://www.w3.org/TR/pointerlock/
  15. * @module mouse
  16. * @local UseMouseControlCallback
  17. * @local PointerlockEnabledCallback
  18. * @local PointerlockDisabledCallback
  19. * @local PointerlockMouseMoveCallback
  20. * @local RotationCallback
  21. */
  22. function Mouse(ns, exports) {
  23. var m_cam = m_cam_fact(ns);
  24. var m_cont = m_cont_fact(ns);
  25. var m_ctl = m_ctl_fact(ns);
  26. var m_phy = m_phy_fact(ns);
  27. var m_print = m_print_fact(ns);
  28. var m_scs = m_scs_fact(ns);
  29. var m_util = m_util_fact(ns);
  30. var m_main = m_main_fact(ns);
  31. var FPS_MOUSE_MULT = 0.0004;
  32. var DRAG_MOUSE_DELTA_MULT = 2;
  33. var _smooth_factor = 1;
  34. var CAM_SMOOTH_CHARACTER_MOUSE = 0.1;
  35. var CAM_SMOOTH_CHARACTER_TOUCH = 0.2; // unused
  36. var PLS_NONE = 0;
  37. var PLS_POINTERLOCK = 1;
  38. var PLS_DRAG = 2;
  39. // mouse drag control
  40. var _mouse_x = 0;
  41. var _mouse_y = 0;
  42. var _mouse_delta = new Float32Array(2);
  43. var _vec2_tmp = new Float32Array(2);
  44. var _use_mouse_control_cb = null;
  45. var _chosen_object = null;
  46. var _plock_state = PLS_NONE;
  47. var _hover_offset = false;
  48. var _drag_offset = false;
  49. /**
  50. * Callback which allows user to specify whether the camera/character movement
  51. * is controlled by mouse module or not.
  52. * @callback UseMouseControlCallback
  53. * @returns {boolean} False to disable mouse control of active camera/character
  54. */
  55. /**
  56. * Callback which will be executed when pointer lock is enable.
  57. * @callback PointerlockEnabledCallback
  58. */
  59. /**
  60. * Callback which will be executed when pointer lock is disabled.
  61. * @callback PointerlockDisabledCallback
  62. */
  63. /**
  64. * Mouse movement event callback
  65. * @callback PointerlockMouseMoveCallback
  66. * @param {MouseEvent} e mousemove event
  67. */
  68. /**
  69. * Callback for camera/characters rotation
  70. * @callback RotationCallback
  71. * @param {rot_x} x rotation around X-axis
  72. * @param {rot_y} y rotation around Y-axis
  73. */
  74. /**
  75. * Request pointer lock mode.
  76. * Security issues: execute by user event.
  77. * @method module:mouse.request_pointerlock
  78. * @param {HTMLElement} elem Element
  79. * @param {PointerlockEnabledCallback} [enabled_cb] Enabled callback
  80. * @param {PointerlockDisabledCallback} [disabled_cb] Disabled callback
  81. * @param {PointerlockMouseMoveCallback} [mouse_move_cb] Mouse movement event callback
  82. * @param {UseMouseControlCallback} [use_mouse_control_cb] Callback to check the camera/character control
  83. * @param {RotationCallback} [rotation_cb] Callback for camera rotation. If not specified, the default one will be used.
  84. */
  85. exports.request_pointerlock = function(elem, enabled_cb, disabled_cb,
  86. mouse_move_cb, use_mouse_control_cb, rotation_cb) {
  87. if (_plock_state == PLS_POINTERLOCK)
  88. return;
  89. _plock_state = PLS_POINTERLOCK;
  90. enabled_cb = enabled_cb || function() {};
  91. disabled_cb = disabled_cb || function() {};
  92. rotation_cb = rotation_cb || default_rotation_cb;
  93. use_mouse_control_cb = use_mouse_control_cb || function() {return true};
  94. mouse_move_cb = mouse_move_cb || function(e) {
  95. if (use_mouse_control_cb()) {
  96. // NOTE: for compatibility with older browsers
  97. if (typeof e.movementX == "number") {
  98. var mx = e.movementX;
  99. var my = e.movementY;
  100. } else if (typeof e.webkitMovementX == "number") {
  101. var mx = e.webkitMovementX;
  102. var my = e.webkitMovementY;
  103. } else if (typeof e.mozMovementX == "number") {
  104. var mx = e.mozMovementX;
  105. var my = e.mozMovementY;
  106. } else {
  107. var mx = 0;
  108. var my = 0;
  109. }
  110. _mouse_delta[0] += mx;
  111. _mouse_delta[1] += my;
  112. }
  113. }
  114. function on_pointerlock_change() {
  115. if (document.pointerLockElement === elem ||
  116. document.webkitPointerLockElement === elem ||
  117. document.mozPointerLockElement === elem) {
  118. //m_print.log("Pointer Lock enabled");
  119. exit_mouse_drag(elem);
  120. elem.addEventListener("mousemove", mouse_move_cb, false);
  121. var camera = m_scs.get_active_camera();
  122. if (!m_ctl.check_sensor_manifold(camera, "SMOOTH_PL")) {
  123. var elapsed = m_ctl.create_elapsed_sensor();
  124. m_ctl.create_sensor_manifold(camera, "SMOOTH_PL", m_ctl.CT_CONTINUOUS,
  125. [elapsed], null, smooth_cb, rotation_cb);
  126. }
  127. enabled_cb();
  128. } else {
  129. //m_print.log("Pointer Lock disabled");
  130. elem.removeEventListener("mousemove", mouse_move_cb, false);
  131. _plock_state = PLS_NONE;
  132. document.removeEventListener("pointerlockchange", on_pointerlock_change, false);
  133. document.removeEventListener("webkitpointerlockchange", on_pointerlock_change, false);
  134. document.removeEventListener("mozpointerlockchange", on_pointerlock_change, false);
  135. disabled_cb();
  136. }
  137. }
  138. document.addEventListener("pointerlockchange", on_pointerlock_change, false);
  139. document.addEventListener("webkitpointerlockchange", on_pointerlock_change, false);
  140. document.addEventListener("mozpointerlockchange", on_pointerlock_change, false);
  141. var request_plock = elem.requestPointerLock ||
  142. elem.webkitRequestPointerLock || elem.mozRequestPointerLock;
  143. if (typeof request_plock === "function")
  144. request_plock.apply(elem);
  145. else
  146. m_print.error("Pointer lock is not available");
  147. }
  148. /**
  149. * Exit the pointer lock mode.
  150. * @method module:mouse.exit_pointerlock
  151. */
  152. exports.exit_pointerlock = exit_pointerlock;
  153. function exit_pointerlock() {
  154. if (_plock_state == PLS_POINTERLOCK)
  155. _plock_state = PLS_NONE;
  156. var exit_plock = document.exitPointerLock || document.webkitExitPointerLock ||
  157. document.mozExitPointerLock;
  158. if (typeof exit_plock === "function")
  159. exit_plock.apply(document);
  160. m_ctl.remove_sensor_manifold(m_scs.get_active_camera(), "SMOOTH_PL");
  161. }
  162. /**
  163. * Check the pointer lock.
  164. * @method module:mouse.check_pointerlock
  165. * @param {HTMLElement} elem Element
  166. * @returns {boolean} Check result
  167. */
  168. exports.check_pointerlock = function(elem) {
  169. var request_plock = elem.requestPointerLock ||
  170. elem.webkitRequestPointerLock || elem.mozRequestPointerLock;
  171. if (typeof request_plock === "function")
  172. return true;
  173. else
  174. return false;
  175. }
  176. /**
  177. * Request drag mode.
  178. * @param {HTMLElement} elem Element
  179. * @param {UseMouseControlCallback} [use_mouse_control_cb] Callback to check the mouse control
  180. * @param {RotationCallback} [rotation_cb] Callback for camera rotation. If not specified, the default one will be used.
  181. * @param {boolean} [relative_canvas=false] Calculate coordinates relative to canvas.
  182. * @method module:mouse.request_mouse_drag
  183. */
  184. exports.request_mouse_drag = request_mouse_drag;
  185. function request_mouse_drag(elem, use_mouse_control_cb, rotation_cb, relative_canvas) {
  186. if (_plock_state == PLS_DRAG)
  187. return;
  188. _plock_state = PLS_DRAG;
  189. exit_pointerlock();
  190. _use_mouse_control_cb = use_mouse_control_cb || function() {return true};
  191. rotation_cb = rotation_cb || default_rotation_cb;
  192. _drag_offset = Boolean(relative_canvas);
  193. elem.addEventListener("mousedown", drag_mouse_down_cb, false);
  194. elem.addEventListener("mouseup", drag_mouse_up_cb, false);
  195. var camera = m_scs.get_active_camera();
  196. if (!m_ctl.check_sensor_manifold(camera, "SMOOTH_DRAG")) {
  197. var elapsed = m_ctl.create_elapsed_sensor();
  198. m_ctl.create_sensor_manifold(camera, "SMOOTH_DRAG", m_ctl.CT_CONTINUOUS,
  199. [elapsed], null, smooth_cb, rotation_cb);
  200. }
  201. }
  202. /**
  203. * Exit drag mode.
  204. * @param {HTMLElement} elem Element
  205. * @method module:mouse.exit_mouse_drag
  206. */
  207. exports.exit_mouse_drag = exit_mouse_drag;
  208. function exit_mouse_drag(elem) {
  209. if (_plock_state == PLS_DRAG)
  210. _plock_state = PLS_NONE;
  211. elem.removeEventListener("mousedown", drag_mouse_down_cb, false);
  212. elem.removeEventListener("mouseup", drag_mouse_up_cb, false);
  213. elem.removeEventListener("mousemove", drag_mouse_move_cb, false);
  214. m_ctl.remove_sensor_manifold(m_scs.get_active_camera(), "SMOOTH_DRAG");
  215. }
  216. function drag_mouse_move_cb(e) {
  217. if (_use_mouse_control_cb()) {
  218. if (_drag_offset)
  219. var coords = m_cont.get_coords_target_space(e, _vec2_tmp);
  220. else {
  221. var coords = _vec2_tmp;
  222. coords[0] = e.clientX;
  223. coords[1] = e.clientY;
  224. }
  225. _mouse_delta[0] += (coords[0] - _mouse_x) * DRAG_MOUSE_DELTA_MULT;
  226. _mouse_delta[1] += (coords[1] - _mouse_y) * DRAG_MOUSE_DELTA_MULT;
  227. _mouse_x = coords[0];
  228. _mouse_y = coords[1];
  229. }
  230. e.preventDefault();
  231. }
  232. function drag_mouse_down_cb(e) {
  233. if (_drag_offset)
  234. var coords = m_cont.get_coords_target_space(e, _vec2_tmp);
  235. else {
  236. var coords = _vec2_tmp;
  237. coords[0] = e.clientX;
  238. coords[1] = e.clientY;
  239. }
  240. _mouse_x = coords[0];
  241. _mouse_y = coords[1];
  242. e.currentTarget.addEventListener("mousemove", drag_mouse_move_cb, false);
  243. e.preventDefault();
  244. }
  245. function drag_mouse_up_cb(e) {
  246. e.currentTarget.removeEventListener("mousemove", drag_mouse_move_cb, false);
  247. e.preventDefault();
  248. }
  249. function smooth_cb(obj, id, pulse, rot_callback) {
  250. if (Math.abs(_mouse_delta[0]) > 0.01 || Math.abs(_mouse_delta[1]) > 0.01) {
  251. var elapsed = m_ctl.get_sensor_value(obj, id, 0);
  252. var rot_x = m_util.smooth(_mouse_delta[0], 0, elapsed, smooth_coeff_mouse());
  253. var rot_y = m_util.smooth(_mouse_delta[1], 0, elapsed, smooth_coeff_mouse());
  254. _mouse_delta[0] -= rot_x;
  255. _mouse_delta[1] -= rot_y;
  256. rot_callback(-rot_x * FPS_MOUSE_MULT, -rot_y * FPS_MOUSE_MULT);
  257. }
  258. }
  259. function default_rotation_cb(rot_x, rot_y) {
  260. var character = m_scs.get_first_character();
  261. var camera = m_scs.get_active_camera();
  262. m_cam.rotate_camera(camera, rot_x, rot_y);
  263. if (character) {
  264. var angles = m_cam.get_camera_angles_char(camera, _vec2_tmp);
  265. m_phy.set_character_rotation_h(character, angles[0]);
  266. m_phy.set_character_vert_move_dir_angle(character, angles[1]);
  267. }
  268. }
  269. /**
  270. * Enable objects outlining by mouse hover.
  271. * @param {boolean} [relative_canvas=false] Calculate coordinates relative to canvas.
  272. * @method module:mouse.enable_mouse_hover_outline
  273. */
  274. exports.enable_mouse_hover_outline = enable_mouse_hover_outline;
  275. function enable_mouse_hover_outline(relative_canvas) {
  276. _hover_offset = Boolean(relative_canvas);
  277. if (!m_main.detect_mobile()) {
  278. var main_canvas = m_cont.get_canvas();
  279. main_canvas.addEventListener("mousemove", objects_outline);
  280. }
  281. }
  282. /**
  283. * Disable objects outlining by mouse hover.
  284. * @method module:mouse.disable_mouse_hover_outline
  285. */
  286. exports.disable_mouse_hover_outline = disable_mouse_hover_outline;
  287. function disable_mouse_hover_outline() {
  288. if (!m_main.detect_mobile()) {
  289. var main_canvas = m_cont.get_canvas();
  290. main_canvas.removeEventListener("mousemove", objects_outline);
  291. if (_chosen_object)
  292. m_scs.set_outline_intensity(_chosen_object, 0);
  293. }
  294. }
  295. function objects_outline(e) {
  296. if (_hover_offset) {
  297. var c_coord = m_cont.get_coords_target_space(e, false, _vec2_tmp);
  298. var obj = m_scs.pick_object(c_coord[0], c_coord[1]);
  299. } else
  300. var obj = m_scs.pick_object(e.clientX, e.clientY);
  301. if (obj) {
  302. if (m_scs.outlining_is_enabled(obj))
  303. m_scs.set_outline_intensity(obj, 1);
  304. if (m_scs.outlining_is_enabled(_chosen_object) && obj != _chosen_object)
  305. m_scs.set_outline_intensity(_chosen_object, 0);
  306. } else
  307. if (m_scs.outlining_is_enabled(_chosen_object))
  308. m_scs.set_outline_intensity(_chosen_object, 0);
  309. _chosen_object = obj;
  310. }
  311. /**
  312. * Get mouse/touch X coordinate.
  313. * @param {MouseEvent|TouchEvent} event Mouse/touch event
  314. * @param {boolean} [use_target_touches=false] Use only those touches that were
  315. * started on the event target element (the targetTouches property).
  316. * @param {boolean} [relative_canvas=false] Return coordinates relative to event
  317. * target.
  318. * @method module:mouse.get_coords_x
  319. * @returns {number} Mouse/touch X coordinate or -1 if not defined.
  320. * @example
  321. * var m_cont = require("container");
  322. * var m_input = require("input");
  323. * var m_mouse = require("mouse");
  324. *
  325. * var canvas = m_cont.get_canvas();
  326. * m_input.add_click_listener(canvas, function(event) {
  327. * var x = m_mouse.get_coords_x(event);
  328. * var y = m_mouse.get_coords_y(event);
  329. * });
  330. */
  331. exports.get_coords_x = get_coords_x;
  332. function get_coords_x(event, use_target_touches, relative_canvas) {
  333. if (relative_canvas)
  334. return m_cont.get_coords_target_space(event, use_target_touches,
  335. _vec2_tmp)[0];
  336. if ("clientX" in event)
  337. return event.clientX;
  338. if (event.type == "touchend")
  339. var touches = event.changedTouches;
  340. else
  341. var touches = use_target_touches ? event.targetTouches : event.touches;
  342. if (touches && touches[0] && "clientX" in touches[0])
  343. return touches[0].clientX;
  344. return -1;
  345. }
  346. /**
  347. * Get mouse/touch Y coordinate.
  348. * @param {MouseEvent|TouchEvent} event Mouse/touch event
  349. * @param {boolean} [use_target_touches=false] Use only those touches that were
  350. * started on the event target element (the targetTouches property).
  351. * @param {boolean} [relative_canvas=false] Return coordinates relative to event
  352. * target.
  353. * @method module:mouse.get_coords_y
  354. * @returns {number} Mouse/touch Y coordinate or -1 if not defined.
  355. * @example
  356. * var m_cont = require("container");
  357. * var m_input = require("input");
  358. * var m_mouse = require("mouse");
  359. *
  360. * var canvas = m_cont.get_canvas();
  361. * m_input.add_click_listener(canvas, function(event) {
  362. * var x = m_mouse.get_coords_x(event);
  363. * var y = m_mouse.get_coords_y(event);
  364. * });
  365. */
  366. exports.get_coords_y = get_coords_y;
  367. function get_coords_y(event, use_target_touches, relative_canvas) {
  368. if (relative_canvas)
  369. return m_cont.get_coords_target_space(event, use_target_touches,
  370. _vec2_tmp)[1];
  371. if ("clientY" in event)
  372. return event.clientY;
  373. if (event.type == "touchend")
  374. var touches = event.changedTouches;
  375. else
  376. var touches = use_target_touches ? event.targetTouches : event.touches;
  377. if (touches && touches[0] && "clientY" in touches[0])
  378. return touches[0].clientY;
  379. return -1;
  380. }
  381. function smooth_coeff_mouse() {
  382. return CAM_SMOOTH_CHARACTER_MOUSE * _smooth_factor;
  383. }
  384. function smooth_coeff_touch() {
  385. return CAM_SMOOTH_CHARACTER_TOUCH * _smooth_factor;
  386. }
  387. /**
  388. * Set smooth factor for camera rotation while in pointerlock mode.
  389. * @method module:mouse.set_plock_smooth_factor
  390. * @param {number} value New smooth factor
  391. */
  392. exports.set_plock_smooth_factor = function(value) {
  393. _smooth_factor = value;
  394. }
  395. /**
  396. * Get smooth factor for camera rotation while in pointerlock mode.
  397. * @method module:mouse.get_plock_smooth_factor
  398. * @returns {number} Smooth factor
  399. */
  400. exports.get_plock_smooth_factor = function() {
  401. return _smooth_factor;
  402. }
  403. };
  404. var mouse_factory = register("mouse", Mouse);
  405. export default mouse_factory;