import register from "../util/register.js";
import m_cam_fact from "../extern/camera.js";
import m_cfg_fact from "../extern/config.js";
import m_cons_fact from "../extern/constraints.js";
import m_cont_fact from "../extern/container.js";
import m_ctl_fact from "../extern/controls.js";
import m_data_fact from "../extern/data.js";
import m_dbg_fact from "../extern/debug.js";
import m_input_fact from "../extern/input.js";
import m_main_fact from "../extern/main.js";
import m_phy_fact from "../extern/physics.js";
import m_print_fact from "../intern/print.js";
import m_screen_fact from "../extern/screen.js";
import m_scs_fact from "../extern/scenes.js";
import m_trans_fact from "../extern/transform.js";
import m_util_fact from "../extern/util.js";
import * as m_vec3 from "../libs/gl_matrix/vec3.js";
/**
* Application add-on.
* Provides generic routines for the engine's initialization, UI and I/O.
* @module app
* @local AppInitCallback
* @local AnimFinishCallback
* @local QueueObject
*/
function App(ns, exports) {
var m_cam = m_cam_fact(ns);
var m_cfg = m_cfg_fact(ns);
var m_cons = m_cons_fact(ns);
var m_cont = m_cont_fact(ns);
var m_ctl = m_ctl_fact(ns);
var m_data = m_data_fact(ns);
var m_dbg = m_dbg_fact(ns);
var m_input = m_input_fact(ns);
var m_main = m_main_fact(ns);
var m_phy = m_phy_fact(ns);
var m_print = m_print_fact(ns);
var m_screen = m_screen_fact(ns);
var m_scs = m_scs_fact(ns);
var m_trans = m_trans_fact(ns);
var m_util = m_util_fact(ns);
// Constants used for the target camera
var TARGET_KEY_ZOOM_POW1 = 1.0;
var TARGET_KEY_ZOOM_POW2 = 0.15;
var TARGET_TOUCH_ZOOM_FACTOR = 0.03;
// Constants used for the eye camera
var EYE_KEY_TRANS_FACTOR = 5.0;
var EYE_ROTATION_DECREMENT = 0.5;
// Constants used for both target and eye camera
var TARGET_EYE_MOUSE_ROT_MULT_PX = 0.003;
var TARGET_EYE_MOUSE_PAN_MULT_PX = 0.00075;
var TARGET_EYE_TOUCH_ROT_MULT_PX = 0.003;
var TARGET_EYE_TOUCH_PAN_MULT_PX = 0.00075;
var TARGET_EYE_KEY_ROT_FACTOR = 0.75;
// Constants used for the hover camera
var HOVER_MOUSE_PAN_MULT_PX = 0.003;
var HOVER_MOUSE_ROT_MULT_PX = 0.00075;
var HOVER_TOUCH_PAN_MULT_PX = 0.003;
var HOVER_TOUCH_ROT_MULT_PX = 0.003;
var HOVER_KEY_TRANS_FACTOR = 0.5;
var HOVER_KEY_ZOOM_FACTOR = 30.0;
var HOVER_MOUSE_TOUCH_TRANS_FACTOR = 0.2;
var HOVER_MOUSE_ZOOM_FACTOR = 2.0;
var HOVER_TOUCH_ZOOM_FACTOR = 2.0;
var HOVER_ZOOM_FACTOR_MIN = 2.0;
var HOVER_SPEED_MIN = 1.0;
var _smooth_factor = 1;
// Constants used for camera smoothing
var CAM_SMOOTH_ZOOM_MOUSE = 0.1;
var CAM_SMOOTH_ZOOM_TOUCH = 0.15;
var CAM_SMOOTH_ROT_TRANS_MOUSE = 0.08;
var CAM_SMOOTH_ROT_TRANS_TOUCH = 0.12;
// Constants used for camera physics
var CHAR_HEAD_POSITION = 0.5;
var CAM_COLL_DELTA = 0.25
// NOTE: EPSILON_DELTA << EPSILON_DISTANCE to prevent camera freezing near pivot
var EPSILON_DISTANCE = 0.001;
var EPSILON_DELTA = 0.00001;
var FRAME_WAITING_PER = 10;
var AXIS_THRESHOLD = 0.1;
var TRANS_GMPD_KOEF = 0.002;
var ZOOM_GMPD_KOEF = 0.05;
var LOW_BANDWIDTH_LIMIT = 1.5;
// Cached HTML elements
var _fps_logger_elem = null;
// Global flags used for re-initializing of application camera controls
// (which should be done after switching camera type)
var _disable_default_pivot = false;
var _disable_letter_controls = false;
var _disable_zoom = false;
var _element = null;
var _allow_element_exit = false;
var _disable_gamepad_controls = false;
// Cached arrays
var _vec2_tmp = new Float32Array(2);
var _vec2_tmp2 = new Float32Array(2);
var _vec3_tmp = new Float32Array(3);
var _vec3_tmp2 = new Float32Array(3);
var _quat4_tmp = new Float32Array(4);
var _velocity_tmp = {};
var _limits_tmp = {};
/**
* Application initialization callback.
* @callback AppInitCallback
* @param {HTMLElement} canvas Initialized canvas element.
* @param {boolean} success Success flag.
*/
/**
* Initialize the engine.
* The "options" object may be extended by adding properties from the engine's
* configuration (see {@link module:config|config} module).
* In that case they will be applied before engine initialization.
* @param {Object} [options={}] Initialization options.
* @param {string} [options.canvas_container_id=null] Canvas container ID.
* @param {AppInitCallback} [options.callback=function(){}] Initialization callback.
* @param {string[]} [options.error_purge_elements=null] Array of IDs of HTML
* elements to be removed in case of initialization error.
* @param {boolean} [options.show_hud_debug_info=false] Show HUD with
* developer info.
* @param {boolean} [options.show_fps=false] Show FPS counter.
* @param {string} [options.fps_elem_id=null] Custom fps counter id.
* @param {string} [options.fps_wrapper_id=null] Show FPS wrapper with
* current id.
* @param {boolean} [options.report_init_failure=true] Show elements with info
* about init failure
* @param {boolean} [options.pause_invisible=true] Pause engine simulation if
* page is not visible (in other tab or minimized).
* @param {boolean} [options.key_pause_enabled=true] Enable key pause
* @param {boolean} [options.autoresize=false] Automatically resize canvas to
* match the size of container element.
* @param {number} [options.force_container_ratio=0] Automatically resize
* canvas container height, based on its width and passed ratio value.
* @param {boolean} [options.min_capabilities=false] Set min capability mode
* @param {boolean} [options.debug_loading=false] Print loading info into the console
* @cc_externs canvas_container_id callback show_hud_debug_info
* @cc_externs sfx_mix_mode show_fps fps_elem_id error_purge_elements
* @cc_externs report_init_failure pause_invisible key_pause_enabled
* @cc_externs alpha alpha_sort_threshold assets_dds_available assets_pvr_available
* @cc_externs assets_min50_available quality fps_wrapper_id
* @cc_externs console_verbose physics_enabled autoresize track_container_position
* @cc_externs force_container_ratio from to elem prop cb duration opt_prefix
* @cc_externs opt_suffix min_capabilities srgb_type debug_loading
*/
exports.init = function(options) {
options = options || {};
var autoresize = false;
var canvas_container_id = null;
var callback = function() {};
var error_purge_elements = null;
var fps_elem_id = null;
var force_container_ratio = 0;
var fps_wrapper_id = null;
var key_pause_enabled = true;
var min_capabilities = false;
var pause_invisible = true;
var report_init_failure = true;
var sfx_mix_mode = false;
var show_fps = false;
var show_hud_debug_info = false;
var track_container_position = false;
var quality = m_cfg.get("quality");
for (var opt in options) {
switch (opt) {
case "canvas_container_id":
canvas_container_id = options.canvas_container_id;
break;
case "callback":
callback = options.callback;
break;
case "autoresize":
autoresize = options.autoresize;
break;
case "show_hud_debug_info":
show_hud_debug_info = options.show_hud_debug_info;
break;
case "sfx_mix_mode":
sfx_mix_mode = options.sfx_mix_mode;
break;
case "show_fps":
show_fps = options.show_fps;
break;
case "track_container_position":
track_container_position = options.track_container_position;
m_print.error_once("track_container_position deprecated. " +
"Not needed anymore. Use the container.get_coords_target_space method.");
break;
case "fps_wrapper_id":
fps_wrapper_id = options.fps_wrapper_id;
break;
case "fps_elem_id":
fps_elem_id = options.fps_elem_id;
break;
case "error_purge_elements":
error_purge_elements = options.error_purge_elements;
break;
case "report_init_failure":
report_init_failure = options.report_init_failure;
break;
case "pause_invisible":
pause_invisible = options.pause_invisible;
break;
case "key_pause_enabled":
key_pause_enabled = options.key_pause_enabled;
break;
case "force_container_ratio":
force_container_ratio = options.force_container_ratio;
break;
case "min_capabilities":
min_capabilities = options.min_capabilities;
break;
case "quality":
quality = options.quality;
break;
default:
m_cfg.set(opt, options[opt]);
break;
}
}
if (quality != m_cfg.P_AUTO)
m_cfg.set("quality", quality);
var on_key_pause = function(e) {
if (e.keyCode == m_ctl.KEY_P) {
if (m_main.is_paused())
m_main.resume();
else
m_main.pause();
}
}
if (key_pause_enabled)
document.addEventListener("keydown", on_key_pause, false);
m_cfg.set("show_hud_debug_info", show_hud_debug_info);
m_cfg.set("sfx_mix_mode", sfx_mix_mode);
var init_hud_canvas = show_hud_debug_info || sfx_mix_mode || null;
var onload_cb = function() {
var init_ok = setup_canvas(canvas_container_id, init_hud_canvas,
report_init_failure, error_purge_elements);
var canvas_elem = m_cont.get_canvas();
if (!init_ok) {
callback(canvas_elem, false);
return;
}
var canvas_container_elem = m_cont.get_container();
m_cont.resize_to_container();
if (show_fps) {
create_fps_logger_elem(fps_elem_id, fps_wrapper_id);
m_main.set_fps_callback(fps_callback);
}
if (pause_invisible) {
handle_page_visibility();
handle_position_visibility();
}
if (force_container_ratio) {
m_main.append_loop_cb(function() {
canvas_container_elem.style.height =
canvas_container_elem.clientWidth / force_container_ratio + "px";
});
}
if (autoresize)
m_main.append_loop_cb(function() {
m_cont.resize_to_container();
});
if (track_container_position) {
m_main.append_loop_cb(function() {
m_cont.force_offsets_updating();
});
}
if (min_capabilities)
m_cfg.reset_limits();
if (quality == m_cfg.P_AUTO) {
m_dbg.test_performance(function(time, bw) {
if (time == 0) {
m_print.log("QUALITY TEST RESULT: UNSUPPORTED, using HIGH");
m_cfg.apply_quality(m_cfg.P_HIGH);
} else if (bw < LOW_BANDWIDTH_LIMIT) {
m_print.log("QUALITY TEST RESULT: LOW (" + bw.toFixed(1) + " GB/s)");
m_cfg.apply_quality(m_cfg.P_LOW);
} else {
m_print.log("QUALITY TEST RESULT: HIGH (" + bw.toFixed(1) + " GB/s)");
m_cfg.apply_quality(m_cfg.P_HIGH);
}
callback(canvas_elem, true);
});
} else
callback(canvas_elem, true);
};
var onunload_cb = function() {
m_data.cleanup();
};
if (document.readyState == "complete")
window.setTimeout(onload_cb, 0);
else
window.addEventListener("load", onload_cb, false);
window.addEventListener("unload", onunload_cb, false);
}
function handle_page_visibility() {
var was_paused = m_main.is_paused();
var visibility_change = function() {
if (document.hidden) {
was_paused = m_main.is_paused();
m_main.pause();
} else if (!was_paused)
m_main.resume();
}
document.addEventListener("visibilitychange", visibility_change, false);
}
function handle_position_visibility() {
var frame_counter = 0;
var container = m_cont.get_container();
var was_paused = m_main.is_paused();
var check_paused = true;
var visibility_change = function() {
if (frame_counter % FRAME_WAITING_PER == 0) {
var coords = container.getBoundingClientRect();
if (coords.top > window.innerHeight
|| coords.bottom < 0) {
if (check_paused)
was_paused = m_main.is_paused();
m_main.pause();
check_paused = false;
} else if (!was_paused && !check_paused) {
m_main.resume();
check_paused = true;
}
}
frame_counter++;
}
m_main.append_loop_cb(visibility_change);
}
function setup_canvas(canvas_container_id, init_hud_canvas,
report_init_failure, purge_elements) {
var canvas_elem = document.createElement("canvas");
var append_to = document.getElementById(canvas_container_id);
if (!append_to) {
m_print.error("Warning: canvas container \"" + canvas_container_id +
"\" not found, appending to body");
append_to = document.body;
}
canvas_elem.style.position = "absolute";
canvas_elem.style.left = 0;
canvas_elem.style.top = 0;
canvas_elem.style.width = append_to.offsetWidth + "px";
canvas_elem.style.height = append_to.offsetHeight + "px";
canvas_elem.width = append_to.offsetWidth * window.devicePixelRatio;
canvas_elem.height = append_to.offsetHeight * window.devicePixelRatio;
if (init_hud_canvas) {
var canvas_elem_hud = document.createElement("canvas");
// NOTE: pointer-events only for Chrome, Firefox, Safari
canvas_elem_hud.style.position = "absolute";
canvas_elem.style.left = 0;
canvas_elem.style.top = 0;
canvas_elem_hud.style.pointerEvents = "none";
} else
var canvas_elem_hud = null;
append_to.appendChild(canvas_elem);
if (!m_main.init(canvas_elem, canvas_elem_hud)) {
if (report_init_failure)
report_app_error("Browser could not initialize WebGL", "For more info visit",
"https://www.blend4web.com/doc/en/problems_and_solutions.html#problems-upon-startup", purge_elements);
return false;
}
if (canvas_elem_hud)
m_cont.insert_to_container(canvas_elem_hud, "LAST");
return true;
}
function create_fps_logger_elem(fps_elem_id, fps_wrapper_id) {
if (fps_elem_id) {
if (fps_wrapper_id)
document.getElementById(fps_wrapper_id).style.display = "block";
_fps_logger_elem = document.getElementById(fps_elem_id);
} else {
_fps_logger_elem = document.createElement("div");
_fps_logger_elem.innerHTML = 0;
_fps_logger_elem.style.cssText =
"position:absolute;" +
"font-family: Arial, sans-serif;" +
"top: 20px;" +
"right: 20px;" +
"text-shadow: 0px 0px 6px #000;" +
"font-size: 40px;" +
"line-height: 50px;" +
"font-weight: bold;" +
"color: #fff;";
m_cont.insert_to_container(_fps_logger_elem, "JUST_AFTER_CANVAS");
}
}
function fps_callback(fps, phy_fps) {
var fps_str = String(fps);
if (phy_fps)
fps_str += "/" + String(phy_fps);
_fps_logger_elem.innerHTML = fps_str;
}
function elem_cloned(elem_id) {
var target = document.getElementById(elem_id);
// clone to prevent adding event listeners more than once
var new_element = target.cloneNode(true);
target.parentNode.replaceChild(new_element, target);
return new_element;
}
exports.set_onclick = function(elem_id, callback) {
var elem = elem_cloned(elem_id);
elem.addEventListener("mouseup", function(e) {
callback(elem.value);
}, false);
}
exports.set_onchange = function(elem_id, callback) {
var elem = elem_cloned(elem_id);
elem.addEventListener("change", function(e) {
var checked = elem.checked;
var rslt = checked != undefined ? checked : elem.value;
callback(rslt);
}, false);
}
exports.set_onkeypress = function(elem_id, callback) {
var elem = elem_cloned(elem_id);
elem.addEventListener("keypress", function(e) {
callback(e.keyCode, elem.value);
}, false);
}
function trans_hover_cam_horiz_local(camobj, dir, fact) {
var dist = Math.max(m_cam.hover_get_distance(camobj), HOVER_SPEED_MIN);
var obj_quat = m_trans.get_rotation(camobj, _quat4_tmp);
var abs_dir = m_util.quat_to_dir(obj_quat, dir, _vec3_tmp);
abs_dir[2] = 0;
m_vec3.normalize(abs_dir, abs_dir);
m_vec3.scale(abs_dir, dist * fact, abs_dir);
var obj_trans = m_trans.get_translation(camobj, _vec3_tmp2);
m_vec3.add(obj_trans, abs_dir, obj_trans)
m_cam.set_translation(camobj, obj_trans);
}
function zoom_hover_cam(camobj, fact) {
var limits = m_cam.hover_get_vertical_limits(camobj, _limits_tmp);
if (limits.up != limits.down) {
var y_angle = m_cam.get_camera_angles(camobj, _vec2_tmp2)[1];
var angle_factor = (limits.down - y_angle) / (limits.down - limits.up);
var dist_limits = m_cam.hover_get_distance_limits(camobj, _limits_tmp);
angle_factor = Math.max(angle_factor, HOVER_ZOOM_FACTOR_MIN / dist_limits.max);
m_cam.rotate_camera(camobj, 0, angle_factor * fact);
}
}
function trans_eye_cam_local(camobj, fact_x, fact_y, fact_z) {
fact_x *= EYE_KEY_TRANS_FACTOR;
fact_y *= EYE_KEY_TRANS_FACTOR;
fact_z *= EYE_KEY_TRANS_FACTOR;
m_trans.move_local(camobj, fact_x, fact_y, fact_z);
}
function calc_fact_from(fact_to) {
return fact_to / (1 - fact_to);
}
function trans_targ_cam_local(camobj, fact_view, elapsed) {
var dist = m_cam.target_get_distance(camobj);
var abs_fact_view = Math.abs(fact_view);
var fact = Math.pow(abs_fact_view * elapsed, TARGET_KEY_ZOOM_POW1
- Math.pow(abs_fact_view * elapsed, TARGET_KEY_ZOOM_POW2));
if (fact_view < 0)
if (dist > EPSILON_DISTANCE)
fact_view = - dist * fact;
else
fact_view = 0;
else
fact_view = dist * calc_fact_from(fact);
m_trans.move_local(camobj, 0, 0, fact_view);
}
function get_dest_mouse_touch(obj, value, fact, dist, dest_value) {
var t_mult = dist * fact;
for (var i = value; i > 0; --i) {
dist += t_mult;
dest_value += t_mult;
t_mult = dist * fact;
}
return dest_value;
}
function get_dest_zoom(obj, value, velocity_zoom, dest_value, dev_fact,
use_pivot) {
if (use_pivot) {
// camera zooming
var cam_pivot = m_cam.target_get_pivot(obj, _vec3_tmp);
var cam_eye = m_cam.get_translation(obj);
var dist = m_vec3.dist(cam_pivot, cam_eye) + dest_value;
if (value > 0)
dest_value = get_dest_mouse_touch(obj, value,
-velocity_zoom, dist, dest_value);
else
dest_value = get_dest_mouse_touch(obj, -value,
calc_fact_from(velocity_zoom), dist, dest_value);
} else
// use_hover == True
dest_value -= value * dev_fact * velocity_zoom;
return dest_value;
}
/**
* Assign keyboard and mouse controls to the active camera.
* (arrow keys, ADSW, wheel and others)
* @method module:app.enable_camera_controls
* @param {boolean} [disable_default_pivot=false] Do not use the possible
* camera-defined pivot point
* @param {boolean} [disable_letter_controls=false] Disable keyboard letter controls
* (only arrow keys will be used to control the camera).
* @param {boolean} [disable_zoom=false] Disable zoom
* @param {HTMLElement} [element] HTML element to add event listeners to.
* The canvas container will be use by default.
* @param {boolean} [allow_element_exit=false] Continue receiving mouse events
* even when the mouse is leaving the HTML element
* @param {boolean} [disable_gamepad_controls=false] Disable gamepad controls
* @example
* var m_app = require("app");
* m_app.enable_camera_controls();
*/
exports.enable_camera_controls = enable_camera_controls;
function enable_camera_controls(disable_default_pivot, disable_letter_controls,
disable_zoom, element, allow_element_exit,
disable_gamepad_controls) {
_disable_default_pivot = disable_default_pivot;
_disable_letter_controls = disable_letter_controls;
_disable_zoom = disable_zoom;
_element = element;
_allow_element_exit = allow_element_exit;
_disable_gamepad_controls = disable_gamepad_controls;
var obj = m_scs.get_active_camera();
enable_cam_controls_resetting(obj);
var use_pivot = false;
var character = null;
var use_hover = false;
switch (m_cam.get_move_style(obj)) {
case m_cam.MS_TARGET_CONTROLS:
use_pivot = !disable_default_pivot;
break;
case m_cam.MS_EYE_CONTROLS:
character = m_scs.get_first_character();
break;
case m_cam.MS_STATIC:
return;
case m_cam.MS_HOVER_CONTROLS:
use_hover = true;
break;
}
var velocity = m_cam.get_velocities(obj, _velocity_tmp);
var elapsed = m_ctl.create_elapsed_sensor();
if (m_phy.has_simulated_physics(obj)) {
var collision = m_ctl.create_collision_sensor(obj, null, true);
var collision_cb = function(obj, id, pulse) {
var coll_dist = m_ctl.get_sensor_payload(obj, id, 0).coll_dist;
if (coll_dist < 0) {
var coll_norm = m_ctl.get_sensor_payload(obj, id, 0).coll_norm;
var recover_offset = _vec3_tmp;
m_vec3.scale(coll_norm, -CAM_COLL_DELTA * coll_dist, recover_offset);
var trans = m_trans.get_translation(obj, _vec3_tmp2);
m_vec3.add(trans, recover_offset, trans);
m_trans.set_translation_v(obj, trans);
}
}
m_ctl.create_sensor_manifold(obj, "CAMERA_COLLISION", m_ctl.CT_POSITIVE,
[collision], null, collision_cb);
}
if (character) {
// apply camera transform to character
var trans = m_trans.get_translation(obj);
var quat = m_trans.get_rotation(obj);
var char_quat = m_util.cam_quat_to_mesh_quat(quat);
trans[2] -= CHAR_HEAD_POSITION;
m_phy.set_transform(character, trans, char_quat);
m_cons.append_stiff_trans(obj, character, [0, 0, 0.5]);
var char_dir = new Float32Array(2);
var is_fly = true;
m_phy.set_character_move_type(character, m_phy.CM_FLY);
var move_type_cb = function() {
is_fly = !is_fly;
m_phy.set_character_move_type(character,
is_fly ? m_phy.CM_FLY : m_phy.CM_WALK);
}
m_ctl.create_kb_sensor_manifold(obj, "TOGGLE_CHAR_MOVE_TYPE",
m_ctl.CT_SHOT, m_ctl.KEY_C, move_type_cb);
}
var key_cb = function(obj, id, pulse) {
if (pulse == 1) {
var elapsed = m_ctl.get_sensor_value(obj, id, 0);
m_cam.get_velocities(obj, velocity);
switch (id) {
case "FORWARD":
if (character)
char_dir[0] = 1;
else if (use_hover) {
var hover_angle = m_cam.get_camera_angles(obj, _vec2_tmp2)[1];
var axis = (Math.abs(hover_angle) >= Math.PI / 4) ? m_util.AXIS_Y : m_util.AXIS_MZ;
trans_hover_cam_horiz_local(obj, axis,
velocity.trans * HOVER_KEY_TRANS_FACTOR * elapsed);
} else if (use_pivot)
trans_targ_cam_local(obj, -velocity.zoom, elapsed);
else
trans_eye_cam_local(obj, 0, 0, -velocity.trans * elapsed);
break;
case "BACKWARD":
if (character)
char_dir[0] = -1;
else if (use_hover) {
var hover_angle = m_cam.get_camera_angles(obj, _vec2_tmp2)[1];
var axis = (Math.abs(hover_angle) >= Math.PI / 4) ? m_util.AXIS_MY : m_util.AXIS_Z;
trans_hover_cam_horiz_local(obj, axis,
velocity.trans * HOVER_KEY_TRANS_FACTOR * elapsed);
} else if (use_pivot)
trans_targ_cam_local(obj, velocity.zoom, elapsed);
else
trans_eye_cam_local(obj, 0, 0, velocity.trans * elapsed);
break;
case "UP":
if (use_hover)
zoom_hover_cam(obj, - velocity.zoom * HOVER_KEY_ZOOM_FACTOR
* elapsed);
else if (!character)
trans_eye_cam_local(obj, 0, velocity.trans * elapsed, 0);
break;
case "DOWN":
if (use_hover)
zoom_hover_cam(obj, velocity.zoom * HOVER_KEY_ZOOM_FACTOR
* elapsed);
else if (!character)
trans_eye_cam_local(obj, 0, -velocity.trans * elapsed, 0);
break;
case "LEFT":
if (character)
char_dir[1] = 1;
else if (use_hover)
trans_hover_cam_horiz_local(obj, m_util.AXIS_MX,
velocity.trans * HOVER_KEY_TRANS_FACTOR * elapsed);
else
trans_eye_cam_local(obj, -velocity.trans * elapsed, 0, 0);
break;
case "RIGHT":
if (character)
char_dir[1] = -1;
else if (use_hover)
trans_hover_cam_horiz_local(obj, m_util.AXIS_X,
velocity.trans * HOVER_KEY_TRANS_FACTOR * elapsed);
else
trans_eye_cam_local(obj, velocity.trans * elapsed, 0, 0);
break;
case "ROT_LEFT":
if (use_pivot)
m_cam.rotate_camera(obj, -velocity.rot
* TARGET_EYE_KEY_ROT_FACTOR * elapsed, 0);
else
m_cam.rotate_camera(obj, velocity.rot * TARGET_EYE_KEY_ROT_FACTOR
* elapsed, 0);
break;
case "ROT_RIGHT":
if (use_pivot)
m_cam.rotate_camera(obj, velocity.rot
* TARGET_EYE_KEY_ROT_FACTOR * elapsed, 0);
else
m_cam.rotate_camera(obj, -velocity.rot * TARGET_EYE_KEY_ROT_FACTOR
* elapsed, 0);
break;
case "ROT_UP":
if (use_pivot)
m_cam.rotate_camera(obj, 0, -velocity.rot
* TARGET_EYE_KEY_ROT_FACTOR * elapsed);
else
m_cam.rotate_camera(obj, 0, velocity.rot
* TARGET_EYE_KEY_ROT_FACTOR * elapsed);
break;
case "ROT_DOWN":
if (use_pivot)
m_cam.rotate_camera(obj, 0, velocity.rot
* TARGET_EYE_KEY_ROT_FACTOR * elapsed);
else
m_cam.rotate_camera(obj, 0, -velocity.rot
* TARGET_EYE_KEY_ROT_FACTOR * elapsed);
break;
default:
break;
}
} else {
switch (id) {
case "FORWARD":
case "BACKWARD":
if (character)
char_dir[0] = 0;
break;
case "LEFT":
case "RIGHT":
if (character)
char_dir[1] = 0;
break;
}
}
if (character) {
m_phy.set_character_move_dir(character, char_dir[0], char_dir[1]);
var angles = m_cam.get_camera_angles_char(obj, _vec2_tmp);
m_phy.set_character_rotation_h(character, angles[0]);
m_phy.set_character_vert_move_dir_angle(character, angles[1]);
}
}
var key_w, key_s, key_a, key_d, key_r, key_f, gmpd_btn_6, gmpd_btn_7;
var lh_axis, lv_axis, rh_axis, rv_axis;
if (!disable_gamepad_controls) {
var gmpd_indices = m_input.check_enable_gamepad_indices();
if (gmpd_indices.length)
var gamepad_id = gmpd_indices[gmpd_indices.length - 1];
else
var gamepad_id = 0;
gmpd_btn_6 = m_ctl.create_gamepad_btn_sensor(m_input.GMPD_BUTTON_6,
gamepad_id);
gmpd_btn_7 = m_ctl.create_gamepad_btn_sensor(m_input.GMPD_BUTTON_7,
gamepad_id);
lh_axis = m_ctl.create_gamepad_axis_sensor(m_input.GMPD_AXIS_0, gmpd_indices);
lv_axis = m_ctl.create_gamepad_axis_sensor(m_input.GMPD_AXIS_1, gmpd_indices);
rh_axis = m_ctl.create_gamepad_axis_sensor(m_input.GMPD_AXIS_2, gmpd_indices);
rv_axis = m_ctl.create_gamepad_axis_sensor(m_input.GMPD_AXIS_3, gmpd_indices);
} else
gmpd_btn_6 = gmpd_btn_7 = lh_axis = lv_axis = rh_axis = rv_axis =
m_ctl.create_custom_sensor(0);
if (!disable_letter_controls) {
key_w = m_ctl.create_keyboard_sensor(m_ctl.KEY_W);
key_s = m_ctl.create_keyboard_sensor(m_ctl.KEY_S);
key_a = m_ctl.create_keyboard_sensor(m_ctl.KEY_A);
key_d = m_ctl.create_keyboard_sensor(m_ctl.KEY_D);
key_r = m_ctl.create_keyboard_sensor(m_ctl.KEY_R);
key_f = m_ctl.create_keyboard_sensor(m_ctl.KEY_F);
} else
key_w = key_s = key_a = key_d = key_r = key_f = m_ctl.create_custom_sensor(0);
var key_up = m_ctl.create_keyboard_sensor(m_ctl.KEY_UP);
var key_down = m_ctl.create_keyboard_sensor(m_ctl.KEY_DOWN);
var key_left = m_ctl.create_keyboard_sensor(m_ctl.KEY_LEFT);
var key_right = m_ctl.create_keyboard_sensor(m_ctl.KEY_RIGHT);
// var key_single_logic = null;
// var key_double_logic = function(s) {
// return s[0] && (s[1] || s[2]);
// }
var key_triple_logic = function(s) {
return s[0] && (s[1] || s[2] || s[3]);
}
var key_double_pos_logic = function(s) {
return s[0] && (s[1] || s[2] > AXIS_THRESHOLD);
}
var key_double_neg_logic = function(s) {
return s[0] && (s[1] || s[2] < -AXIS_THRESHOLD);
}
var key_triple_pos_logic = function(s) {
return s[0] && (s[1] || s[2] || s[3] > AXIS_THRESHOLD);
}
var key_triple_neg_logic = function(s) {
return s[0] && (s[1] || s[2] || s[3] < -AXIS_THRESHOLD);
}
if (!use_hover) {
m_ctl.create_sensor_manifold(obj, "FORWARD", m_ctl.CT_CONTINUOUS,
[elapsed, key_w, lv_axis], key_double_neg_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "BACKWARD", m_ctl.CT_CONTINUOUS,
[elapsed, key_s, lv_axis], key_double_pos_logic, key_cb);
}
if (use_pivot) {
m_ctl.create_sensor_manifold(obj, "ROT_UP", m_ctl.CT_CONTINUOUS,
[elapsed, key_up, key_r, rv_axis], key_triple_neg_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "ROT_DOWN", m_ctl.CT_CONTINUOUS,
[elapsed, key_down, key_f, rv_axis], key_triple_pos_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "ROT_LEFT", m_ctl.CT_CONTINUOUS,
[elapsed, key_left, key_a, rh_axis], key_triple_neg_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "ROT_RIGHT", m_ctl.CT_CONTINUOUS,
[elapsed, key_right, key_d, rh_axis], key_triple_pos_logic, key_cb);
} else if (use_hover) {
m_ctl.create_sensor_manifold(obj, "LEFT", m_ctl.CT_CONTINUOUS,
[elapsed, key_left, key_a, lh_axis], key_triple_neg_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "RIGHT", m_ctl.CT_CONTINUOUS,
[elapsed, key_right, key_d, lh_axis], key_triple_pos_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "FORWARD", m_ctl.CT_CONTINUOUS,
[elapsed, key_up, key_w, lv_axis], key_triple_neg_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "BACKWARD", m_ctl.CT_CONTINUOUS,
[elapsed, key_down, key_s, lv_axis], key_triple_pos_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "UP", m_ctl.CT_CONTINUOUS,
[elapsed, key_f, rv_axis], key_double_pos_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "DOWN", m_ctl.CT_CONTINUOUS,
[elapsed, key_r, rv_axis], key_double_neg_logic, key_cb);
} else {
m_ctl.create_sensor_manifold(obj, "UP", m_ctl.CT_CONTINUOUS,
[elapsed, key_r, gmpd_btn_6], key_triple_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "DOWN", m_ctl.CT_CONTINUOUS,
[elapsed, key_f, gmpd_btn_7], key_triple_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "LEFT", m_ctl.CT_CONTINUOUS,
[elapsed, key_a, lh_axis], key_double_neg_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "RIGHT", m_ctl.CT_CONTINUOUS,
[elapsed, key_d, lh_axis], key_double_pos_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "ROT_UP", m_ctl.CT_CONTINUOUS,
[elapsed, key_up, rv_axis], key_double_neg_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "ROT_DOWN", m_ctl.CT_CONTINUOUS,
[elapsed, key_down, rv_axis], key_double_pos_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "ROT_LEFT", m_ctl.CT_CONTINUOUS,
[elapsed, key_left, rh_axis], key_double_neg_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "ROT_RIGHT", m_ctl.CT_CONTINUOUS,
[elapsed, key_right, rh_axis], key_double_pos_logic, key_cb);
}
if (!disable_zoom) {
// mouse wheel: camera zooming and translation speed adjusting
var dest_zoom_mouse = 0;
var mouse_wheel = m_ctl.create_mouse_wheel_sensor(element);
// camera zooming with touch
var dest_zoom_touch = 0;
var touch_zoom = m_ctl.create_touch_zoom_sensor(element);
var mouse_wheel_cb = function(obj, id, pulse) {
if (pulse == 1) {
var value = m_ctl.get_sensor_value(obj, id, 0);
m_cam.get_velocities(obj, velocity);
if (use_pivot || use_hover) {
dest_zoom_mouse = get_dest_zoom(obj, value, velocity.zoom,
dest_zoom_mouse, HOVER_MOUSE_ZOOM_FACTOR, use_pivot);
} else {
// translation speed adjusting
var factor = value * velocity.zoom;
velocity.trans *= (1 + factor);
m_cam.set_velocities(obj, velocity);
}
}
}
m_ctl.create_sensor_manifold(obj, "MOUSE_WHEEL", m_ctl.CT_LEVEL,
[mouse_wheel], null, mouse_wheel_cb);
var touch_zoom_cb = function(obj, id, pulse, param) {
if (pulse == 1) {
var value = m_ctl.get_sensor_value(obj, id, 0);
m_cam.get_velocities(obj, velocity);
if (m_ctl.get_sensor_payload(obj, id, 0)
=== m_ctl.PL_MULTITOUCH_MOVE_ZOOM) {
dest_zoom_touch = get_dest_zoom(obj, value, velocity.zoom
* TARGET_TOUCH_ZOOM_FACTOR, dest_zoom_touch,
HOVER_TOUCH_ZOOM_FACTOR, use_pivot);
}
}
}
m_ctl.create_sensor_manifold(obj, "TOUCH_ZOOM", m_ctl.CT_LEVEL,
[touch_zoom], null, touch_zoom_cb);
// camera zoom smoothing
var zoom_interp_cb = function(obj, id, pulse) {
if (use_pivot || use_hover) {
if (Math.abs(dest_zoom_mouse) > EPSILON_DELTA
|| Math.abs(dest_zoom_touch) > EPSILON_DELTA) {
var value = m_ctl.get_sensor_value(obj, id, 0);
var zoom_mouse = m_util.smooth(dest_zoom_mouse, 0, value,
smooth_coeff_zoom_mouse());
dest_zoom_mouse -= zoom_mouse;
var zoom_touch = m_util.smooth(dest_zoom_touch, 0, value,
smooth_coeff_zoom_touch());
dest_zoom_touch -= zoom_touch;
if (use_hover) {
zoom_hover_cam(obj, - (zoom_mouse + zoom_touch));
} else {
var res_dist = m_cam.target_get_distance(obj)
+ zoom_mouse + zoom_touch;
// NOTE: prevent zoom overshooting.
res_dist = Math.max(res_dist, EPSILON_DISTANCE);
m_cam.target_set_distance(obj, res_dist);
}
} else {
dest_zoom_mouse = 0;
dest_zoom_touch = 0;
}
}
}
m_ctl.create_sensor_manifold(obj, "ZOOM_INTERPOL", m_ctl.CT_POSITIVE,
[elapsed], null, zoom_interp_cb);
}
// camera rotation and translation with mouse
var dest_x_mouse = 0;
var dest_y_mouse = 0;
// camera panning with mouse
var dest_pan_x_mouse = 0;
var dest_pan_y_mouse = 0;
var mouse_cb = function(obj, id, pulse, param) {
if (pulse == 1) {
var value = m_ctl.get_sensor_value(obj, id, 1);
m_cam.get_velocities(obj, velocity);
if (!use_hover) {
var left_mult = TARGET_EYE_MOUSE_ROT_MULT_PX * velocity.rot;
var right_mult = TARGET_EYE_MOUSE_PAN_MULT_PX * velocity.trans;
} else {
var left_mult = HOVER_MOUSE_PAN_MULT_PX * velocity.trans;
var right_mult = HOVER_MOUSE_ROT_MULT_PX * velocity.rot;
}
if (m_ctl.get_sensor_payload(obj, id, 0).which === 1) {
dest_x_mouse += (param == "X") ? -value * left_mult : 0;
dest_y_mouse += (param == "Y") ? -value * left_mult : 0;
} else if (m_ctl.get_sensor_payload(obj, id, 0).which === 2
|| m_ctl.get_sensor_payload(obj, id, 0).which === 3) {
dest_pan_x_mouse += (param == "X") ? -value * right_mult : 0;
dest_pan_y_mouse += (param == "Y") ? -value * right_mult : 0;
}
}
}
// camera panning with gamepad
var dest_pan_x_gmpd = 0;
var dest_pan_y_gmpd = 0;
if (!disable_gamepad_controls) {
var gmpd_panning_x_pos_cb = function(obj, id, pulse) {
m_cam.get_velocities(obj, velocity);
dest_pan_x_gmpd += velocity.trans * TRANS_GMPD_KOEF;
}
var gmpd_panning_y_pos_cb = function(obj, id, pulse) {
m_cam.get_velocities(obj, velocity);
dest_pan_y_gmpd += velocity.zoom * ZOOM_GMPD_KOEF;
}
var gmpd_panning_x_neg_cb = function(obj, id, pulse) {
m_cam.get_velocities(obj, velocity);
dest_pan_x_gmpd -= velocity.trans * TRANS_GMPD_KOEF;
}
var gmpd_panning_y_neg_cb = function(obj, id, pulse) {
m_cam.get_velocities(obj, velocity);
dest_pan_y_gmpd -= velocity.zoom * ZOOM_GMPD_KOEF;
}
if (use_pivot) {
m_ctl.create_sensor_manifold(obj, "GMPD_PAN_Y_POS", m_ctl.CT_CONTINUOUS,
[gmpd_btn_6], null, gmpd_panning_y_pos_cb);
m_ctl.create_sensor_manifold(obj, "GMPD_PAN_Y_NEG", m_ctl.CT_CONTINUOUS,
[gmpd_btn_7], null, gmpd_panning_y_neg_cb);
m_ctl.create_sensor_manifold(obj, "GMPD_PAN_X_POS", m_ctl.CT_CONTINUOUS,
[lh_axis], function(s) {return s[0] < -AXIS_THRESHOLD}, gmpd_panning_x_neg_cb);
m_ctl.create_sensor_manifold(obj, "GMPD_PAN_X_NEG", m_ctl.CT_CONTINUOUS,
[lh_axis], function(s) {return s[0] > AXIS_THRESHOLD}, gmpd_panning_x_pos_cb);
} else if (use_hover) {
m_ctl.create_sensor_manifold(obj, "GMPD_PAN_X_POS", m_ctl.CT_CONTINUOUS,
[rh_axis], function(s) {return s[0] < -AXIS_THRESHOLD}, gmpd_panning_x_neg_cb);
m_ctl.create_sensor_manifold(obj, "GMPD_PAN_X_NEG", m_ctl.CT_CONTINUOUS,
[rh_axis], function(s) {return s[0] > AXIS_THRESHOLD}, gmpd_panning_x_pos_cb);
}
}
if (allow_element_exit) {
var mouse_move_x = m_ctl.create_mouse_move_sensor("X", window);
var mouse_move_y = m_ctl.create_mouse_move_sensor("Y", window);
var mouse_down_c = m_ctl.create_mouse_click_sensor(element);
var mouse_down_w = m_ctl.create_mouse_click_sensor(window);
var element_exit_state = false;
var element_exit_logic = function(s) {
if (s[2])
element_exit_state = true;
else if (!s[0])
element_exit_state = false;
return element_exit_state && s[0];
}
m_ctl.create_sensor_manifold(obj, "MOUSE_X", m_ctl.CT_POSITIVE,
[mouse_down_w, mouse_move_x, mouse_down_c], element_exit_logic, mouse_cb, "X");
m_ctl.create_sensor_manifold(obj, "MOUSE_Y", m_ctl.CT_POSITIVE,
[mouse_down_w, mouse_move_y, mouse_down_c], element_exit_logic, mouse_cb, "Y");
var device = m_input.get_device_by_type_element(m_input.DEVICE_MOUSE, window);
m_input.switch_prevent_default(device, false);
} else {
var mouse_move_x = m_ctl.create_mouse_move_sensor("X", element);
var mouse_move_y = m_ctl.create_mouse_move_sensor("Y", element);
var mouse_down = m_ctl.create_mouse_click_sensor(element);
m_ctl.create_sensor_manifold(obj, "MOUSE_X", m_ctl.CT_POSITIVE,
[mouse_down, mouse_move_x], null, mouse_cb, "X");
m_ctl.create_sensor_manifold(obj, "MOUSE_Y", m_ctl.CT_POSITIVE,
[mouse_down, mouse_move_y], null, mouse_cb, "Y");
}
// camera rotation and translation with touch
var dest_x_touch = 0;
var dest_y_touch = 0;
// camera panning with touch
var dest_pan_x_touch = 0;
var dest_pan_y_touch = 0;
var touch_move_x = m_ctl.create_touch_move_sensor("X", element);
var touch_move_y = m_ctl.create_touch_move_sensor("Y", element);
var touch_cb = function(obj, id, pulse, param) {
if (pulse == 1) {
m_cam.get_velocities(obj, velocity);
if (use_hover)
var r_mult = HOVER_TOUCH_PAN_MULT_PX * velocity.trans;
else
var r_mult = TARGET_EYE_TOUCH_ROT_MULT_PX * velocity.rot;
var value = m_ctl.get_sensor_value(obj, id, 0);
if (m_ctl.get_sensor_payload(obj, id, 0).gesture
=== m_ctl.PL_SINGLE_TOUCH_MOVE) {
dest_x_touch += (param == "X") ? -value * r_mult : 0;
dest_y_touch += (param == "Y") ? -value * r_mult : 0;
} else if (m_ctl.get_sensor_payload(obj, id, 0).gesture
=== m_ctl.PL_MULTITOUCH_MOVE_PAN) {
if (!use_hover) {
var pan_mult = TARGET_EYE_TOUCH_PAN_MULT_PX * velocity.trans;
} else {
var pan_mult = HOVER_TOUCH_ROT_MULT_PX * velocity.rot;
}
dest_pan_x_touch += (param == "X") ? -value * pan_mult : 0;
dest_pan_y_touch += (param == "Y") ? -value * pan_mult : 0;
}
}
}
m_ctl.create_sensor_manifold(obj, "TOUCH_X", m_ctl.CT_POSITIVE,
[touch_move_x], null, touch_cb, "X");
m_ctl.create_sensor_manifold(obj, "TOUCH_Y", m_ctl.CT_POSITIVE,
[touch_move_y], null, touch_cb, "Y");
// camera rotation and translation smoothing
var rot_trans_interp_cb = function(obj, id, pulse) {
if ( Math.abs(dest_x_mouse) > EPSILON_DELTA ||
Math.abs(dest_y_mouse) > EPSILON_DELTA ||
Math.abs(dest_x_touch) > EPSILON_DELTA ||
Math.abs(dest_y_touch) > EPSILON_DELTA ||
Math.abs(dest_pan_x_mouse) > EPSILON_DELTA ||
Math.abs(dest_pan_y_mouse) > EPSILON_DELTA ||
Math.abs(dest_pan_x_touch) > EPSILON_DELTA ||
Math.abs(dest_pan_y_touch) > EPSILON_DELTA ||
Math.abs(dest_pan_x_gmpd) > EPSILON_DELTA ||
Math.abs(dest_pan_y_gmpd) > EPSILON_DELTA) {
var value = m_ctl.get_sensor_value(obj, id, 0);
var x_mouse = m_util.smooth(dest_x_mouse, 0, value,
smooth_coeff_rot_trans_mouse());
var y_mouse = m_util.smooth(dest_y_mouse, 0, value,
smooth_coeff_rot_trans_mouse());
dest_x_mouse -= x_mouse;
dest_y_mouse -= y_mouse;
var x_touch = m_util.smooth(dest_x_touch, 0, value,
smooth_coeff_rot_trans_touch());
var y_touch = m_util.smooth(dest_y_touch, 0, value,
smooth_coeff_rot_trans_touch());
dest_x_touch -= x_touch;
dest_y_touch -= y_touch;
var trans_x_mouse = m_util.smooth(dest_pan_x_mouse, 0,
value, smooth_coeff_rot_trans_mouse());
var trans_y_mouse = m_util.smooth(dest_pan_y_mouse, 0,
value, smooth_coeff_rot_trans_mouse());
dest_pan_x_mouse -= trans_x_mouse;
dest_pan_y_mouse -= trans_y_mouse;
var trans_x_touch = m_util.smooth(dest_pan_x_touch, 0,
value, smooth_coeff_rot_trans_touch());
var trans_y_touch = m_util.smooth(dest_pan_y_touch, 0,
value, smooth_coeff_rot_trans_touch());
dest_pan_x_touch -= trans_x_touch;
dest_pan_y_touch -= trans_y_touch;
var trans_x_gmpd = m_util.smooth(dest_pan_x_gmpd, 0,
value, smooth_coeff_rot_trans_mouse());
var trans_y_gmpd = m_util.smooth(dest_pan_y_gmpd, 0,
value, smooth_coeff_rot_trans_mouse());
dest_pan_x_gmpd -= trans_x_gmpd;
dest_pan_y_gmpd -= trans_y_gmpd;
if (use_pivot) {
m_cam.rotate_camera(obj, x_mouse + x_touch,
y_mouse + y_touch);
var dist = m_cam.target_get_distance(obj);
m_cam.target_pan_pivot(obj,
dist * (trans_x_mouse + trans_x_touch + trans_x_gmpd),
dist * (trans_y_mouse + trans_y_touch + trans_y_gmpd));
} else if (use_hover) {
if (x_mouse + x_touch) {
trans_hover_cam_horiz_local(obj, m_util.AXIS_X,
(x_mouse + x_touch)
* HOVER_MOUSE_TOUCH_TRANS_FACTOR);
}
if (y_mouse + y_touch) {
var hover_angle = m_cam.get_camera_angles(obj, _vec2_tmp2)[1];
var axis = (Math.abs(hover_angle) > Math.PI / 4) ? m_util.AXIS_MY : m_util.AXIS_Z;
trans_hover_cam_horiz_local(obj, axis, (y_mouse + y_touch)
* HOVER_MOUSE_TOUCH_TRANS_FACTOR);
}
m_cam.rotate_camera(obj, trans_x_mouse + trans_x_touch + trans_x_gmpd, 0);
} else {
m_cam.rotate_camera(obj, (x_mouse + x_touch) * EYE_ROTATION_DECREMENT,
(y_mouse + y_touch) * EYE_ROTATION_DECREMENT);
if (character) {
var angles = m_cam.get_camera_angles_char(obj, _vec2_tmp);
m_phy.set_character_rotation_h(character, angles[0]);
m_phy.set_character_vert_move_dir_angle(character, angles[1]);
}
}
}
}
m_ctl.create_sensor_manifold(obj, "ROT_TRANS_INTERPOL", m_ctl.CT_POSITIVE,
[elapsed], null, rot_trans_interp_cb);
m_ctl.create_kb_sensor_manifold(obj, "DEC_STEREO_DIST", m_ctl.CT_SHOT,
m_ctl.KEY_LEFT_SQ_BRACKET, function(obj, id, pulse) {
var dist = m_cam.get_stereo_distance(obj);
m_cam.set_stereo_distance(obj, 0.9 * dist);
});
m_ctl.create_kb_sensor_manifold(obj, "INC_STEREO_DIST", m_ctl.CT_SHOT,
m_ctl.KEY_RIGHT_SQ_BRACKET, function(obj, id, pulse) {
var dist = m_cam.get_stereo_distance(obj);
m_cam.set_stereo_distance(obj, 1.1 * dist);
});
}
/**
* Register sensors for resetting the camera controls if the camera move style
* was changed.
*/
function enable_cam_controls_resetting(cam) {
var prev_ms = m_cam.get_move_style(cam);
var move_style_cb = function() {
var curr_ms = m_cam.get_move_style(cam);
var is_changed = curr_ms != prev_ms;
prev_ms = curr_ms;
return is_changed;
}
var cb_sensor = m_ctl.create_callback_sensor(move_style_cb);
function reset_controls_cb(cam, id, pulse) {
disable_camera_controls();
enable_camera_controls(_disable_default_pivot, _disable_letter_controls,
_disable_zoom, _element, _allow_element_exit,
_disable_gamepad_controls);
}
m_ctl.create_sensor_manifold(cam, "CHANGE_MOVE_STYLE", m_ctl.CT_POSITIVE,
[cb_sensor], null, reset_controls_cb);
}
/**
* Disable controls for the active camera.
* @method module:app.disable_camera_controls
* @example
* var m_app = require("app");
* m_app.disable_camera_controls();
*/
exports.disable_camera_controls = disable_camera_controls;
function disable_camera_controls() {
var cam = m_scs.get_active_camera();
if (m_ctl.check_sensor_manifold(cam, "TOGGLE_CHAR_MOVE_TYPE")
&& m_scs.get_first_character())
m_cons.remove(cam);
var cam_std_manifolds = ["FORWARD", "BACKWARD", "ROT_UP", "ROT_DOWN",
"ROT_LEFT", "ROT_RIGHT", "UP", "DOWN", "LEFT", "RIGHT",
"MOUSE_WHEEL", "TOUCH_ZOOM", "ZOOM_INTERPOL", "MOUSE_X", "MOUSE_Y",
"TOUCH_X", "TOUCH_Y", "ROT_TRANS_INTERPOL", "CHANGE_MOVE_STYLE",
"TOGGLE_CHAR_MOVE_TYPE"];
for (var i = 0; i < cam_std_manifolds.length; i++)
m_ctl.remove_sensor_manifold(cam, cam_std_manifolds[i]);
}
/**
* Assign some controls to the non-camera object.
* @param {Object3D} obj Object 3D
* @param {HTMLElement} [element=Canvas container element] HTML element
*/
exports.enable_object_controls = function(obj, element) {
var trans_speed = 1;
var is_vehicle = m_phy.is_vehicle_chassis(obj) ||
m_phy.is_vehicle_hull(obj);
var key_cb = function(obj, id, pulse) {
if (pulse == 1) {
var elapsed = m_ctl.get_sensor_value(obj, id, 0);
switch (id) {
case "FORWARD":
if (is_vehicle)
m_phy.vehicle_throttle(obj, 1);
else
m_trans.move_local(obj, 0, -trans_speed * elapsed, 0);
break;
case "BACKWARD":
if (is_vehicle)
m_phy.vehicle_throttle(obj, -1);
else
m_trans.move_local(obj, 0, trans_speed * elapsed, 0);
break;
case "LEFT":
if (is_vehicle)
m_phy.vehicle_steer(obj, -1);
else
m_trans.move_local(obj, trans_speed * elapsed, 0, 0);
break;
case "RIGHT":
if (is_vehicle)
m_phy.vehicle_steer(obj, 1);
else
m_trans.move_local(obj, -trans_speed * elapsed, 0, 0);
break;
default:
break;
}
} else {
switch (id) {
case "FORWARD":
case "BACKWARD":
if (is_vehicle)
m_phy.vehicle_throttle(obj, 0);
break;
case "LEFT":
case "RIGHT":
if (is_vehicle)
m_phy.vehicle_steer(obj, 0);
break;
default:
break;
}
}
}
var elapsed = m_ctl.create_elapsed_sensor();
var key_w = m_ctl.create_keyboard_sensor(m_ctl.KEY_W);
var key_s = m_ctl.create_keyboard_sensor(m_ctl.KEY_S);
var key_a = m_ctl.create_keyboard_sensor(m_ctl.KEY_A);
var key_d = m_ctl.create_keyboard_sensor(m_ctl.KEY_D);
var key_up = m_ctl.create_keyboard_sensor(m_ctl.KEY_UP);
var key_down = m_ctl.create_keyboard_sensor(m_ctl.KEY_DOWN);
var key_left = m_ctl.create_keyboard_sensor(m_ctl.KEY_LEFT);
var key_right = m_ctl.create_keyboard_sensor(m_ctl.KEY_RIGHT);
var key_logic = function(s) {
return s[0] && (s[1] || s[2]);
}
m_ctl.create_sensor_manifold(obj, "FORWARD", m_ctl.CT_CONTINUOUS,
[elapsed, key_w, key_up], key_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "BACKWARD", m_ctl.CT_CONTINUOUS,
[elapsed, key_s, key_down], key_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "LEFT", m_ctl.CT_CONTINUOUS,
[elapsed, key_a, key_left], key_logic, key_cb);
m_ctl.create_sensor_manifold(obj, "RIGHT", m_ctl.CT_CONTINUOUS,
[elapsed, key_d, key_right], key_logic, key_cb);
}
/**
* Remove controls from the non-camera object.
* @param {Object3D} obj Object.
*/
exports.disable_object_controls = function(obj) {
var obj_std_manifolds = ["FORWARD", "BACKWARD", "LEFT", "RIGHT"];
for (var i = 0; i < obj_std_manifolds.length; i++)
m_ctl.remove_sensor_manifold(obj, obj_std_manifolds[i]);
}
/**
* Enable debug controls:
* <ul>
* <li>K - make camera debug shot
* <li>L - make light debug shot
* <li>M - flashback messages
* </ul>
*/
exports.enable_debug_controls = function() {
m_ctl.create_kb_sensor_manifold(null, "CAMERA_SHOT", m_ctl.CT_SHOT,
m_ctl.KEY_K, function() {m_dbg.make_camera_frustum_shot();});
m_ctl.create_kb_sensor_manifold(null, "LIGHT_SHOT", m_ctl.CT_SHOT,
m_ctl.KEY_L, function() {m_dbg.make_light_frustum_shot();});
m_ctl.create_kb_sensor_manifold(null, "TELEMETRY", m_ctl.CT_SHOT,
m_ctl.KEY_T, function() {m_dbg.plot_telemetry();});
}
/**
* Request fullscreen mode.
* Security issues: execute by user event.
* @method module:app.request_fullscreen
* @param {HTMLElement} elem Element
* @param {FullscreenEnabledCallback} enabled_cb Enabled callback
* @param {FullscreenDisabledCallback} disabled_cb Disabled callback
* @deprecated Use {@link module:screen.request_fullscreen} instead
*/
exports.request_fullscreen = function(elem, enabled_cb, disabled_cb) {
m_print.error_deprecated("request_fullscreen", "screen.request_fullscreen");
m_screen.request_fullscreen(elem, enabled_cb, disabled_cb);
};
/**
* Exit fullscreen mode.
* @method module:app.exit_fullscreen
* @deprecated Use {@link module:screen.exit_fullscreen} instead
*/
exports.exit_fullscreen = function() {
m_print.error_deprecated("exit_fullscreen", "screen.exit_fullscreen");
m_screen.exit_fullscreen();
};
/**
* Check whether fullscreen mode is available.
* @method module:app.check_fullscreen
* @returns {boolean} Result of the check.
* @deprecated Use {@link module:screen.check_fullscreen} instead
*/
exports.check_fullscreen = function() {
m_print.error_deprecated("check_fullscreen", "screen.check_fullscreen");
return m_screen.check_fullscreen();
};
exports.report_app_error = report_app_error;
/**
* Report an application error.
* Creates standard HTML elements with error info and inserts them into the page body.
* @method module:app.report_app_error
* @param {string} text_message Message to place on upper element.
* @param {string} link_message Message to place on bottom element.
* @param {string} link Link to place on bottom element.
* @param {string[]} purge_elements Array of element IDs to destroy just before the error
* elements are inserted.
*/
function report_app_error(text_message, link_message, link, purge_elements) {
var elem = document.createElement("div");
var top_elem = document.createElement("div");
var bottom_elem = document.createElement("div");
if (purge_elements) {
for (var i = 0; i < purge_elements.length; i++) {
var purge_elem = document.getElementById(purge_elements[i]);
if (purge_elem)
purge_elem.parentNode.removeChild(purge_elem);
}
}
elem.style.cssText = "z-index:10;width:100%;height:auto;position:absolute;top:50%;margin-top:150px;text-align:center;";
top_elem.style.cssText = "color:#fff;font-size:24px;";
bottom_elem.style.cssText = "color:#fff;font-size:20px;";
top_elem.innerHTML = text_message;
bottom_elem.innerHTML = link_message + ' ' + '<a style="color:#fff;font-size:20px;width:100%;" href="' + link + '">'+link.replace("https://www.", "")+'</a>';
elem.appendChild(top_elem);
elem.appendChild(bottom_elem);
document.body.appendChild(elem);
}
/**
* Retrieve parameters of the page's URL.
* @param {boolean} [allow_param_array=false] Create arrays of parameters if they share the same name.
* @returns {Object|Null} URL parameters in a key-value format.
*/
exports.get_url_params = function(allow_param_array) {
allow_param_array = !!allow_param_array;
var url = decodeURIComponent(location.href.toString());
if (url.indexOf("?") == -1)
return null;
var params = url.split("?")[1].split("&");
var out = {};
for (var i = 0; i < params.length; i++) {
var param = params[i].split("=");
var prop_name = param[0];
if (param.length > 1) {
var prop_val = param[1];
if (allow_param_array) {
if (prop_name in out)
out[prop_name].push(prop_val);
else
out[prop_name] = [prop_val];
} else
out[prop_name] = prop_val;
} else {
if (allow_param_array) {
if (!(prop_name in out))
out[prop_name] = [];
} else
out[prop_name] = '';
}
}
return out;
}
/**
* Animate css-property value.
* @method module:app.css_animate
* @param {HTMLElement} elem HTML-element.
* @param {string} prop Animated css-property.
* @param {number} from Value from.
* @param {number} to Value to.
* @param {number} timeout Time for animation.
* @param {string} [opt_prefix] Prefix of css-property (" scale(", "%" and etc).
* @param {string} [opt_suffix] Suffix of css-property (" px", "%" and etc).
* @param {GenericCallback} [opt_callback] Finish callback function.
*/
exports.css_animate = function(elem, prop, from, to, timeout, opt_prefix, opt_suffix, opt_callback) {
if (!elem || !prop || !isFinite(from) || !isFinite(to) || !isFinite(timeout))
return;
opt_prefix = opt_prefix || "";
opt_suffix = opt_suffix || "";
opt_callback = opt_callback || function() {};
var elem_style = elem.style;
var vendor_prop = prop.charAt(0).toUpperCase() + prop.slice(1);
if (elem_style[prop] != undefined) {
} else if (elem_style["webkit" + vendor_prop] != undefined) {
prop = "webkit" + vendor_prop;
} else if (elem_style["ms" + vendor_prop] != undefined) {
prop = "ms" + vendor_prop;
} else if (elem_style["moz" + vendor_prop] != undefined) {
prop = "moz" + vendor_prop;
} else
return;
function css_anim_cb(val) {
if (!elem)
return;
elem_style[prop] = opt_prefix + val + opt_suffix;
if (from > to && val <= to || from < to && val >= to) {
elem_style[prop] = opt_prefix + to + opt_suffix;
opt_callback();
}
}
animate(elem, from, to, timeout, css_anim_cb);
}
/**
* Animate html tag attribute.
* @method module:app.attr_animate
* @param {HTMLElement} elem HTML-element.
* @param {string} attr_name Animated attribute name.
* @param {number} from Value from.
* @param {number} to Value to.
* @param {number} timeout Time for animation.
* @param {GenericCallback} [opt_callback] Finish callback function.
*/
exports.attr_animate = function(elem, attr_name, from, to, timeout, opt_callback) {
if (!elem || !attr_name || !isFinite(from) || !isFinite(to) || !isFinite(timeout))
return;
opt_callback = opt_callback || function() {};
function attr_anim_cb(val) {
if (!elem)
return;
if (val >= 0)
elem.setAttribute(attr_name, val);
if (from > to && val <= to || from < to && val >= to) {
elem.setAttribute(attr_name, to);
opt_callback();
}
}
animate(elem, from, to, timeout, attr_anim_cb);
}
// Animate value from "from" to "to" for "timeout" mseconds.
function animate(elem, from, to, timeout, anim_cb) {
var start = performance.now();
function cb() {
var elapsed_total = performance.now() - start;
var value = from + elapsed_total * (to - from) / timeout;
anim_cb(value);
if (elapsed_total >= timeout || !elem.parentNode)
m_main.remove_loop_cb(cb);
}
m_main.append_loop_cb(cb);
}
/**
* Animation finish callback.
* @callback AnimFinishCallback
*/
/**
* Queue object params.
* @typedef {Object} QueueObject
* @property {string} type Animation type.
* @property {HTMLElement} elem Animated html element.
* @property {string} prop Animated property.
* @property {number} from Initial property value.
* @property {number} to Target property value.
* @property {number} duration Animation duration in ms.
* @property {string} [opt_prefix=''] Prefix for the css property.
* @property {string} [opt_suffix=''] Prefix for the css property.
* @property {AnimFinishCallback} [cb=function(){}] Animation finish callback.
*/
/**
* Animate queue of the html elements.
* @method module:app.queue_animate
* @param {QueueObject[]} queue Array of the queue objects.
*/
exports.queue_animate = function(queue) {
if (!queue.length)
return;
var queue_obj = queue.shift();
var elem = queue_obj.elem;
var prop = queue_obj.prop;
var from = queue_obj.from;
var to = queue_obj.to;
var duration = queue_obj.duration;
var prefix = queue_obj.opt_prefix;
var suffix = queue_obj.opt_suffix;
var cb = function() {
if (queue_obj.cb)
queue_obj.cb();
exports.queue_animate(queue);
}
if (queue_obj.type == "css")
exports.css_animate(elem, prop, from, to, duration, prefix, suffix, cb);
else if (queue_obj.type == "attr")
exports.attr_animate(elem, prop, from, to, duration, cb);
}
function smooth_coeff_zoom_mouse() {
return CAM_SMOOTH_ZOOM_MOUSE * _smooth_factor;
}
function smooth_coeff_zoom_touch() {
return CAM_SMOOTH_ZOOM_TOUCH * _smooth_factor;
}
function smooth_coeff_rot_trans_mouse() {
return CAM_SMOOTH_ROT_TRANS_MOUSE * _smooth_factor;
}
function smooth_coeff_rot_trans_touch() {
return CAM_SMOOTH_ROT_TRANS_TOUCH * _smooth_factor;
}
/**
* Set smooth factor for camera rotation and zoom.
* @method module:app.set_camera_smooth_factor
* @param {number} value New smooth factor
*/
exports.set_camera_smooth_factor = function(value) {
_smooth_factor = value;
}
/**
* Get smooth factor for camera rotation and zoom.
* @method module:app.get_camera_smooth_factor
* @returns {number} Smooth factor
*/
exports.get_camera_smooth_factor = function() {
return _smooth_factor;
}
}
var app_factory = register("app", App);
export default app_factory;