Source: addons/camera_anim.js

  1. import register from "../util/register.js";
  2. import m_cam_fact from "../extern/camera.js";
  3. import m_ctl_fact from "../extern/controls.js";
  4. import m_print_fact from "../intern/print.js";
  5. import m_scs_fact from "../extern/scenes.js";
  6. import m_time_fact from "../extern/time.js";
  7. import m_trans_fact from "../extern/transform.js";
  8. import m_tsr_fact from "../extern/tsr.js";
  9. import m_util_fact from "../extern/util.js";
  10. import * as m_vec3 from "../libs/gl_matrix/vec3.js";
  11. /**
  12. * Camera animation add-on.
  13. * Implements procedural animation for the camera.
  14. * @module camera_anim
  15. * @local TrackToTargetZoomCallback
  16. * @local TrackToTargetCallback
  17. * @local AutoRotateDisabledCallback
  18. * @local MoveCameraToPointCallback
  19. * @local RotateCameraCallback
  20. */
  21. function Camera_anim(ns, exports) {
  22. var m_cam = m_cam_fact(ns);
  23. var m_ctl = m_ctl_fact(ns);
  24. var m_print = m_print_fact(ns);
  25. var m_scs = m_scs_fact(ns);
  26. var m_time = m_time_fact(ns);
  27. var m_trans = m_trans_fact(ns);
  28. var m_tsr = m_tsr_fact(ns);
  29. var m_util = m_util_fact(ns);
  30. var ROTATION_OFFSET = 0.2;
  31. var ROTATION_LIMITS_EPS = 1E-6;
  32. var DEFAULT_CAM_LIN_SPEED = 1;
  33. var DEFAULT_CAM_ANGLE_SPEED = 0.01;
  34. var DEFAULT_CAM_ROTATE_TIME = 1000;
  35. // cache vars
  36. var _vec2_tmp = new Float32Array(2);
  37. var _vec2_tmp2 = new Float32Array(2);
  38. var _vec3_tmp = new Float32Array(3);
  39. var _vec3_tmp2 = new Float32Array(3);
  40. var _vec3_tmp3 = new Float32Array(3);
  41. var _tsr_tmp = m_tsr.create();
  42. var _tsr_tmp2 = m_tsr.create();
  43. var _tsr_tmp3 = m_tsr.create();
  44. var _limits_tmp = {};
  45. var _is_camera_moving = false;
  46. var _is_camera_rotating = false;
  47. var _is_camera_stop_moving = false;
  48. var _is_camera_stop_rotating = false;
  49. /**
  50. * Callback to be executed when the camera finishes its track animation.
  51. * See track_to_target() method.
  52. * @callback TrackToTargetCallback
  53. */
  54. /**
  55. * Callback to be executed when the camera finishes its zoom-in animation.
  56. * See track_to_target() method.
  57. * @callback TrackToTargetZoomCallback
  58. */
  59. /**
  60. * Smoothly rotate the EYE camera to make it pointing at the specified
  61. * target (an object or some position). Then smoothly zoom on this target,
  62. * pause and zoom back.
  63. * @param {Object3D} cam_obj Camera object 3D
  64. * @param {(Object3D|Vec3)} target Target object or target position
  65. * @param {?number} [rot_speed=1] Rotation speed, radians per second
  66. * @param {?number} [zoom_mult=2] Zoom level value
  67. * @param {?number} [zoom_time=1] Time it takes to zoom on the target, seconds
  68. * @param {?number} [zoom_delay=1] Delay before the camera zooms back, seconds
  69. * @param {?TrackToTargetCallback} [track_cb] Track finishing callback
  70. * @param {?TrackToTargetZoomCallback} [zoom_in_cb] Zoom-in callback
  71. */
  72. exports.track_to_target = function(cam_obj, target, rot_speed, zoom_mult,
  73. zoom_time, zoom_delay, track_cb, zoom_in_cb) {
  74. if (!m_cam.is_eye_camera(cam_obj)) {
  75. m_print.error("track_to_target(): Wrong camera object or camera move style");
  76. return;
  77. }
  78. if (m_util.is_vector(target))
  79. var obj_pos = target;
  80. else {
  81. var obj_pos = _vec3_tmp;
  82. m_trans.get_object_center(target, false, obj_pos);
  83. }
  84. rot_speed = rot_speed || 1;
  85. zoom_mult = zoom_mult || 2;
  86. zoom_time = zoom_time || 1;
  87. zoom_delay = zoom_delay || 1;
  88. var cam_pos = m_trans.get_translation(cam_obj, _vec3_tmp2);
  89. var dir_to_target = m_vec3.subtract(obj_pos, cam_pos, _vec3_tmp3);
  90. var start_angles = m_cam.get_camera_angles(cam_obj, _vec2_tmp);
  91. var finish_angles = m_cam.get_camera_angles_dir(dir_to_target, _vec2_tmp2);
  92. var phi_angle = finish_angles[0] - start_angles[0];
  93. var theta_angle = finish_angles[1] - start_angles[1];
  94. // calculate arc angle on a unit sphere using the spherical law of cosines
  95. var arc_angle = Math.acos(Math.cos(phi_angle) * Math.cos(theta_angle));
  96. var rot_time = Math.abs(arc_angle / rot_speed);
  97. var zoom_dist = m_vec3.length(dir_to_target) * (1 - 1 / zoom_mult);
  98. var smooth_function = function(x) {
  99. var f = 6 * x * (1 - x);
  100. return f;
  101. }
  102. var _start_time = m_time.get_timeline();
  103. var _stage = 0;
  104. var track_to_target_cb = function(obj, id, pulse) {
  105. // NOTE: if move_style was changed during the tracking
  106. if (!m_cam.is_eye_camera(obj)) {
  107. disable_cb();
  108. return;
  109. }
  110. if (pulse == 1) {
  111. var curr_time = m_ctl.get_sensor_value(obj, id, 0) - _start_time;
  112. var elapsed = m_ctl.get_sensor_value(obj, id, 1);
  113. if (curr_time < rot_time) {
  114. var smooth_coeff = smooth_function(curr_time / rot_time);
  115. var phi_delta = smooth_coeff * phi_angle * elapsed / rot_time;
  116. var theta_delta = smooth_coeff * theta_angle * elapsed / rot_time;
  117. m_cam.rotate_camera(cam_obj, phi_delta, theta_delta);
  118. } else if (curr_time < rot_time + zoom_time) {
  119. if (_stage == 0) {
  120. m_cam.rotate_camera(cam_obj, finish_angles[0], finish_angles[1], true);
  121. _stage++;
  122. }
  123. var smooth_coeff = smooth_function(curr_time - rot_time / zoom_time);
  124. var delta_dist = smooth_coeff * zoom_dist * elapsed / zoom_time;
  125. m_trans.move_local(obj, 0, 0, -delta_dist);
  126. } else if (curr_time < rot_time + zoom_time + zoom_delay) {
  127. if (_stage <= 1) {
  128. m_cam.rotate_camera(cam_obj, finish_angles[0], finish_angles[1], true);
  129. m_cam.eye_set_look_at(cam_obj, cam_pos);
  130. m_trans.move_local(obj, 0, 0, -zoom_dist);
  131. if (zoom_in_cb)
  132. zoom_in_cb();
  133. _stage++;
  134. }
  135. } else if (curr_time < rot_time + zoom_time + zoom_delay + zoom_time) {
  136. if (_stage <= 2) {
  137. m_cam.rotate_camera(cam_obj, finish_angles[0], finish_angles[1], true);
  138. m_cam.eye_set_look_at(cam_obj, cam_pos);
  139. m_trans.move_local(obj, 0, 0, -zoom_dist);
  140. _stage++;
  141. }
  142. var smooth_coeff = smooth_function(curr_time - rot_time - zoom_time - zoom_delay / zoom_time);
  143. var delta_dist = smooth_coeff * zoom_dist * elapsed / zoom_time;
  144. m_trans.move_local(obj, 0, 0, delta_dist);
  145. } else {
  146. m_cam.rotate_camera(cam_obj, finish_angles[0], finish_angles[1], true);
  147. m_cam.eye_set_look_at(cam_obj, cam_pos);
  148. disable_cb();
  149. }
  150. }
  151. }
  152. var disable_cb = function() {
  153. m_ctl.remove_sensor_manifold(cam_obj, "TRACK_TO_TARGET");
  154. if (track_cb)
  155. track_cb();
  156. }
  157. var timeline = m_ctl.create_timeline_sensor();
  158. var elapsed = m_ctl.create_elapsed_sensor();
  159. m_ctl.create_sensor_manifold(cam_obj, "TRACK_TO_TARGET", m_ctl.CT_CONTINUOUS,
  160. [timeline, elapsed], null, track_to_target_cb);
  161. }
  162. function init_limited_rotation_ratio(obj, limits, auto_rotate_ratio) {
  163. var phi = m_cam.get_camera_angles(obj, _vec2_tmp)[0];
  164. var delts = get_delta_to_limits(obj, phi, limits.left, limits.right, _vec2_tmp2);
  165. return auto_rotate_ratio * Math.min(1, delts[0] / ROTATION_OFFSET,
  166. delts[1] / ROTATION_OFFSET);
  167. }
  168. function get_delta_to_limits(obj, angle, limit_left, limit_right, dest) {
  169. // accurate delta calculation
  170. var diff_left = m_util.angle_wrap_0_2pi(angle - limit_left);
  171. var delta_to_left = Math.min(diff_left, 2 * Math.PI - diff_left);
  172. var diff_right = m_util.angle_wrap_0_2pi(limit_right - angle);
  173. var delta_to_right = Math.min(diff_right, 2 * Math.PI - diff_right);
  174. // some precision errors could be near the limits
  175. if (Math.abs(delta_to_left) < ROTATION_LIMITS_EPS
  176. || 2 * Math.PI - Math.abs(delta_to_left) < ROTATION_LIMITS_EPS)
  177. delta_to_left = 0;
  178. if (Math.abs(delta_to_right) < ROTATION_LIMITS_EPS
  179. || 2 * Math.PI - Math.abs(delta_to_right) < ROTATION_LIMITS_EPS)
  180. delta_to_right = 0;
  181. if (m_cam.is_eye_camera(obj)) {
  182. dest[0] = delta_to_right; // to min angle
  183. dest[1] = delta_to_left; // to max angle
  184. } else {
  185. dest[0] = delta_to_left; // to min angle
  186. dest[1] = delta_to_right; // to max angle
  187. }
  188. return dest;
  189. }
  190. /**
  191. * Callback to be executed when auto-rotating is disabled.
  192. * It is fired when either the user manually rotates the camera,
  193. * or the auto_rotate() method is executed again.
  194. * @callback AutoRotateDisabledCallback
  195. */
  196. /**
  197. * Switch auto-rotation of the TARGET or HOVER camera around its pivot, or
  198. * auto-rotating of the EYE camera around itself.
  199. * When it is called for the first time, auto-rotation is enabled
  200. * while the next call will disable auto-rotation.
  201. * @param {number} auto_rotate_ratio Rotation speed multiplier
  202. * @param {AutoRotateDisabledCallback} [callback] Callback to be executed when auto-rotation is disabled
  203. * @param {boolean} [disable_on_mouse_wheel] Disable camera auto-rotation after mouse scrolling.
  204. */
  205. exports.auto_rotate = function(auto_rotate_ratio, callback, disable_on_mouse_wheel) {
  206. callback = callback || function(){};
  207. var obj = m_scs.get_active_camera();
  208. if (m_cam.is_static_camera(obj)) {
  209. m_print.error("auto_rotate(): Wrong camera move style");
  210. return;
  211. }
  212. var angle_limits = {};
  213. var rot_offset = 0;
  214. var cur_rotate_ratio = 0;
  215. function update_limited_rotation_params(curr_limits) {
  216. angle_limits = angle_limits || {};
  217. angle_limits.left = curr_limits.left;
  218. angle_limits.right = curr_limits.right;
  219. rot_offset = Math.min(ROTATION_OFFSET,
  220. (m_util.angle_wrap_0_2pi(angle_limits.right - angle_limits.left)) / 2);
  221. cur_rotate_ratio = init_limited_rotation_ratio(obj,
  222. angle_limits, auto_rotate_ratio);
  223. }
  224. function elapsed_cb(obj, id, pulse) {
  225. if (pulse == 1) {
  226. var move_style = m_cam.get_move_style(obj);
  227. // NOTE: if move_style was changed to STATIC during the autorotation
  228. if (move_style == m_cam.MS_STATIC)
  229. disable_cb();
  230. else if ((move_style == m_cam.MS_TARGET_CONTROLS
  231. || move_style == m_cam.MS_EYE_CONTROLS)
  232. && m_cam.has_horizontal_rot_limits(obj)) {
  233. var curr_limits = (move_style == m_cam.MS_EYE_CONTROLS)
  234. ? m_cam.eye_get_horizontal_limits(obj, _limits_tmp)
  235. : m_cam.target_get_horizontal_limits(obj, _limits_tmp);
  236. if (angle_limits === null || curr_limits.left != angle_limits.left
  237. || curr_limits.right != angle_limits.right)
  238. update_limited_rotation_params(curr_limits);
  239. limited_auto_rotate(obj, id);
  240. } else {
  241. angle_limits = null;
  242. unlimited_auto_rotate(obj, id);
  243. }
  244. }
  245. }
  246. function limited_auto_rotate(obj, id) {
  247. var value = m_ctl.get_sensor_value(obj, id, 0);
  248. var phi = m_cam.get_camera_angles(obj, _vec2_tmp)[0];
  249. var delts = get_delta_to_limits(obj, phi, angle_limits.left, angle_limits.right,
  250. _vec2_tmp2);
  251. if (delts[1] > rot_offset
  252. && delts[0] > rot_offset)
  253. cur_rotate_ratio = m_util.sign(cur_rotate_ratio) * auto_rotate_ratio;
  254. else if (delts[1] < rot_offset)
  255. cur_rotate_ratio = cur_rotate_ratio -
  256. Math.pow(auto_rotate_ratio, 2) / (2 * ROTATION_OFFSET) * value;
  257. else if (delts[0] < rot_offset)
  258. cur_rotate_ratio = cur_rotate_ratio +
  259. Math.pow(auto_rotate_ratio, 2) / (2 * ROTATION_OFFSET) * value;
  260. m_cam.rotate_camera(obj, value * cur_rotate_ratio, 0);
  261. }
  262. function unlimited_auto_rotate(obj, id) {
  263. var value = m_ctl.get_sensor_value(obj, id, 0);
  264. m_cam.rotate_camera(obj, value * auto_rotate_ratio, 0);
  265. }
  266. function disable_cb() {
  267. m_ctl.remove_sensor_manifold(obj, "AUTO_ROTATE");
  268. m_ctl.remove_sensor_manifold(obj, "DISABLE_AUTO_ROTATE");
  269. callback();
  270. }
  271. if (!m_ctl.check_sensor_manifold(obj, "AUTO_ROTATE")) {
  272. var mouse_move_x = m_ctl.create_mouse_move_sensor("X");
  273. var mouse_move_y = m_ctl.create_mouse_move_sensor("Y");
  274. var mouse_down = m_ctl.create_mouse_click_sensor();
  275. var touch_move = m_ctl.create_touch_move_sensor();
  276. var touch_zoom = m_ctl.create_touch_zoom_sensor();
  277. var elapsed = m_ctl.create_elapsed_sensor();
  278. if (disable_on_mouse_wheel)
  279. var wheel_zoom = m_ctl.create_mouse_wheel_sensor();
  280. else
  281. var wheel_zoom = m_ctl.create_custom_sensor(0);
  282. var logic_func = function(s) {return (s[0] && s[2]) || (s[1] && s[2]) || s[3] || s[4] || s[5]};
  283. m_ctl.create_sensor_manifold(obj, "DISABLE_AUTO_ROTATE", m_ctl.CT_LEVEL,
  284. [mouse_move_x, mouse_move_y, mouse_down,
  285. touch_move, touch_zoom, wheel_zoom], logic_func,
  286. disable_cb);
  287. m_ctl.create_sensor_manifold(obj, "AUTO_ROTATE", m_ctl.CT_CONTINUOUS,
  288. [elapsed], function(s) {return s[0]},
  289. elapsed_cb);
  290. } else
  291. disable_cb();
  292. }
  293. /**
  294. * Check if the camera is auto-rotating.
  295. * @method module:camera_anim.is_auto_rotate
  296. * @returns {boolean} Result of the check: true - when the camera is
  297. * auto-rotating, false - otherwise.
  298. */
  299. exports.is_auto_rotate = function() {
  300. var obj = m_scs.get_active_camera();
  301. return m_ctl.check_sensor_manifold(obj, "AUTO_ROTATE");
  302. }
  303. /**
  304. * Check if auto-rotation is possible for the camera.
  305. * For example, the STATIC camera cannot be rotated.
  306. * @method module:camera_anim.check_auto_rotate
  307. * @returns {boolean} Result of the check: true - when auto-rotation is
  308. * possible, false - otherwise.
  309. */
  310. exports.check_auto_rotate = function() {
  311. var obj = m_scs.get_active_camera();
  312. var cam_type = m_cam.get_move_style(obj);
  313. if (cam_type == m_cam.MS_STATIC)
  314. return false;
  315. return true;
  316. }
  317. /**
  318. * Callback to be executed when camera is finishes its moving animation.
  319. * See move_camera_to_point() method
  320. * @callback MoveCameraToPointCallback
  321. */
  322. /**
  323. * Smoothly move the camera to the target point. Intended for STATIC cameras only.
  324. * @param {(Object3D|tsr)} cam_obj Camera object 3D
  325. * @param {(Object3D|tsr)} point_obj Target point object 3D
  326. * @param {number} cam_lin_speed Camera linear speed, meters per second
  327. * @param {number} cam_angle_speed Camera angular speed, radians per second
  328. * @param {MoveCameraToPointCallback} [cb] Finishing callback
  329. */
  330. exports.move_camera_to_point = function(cam_obj, point_obj, cam_lin_speed, cam_angle_speed, cb) {
  331. if (m_cam.get_move_style(cam_obj) != m_cam.MS_STATIC) {
  332. m_print.error("move_camera_to_point(): wrong camera type");
  333. return;
  334. }
  335. if (_is_camera_moving)
  336. return;
  337. if (!cam_obj) {
  338. m_print.error("move_camera_to_point(): you must specify the camera object");
  339. return;
  340. }
  341. if (!point_obj) {
  342. m_print.error("move_camera_to_point(): you must specify the point object");
  343. return;
  344. }
  345. cam_lin_speed = cam_lin_speed || DEFAULT_CAM_LIN_SPEED;
  346. cam_angle_speed = cam_angle_speed || DEFAULT_CAM_ANGLE_SPEED;
  347. if (m_util.is_vector(cam_obj))
  348. var cam_tsr = cam_obj;
  349. else
  350. var cam_tsr = m_trans.get_tsr(cam_obj, _tsr_tmp);
  351. if (m_util.is_vector(point_obj))
  352. var point_tsr = point_obj;
  353. else
  354. var point_tsr = m_trans.get_tsr(point_obj, _tsr_tmp2);
  355. var distance = m_vec3.distance(m_tsr.get_trans_view(cam_tsr),
  356. m_tsr.get_trans_view(point_tsr));
  357. var move_time = distance / cam_lin_speed;
  358. var current_cam_dir = m_util.quat_to_dir(m_tsr.get_quat_view(cam_tsr),
  359. m_util.AXIS_MZ, _vec3_tmp);
  360. var target_cam_dir = m_util.quat_to_dir(m_tsr.get_quat_view(point_tsr),
  361. m_util.AXIS_MZ, _vec3_tmp2);
  362. var vec_dot = Math.min(Math.abs(m_vec3.dot(current_cam_dir,
  363. target_cam_dir)), 1);
  364. var angle = Math.acos(vec_dot);
  365. var rotate_time = angle / cam_angle_speed;
  366. var time = Math.max(move_time, rotate_time) * 1000;
  367. _is_camera_moving = true;
  368. var cur_animator = m_time.animate(0, 1, time, function(e) {
  369. var new_tsr = m_tsr.interpolate(cam_tsr, point_tsr,
  370. m_util.smooth_step(e), _tsr_tmp3);
  371. if (_is_camera_stop_moving) {
  372. m_time.clear_animation(cur_animator);
  373. _is_camera_stop_moving = false;
  374. _is_camera_moving = false;
  375. return;
  376. }
  377. m_trans.set_tsr(cam_obj, new_tsr);
  378. if (e == 1) {
  379. _is_camera_moving = false;
  380. if (cb)
  381. cb();
  382. }
  383. });
  384. }
  385. /**
  386. * Callback to be executed when camera is finishes its rotate animation.
  387. * See rotate_camera() method
  388. * @callback RotateCameraCallback
  389. */
  390. /**
  391. * Smoothly rotate the camera. Intended for non-STATIC cameras.
  392. * @param {Object3D} cam_obj Camera object 3D
  393. * @param {number} angle_phi Horizontal rotation angle (in radians)
  394. * @param {number} angle_theta Vertical rotation angle (in radians)
  395. * @param {number} [time=1000] Rotation time in ms
  396. * @param {RotateCameraCallback} [cb] Finishing callback
  397. */
  398. exports.rotate_camera = function(cam_obj, angle_phi, angle_theta, time, cb) {
  399. if (m_cam.get_move_style(cam_obj) == m_cam.MS_STATIC) {
  400. m_print.error("rotate_camera(): not supported for STATIC cameras");
  401. return;
  402. }
  403. if (_is_camera_rotating)
  404. return;
  405. if (!cam_obj) {
  406. m_print.error("rotate_camera(): you must specify the camera object");
  407. return;
  408. }
  409. if (!angle_phi && !angle_theta) {
  410. m_print.error("rotate_camera(): you must specify the rotation angle");
  411. return;
  412. }
  413. time = time || DEFAULT_CAM_ROTATE_TIME;
  414. _is_camera_rotating = true;
  415. function fin_cb() {
  416. if (_is_camera_rotating) {
  417. _is_camera_rotating = false;
  418. if (cb)
  419. cb();
  420. }
  421. }
  422. var delta_phi = 0;
  423. var cur_animator_phi = m_time.animate(0, angle_phi, time, function(e) {
  424. if (_is_camera_stop_rotating || e >= angle_phi) {
  425. _is_camera_stop_rotating = false;
  426. m_time.clear_animation(cur_animator_phi);
  427. fin_cb();
  428. return;
  429. }
  430. m_cam.rotate_camera(cam_obj, delta_phi - e, 0);
  431. delta_phi = e;
  432. });
  433. var delta_theta = 0;
  434. var cur_animator_theta = m_time.animate(0, angle_theta, time, function(e) {
  435. if (_is_camera_stop_rotating || e >= angle_theta) {
  436. _is_camera_stop_rotating = false;
  437. m_time.clear_animation(cur_animator_theta);
  438. fin_cb();
  439. return;
  440. }
  441. m_cam.rotate_camera(cam_obj, 0, delta_theta - e);
  442. delta_theta = e;
  443. });
  444. }
  445. /**
  446. * Stop camera moving.
  447. * @method module:camera_anim.stop_cam_moving
  448. */
  449. exports.stop_cam_moving = function() {
  450. _is_camera_stop_moving = true;
  451. }
  452. /**
  453. * Stop camera rotating.
  454. * @method module:camera_anim.stop_cam_rotating
  455. */
  456. exports.stop_cam_rotating = function() {
  457. _is_camera_stop_rotating = true;
  458. }
  459. /**
  460. * Check if the camera is being moved by the
  461. * {@link module:camera_anim.move_camera_to_point|move_camera_to_point} function.
  462. * @method module:camera_anim.is_moving
  463. * @returns {boolean} Result of the check: true - when the camera is
  464. * moving, false - otherwise.
  465. */
  466. exports.is_moving = function() {
  467. return _is_camera_moving;
  468. }
  469. /**
  470. * Check if the camera is being rotated by the
  471. * {@link module:camera_anim.rotate_camera|rotate_camera} function.
  472. * @method module:camera_anim.is_rotating
  473. * @returns {boolean} Result of the check: true - when the camera is
  474. * rotating, false - otherwise.
  475. */
  476. exports.is_rotating = function() {
  477. return _is_camera_rotating;
  478. }
  479. }
  480. var camera_anim_factory = register("camera_anim", Camera_anim);
  481. export default camera_anim_factory;