import register from "../util/register.js";
import m_cam_fact from "../extern/camera.js";
import m_cont_fact from "../extern/container.js";
import m_ctl_fact from "../extern/controls.js";
import m_phy_fact from "../extern/physics.js";
import m_print_fact from "../intern/print.js";
import m_scs_fact from "../extern/scenes.js";
import m_util_fact from "../extern/util.js";
import m_main_fact from "../extern/main.js";
/**
* Pointer lock and mouse actions add-on.
* Provides support for mouse pointer lock and low-level movement.
* For more generic cases use {@link module:controls|sensor-based API}.
* @see http://www.w3.org/TR/pointerlock/
* @module mouse
* @local UseMouseControlCallback
* @local PointerlockEnabledCallback
* @local PointerlockDisabledCallback
* @local PointerlockMouseMoveCallback
* @local RotationCallback
*/
function Mouse(ns, exports) {
var m_cam = m_cam_fact(ns);
var m_cont = m_cont_fact(ns);
var m_ctl = m_ctl_fact(ns);
var m_phy = m_phy_fact(ns);
var m_print = m_print_fact(ns);
var m_scs = m_scs_fact(ns);
var m_util = m_util_fact(ns);
var m_main = m_main_fact(ns);
var FPS_MOUSE_MULT = 0.0004;
var DRAG_MOUSE_DELTA_MULT = 2;
var _smooth_factor = 1;
var CAM_SMOOTH_CHARACTER_MOUSE = 0.1;
var CAM_SMOOTH_CHARACTER_TOUCH = 0.2; // unused
var PLS_NONE = 0;
var PLS_POINTERLOCK = 1;
var PLS_DRAG = 2;
// mouse drag control
var _mouse_x = 0;
var _mouse_y = 0;
var _mouse_delta = new Float32Array(2);
var _vec2_tmp = new Float32Array(2);
var _use_mouse_control_cb = null;
var _chosen_object = null;
var _plock_state = PLS_NONE;
var _hover_offset = false;
var _drag_offset = false;
/**
* Callback which allows user to specify whether the camera/character movement
* is controlled by mouse module or not.
* @callback UseMouseControlCallback
* @returns {boolean} False to disable mouse control of active camera/character
*/
/**
* Callback which will be executed when pointer lock is enable.
* @callback PointerlockEnabledCallback
*/
/**
* Callback which will be executed when pointer lock is disabled.
* @callback PointerlockDisabledCallback
*/
/**
* Mouse movement event callback
* @callback PointerlockMouseMoveCallback
* @param {MouseEvent} e mousemove event
*/
/**
* Callback for camera/characters rotation
* @callback RotationCallback
* @param {rot_x} x rotation around X-axis
* @param {rot_y} y rotation around Y-axis
*/
/**
* Request pointer lock mode.
* Security issues: execute by user event.
* @method module:mouse.request_pointerlock
* @param {HTMLElement} elem Element
* @param {PointerlockEnabledCallback} [enabled_cb] Enabled callback
* @param {PointerlockDisabledCallback} [disabled_cb] Disabled callback
* @param {PointerlockMouseMoveCallback} [mouse_move_cb] Mouse movement event callback
* @param {UseMouseControlCallback} [use_mouse_control_cb] Callback to check the camera/character control
* @param {RotationCallback} [rotation_cb] Callback for camera rotation. If not specified, the default one will be used.
*/
exports.request_pointerlock = function(elem, enabled_cb, disabled_cb,
mouse_move_cb, use_mouse_control_cb, rotation_cb) {
if (_plock_state == PLS_POINTERLOCK)
return;
_plock_state = PLS_POINTERLOCK;
enabled_cb = enabled_cb || function() {};
disabled_cb = disabled_cb || function() {};
rotation_cb = rotation_cb || default_rotation_cb;
use_mouse_control_cb = use_mouse_control_cb || function() {return true};
mouse_move_cb = mouse_move_cb || function(e) {
if (use_mouse_control_cb()) {
// NOTE: for compatibility with older browsers
if (typeof e.movementX == "number") {
var mx = e.movementX;
var my = e.movementY;
} else if (typeof e.webkitMovementX == "number") {
var mx = e.webkitMovementX;
var my = e.webkitMovementY;
} else if (typeof e.mozMovementX == "number") {
var mx = e.mozMovementX;
var my = e.mozMovementY;
} else {
var mx = 0;
var my = 0;
}
_mouse_delta[0] += mx;
_mouse_delta[1] += my;
}
}
function on_pointerlock_change() {
if (document.pointerLockElement === elem ||
document.webkitPointerLockElement === elem ||
document.mozPointerLockElement === elem) {
//m_print.log("Pointer Lock enabled");
exit_mouse_drag(elem);
elem.addEventListener("mousemove", mouse_move_cb, false);
var camera = m_scs.get_active_camera();
if (!m_ctl.check_sensor_manifold(camera, "SMOOTH_PL")) {
var elapsed = m_ctl.create_elapsed_sensor();
m_ctl.create_sensor_manifold(camera, "SMOOTH_PL", m_ctl.CT_CONTINUOUS,
[elapsed], null, smooth_cb, rotation_cb);
}
enabled_cb();
} else {
//m_print.log("Pointer Lock disabled");
elem.removeEventListener("mousemove", mouse_move_cb, false);
_plock_state = PLS_NONE;
document.removeEventListener("pointerlockchange", on_pointerlock_change, false);
document.removeEventListener("webkitpointerlockchange", on_pointerlock_change, false);
document.removeEventListener("mozpointerlockchange", on_pointerlock_change, false);
disabled_cb();
}
}
document.addEventListener("pointerlockchange", on_pointerlock_change, false);
document.addEventListener("webkitpointerlockchange", on_pointerlock_change, false);
document.addEventListener("mozpointerlockchange", on_pointerlock_change, false);
var request_plock = elem.requestPointerLock ||
elem.webkitRequestPointerLock || elem.mozRequestPointerLock;
if (typeof request_plock === "function")
request_plock.apply(elem);
else
m_print.error("Pointer lock is not available");
}
/**
* Exit the pointer lock mode.
* @method module:mouse.exit_pointerlock
*/
exports.exit_pointerlock = exit_pointerlock;
function exit_pointerlock() {
if (_plock_state == PLS_POINTERLOCK)
_plock_state = PLS_NONE;
var exit_plock = document.exitPointerLock || document.webkitExitPointerLock ||
document.mozExitPointerLock;
if (typeof exit_plock === "function")
exit_plock.apply(document);
m_ctl.remove_sensor_manifold(m_scs.get_active_camera(), "SMOOTH_PL");
}
/**
* Check the pointer lock.
* @method module:mouse.check_pointerlock
* @param {HTMLElement} elem Element
* @returns {boolean} Check result
*/
exports.check_pointerlock = function(elem) {
var request_plock = elem.requestPointerLock ||
elem.webkitRequestPointerLock || elem.mozRequestPointerLock;
if (typeof request_plock === "function")
return true;
else
return false;
}
/**
* Request drag mode.
* @param {HTMLElement} elem Element
* @param {UseMouseControlCallback} [use_mouse_control_cb] Callback to check the mouse control
* @param {RotationCallback} [rotation_cb] Callback for camera rotation. If not specified, the default one will be used.
* @param {boolean} [relative_canvas=false] Calculate coordinates relative to canvas.
* @method module:mouse.request_mouse_drag
*/
exports.request_mouse_drag = request_mouse_drag;
function request_mouse_drag(elem, use_mouse_control_cb, rotation_cb, relative_canvas) {
if (_plock_state == PLS_DRAG)
return;
_plock_state = PLS_DRAG;
exit_pointerlock();
_use_mouse_control_cb = use_mouse_control_cb || function() {return true};
rotation_cb = rotation_cb || default_rotation_cb;
_drag_offset = Boolean(relative_canvas);
elem.addEventListener("mousedown", drag_mouse_down_cb, false);
elem.addEventListener("mouseup", drag_mouse_up_cb, false);
var camera = m_scs.get_active_camera();
if (!m_ctl.check_sensor_manifold(camera, "SMOOTH_DRAG")) {
var elapsed = m_ctl.create_elapsed_sensor();
m_ctl.create_sensor_manifold(camera, "SMOOTH_DRAG", m_ctl.CT_CONTINUOUS,
[elapsed], null, smooth_cb, rotation_cb);
}
}
/**
* Exit drag mode.
* @param {HTMLElement} elem Element
* @method module:mouse.exit_mouse_drag
*/
exports.exit_mouse_drag = exit_mouse_drag;
function exit_mouse_drag(elem) {
if (_plock_state == PLS_DRAG)
_plock_state = PLS_NONE;
elem.removeEventListener("mousedown", drag_mouse_down_cb, false);
elem.removeEventListener("mouseup", drag_mouse_up_cb, false);
elem.removeEventListener("mousemove", drag_mouse_move_cb, false);
m_ctl.remove_sensor_manifold(m_scs.get_active_camera(), "SMOOTH_DRAG");
}
function drag_mouse_move_cb(e) {
if (_use_mouse_control_cb()) {
if (_drag_offset)
var coords = m_cont.get_coords_target_space(e, _vec2_tmp);
else {
var coords = _vec2_tmp;
coords[0] = e.clientX;
coords[1] = e.clientY;
}
_mouse_delta[0] += (coords[0] - _mouse_x) * DRAG_MOUSE_DELTA_MULT;
_mouse_delta[1] += (coords[1] - _mouse_y) * DRAG_MOUSE_DELTA_MULT;
_mouse_x = coords[0];
_mouse_y = coords[1];
}
e.preventDefault();
}
function drag_mouse_down_cb(e) {
if (_drag_offset)
var coords = m_cont.get_coords_target_space(e, _vec2_tmp);
else {
var coords = _vec2_tmp;
coords[0] = e.clientX;
coords[1] = e.clientY;
}
_mouse_x = coords[0];
_mouse_y = coords[1];
e.currentTarget.addEventListener("mousemove", drag_mouse_move_cb, false);
e.preventDefault();
}
function drag_mouse_up_cb(e) {
e.currentTarget.removeEventListener("mousemove", drag_mouse_move_cb, false);
e.preventDefault();
}
function smooth_cb(obj, id, pulse, rot_callback) {
if (Math.abs(_mouse_delta[0]) > 0.01 || Math.abs(_mouse_delta[1]) > 0.01) {
var elapsed = m_ctl.get_sensor_value(obj, id, 0);
var rot_x = m_util.smooth(_mouse_delta[0], 0, elapsed, smooth_coeff_mouse());
var rot_y = m_util.smooth(_mouse_delta[1], 0, elapsed, smooth_coeff_mouse());
_mouse_delta[0] -= rot_x;
_mouse_delta[1] -= rot_y;
rot_callback(-rot_x * FPS_MOUSE_MULT, -rot_y * FPS_MOUSE_MULT);
}
}
function default_rotation_cb(rot_x, rot_y) {
var character = m_scs.get_first_character();
var camera = m_scs.get_active_camera();
m_cam.rotate_camera(camera, rot_x, rot_y);
if (character) {
var angles = m_cam.get_camera_angles_char(camera, _vec2_tmp);
m_phy.set_character_rotation_h(character, angles[0]);
m_phy.set_character_vert_move_dir_angle(character, angles[1]);
}
}
/**
* Enable objects outlining by mouse hover.
* @param {boolean} [relative_canvas=false] Calculate coordinates relative to canvas.
* @method module:mouse.enable_mouse_hover_outline
*/
exports.enable_mouse_hover_outline = enable_mouse_hover_outline;
function enable_mouse_hover_outline(relative_canvas) {
_hover_offset = Boolean(relative_canvas);
if (!m_main.detect_mobile()) {
var main_canvas = m_cont.get_canvas();
main_canvas.addEventListener("mousemove", objects_outline);
}
}
/**
* Disable objects outlining by mouse hover.
* @method module:mouse.disable_mouse_hover_outline
*/
exports.disable_mouse_hover_outline = disable_mouse_hover_outline;
function disable_mouse_hover_outline() {
if (!m_main.detect_mobile()) {
var main_canvas = m_cont.get_canvas();
main_canvas.removeEventListener("mousemove", objects_outline);
if (_chosen_object)
m_scs.set_outline_intensity(_chosen_object, 0);
}
}
function objects_outline(e) {
if (_hover_offset) {
var c_coord = m_cont.get_coords_target_space(e, false, _vec2_tmp);
var obj = m_scs.pick_object(c_coord[0], c_coord[1]);
} else
var obj = m_scs.pick_object(e.clientX, e.clientY);
if (obj) {
if (m_scs.outlining_is_enabled(obj))
m_scs.set_outline_intensity(obj, 1);
if (m_scs.outlining_is_enabled(_chosen_object) && obj != _chosen_object)
m_scs.set_outline_intensity(_chosen_object, 0);
} else
if (m_scs.outlining_is_enabled(_chosen_object))
m_scs.set_outline_intensity(_chosen_object, 0);
_chosen_object = obj;
}
/**
* Get mouse/touch X coordinate.
* @param {MouseEvent|TouchEvent} event Mouse/touch event
* @param {boolean} [use_target_touches=false] Use only those touches that were
* started on the event target element (the targetTouches property).
* @param {boolean} [relative_canvas=false] Return coordinates relative to event
* target.
* @method module:mouse.get_coords_x
* @returns {number} Mouse/touch X coordinate or -1 if not defined.
* @example
* var m_cont = require("container");
* var m_input = require("input");
* var m_mouse = require("mouse");
*
* var canvas = m_cont.get_canvas();
* m_input.add_click_listener(canvas, function(event) {
* var x = m_mouse.get_coords_x(event);
* var y = m_mouse.get_coords_y(event);
* });
*/
exports.get_coords_x = get_coords_x;
function get_coords_x(event, use_target_touches, relative_canvas) {
if (relative_canvas)
return m_cont.get_coords_target_space(event, use_target_touches,
_vec2_tmp)[0];
if ("clientX" in event)
return event.clientX;
if (event.type == "touchend")
var touches = event.changedTouches;
else
var touches = use_target_touches ? event.targetTouches : event.touches;
if (touches && touches[0] && "clientX" in touches[0])
return touches[0].clientX;
return -1;
}
/**
* Get mouse/touch Y coordinate.
* @param {MouseEvent|TouchEvent} event Mouse/touch event
* @param {boolean} [use_target_touches=false] Use only those touches that were
* started on the event target element (the targetTouches property).
* @param {boolean} [relative_canvas=false] Return coordinates relative to event
* target.
* @method module:mouse.get_coords_y
* @returns {number} Mouse/touch Y coordinate or -1 if not defined.
* @example
* var m_cont = require("container");
* var m_input = require("input");
* var m_mouse = require("mouse");
*
* var canvas = m_cont.get_canvas();
* m_input.add_click_listener(canvas, function(event) {
* var x = m_mouse.get_coords_x(event);
* var y = m_mouse.get_coords_y(event);
* });
*/
exports.get_coords_y = get_coords_y;
function get_coords_y(event, use_target_touches, relative_canvas) {
if (relative_canvas)
return m_cont.get_coords_target_space(event, use_target_touches,
_vec2_tmp)[1];
if ("clientY" in event)
return event.clientY;
if (event.type == "touchend")
var touches = event.changedTouches;
else
var touches = use_target_touches ? event.targetTouches : event.touches;
if (touches && touches[0] && "clientY" in touches[0])
return touches[0].clientY;
return -1;
}
function smooth_coeff_mouse() {
return CAM_SMOOTH_CHARACTER_MOUSE * _smooth_factor;
}
function smooth_coeff_touch() {
return CAM_SMOOTH_CHARACTER_TOUCH * _smooth_factor;
}
/**
* Set smooth factor for camera rotation while in pointerlock mode.
* @method module:mouse.set_plock_smooth_factor
* @param {number} value New smooth factor
*/
exports.set_plock_smooth_factor = function(value) {
_smooth_factor = value;
}
/**
* Get smooth factor for camera rotation while in pointerlock mode.
* @method module:mouse.get_plock_smooth_factor
* @returns {number} Smooth factor
*/
exports.get_plock_smooth_factor = function() {
return _smooth_factor;
}
};
var mouse_factory = register("mouse", Mouse);
export default mouse_factory;