Source: addons/hmd.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_input_fact from "../extern/input.js";
  5. import * as m_quat from "../libs/gl_matrix/quat.js";
  6. import m_scenes_fact from "../extern/scenes.js";
  7. import m_screen_fact from "../extern/screen.js";
  8. import m_trans_fact from "../extern/transform.js";
  9. import m_util_fact from "../extern/util.js";
  10. import * as m_vec3 from "../libs/gl_matrix/vec3.js";
  11. /**
  12. * Head Mounted Devices add-on.
  13. * Provides support for HMD/VR devices using {@link https://w3c.github.io/webvr/|WebVR API}.
  14. * <p>For more info about stereo rendering check out the {@link https://www.blend4web.com/doc/en/stereo_rendering.html|user manual}.
  15. * @module hmd
  16. */
  17. function HMD(ns, exports) {
  18. var m_cam = m_cam_fact(ns);
  19. var m_ctl = m_ctl_fact(ns);
  20. var m_input = m_input_fact(ns);
  21. var m_scenes = m_scenes_fact(ns);
  22. var m_screen = m_screen_fact(ns);
  23. var m_trans = m_trans_fact(ns);
  24. var m_util = m_util_fact(ns);
  25. var _last_cam_quat = m_quat.create();
  26. var _yaw_cam_angle = 0;
  27. var _was_target_camera = false;
  28. var _was_hover_camera = false;
  29. var _was_static_camera = false;
  30. var _vec3_tmp = m_vec3.create();
  31. var _vec3_tmp2 = m_vec3.create();
  32. var _vec3_tmp3 = m_vec3.create();
  33. var _quat_tmp = m_quat.create();
  34. var _quat_tmp2 = m_quat.create();
  35. var _quat_tmp3 = m_quat.create();
  36. var _quat_tmp4 = m_quat.create();
  37. var _offset_quat = m_quat.create();
  38. var _offset_pos = m_vec3.create();
  39. var _empty_params = {pivot: m_vec3.create()};
  40. /**
  41. * HMD behavior enum.
  42. * @see {@link module:hmd.HMD_NONE_MOUSE_ALL_AXES},
  43. * {@link module:hmd.HMD_ALL_AXES_MOUSE_NONE},
  44. * {@link module:hmd.HMD_ROLL_PITCH_MOUSE_YAW},
  45. * {@link module:hmd.HMD_ALL_AXES_MOUSE_YAW}
  46. * @typedef {number} HMDBehavior
  47. */
  48. /**
  49. * HMD behavior: HMD does not affect camera orientation,
  50. * mouse controls camera rotation.
  51. * @const {HMDBehavior} module:hmd.HMD_NONE_MOUSE_ALL_AXES
  52. */
  53. var HMD_NONE_MOUSE_ALL_AXES = 0;
  54. exports.HMD_NONE_MOUSE_ALL_AXES = HMD_NONE_MOUSE_ALL_AXES;
  55. /**
  56. * HMD behavior: HMD controls camera rotation,
  57. * mouse does not affect camera orientation.
  58. * @const {HMDBehavior} module:hmd.HMD_ALL_AXES_MOUSE_NONE
  59. */
  60. var HMD_ALL_AXES_MOUSE_NONE = 1;
  61. exports.HMD_ALL_AXES_MOUSE_NONE = HMD_ALL_AXES_MOUSE_NONE;
  62. /**
  63. * HMD behavior: HMD controls roll and pitch rotation,
  64. * mouse controls yaw rotation.
  65. * @const {HMDBehavior} module:hmd.HMD_ROLL_PITCH_MOUSE_YAW
  66. */
  67. var HMD_ROLL_PITCH_MOUSE_YAW = 2;
  68. exports.HMD_ROLL_PITCH_MOUSE_YAW = HMD_ROLL_PITCH_MOUSE_YAW;
  69. /**
  70. * HMD behavior: HMD affects camera rotation,
  71. * mouse affect yaw rotation.
  72. * @const {HMDBehavior} module:hmd.HMD_ALL_AXES_MOUSE_YAW
  73. */
  74. var HMD_ALL_AXES_MOUSE_YAW = 3;
  75. exports.HMD_ALL_AXES_MOUSE_YAW = HMD_ALL_AXES_MOUSE_YAW;
  76. /**
  77. * Enable HMD.
  78. * @method module:hmd.enable_hmd
  79. * @param {HMDBehavior} control_type Camera rotation type.
  80. */
  81. exports.enable_hmd = function(control_type) {
  82. var sensor = null;
  83. var device = m_input.get_device_by_type_element(m_input.DEVICE_HMD);
  84. if (device) {
  85. if (m_input.get_value_param(device, m_input.HMD_WEBVR_TYPE) &
  86. (m_input.HMD_WEBVR_MOBILE | m_input.HMD_WEBVR_DESKTOP | m_input.HMD_WEBVR1))
  87. // use state of the WebVR device
  88. sensor = m_ctl.create_hmd_quat_sensor();
  89. else
  90. // use gyroscope state
  91. sensor = m_ctl.create_gyro_quat_sensor();
  92. process_hmd(control_type, sensor);
  93. }
  94. }
  95. /**
  96. * Check if the browser supports WebVR API or it is a mobile version of the browser.
  97. * @method module:hmd.check_browser_support
  98. * @return {boolean} Checking result.
  99. */
  100. exports.check_browser_support = function() {
  101. return Boolean(m_input.can_use_device(m_input.DEVICE_HMD));
  102. }
  103. /**
  104. * Enable VR controllers.
  105. * @method module:hmd.enable_controllers
  106. * @param {Object3D} [gamepad_1] Object presenting controller.
  107. * @param {Object3D} [gamepad_2] Object presenting controller.
  108. * @example
  109. * var m_hmd = require("hmd");
  110. * var m_scenes = require("scenes");
  111. *
  112. * var gamepad_1 = m_scenes.get_object_by_name("my_gamepad_1");
  113. * var gamepad_2 = m_scenes.get_object_by_name("my_gamepad_2");
  114. * m_hmd.enable_controllers(gamepad_1, gamepad_2);
  115. */
  116. exports.enable_controllers = function(gamepad_1, gamepad_2) {
  117. // TODO: add default models for gamepad_1, gamepad_2
  118. disable_controllers();
  119. if (!gamepad_1 && !gamepad_2)
  120. return;
  121. var gamepad_id = m_input.get_vr_controller_id(0);
  122. var gm_pos_sensor = m_ctl.create_gamepad_position_sensor(gamepad_id);
  123. var gm_ori_sensor = m_ctl.create_gamepad_orientation_sensor(gamepad_id);
  124. var gamepad_id2 = m_input.get_vr_controller_id(1);
  125. var gm_pos_sensor2 = m_ctl.create_gamepad_position_sensor(gamepad_id2);
  126. var gm_ori_sensor2 = m_ctl.create_gamepad_orientation_sensor(gamepad_id2);
  127. function position_cb(obj, id, pulse) {
  128. if (gamepad_1) {
  129. var gmpos1 = m_ctl.get_sensor_payload(obj, id, 0);
  130. var gmori1 = m_ctl.get_sensor_payload(obj, id, 2);
  131. m_vec3.add(_offset_pos, gmpos1, gmpos1);
  132. m_trans.set_translation_v(gamepad_1, gmpos1);
  133. m_trans.set_rotation_v(gamepad_1, gmori1);
  134. }
  135. if (gamepad_2) {
  136. var gmpos2 = m_ctl.get_sensor_payload(obj, id, 1);
  137. var gmori2 = m_ctl.get_sensor_payload(obj, id, 3);
  138. m_vec3.add(_offset_pos, gmpos2, gmpos2);
  139. m_trans.set_translation_v(gamepad_2, gmpos2);
  140. m_trans.set_rotation_v(gamepad_2, gmori2);
  141. }
  142. }
  143. m_ctl.create_sensor_manifold(null, "VR_CONTROLLERS", m_ctl.CT_CONTINUOUS,
  144. [gm_pos_sensor, gm_pos_sensor2, gm_ori_sensor, gm_ori_sensor2],
  145. null, position_cb);
  146. }
  147. /**
  148. * Disable VR controllers.
  149. * @method module:hmd.disable_controllers
  150. * @example
  151. * var m_hmd = require("hmd");
  152. *
  153. * m_hmd.disable_controllers();
  154. */
  155. exports.disable_controllers = disable_controllers;
  156. function disable_controllers() {
  157. m_ctl.remove_sensor_manifold(null, "VR_CONTROLLERS");
  158. }
  159. function process_hmd(control_type, sensor) {
  160. if (!sensor)
  161. return;
  162. var cam_obj = m_scenes.get_active_camera();
  163. if (!cam_obj)
  164. return;
  165. m_screen.request_split_screen(function() {
  166. if (!m_cam.is_eye_camera(cam_obj)) {
  167. _was_target_camera = m_cam.is_target_camera(cam_obj);
  168. _was_hover_camera = m_cam.is_hover_camera(cam_obj);
  169. _was_static_camera = m_cam.is_static_camera(cam_obj);
  170. m_cam.eye_setup(cam_obj);
  171. }
  172. var elapsed = m_ctl.create_elapsed_sensor();
  173. var pos_sensor = m_ctl.create_hmd_position_sensor();
  174. _last_cam_quat = m_trans.get_rotation(cam_obj, _last_cam_quat);
  175. m_ctl.create_sensor_manifold(null, "HMD_ROTATE_CAMERA", m_ctl.CT_CONTINUOUS,
  176. [elapsed, sensor, pos_sensor], null, move_cam_cb);
  177. });
  178. function move_cam_cb(obj, id, pulse) {
  179. if (pulse > 0) {
  180. var cam_obj = m_scenes.get_active_camera();
  181. if (!cam_obj)
  182. return;
  183. // NOTE: It is executed every frame.
  184. // uses _vec3_tmp, _vec3_tmp2, _vec3_tmp3, _quat_tmp, _quat_tmp2
  185. if (m_cam.is_eye_camera(cam_obj)) {
  186. var hmd_quat = m_ctl.get_sensor_payload(obj, id, 1);
  187. var hmd_pos = m_ctl.get_sensor_payload(obj, id, 2);
  188. var position = m_vec3.add(hmd_pos, _offset_pos, _vec3_tmp);
  189. m_trans.set_translation_v(cam_obj, position);
  190. if (hmd_quat) {
  191. if (control_type == HMD_ALL_AXES_MOUSE_NONE) {
  192. hmd_quat = m_quat.multiply(_offset_quat, hmd_quat, _quat_tmp4);
  193. var up_axis = m_vec3.transformQuat(m_util.AXIS_MY, hmd_quat, _vec3_tmp);
  194. m_cam.set_vertical_axis(cam_obj, up_axis);
  195. m_trans.set_rotation_v(cam_obj, hmd_quat);
  196. } else if (control_type == HMD_ROLL_PITCH_MOUSE_YAW ||
  197. control_type == HMD_ALL_AXES_MOUSE_YAW) {
  198. var cam_quat = m_trans.get_rotation(cam_obj,
  199. _quat_tmp2);
  200. var inv_cam_quat = m_quat.invert(cam_quat,
  201. _quat_tmp2);
  202. var diff_cam_quat = m_quat.multiply(_last_cam_quat,
  203. inv_cam_quat, _quat_tmp2);
  204. var cur_vertical_axis = m_cam.get_vertical_axis(cam_obj,
  205. _vec3_tmp);
  206. if (Math.abs(cur_vertical_axis[2]) < Math.PI / 4)
  207. var first_horiz_vec = m_vec3.cross(cur_vertical_axis,
  208. m_util.AXIS_MY, _vec3_tmp2);
  209. else if (Math.abs(cur_vertical_axis[1]) < Math.PI / 4)
  210. var first_horiz_vec = m_vec3.cross(cur_vertical_axis,
  211. m_util.AXIS_Z, _vec3_tmp2);
  212. m_vec3.normalize(first_horiz_vec, first_horiz_vec);
  213. var rotated_first_horiz_vec = m_vec3.transformQuat(
  214. first_horiz_vec, diff_cam_quat, _vec3_tmp3);
  215. var vertical_coef = m_vec3.dot(cur_vertical_axis,
  216. rotated_first_horiz_vec);
  217. var second_horiz_vec = m_vec3.scaleAndAdd(rotated_first_horiz_vec,
  218. cur_vertical_axis, -vertical_coef, _vec3_tmp3);
  219. m_vec3.normalize(second_horiz_vec, second_horiz_vec);
  220. var sign_horiz_vec = m_vec3.cross(cur_vertical_axis,
  221. first_horiz_vec, _vec3_tmp);
  222. var abs_yaw_angle = Math.acos(m_util.clamp(
  223. m_vec3.dot(first_horiz_vec, second_horiz_vec),
  224. 0, 1));
  225. var sign_yaw_angle = m_util.sign(m_vec3.dot(
  226. second_horiz_vec, sign_horiz_vec));
  227. var diff_yaw_cam_angle = abs_yaw_angle * sign_yaw_angle;
  228. _yaw_cam_angle += diff_yaw_cam_angle;
  229. var yaw_cam_quat = m_quat.setAxisAngle(m_util.AXIS_Z,
  230. -_yaw_cam_angle, _quat_tmp2);
  231. if (control_type == HMD_ALL_AXES_MOUSE_YAW) {
  232. var new_cam_quat = m_quat.multiply(yaw_cam_quat,
  233. hmd_quat, _quat_tmp);
  234. } else {
  235. var yaw_hmd_quat = m_util.quat_project(hmd_quat, m_util.AXIS_MZ,
  236. m_util.AXIS_Z, m_util.AXIS_Y, _quat_tmp3);
  237. var yaw_hmd_inv_quat = m_quat.invert(yaw_hmd_quat, _quat_tmp3);
  238. var vertical_hmd_quat = m_quat.multiply(
  239. yaw_hmd_inv_quat, hmd_quat, _quat_tmp3);
  240. var new_cam_quat = m_quat.multiply(yaw_cam_quat,
  241. vertical_hmd_quat, _quat_tmp);
  242. }
  243. var up_axis = m_vec3.transformQuat(m_util.AXIS_MY,
  244. new_cam_quat, _vec3_tmp);
  245. m_cam.set_vertical_axis(cam_obj, up_axis);
  246. m_trans.set_rotation_v(cam_obj, new_cam_quat);
  247. m_quat.copy(new_cam_quat, _last_cam_quat);
  248. }
  249. }
  250. }
  251. }
  252. }
  253. }
  254. /**
  255. * Disable HMD.
  256. * @method module:hmd.disable_hmd
  257. */
  258. exports.disable_hmd = function() {
  259. if (!m_ctl.check_sensor_manifold(null, "HMD_ROTATE_CAMERA"))
  260. return;
  261. m_ctl.remove_sensor_manifold(null, "HMD_ROTATE_CAMERA");
  262. m_screen.exit_split_screen();
  263. var cam_obj = m_scenes.get_active_camera();
  264. // TODO: add restoring camera's params
  265. if (_was_target_camera)
  266. m_cam.target_setup(cam_obj, _empty_params);
  267. else if (_was_hover_camera)
  268. m_cam.hover_setup(cam_obj, _empty_params);
  269. else if (_was_static_camera)
  270. m_cam.static_setup(cam_obj, _empty_params);
  271. // correct up camera (non-vr mode)
  272. m_cam.set_vertical_axis(cam_obj, m_util.AXIS_Z);
  273. // TODO: update_transform
  274. var cam_quat = m_trans.get_rotation(cam_obj, _quat_tmp);
  275. m_trans.set_rotation_v(cam_obj, cam_quat);
  276. }
  277. /**
  278. * Set hmd initial rotation quat.
  279. * @method module:hmd.set_rotate_quat
  280. * @param {Quat} quat Initial rotation quaternion.
  281. * @example
  282. * var m_hmd = require("hmd");
  283. *
  284. * m_hmd.set_rotate_quat([0,0,0,1]);
  285. */
  286. exports.set_rotate_quat = function(quat) {
  287. m_quat.copy(quat, _offset_quat);
  288. }
  289. /**
  290. * Get hmd initial rotation quat.
  291. * @method module:hmd.get_rotate_quat
  292. * @param {Quat} dest Initial rotation quaternion.
  293. * @return {Quat} dest.
  294. * @example
  295. * var m_hmd = require("hmd");
  296. * var m_quat = require("quat");
  297. * var _quat_tmp = m_quat.create();
  298. *
  299. * var quat = m_hmd.get_rotate_quat(_quat_tmp);
  300. */
  301. exports.get_rotate_quat = function(dest) {
  302. m_quat.copy(_offset_quat, dest);
  303. return dest;
  304. }
  305. /**
  306. * Set hmd initial position.
  307. * @method module:hmd.set_position
  308. * @param {Vec3} position Initial position.
  309. * @example
  310. * var m_hmd = require("hmd");
  311. *
  312. * m_hmd.set_position([0,0,0]);
  313. */
  314. exports.set_position = function(position) {
  315. m_quat.copy(position, _offset_pos);
  316. }
  317. /**
  318. * Get hmd initial position.
  319. * @method module:hmd.get_position
  320. * @param {Vec3} dest Initial position.
  321. * @return {Vec3} dest.
  322. * @example
  323. * var m_hmd = require("hmd");
  324. * var m_vec3 = require("vec3");
  325. * var _vec3_tmp = m_vec3.create();
  326. *
  327. * var pos = m_hmd.get_position(_vec3_tmp);
  328. */
  329. exports.get_position = function(dest) {
  330. m_quat.copy(_offset_pos, dest);
  331. return dest;
  332. }
  333. };
  334. var hmd_factory = register("hmd", HMD);
  335. export default hmd_factory;