aframe-gamepad-controls.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. /******/ (function(modules) { // webpackBootstrap
  2. /******/ // The module cache
  3. /******/ var installedModules = {};
  4. /******/ // The require function
  5. /******/ function __webpack_require__(moduleId) {
  6. /******/ // Check if module is in cache
  7. /******/ if(installedModules[moduleId])
  8. /******/ return installedModules[moduleId].exports;
  9. /******/ // Create a new module (and put it into the cache)
  10. /******/ var module = installedModules[moduleId] = {
  11. /******/ exports: {},
  12. /******/ id: moduleId,
  13. /******/ loaded: false
  14. /******/ };
  15. /******/ // Execute the module function
  16. /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  17. /******/ // Flag the module as loaded
  18. /******/ module.loaded = true;
  19. /******/ // Return the exports of the module
  20. /******/ return module.exports;
  21. /******/ }
  22. /******/ // expose the modules object (__webpack_modules__)
  23. /******/ __webpack_require__.m = modules;
  24. /******/ // expose the module cache
  25. /******/ __webpack_require__.c = installedModules;
  26. /******/ // __webpack_public_path__
  27. /******/ __webpack_require__.p = "";
  28. /******/ // Load entry module and return exports
  29. /******/ return __webpack_require__(0);
  30. /******/ })
  31. /************************************************************************/
  32. /******/ ([
  33. /* 0 */
  34. /***/ function(module, exports, __webpack_require__) {
  35. // Browser distrubution of the A-Frame component.
  36. (function (AFRAME) {
  37. if (!AFRAME) {
  38. console.error('Component attempted to register before AFRAME was available.');
  39. return;
  40. }
  41. (AFRAME.aframeCore || AFRAME).registerComponent('gamepad-controls', __webpack_require__(1));
  42. }(window.AFRAME));
  43. /***/ },
  44. /* 1 */
  45. /***/ function(module, exports, __webpack_require__) {
  46. /**
  47. * Gamepad controls for A-Frame.
  48. *
  49. * For more information about the Gamepad API, see:
  50. * https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API
  51. */
  52. var GamepadButton = __webpack_require__(2),
  53. GamepadButtonEvent = __webpack_require__(3);
  54. var MAX_DELTA = 200, // ms
  55. PI_2 = Math.PI / 2;
  56. var JOYSTICK_EPS = 0.2;
  57. module.exports = {
  58. /*******************************************************************
  59. * Statics
  60. */
  61. GamepadButton: GamepadButton,
  62. /*******************************************************************
  63. * Schema
  64. */
  65. schema: {
  66. // Controller 0-3
  67. controller: { default: 0, oneOf: [0, 1, 2, 3] },
  68. // Enable/disable features
  69. enabled: { default: true },
  70. movementEnabled: { default: true },
  71. lookEnabled: { default: true },
  72. flyEnabled: { default: false },
  73. invertAxisY: { default: false },
  74. // Constants
  75. easing: { default: 20 },
  76. acceleration: { default: 65 },
  77. sensitivity: { default: 0.04 },
  78. // Control axes
  79. pitchAxis: { default: 'x', oneOf: [ 'x', 'y', 'z' ] },
  80. yawAxis: { default: 'y', oneOf: [ 'x', 'y', 'z' ] },
  81. rollAxis: { default: 'z', oneOf: [ 'x', 'y', 'z' ] },
  82. // Debugging
  83. debug: { default: false }
  84. },
  85. /*******************************************************************
  86. * Core
  87. */
  88. /**
  89. * Called once when component is attached. Generally for initial setup.
  90. */
  91. init: function () {
  92. // Movement
  93. this.velocity = new THREE.Vector3(0, 0, 0);
  94. this.direction = new THREE.Vector3(0, 0, 0);
  95. // Rotation
  96. this.pitch = new THREE.Object3D();
  97. this.yaw = new THREE.Object3D();
  98. this.yaw.position.y = 10;
  99. this.yaw.add(this.pitch);
  100. // Button state
  101. this.buttons = {};
  102. if (!this.getGamepad()) {
  103. console.warn(
  104. 'Gamepad #%d not found. Connect controller and press any button to continue.',
  105. this.data.controller
  106. );
  107. }
  108. },
  109. /**
  110. * Called on each iteration of main render loop.
  111. */
  112. tick: function (t, dt) {
  113. this.updateRotation(dt);
  114. this.updatePosition(dt);
  115. this.updateButtonState();
  116. },
  117. /*******************************************************************
  118. * Movement
  119. */
  120. updatePosition: function (dt) {
  121. var data = this.data;
  122. var acceleration = data.acceleration;
  123. var easing = data.easing;
  124. var velocity = this.velocity;
  125. var rollAxis = data.rollAxis;
  126. var pitchAxis = data.pitchAxis;
  127. var el = this.el;
  128. var gamepad = this.getGamepad();
  129. // If data has changed or FPS is too low
  130. // we reset the velocity
  131. if (dt > MAX_DELTA) {
  132. velocity[rollAxis] = 0;
  133. velocity[pitchAxis] = 0;
  134. return;
  135. }
  136. velocity[rollAxis] -= velocity[rollAxis] * easing * dt / 1000;
  137. velocity[pitchAxis] -= velocity[pitchAxis] * easing * dt / 1000;
  138. var position = el.getAttribute('position');
  139. if (data.enabled && data.movementEnabled && gamepad) {
  140. var dpad = this.getDpad(),
  141. inputX = dpad.x || this.getJoystick(0).x,
  142. inputY = dpad.y || this.getJoystick(0).y;
  143. if (Math.abs(inputX) > JOYSTICK_EPS) {
  144. velocity[pitchAxis] += inputX * acceleration * dt / 1000;
  145. }
  146. if (Math.abs(inputY) > JOYSTICK_EPS) {
  147. velocity[rollAxis] += inputY * acceleration * dt / 1000;
  148. }
  149. }
  150. var movementVector = this.getMovementVector(dt);
  151. el.object3D.translateX(movementVector.x);
  152. el.object3D.translateY(movementVector.y);
  153. el.object3D.translateZ(movementVector.z);
  154. el.setAttribute('position', {
  155. x: position.x + movementVector.x,
  156. y: position.y + movementVector.y,
  157. z: position.z + movementVector.z
  158. });
  159. },
  160. getMovementVector: function (dt) {
  161. if (this._getMovementVector) {
  162. return this._getMovementVector(dt);
  163. }
  164. var euler = new THREE.Euler(0, 0, 0, 'YXZ'),
  165. rotation = new THREE.Vector3();
  166. this._getMovementVector = function (dt) {
  167. rotation.copy(this.el.getAttribute('rotation'));
  168. this.direction.copy(this.velocity);
  169. this.direction.multiplyScalar(dt / 1000);
  170. if (!rotation) { return this.direction; }
  171. if (!this.data.flyEnabled) { rotation.x = 0; }
  172. euler.set(
  173. THREE.Math.degToRad(rotation.x),
  174. THREE.Math.degToRad(rotation.y),
  175. 0
  176. );
  177. this.direction.applyEuler(euler);
  178. return this.direction;
  179. };
  180. return this._getMovementVector(dt);
  181. },
  182. /*******************************************************************
  183. * Rotation
  184. */
  185. updateRotation: function () {
  186. if (this._updateRotation) {
  187. return this._updateRotation();
  188. }
  189. var initialRotation = new THREE.Vector3(),
  190. prevInitialRotation = new THREE.Vector3(),
  191. prevFinalRotation = new THREE.Vector3();
  192. var tCurrent,
  193. tLastLocalActivity = 0,
  194. tLastExternalActivity = 0;
  195. var ROTATION_EPS = 0.0001,
  196. DEBOUNCE = 500;
  197. this._updateRotation = function () {
  198. if (!this.data.lookEnabled || !this.getGamepad()) {
  199. return;
  200. }
  201. tCurrent = Date.now();
  202. initialRotation.copy(this.el.getAttribute('rotation') || initialRotation);
  203. // If initial rotation for this frame is different from last frame, and
  204. // doesn't match last gamepad state, assume an external component is
  205. // active on this element.
  206. if (initialRotation.distanceToSquared(prevInitialRotation) > ROTATION_EPS
  207. && initialRotation.distanceToSquared(prevFinalRotation) > ROTATION_EPS) {
  208. prevInitialRotation.copy(initialRotation);
  209. tLastExternalActivity = tCurrent;
  210. return;
  211. }
  212. prevInitialRotation.copy(initialRotation);
  213. // If external controls have been active in last 500ms, wait.
  214. if (tCurrent - tLastExternalActivity < DEBOUNCE) {
  215. return;
  216. }
  217. var lookVector = this.getJoystick(1);
  218. if (Math.abs(lookVector.x) <= JOYSTICK_EPS) lookVector.x = 0;
  219. if (Math.abs(lookVector.y) <= JOYSTICK_EPS) lookVector.y = 0;
  220. if (this.data.invertAxisY) lookVector.y = -lookVector.y;
  221. // If external controls have been active more recently than gamepad,
  222. // and gamepad hasn't moved, don't overwrite the existing rotation.
  223. if (tLastExternalActivity > tLastLocalActivity && !lookVector.lengthSq()) {
  224. return;
  225. }
  226. lookVector.multiplyScalar(this.data.sensitivity);
  227. this.yaw.rotation.y -= lookVector.x;
  228. this.pitch.rotation.x -= lookVector.y;
  229. this.pitch.rotation.x = Math.max(-PI_2, Math.min(PI_2, this.pitch.rotation.x));
  230. this.el.setAttribute('rotation', {
  231. x: THREE.Math.radToDeg(this.pitch.rotation.x),
  232. y: THREE.Math.radToDeg(this.yaw.rotation.y),
  233. z: 0
  234. });
  235. prevFinalRotation.copy(this.el.getAttribute('rotation'));
  236. tLastLocalActivity = tCurrent;
  237. };
  238. return this._updateRotation();
  239. },
  240. /*******************************************************************
  241. * Button events
  242. */
  243. updateButtonState: function () {
  244. var gamepad = this.getGamepad();
  245. if (this.data.enabled && gamepad) {
  246. // Fire DOM events for button state changes.
  247. for (var i = 0; i < gamepad.buttons.length; i++) {
  248. if (gamepad.buttons[i].pressed && !this.buttons[i]) {
  249. this.emit(new GamepadButtonEvent('gamepadbuttondown', i, gamepad.buttons[i]));
  250. } else if (!gamepad.buttons[i].pressed && this.buttons[i]) {
  251. this.emit(new GamepadButtonEvent('gamepadbuttonup', i, gamepad.buttons[i]));
  252. }
  253. this.buttons[i] = gamepad.buttons[i].pressed;
  254. }
  255. } else if (Object.keys(this.buttons)) {
  256. // Reset state if controls are disabled or controller is lost.
  257. this.buttons = {};
  258. }
  259. },
  260. emit: function (event) {
  261. // Emit original event.
  262. this.el.emit(event.type, event);
  263. // Emit convenience event, identifying button index.
  264. this.el.emit(
  265. event.type + ':' + event.index,
  266. new GamepadButtonEvent(event.type, event.index, event)
  267. );
  268. },
  269. /*******************************************************************
  270. * Gamepad state
  271. */
  272. /**
  273. * Returns the Gamepad instance attached to the component. If connected,
  274. * a proxy-controls component may provide access to Gamepad input from a
  275. * remote device.
  276. *
  277. * @return {Gamepad}
  278. */
  279. getGamepad: function () {
  280. var localGamepad = navigator.getGamepads
  281. && navigator.getGamepads()[this.data.controller],
  282. proxyControls = this.el.sceneEl.components['proxy-controls'],
  283. proxyGamepad = proxyControls && proxyControls.isConnected()
  284. && proxyControls.getGamepad(this.data.controller);
  285. return proxyGamepad || localGamepad;
  286. },
  287. /**
  288. * Returns the state of the given button.
  289. * @param {number} index The button (0-N) for which to find state.
  290. * @return {GamepadButton}
  291. */
  292. getButton: function (index) {
  293. return this.getGamepad().buttons[index];
  294. },
  295. /**
  296. * Returns state of the given axis. Axes are labelled 0-N, where 0-1 will
  297. * represent X/Y on the first joystick, and 2-3 X/Y on the second.
  298. * @param {number} index The axis (0-N) for which to find state.
  299. * @return {number} On the interval [-1,1].
  300. */
  301. getAxis: function (index) {
  302. return this.getGamepad().axes[index];
  303. },
  304. /**
  305. * Returns the state of the given joystick (0 or 1) as a THREE.Vector2.
  306. * @param {number} id The joystick (0, 1) for which to find state.
  307. * @return {THREE.Vector2}
  308. */
  309. getJoystick: function (index) {
  310. var gamepad = this.getGamepad();
  311. switch (index) {
  312. case 0: return new THREE.Vector2(gamepad.axes[0], gamepad.axes[1]);
  313. case 1: return new THREE.Vector2(gamepad.axes[2], gamepad.axes[3]);
  314. default: throw new Error('Unexpected joystick index "%d".', index);
  315. }
  316. },
  317. /**
  318. * Returns the state of the dpad as a THREE.Vector2.
  319. * @return {THREE.Vector2}
  320. */
  321. getDpad: function () {
  322. var gamepad = this.getGamepad();
  323. if (!gamepad.buttons[GamepadButton.DPAD_RIGHT]) {
  324. return new THREE.Vector2();
  325. }
  326. return new THREE.Vector2(
  327. (gamepad.buttons[GamepadButton.DPAD_RIGHT].pressed ? 1 : 0)
  328. + (gamepad.buttons[GamepadButton.DPAD_LEFT].pressed ? -1 : 0),
  329. (gamepad.buttons[GamepadButton.DPAD_UP].pressed ? -1 : 0)
  330. + (gamepad.buttons[GamepadButton.DPAD_DOWN].pressed ? 1 : 0)
  331. );
  332. },
  333. /**
  334. * Returns true if the gamepad is currently connected to the system.
  335. * @return {boolean}
  336. */
  337. isConnected: function () {
  338. var gamepad = this.getGamepad();
  339. return !!(gamepad && gamepad.connected);
  340. },
  341. /**
  342. * Returns a string containing some information about the controller. Result
  343. * may vary across browsers, for a given controller.
  344. * @return {string}
  345. */
  346. getID: function () {
  347. return this.getGamepad().id;
  348. }
  349. };
  350. /***/ },
  351. /* 2 */
  352. /***/ function(module, exports) {
  353. module.exports = Object.assign(function GamepadButton () {}, {
  354. FACE_1: 0,
  355. FACE_2: 1,
  356. FACE_3: 2,
  357. FACE_4: 3,
  358. L_SHOULDER_1: 4,
  359. R_SHOULDER_1: 5,
  360. L_SHOULDER_2: 6,
  361. R_SHOULDER_2: 7,
  362. SELECT: 8,
  363. START: 9,
  364. DPAD_UP: 12,
  365. DPAD_DOWN: 13,
  366. DPAD_LEFT: 14,
  367. DPAD_RIGHT: 15,
  368. VENDOR: 16,
  369. });
  370. /***/ },
  371. /* 3 */
  372. /***/ function(module, exports) {
  373. function GamepadButtonEvent (type, index, details) {
  374. this.type = type;
  375. this.index = index;
  376. this.pressed = details.pressed;
  377. this.value = details.value;
  378. }
  379. module.exports = GamepadButtonEvent;
  380. /***/ }
  381. /******/ ]);