├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── browser.js ├── dist ├── aframe-gamepad-controls.js └── aframe-gamepad-controls.min.js ├── examples ├── basic │ └── index.html ├── index.html └── scene │ ├── assets │ ├── 321103__nsstudios__blip1.wav │ ├── peach-gradient-1.jpg │ ├── tex │ │ └── shadow-circle.png │ ├── tree1.dae │ └── tree2.dae │ └── index.html ├── gamepad-controls.js ├── lib ├── GamepadButton.js └── GamepadButtonEvent.js ├── package.json └── tests ├── .jshintrc ├── __init.test.js ├── gamepad-controls.test.js ├── helpers.js └── karma.conf.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | .gh-pages 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "THREE": false, 4 | "window": false, 5 | "document": false, 6 | "require": false, 7 | "console": false, 8 | "module": false 9 | }, 10 | "bitwise": false, 11 | "browser": true, 12 | "eqeqeq": true, 13 | "esnext": true, 14 | "expr": true, 15 | "forin": true, 16 | "immed": true, 17 | "latedef": "nofunc", 18 | "laxbreak": true, 19 | "maxlen": 100, 20 | "newcap": true, 21 | "noarg": true, 22 | "noempty": true, 23 | "noyield": true, 24 | "quotmark": "single", 25 | "smarttabs": false, 26 | "trailing": true, 27 | "undef": true, 28 | "unused": true, 29 | "white": false 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2015 Don McCurdy 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A-Frame `gamepad-controls` 2 | 3 | > **NOTICE**: This project is no longer maintained. I recommend using `movement-controls` from [A-Frame Extras](https://github.com/donmccurdy/aframe-extras) instead. 4 | 5 | Gamepad controls for A-Frame. 6 | 7 | Demo: https://donmccurdy.github.io/aframe-gamepad-controls/ 8 | 9 | ## Overview 10 | 11 | Supports one or more gamepads, attached to an A-Frame scene. When used on a mobile device, `gamepad-controls` can also receive input from a gamepad connected to a host machine, using [ProxyControls.js](https://proxy-controls.donmccurdy.com). 12 | 13 | This component uses the HTML5 [Gamepad API](https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API). The underlying API supports Firefox, Chrome, Edge, and Opera ([as of 01/2016](http://caniuse.com/#search=gamepad)). Safari and Internet Explorer do not currently support gamepads. 14 | 15 | ## Usage (script) 16 | 17 | ```html 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ``` 34 | 35 | ## Usage (NPM) 36 | 37 | Install NPM module. 38 | 39 | ``` 40 | $ npm install aframe-gamepad-controls 41 | ``` 42 | 43 | Register `gamepad-controls` component. 44 | 45 | ```javascript 46 | var AFRAME = require('aframe'); 47 | var GamepadControls = require('aframe-gamepad-controls'); 48 | AFRAME.registerComponent('gamepad-controls', GamepadControls); 49 | ``` 50 | 51 | Add markup. 52 | 53 | ```html 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | ``` 64 | 65 | ## Development 66 | 67 | To edit the component or play with examples, [download the project](https://github.com/donmccurdy/aframe-gamepad-controls/archive/master.zip) and run: 68 | 69 | ```shell 70 | npm install 71 | npm run dev 72 | ``` 73 | 74 | The demo will run at [http://localhost:8000/](http://localhost:8000/). 75 | 76 | ## Mobile / Cardboard + Gamepad 77 | 78 | In Chrome on Android, USB gamepads can be connected with an OTG adapter. For a Nexus 5X, I use [this](http://www.amazon.com/gp/product/B00XHOGEZG). I'm not aware of a way to connect a gamepad in iOS, but definitely let me know if there's something I'm missing. 79 | 80 | The `gamepad-controls` component can also receive remote events with WebRTC, if a `proxy-controls` element is attached to the scene. [More details about ProxyControls.js](https://proxy-controls.donmccurdy.com). 81 | 82 | Example: 83 | 84 | ```html 85 | 86 | 88 | 89 | 90 | ``` 91 | 92 | ## Gear VR 93 | 94 | A-Frame supports the Gear VR controller with `gear-vr-controller`, but for Gear VR's without a controller, this component can be used to handle it's trackpad events. 95 | 96 | For a cursor-based setup, `downEvents` must be set to `gamepadbuttondown`. For example: 97 | 98 | ```html 99 | 100 | ``` 101 | 102 | ## Button Events 103 | 104 | When buttons are pressed on the gamepad, a [GamepadButtonEvent](https://github.com/donmccurdy/aframe-gamepad-controls/blob/master/lib/GamepadButtonEvent.js) is emitted on the element. Components and entities may listen for these events and modify behavior as needed. Example: 105 | 106 | ```javascript 107 | el.addEventListener('gamepadbuttondown', function (e) { 108 | console.log('Button "%d" has been pressed.', e.index); 109 | }); 110 | ``` 111 | 112 | **GamepadButtonEvent:** 113 | 114 | Property | Type | Description 115 | ---------|---------|-------------- 116 | type | string | Either `gamepadbuttondown` or `gamepadbuttonup`. 117 | index | int | Index of the button affected, 0..N. 118 | pressed | boolean | Whether or not the button is currently pressed. 119 | value | float | Distance the button was pressed, if applicable. Value will be 0 or 1 in most cases, but may return a float on the interval [0..1] for trigger-like buttons. 120 | 121 | **Markup-only Binding:** 122 | 123 | For convenience, additional events are fired including the button index, providing a way to bind events to specific buttons using only markup. To play `pew-pew.wav` when `Button 7` is pressed (right trigger on an Xbox controller), you might do this: 124 | 125 | ```html 126 | 129 | 130 | ``` 131 | 132 | Finally, your code may call the `gamepad-controls` component directly to request the state of a button, as a [GamepadButton](https://developer.mozilla.org/en-US/docs/Web/API/GamepadButton) instance: 133 | 134 | ```javascript 135 | el.components['gamepad-controls'].getButton(index); 136 | // Returns a GamepadButton instance. 137 | ``` 138 | 139 | ## Options 140 | 141 | Property | Default | Description 142 | ------------------|---------|------------- 143 | controller | 0 | Which controller (0..3) the object should be attached to. 144 | enabled | true | Enables all events on this controller. 145 | movementEnabled | true | Enables movement via the left thumbstick. 146 | lookEnabled | true | `true`, or `false`. Enables view rotation via the right thumbstick. 147 | flyEnabled | false | Whether or not movement is restricted to the entity’s initial plane. 148 | invertAxisY | false | Invert Y axis of view rotation thumbstick. 149 | debug | false | When true, shows debugging info in the console. 150 | -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | 2 | // Browser distrubution of the A-Frame component. 3 | (function (AFRAME) { 4 | if (!AFRAME) { 5 | console.error('Component attempted to register before AFRAME was available.'); 6 | return; 7 | } 8 | 9 | (AFRAME.aframeCore || AFRAME).registerComponent('gamepad-controls', require('./gamepad-controls')); 10 | 11 | }(window.AFRAME)); 12 | -------------------------------------------------------------------------------- /dist/aframe-gamepad-controls.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) 10 | /******/ return installedModules[moduleId].exports; 11 | 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ exports: {}, 15 | /******/ id: moduleId, 16 | /******/ loaded: false 17 | /******/ }; 18 | 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | 22 | /******/ // Flag the module as loaded 23 | /******/ module.loaded = true; 24 | 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | 29 | 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | 36 | /******/ // __webpack_public_path__ 37 | /******/ __webpack_require__.p = ""; 38 | 39 | /******/ // Load entry module and return exports 40 | /******/ return __webpack_require__(0); 41 | /******/ }) 42 | /************************************************************************/ 43 | /******/ ([ 44 | /* 0 */ 45 | /***/ function(module, exports, __webpack_require__) { 46 | 47 | 48 | // Browser distrubution of the A-Frame component. 49 | (function (AFRAME) { 50 | if (!AFRAME) { 51 | console.error('Component attempted to register before AFRAME was available.'); 52 | return; 53 | } 54 | 55 | (AFRAME.aframeCore || AFRAME).registerComponent('gamepad-controls', __webpack_require__(1)); 56 | 57 | }(window.AFRAME)); 58 | 59 | 60 | /***/ }, 61 | /* 1 */ 62 | /***/ function(module, exports, __webpack_require__) { 63 | 64 | /** 65 | * Gamepad controls for A-Frame. 66 | * 67 | * For more information about the Gamepad API, see: 68 | * https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API 69 | */ 70 | 71 | var GamepadButton = __webpack_require__(2), 72 | GamepadButtonEvent = __webpack_require__(3); 73 | 74 | var MAX_DELTA = 200, // ms 75 | PI_2 = Math.PI / 2; 76 | 77 | var JOYSTICK_EPS = 0.2; 78 | 79 | module.exports = { 80 | 81 | /******************************************************************* 82 | * Statics 83 | */ 84 | 85 | GamepadButton: GamepadButton, 86 | 87 | /******************************************************************* 88 | * Schema 89 | */ 90 | 91 | schema: { 92 | // Controller 0-3 93 | controller: { default: 0, oneOf: [0, 1, 2, 3] }, 94 | 95 | // Enable/disable features 96 | enabled: { default: true }, 97 | movementEnabled: { default: true }, 98 | lookEnabled: { default: true }, 99 | flyEnabled: { default: false }, 100 | invertAxisY: { default: false }, 101 | 102 | // Constants 103 | easing: { default: 20 }, 104 | acceleration: { default: 65 }, 105 | sensitivity: { default: 0.04 }, 106 | 107 | // Control axes 108 | pitchAxis: { default: 'x', oneOf: [ 'x', 'y', 'z' ] }, 109 | yawAxis: { default: 'y', oneOf: [ 'x', 'y', 'z' ] }, 110 | rollAxis: { default: 'z', oneOf: [ 'x', 'y', 'z' ] }, 111 | 112 | // Debugging 113 | debug: { default: false } 114 | }, 115 | 116 | /******************************************************************* 117 | * Core 118 | */ 119 | 120 | /** 121 | * Called once when component is attached. Generally for initial setup. 122 | */ 123 | init: function () { 124 | // Movement 125 | this.velocity = new THREE.Vector3(0, 0, 0); 126 | this.direction = new THREE.Vector3(0, 0, 0); 127 | 128 | // Rotation 129 | this.pitch = new THREE.Object3D(); 130 | this.yaw = new THREE.Object3D(); 131 | this.yaw.position.y = 10; 132 | this.yaw.add(this.pitch); 133 | 134 | // Button state 135 | this.buttons = {}; 136 | 137 | if (!this.getGamepad()) { 138 | console.warn( 139 | 'Gamepad #%d not found. Connect controller and press any button to continue.', 140 | this.data.controller 141 | ); 142 | } 143 | }, 144 | 145 | /** 146 | * Called on each iteration of main render loop. 147 | */ 148 | tick: function (t, dt) { 149 | this.updateRotation(dt); 150 | this.updatePosition(dt); 151 | this.updateButtonState(); 152 | }, 153 | 154 | /******************************************************************* 155 | * Movement 156 | */ 157 | 158 | updatePosition: function (dt) { 159 | var data = this.data; 160 | var acceleration = data.acceleration; 161 | var easing = data.easing; 162 | var velocity = this.velocity; 163 | var rollAxis = data.rollAxis; 164 | var pitchAxis = data.pitchAxis; 165 | var el = this.el; 166 | var gamepad = this.getGamepad(); 167 | 168 | // If data has changed or FPS is too low 169 | // we reset the velocity 170 | if (dt > MAX_DELTA) { 171 | velocity[rollAxis] = 0; 172 | velocity[pitchAxis] = 0; 173 | return; 174 | } 175 | 176 | velocity[rollAxis] -= velocity[rollAxis] * easing * dt / 1000; 177 | velocity[pitchAxis] -= velocity[pitchAxis] * easing * dt / 1000; 178 | 179 | var position = el.getAttribute('position'); 180 | 181 | if (data.enabled && data.movementEnabled && gamepad) { 182 | var dpad = this.getDpad(), 183 | inputX = dpad.x || this.getJoystick(0).x, 184 | inputY = dpad.y || this.getJoystick(0).y; 185 | if (Math.abs(inputX) > JOYSTICK_EPS) { 186 | velocity[pitchAxis] += inputX * acceleration * dt / 1000; 187 | } 188 | if (Math.abs(inputY) > JOYSTICK_EPS) { 189 | velocity[rollAxis] += inputY * acceleration * dt / 1000; 190 | } 191 | } 192 | 193 | var movementVector = this.getMovementVector(dt); 194 | 195 | el.object3D.translateX(movementVector.x); 196 | el.object3D.translateY(movementVector.y); 197 | el.object3D.translateZ(movementVector.z); 198 | 199 | el.setAttribute('position', { 200 | x: position.x + movementVector.x, 201 | y: position.y + movementVector.y, 202 | z: position.z + movementVector.z 203 | }); 204 | }, 205 | 206 | getMovementVector: function (dt) { 207 | if (this._getMovementVector) { 208 | return this._getMovementVector(dt); 209 | } 210 | 211 | var euler = new THREE.Euler(0, 0, 0, 'YXZ'), 212 | rotation = new THREE.Vector3(); 213 | 214 | this._getMovementVector = function (dt) { 215 | rotation.copy(this.el.getAttribute('rotation')); 216 | this.direction.copy(this.velocity); 217 | this.direction.multiplyScalar(dt / 1000); 218 | if (!rotation) { return this.direction; } 219 | if (!this.data.flyEnabled) { rotation.x = 0; } 220 | euler.set( 221 | THREE.Math.degToRad(rotation.x), 222 | THREE.Math.degToRad(rotation.y), 223 | 0 224 | ); 225 | this.direction.applyEuler(euler); 226 | return this.direction; 227 | }; 228 | 229 | return this._getMovementVector(dt); 230 | }, 231 | 232 | /******************************************************************* 233 | * Rotation 234 | */ 235 | 236 | updateRotation: function () { 237 | if (this._updateRotation) { 238 | return this._updateRotation(); 239 | } 240 | 241 | var initialRotation = new THREE.Vector3(), 242 | prevInitialRotation = new THREE.Vector3(), 243 | prevFinalRotation = new THREE.Vector3(); 244 | 245 | var tCurrent, 246 | tLastLocalActivity = 0, 247 | tLastExternalActivity = 0; 248 | 249 | var ROTATION_EPS = 0.0001, 250 | DEBOUNCE = 500; 251 | 252 | this._updateRotation = function () { 253 | if (!this.data.lookEnabled || !this.getGamepad()) { 254 | return; 255 | } 256 | 257 | tCurrent = Date.now(); 258 | initialRotation.copy(this.el.getAttribute('rotation') || initialRotation); 259 | 260 | // If initial rotation for this frame is different from last frame, and 261 | // doesn't match last gamepad state, assume an external component is 262 | // active on this element. 263 | if (initialRotation.distanceToSquared(prevInitialRotation) > ROTATION_EPS 264 | && initialRotation.distanceToSquared(prevFinalRotation) > ROTATION_EPS) { 265 | prevInitialRotation.copy(initialRotation); 266 | tLastExternalActivity = tCurrent; 267 | return; 268 | } 269 | 270 | prevInitialRotation.copy(initialRotation); 271 | 272 | // If external controls have been active in last 500ms, wait. 273 | if (tCurrent - tLastExternalActivity < DEBOUNCE) { 274 | return; 275 | } 276 | 277 | var lookVector = this.getJoystick(1); 278 | if (Math.abs(lookVector.x) <= JOYSTICK_EPS) lookVector.x = 0; 279 | if (Math.abs(lookVector.y) <= JOYSTICK_EPS) lookVector.y = 0; 280 | if (this.data.invertAxisY) lookVector.y = -lookVector.y; 281 | 282 | // If external controls have been active more recently than gamepad, 283 | // and gamepad hasn't moved, don't overwrite the existing rotation. 284 | if (tLastExternalActivity > tLastLocalActivity && !lookVector.lengthSq()) { 285 | return; 286 | } 287 | 288 | lookVector.multiplyScalar(this.data.sensitivity); 289 | this.yaw.rotation.y -= lookVector.x; 290 | this.pitch.rotation.x -= lookVector.y; 291 | this.pitch.rotation.x = Math.max(-PI_2, Math.min(PI_2, this.pitch.rotation.x)); 292 | 293 | this.el.setAttribute('rotation', { 294 | x: THREE.Math.radToDeg(this.pitch.rotation.x), 295 | y: THREE.Math.radToDeg(this.yaw.rotation.y), 296 | z: 0 297 | }); 298 | prevFinalRotation.copy(this.el.getAttribute('rotation')); 299 | tLastLocalActivity = tCurrent; 300 | }; 301 | 302 | return this._updateRotation(); 303 | }, 304 | 305 | /******************************************************************* 306 | * Button events 307 | */ 308 | 309 | updateButtonState: function () { 310 | var gamepad = this.getGamepad(); 311 | if (this.data.enabled && gamepad) { 312 | 313 | // Fire DOM events for button state changes. 314 | for (var i = 0; i < gamepad.buttons.length; i++) { 315 | if (gamepad.buttons[i].pressed && !this.buttons[i]) { 316 | this.emit(new GamepadButtonEvent('gamepadbuttondown', i, gamepad.buttons[i])); 317 | } else if (!gamepad.buttons[i].pressed && this.buttons[i]) { 318 | this.emit(new GamepadButtonEvent('gamepadbuttonup', i, gamepad.buttons[i])); 319 | } 320 | this.buttons[i] = gamepad.buttons[i].pressed; 321 | } 322 | 323 | } else if (Object.keys(this.buttons)) { 324 | // Reset state if controls are disabled or controller is lost. 325 | this.buttons = {}; 326 | } 327 | }, 328 | 329 | emit: function (event) { 330 | // Emit original event. 331 | this.el.emit(event.type, event); 332 | 333 | // Emit convenience event, identifying button index. 334 | this.el.emit( 335 | event.type + ':' + event.index, 336 | new GamepadButtonEvent(event.type, event.index, event) 337 | ); 338 | }, 339 | 340 | /******************************************************************* 341 | * Gamepad state 342 | */ 343 | 344 | /** 345 | * Returns the Gamepad instance attached to the component. If connected, 346 | * a proxy-controls component may provide access to Gamepad input from a 347 | * remote device. 348 | * 349 | * @return {Gamepad} 350 | */ 351 | getGamepad: function () { 352 | var localGamepad = navigator.getGamepads 353 | && navigator.getGamepads()[this.data.controller], 354 | proxyControls = this.el.sceneEl.components['proxy-controls'], 355 | proxyGamepad = proxyControls && proxyControls.isConnected() 356 | && proxyControls.getGamepad(this.data.controller); 357 | return proxyGamepad || localGamepad; 358 | }, 359 | 360 | /** 361 | * Returns the state of the given button. 362 | * @param {number} index The button (0-N) for which to find state. 363 | * @return {GamepadButton} 364 | */ 365 | getButton: function (index) { 366 | return this.getGamepad().buttons[index]; 367 | }, 368 | 369 | /** 370 | * Returns state of the given axis. Axes are labelled 0-N, where 0-1 will 371 | * represent X/Y on the first joystick, and 2-3 X/Y on the second. 372 | * @param {number} index The axis (0-N) for which to find state. 373 | * @return {number} On the interval [-1,1]. 374 | */ 375 | getAxis: function (index) { 376 | return this.getGamepad().axes[index]; 377 | }, 378 | 379 | /** 380 | * Returns the state of the given joystick (0 or 1) as a THREE.Vector2. 381 | * @param {number} id The joystick (0, 1) for which to find state. 382 | * @return {THREE.Vector2} 383 | */ 384 | getJoystick: function (index) { 385 | var gamepad = this.getGamepad(); 386 | switch (index) { 387 | case 0: return new THREE.Vector2(gamepad.axes[0], gamepad.axes[1]); 388 | case 1: return new THREE.Vector2(gamepad.axes[2], gamepad.axes[3]); 389 | default: throw new Error('Unexpected joystick index "%d".', index); 390 | } 391 | }, 392 | 393 | /** 394 | * Returns the state of the dpad as a THREE.Vector2. 395 | * @return {THREE.Vector2} 396 | */ 397 | getDpad: function () { 398 | var gamepad = this.getGamepad(); 399 | if (!gamepad.buttons[GamepadButton.DPAD_RIGHT]) { 400 | return new THREE.Vector2(); 401 | } 402 | return new THREE.Vector2( 403 | (gamepad.buttons[GamepadButton.DPAD_RIGHT].pressed ? 1 : 0) 404 | + (gamepad.buttons[GamepadButton.DPAD_LEFT].pressed ? -1 : 0), 405 | (gamepad.buttons[GamepadButton.DPAD_UP].pressed ? -1 : 0) 406 | + (gamepad.buttons[GamepadButton.DPAD_DOWN].pressed ? 1 : 0) 407 | ); 408 | }, 409 | 410 | /** 411 | * Returns true if the gamepad is currently connected to the system. 412 | * @return {boolean} 413 | */ 414 | isConnected: function () { 415 | var gamepad = this.getGamepad(); 416 | return !!(gamepad && gamepad.connected); 417 | }, 418 | 419 | /** 420 | * Returns a string containing some information about the controller. Result 421 | * may vary across browsers, for a given controller. 422 | * @return {string} 423 | */ 424 | getID: function () { 425 | return this.getGamepad().id; 426 | } 427 | }; 428 | 429 | 430 | /***/ }, 431 | /* 2 */ 432 | /***/ function(module, exports) { 433 | 434 | module.exports = Object.assign(function GamepadButton () {}, { 435 | FACE_1: 0, 436 | FACE_2: 1, 437 | FACE_3: 2, 438 | FACE_4: 3, 439 | 440 | L_SHOULDER_1: 4, 441 | R_SHOULDER_1: 5, 442 | L_SHOULDER_2: 6, 443 | R_SHOULDER_2: 7, 444 | 445 | SELECT: 8, 446 | START: 9, 447 | 448 | DPAD_UP: 12, 449 | DPAD_DOWN: 13, 450 | DPAD_LEFT: 14, 451 | DPAD_RIGHT: 15, 452 | 453 | VENDOR: 16, 454 | }); 455 | 456 | 457 | /***/ }, 458 | /* 3 */ 459 | /***/ function(module, exports) { 460 | 461 | function GamepadButtonEvent (type, index, details) { 462 | this.type = type; 463 | this.index = index; 464 | this.pressed = details.pressed; 465 | this.value = details.value; 466 | } 467 | 468 | module.exports = GamepadButtonEvent; 469 | 470 | 471 | /***/ } 472 | /******/ ]); -------------------------------------------------------------------------------- /dist/aframe-gamepad-controls.min.js: -------------------------------------------------------------------------------- 1 | !function(t){function e(n){if(i[n])return i[n].exports;var o=i[n]={exports:{},id:n,loaded:!1};return t[n].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var i={};return e.m=t,e.c=i,e.p="",e(0)}([function(t,e,i){!function(t){return t?void(t.aframeCore||t).registerComponent("gamepad-controls",i(1)):void console.error("Component attempted to register before AFRAME was available.")}(window.AFRAME)},function(t,e,i){var n=i(2),o=i(3),a=200,s=Math.PI/2,r=.2;t.exports={GamepadButton:n,schema:{controller:{"default":0,oneOf:[0,1,2,3]},enabled:{"default":!0},movementEnabled:{"default":!0},lookEnabled:{"default":!0},flyEnabled:{"default":!1},invertAxisY:{"default":!1},easing:{"default":20},acceleration:{"default":65},sensitivity:{"default":.04},pitchAxis:{"default":"x",oneOf:["x","y","z"]},yawAxis:{"default":"y",oneOf:["x","y","z"]},rollAxis:{"default":"z",oneOf:["x","y","z"]},debug:{"default":!1}},init:function(){this.velocity=new THREE.Vector3(0,0,0),this.direction=new THREE.Vector3(0,0,0),this.pitch=new THREE.Object3D,this.yaw=new THREE.Object3D,this.yaw.position.y=10,this.yaw.add(this.pitch),this.buttons={},this.getGamepad()||console.warn("Gamepad #%d not found. Connect controller and press any button to continue.",this.data.controller)},tick:function(t,e){this.updateRotation(e),this.updatePosition(e),this.updateButtonState()},updatePosition:function(t){var e=this.data,i=e.acceleration,n=e.easing,o=this.velocity,s=e.rollAxis,d=e.pitchAxis,u=this.el,c=this.getGamepad();if(t>a)return o[s]=0,void(o[d]=0);o[s]-=o[s]*n*t/1e3,o[d]-=o[d]*n*t/1e3;var h=u.getAttribute("position");if(e.enabled&&e.movementEnabled&&c){var l=this.getDpad(),p=l.x||this.getJoystick(0).x,E=l.y||this.getJoystick(0).y;Math.abs(p)>r&&(o[d]+=p*i*t/1e3),Math.abs(E)>r&&(o[s]+=E*i*t/1e3)}var f=this.getMovementVector(t);u.object3D.translateX(f.x),u.object3D.translateY(f.y),u.object3D.translateZ(f.z),u.setAttribute("position",{x:h.x+f.x,y:h.y+f.y,z:h.z+f.z})},getMovementVector:function(t){if(this._getMovementVector)return this._getMovementVector(t);var e=new THREE.Euler(0,0,0,"YXZ"),i=new THREE.Vector3;return this._getMovementVector=function(t){return i.copy(this.el.getAttribute("rotation")),this.direction.copy(this.velocity),this.direction.multiplyScalar(t/1e3),i?(this.data.flyEnabled||(i.x=0),e.set(THREE.Math.degToRad(i.x),THREE.Math.degToRad(i.y),0),this.direction.applyEuler(e),this.direction):this.direction},this._getMovementVector(t)},updateRotation:function(){if(this._updateRotation)return this._updateRotation();var t,e=new THREE.Vector3,i=new THREE.Vector3,n=new THREE.Vector3,o=0,a=0,d=1e-4,u=500;return this._updateRotation=function(){if(this.data.lookEnabled&&this.getGamepad()){if(t=Date.now(),e.copy(this.el.getAttribute("rotation")||e),e.distanceToSquared(i)>d&&e.distanceToSquared(n)>d)return i.copy(e),void(a=t);if(i.copy(e),!(u>t-a)){var c=this.getJoystick(1);Math.abs(c.x)<=r&&(c.x=0),Math.abs(c.y)<=r&&(c.y=0),this.data.invertAxisY&&(c.y=-c.y),a>o&&!c.lengthSq()||(c.multiplyScalar(this.data.sensitivity),this.yaw.rotation.y-=c.x,this.pitch.rotation.x-=c.y,this.pitch.rotation.x=Math.max(-s,Math.min(s,this.pitch.rotation.x)),this.el.setAttribute("rotation",{x:THREE.Math.radToDeg(this.pitch.rotation.x),y:THREE.Math.radToDeg(this.yaw.rotation.y),z:0}),n.copy(this.el.getAttribute("rotation")),o=t)}}},this._updateRotation()},updateButtonState:function(){var t=this.getGamepad();if(this.data.enabled&&t)for(var e=0;e 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A-Frame Gamepad Controls • Examples 6 | 7 | 24 | 25 | 26 |

A-Frame Gamepad Controls

27 |

Examples

28 | 29 | Basic 30 | Scene 31 | 32 |
33 |
34 | Fork me on GitHub 35 |
36 |
37 | 38 | -------------------------------------------------------------------------------- /examples/scene/assets/321103__nsstudios__blip1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/aframe-gamepad-controls/379d685f2202bc7e071839ce8be83c176c69695b/examples/scene/assets/321103__nsstudios__blip1.wav -------------------------------------------------------------------------------- /examples/scene/assets/peach-gradient-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/aframe-gamepad-controls/379d685f2202bc7e071839ce8be83c176c69695b/examples/scene/assets/peach-gradient-1.jpg -------------------------------------------------------------------------------- /examples/scene/assets/tex/shadow-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/aframe-gamepad-controls/379d685f2202bc7e071839ce8be83c176c69695b/examples/scene/assets/tex/shadow-circle.png -------------------------------------------------------------------------------- /examples/scene/assets/tree1.dae: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CINEMA4D 15.064 COLLADA Exporter 6 | 7 | 2015-08-28T05:40:04Z 8 | 2015-08-28T05:40:04Z 9 | 10 | Y_UP 11 | 12 | 13 | 14 | tex/shadow-circle.png 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 0.8 0.8 0.8 1 24 | 25 | 26 | 0.2 0.2 0.2 1 27 | 28 | 29 | 0.5 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 0.0627451 0.396078 0.513725 1 41 | 42 | 43 | 0.2 0.2 0.2 1 44 | 45 | 46 | 0.5 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 0.0235294 0.564706 0.607843 1 58 | 59 | 60 | 0.2 0.2 0.2 1 61 | 62 | 63 | 0.5 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 0.164706 0.72549 0.701961 1 75 | 76 | 77 | 0.2 0.2 0.2 1 78 | 79 | 80 | 0.5 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 0.654902 0.47451 0.415686 1 92 | 93 | 94 | 0.2 0.2 0.2 1 95 | 96 | 97 | 0.5 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | ID13 108 | 109 | 110 | 111 | 112 | ID14 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 1 1 1 1 122 | 123 | 124 | 0.88 125 | 126 | 127 | 1 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | -40 -80.5 40 40 -80.5 40 40 -80.5 -40 -40 -80.5 -40 -40 -77.5711 47.0711 40 -77.5711 47.0711 45 -77.5711 45 47.0711 -77.5711 40 47.0711 -77.5711 -40 45 -77.5711 -45 40 -77.5711 -47.0711 -40 -77.5711 -47.0711 -45 -77.5711 -45 -47.0711 -77.5711 -40 -47.0711 -77.5711 40 -45 -77.5711 45 -40 -70.5 50 40 -70.5 50 47.0711 -70.5 47.0711 50 -70.5 40 50 -70.5 -40 47.0711 -70.5 -47.0711 40 -70.5 -50 -40 -70.5 -50 -47.0711 -70.5 -47.0711 -50 -70.5 -40 -50 -70.5 40 -47.0711 -70.5 47.0711 -40 70.5 50 40 70.5 50 47.0711 70.5 47.0711 50 70.5 40 50 70.5 -40 47.0711 70.5 -47.0711 40 70.5 -50 -40 70.5 -50 -47.0711 70.5 -47.0711 -50 70.5 -40 -50 70.5 40 -47.0711 70.5 47.0711 -40 77.5711 47.0711 40 77.5711 47.0711 45 77.5711 45 47.0711 77.5711 40 47.0711 77.5711 -40 45 77.5711 -45 40 77.5711 -47.0711 -40 77.5711 -47.0711 -45 77.5711 -45 -47.0711 77.5711 -40 -47.0711 77.5711 40 -45 77.5711 45 -40 80.5 40 40 80.5 40 40 80.5 -40 -40 80.5 -40 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | -0.0785214 -0.921027 0.381502 0.0785214 -0.921027 0.381502 0 -0.382683 0.92388 0 0 1 0 0.382683 0.92388 -0.0785214 0.921027 0.381502 0.0785214 0.921027 0.381502 0.156558 -0.912487 0.377964 0.357407 -0.357407 0.862856 0.382683 0 0.92388 0.357407 0.357407 0.862856 0.156558 0.912487 0.377964 0.381502 -0.921027 0.0785214 0.377964 -0.912487 0.156558 0.862856 -0.357407 0.357407 0.92388 0 0.382683 0.862856 0.357407 0.357407 0.377964 0.912487 0.156558 0.381502 0.921027 0.0785214 0.381502 -0.921027 -0.0785214 0.92388 -0.382683 -0 1 0 -0 0.92388 0.382683 -0 0.381502 0.921027 -0.0785214 0.377964 -0.912487 -0.156558 0.862856 -0.357407 -0.357407 0.92388 0 -0.382683 0.862856 0.357407 -0.357407 0.377964 0.912487 -0.156558 0.0785214 -0.921027 -0.381502 0.156558 -0.912487 -0.377964 0.357407 -0.357407 -0.862856 0.382683 0 -0.92388 0.357407 0.357407 -0.862856 0.156558 0.912487 -0.377964 0.0785214 0.921027 -0.381502 -0.0785214 -0.921027 -0.381502 0 -0.382683 -0.92388 0 0 -1 0 0.382683 -0.92388 -0.0785214 0.921027 -0.381502 -0.156558 -0.912487 -0.377964 -0.357407 -0.357407 -0.862856 -0.382683 0 -0.92388 -0.357407 0.357407 -0.862856 -0.156558 0.912487 -0.377964 -0.381502 -0.921027 -0.0785214 -0.377964 -0.912487 -0.156558 -0.862856 -0.357407 -0.357407 -0.92388 0 -0.382683 -0.862856 0.357407 -0.357407 -0.377964 0.912487 -0.156558 -0.381502 0.921027 -0.0785214 -0.381502 -0.921027 0.0785214 -0.92388 -0.382683 -0 -1 0 -0 -0.92388 0.382683 -0 -0.381502 0.921027 0.0785214 -0.377964 -0.912487 0.156558 -0.862856 -0.357407 0.357407 -0.92388 0 0.382683 -0.862856 0.357407 0.357407 -0.377964 0.912487 0.156558 -0.156558 -0.912487 0.377964 -0.357407 -0.357407 0.862856 -0.382683 0 0.92388 -0.357407 0.357407 0.862856 -0.156558 0.912487 0.377964 0 1 -0 0 -1 -0 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 0 0 0 0.0455526 0.835876 0.0455526 0.835876 0 0 0.0911051 0.835876 0.0911051 0 0.908895 0.835876 0.908895 0 0.954447 0.835876 0.954447 0 1 0.835876 1 0.876907 0 0.917938 0.0455526 0.917938 0.0911051 0.917938 0.908895 0.917938 0.954447 0.876907 1 0.958969 0 1 0.0455526 1 0.0911051 1 0.908895 1 0.954447 0.958969 1 1 1 1 0 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 194 |

1 1 3 5 1 2 4 0 1 0 0 0 5 2 2 17 2 5 16 2 4 4 2 1 17 3 5 29 3 7 28 3 6 16 3 4 29 4 7 41 4 9 40 4 8 28 4 6 41 6 9 53 6 11 52 5 10 40 5 8 6 7 13 5 1 2 1 1 12 6 8 13 18 8 14 17 8 5 5 8 2 18 9 14 30 9 15 29 9 7 17 9 5 30 10 15 42 10 16 41 10 9 29 10 7 42 11 16 53 6 17 41 6 9 7 12 19 6 13 13 1 12 18 7 14 19 19 14 20 18 14 14 6 14 13 19 15 20 31 15 21 30 15 15 18 15 14 31 16 21 43 16 22 42 16 16 30 16 15 43 18 22 53 18 23 42 17 16 2 19 3 8 19 2 7 12 1 1 12 0 8 20 2 20 20 5 19 20 4 7 20 1 20 21 5 32 21 7 31 21 6 19 21 4 32 22 7 44 22 9 43 22 8 31 22 6 44 23 9 54 23 11 53 18 10 43 18 8 9 24 13 8 19 2 2 19 12 9 25 13 21 25 14 20 25 5 8 25 2 21 26 14 33 26 15 32 26 7 20 26 5 33 27 15 45 27 16 44 27 9 32 27 7 45 28 16 54 23 17 44 23 9 10 29 19 9 30 13 2 29 18 10 31 19 22 31 20 21 31 14 9 31 13 22 32 20 34 32 21 33 32 15 21 32 14 34 33 21 46 33 22 45 33 16 33 33 15 46 35 22 54 35 23 45 34 16 3 36 3 11 36 2 10 29 1 2 29 0 11 37 2 23 37 5 22 37 4 10 37 1 23 38 5 35 38 7 34 38 6 22 38 4 35 39 7 47 39 9 46 39 8 34 39 6 47 40 9 55 40 11 54 35 10 46 35 8 12 41 13 11 36 2 3 36 12 12 42 13 24 42 14 23 42 5 11 42 2 24 43 14 36 43 15 35 43 7 23 43 5 36 44 15 48 44 16 47 44 9 35 44 7 48 45 16 55 40 17 47 40 9 13 46 19 12 47 13 3 46 18 13 48 19 25 48 20 24 48 14 12 48 13 25 49 20 37 49 21 36 49 15 24 49 14 37 50 21 49 50 22 48 50 16 36 50 15 49 52 22 55 52 23 48 51 16 0 53 3 14 53 2 13 46 1 3 46 0 14 54 2 26 54 5 25 54 4 13 54 1 26 55 5 38 55 7 37 55 6 25 55 4 38 56 7 50 56 9 49 56 8 37 56 6 50 57 9 52 57 11 55 52 10 49 52 8 15 58 13 14 53 2 0 53 12 15 59 13 27 59 14 26 59 5 14 59 2 27 60 14 39 60 15 38 60 7 26 60 5 39 61 15 51 61 16 50 61 9 38 61 7 51 62 16 52 57 17 50 57 9 4 0 19 15 63 13 0 0 18 4 64 19 16 64 20 27 64 14 15 64 13 16 65 20 28 65 21 39 65 15 27 65 14 28 66 21 40 66 22 51 66 16 39 66 15 40 5 22 52 5 23 51 67 16 53 68 25 54 68 24 55 68 10 52 68 0 3 69 0 2 69 25 1 69 24 0 69 10

195 |
196 |
197 |
198 | 199 | 200 | 201 | 0 -50 0 0 50 0 10 -50 -0 10 -50 -0 10 50 -0 10 50 -0 8.66025 -50 -5 8.66025 -50 -5 8.66025 50 -5 8.66025 50 -5 5 -50 -8.66025 5 -50 -8.66025 5 50 -8.66025 5 50 -8.66025 6.12323e-16 -50 -10 6.12323e-16 -50 -10 6.12323e-16 50 -10 6.12323e-16 50 -10 -5 -50 -8.66025 -5 -50 -8.66025 -5 50 -8.66025 -5 50 -8.66025 -8.66025 -50 -5 -8.66025 -50 -5 -8.66025 50 -5 -8.66025 50 -5 -10 -50 -1.22465e-15 -10 -50 -1.22465e-15 -10 50 -1.22465e-15 -10 50 -1.22465e-15 -8.66025 -50 5 -8.66025 -50 5 -8.66025 50 5 -8.66025 50 5 -5 -50 8.66025 -5 -50 8.66025 -5 50 8.66025 -5 50 8.66025 -1.83697e-15 -50 10 -1.83697e-15 -50 10 -1.83697e-15 50 10 -1.83697e-15 50 10 5 -50 8.66025 5 -50 8.66025 5 50 8.66025 5 50 8.66025 8.66025 -50 5 8.66025 -50 5 8.66025 50 5 8.66025 50 5 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 0 -1 -0 1 0 -0 0.866026 0 -0.5 0 1 -0 0.5 0 -0.866026 0 0 -1 -0.5 0 -0.866026 -0.866026 0 -0.5 -1 0 -0 -0.866026 0 0.5 -0.5 0 0.866026 0 0 1 0.5 0 0.866026 0.866026 0 0.5 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 0.5 0.5 0 0.5 0.0669873 0.75 0 0 0 1 0.0833333 1 0.0833333 0 1 0.5 0.933013 0.75 0.25 0.933013 0.166667 1 0.166667 0 0.75 0.933013 0.5 1 0.25 1 0.25 0 0.333333 1 0.333333 0 0.416667 1 0.416667 0 0.5 0 0.933013 0.25 0.583333 1 0.583333 0 0.0669873 0.25 0.75 0.0669873 0.666667 1 0.666667 0 0.25 0.0669873 0.75 1 0.75 0 0.833333 1 0.833333 0 0.916667 1 0.916667 0 1 1 1 0 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 237 |

6 0 2 2 0 1 0 0 0 7 2 6 8 2 5 4 1 4 3 1 3 9 3 8 1 3 0 5 3 7 10 0 9 6 0 2 0 0 0 11 4 11 12 4 10 8 2 5 7 2 6 13 3 12 1 3 0 9 3 8 14 0 13 10 0 9 0 0 0 15 5 15 16 5 14 12 4 10 11 4 11 17 3 13 1 3 0 13 3 12 18 0 12 14 0 13 0 0 0 19 6 17 20 6 16 16 5 14 15 5 15 21 3 9 1 3 0 17 3 13 22 0 8 18 0 12 0 0 0 23 7 19 24 7 18 20 6 16 19 6 17 25 3 2 1 3 0 21 3 9 26 0 7 22 0 8 0 0 0 27 8 20 28 8 13 24 7 18 23 7 19 29 3 1 1 3 0 25 3 2 30 0 21 26 0 7 0 0 0 31 9 23 32 9 22 28 8 13 27 8 20 33 3 24 1 3 0 29 3 1 34 0 25 30 0 21 0 0 0 35 10 27 36 10 26 32 9 22 31 9 23 37 3 28 1 3 0 33 3 24 38 0 20 34 0 25 0 0 0 39 11 30 40 11 29 36 10 26 35 10 27 41 3 20 1 3 0 37 3 28 42 0 28 38 0 20 0 0 0 43 12 32 44 12 31 40 11 29 39 11 30 45 3 25 1 3 0 41 3 20 46 0 24 42 0 28 0 0 0 47 13 34 48 13 33 44 12 31 43 12 32 49 3 21 1 3 0 45 3 25 2 0 1 46 0 24 0 0 0 3 1 36 4 1 35 48 13 33 47 13 34 5 3 7 1 3 0 49 3 21

238 |
239 |
240 |
241 | 242 | 243 | 244 | -50 0 50 50 0 50 -50 0 -50 50 0 -50 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 0 1 -0 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 0 0 0 1 1 1 1 0 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 4 280 |

1 0 3 3 0 2 2 0 1 0 0 0

281 |
282 |
283 |
284 |
285 | 286 | 287 | 288 | 0 0 -0 289 | 0 1 0 -0 290 | 1 0 0 0 291 | 0 0 1 -0 292 | 1 1 1 293 | 294 | 0 180 -0 295 | 0 1 0 -0 296 | 1 0 0 0 297 | 0 0 1 -0 298 | 1 1 1 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 0 50 -0 311 | 0 1 0 -0 312 | 1 0 0 0 313 | 0 0 1 -0 314 | 1 1 1 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 0 0 -0 327 | 0 1 0 -0 328 | 1 0 0 0 329 | 0 0 1 -0 330 | 1 1 1 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 |
348 | -------------------------------------------------------------------------------- /examples/scene/assets/tree2.dae: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CINEMA4D 15.064 COLLADA Exporter 6 | 7 | 2015-08-28T05:43:48Z 8 | 2015-08-28T05:43:48Z 9 | 10 | Y_UP 11 | 12 | 13 | 14 | tex/shadow-circle.png 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 0.8 0.8 0.8 1 24 | 25 | 26 | 0.2 0.2 0.2 1 27 | 28 | 29 | 0.5 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 0.0627451 0.396078 0.513725 1 41 | 42 | 43 | 0.2 0.2 0.2 1 44 | 45 | 46 | 0.5 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 0.0235294 0.564706 0.607843 1 58 | 59 | 60 | 0.2 0.2 0.2 1 61 | 62 | 63 | 0.5 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 0.164706 0.72549 0.701961 1 75 | 76 | 77 | 0.2 0.2 0.2 1 78 | 79 | 80 | 0.5 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 0.654902 0.47451 0.415686 1 92 | 93 | 94 | 0.2 0.2 0.2 1 95 | 96 | 97 | 0.5 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | ID13 108 | 109 | 110 | 111 | 112 | ID14 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 1 1 1 1 122 | 123 | 124 | 0.88 125 | 126 | 127 | 1 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | -32.0367 -60 32.0367 32.0367 -60 32.0367 32.0367 -60 -32.0367 -32.0367 -60 -32.0367 -32.0367 -57.6676 37.6676 32.0367 -57.6676 37.6676 36.0183 -57.6676 36.0183 37.6676 -57.6676 32.0367 37.6676 -57.6676 -32.0367 36.0183 -57.6676 -36.0183 32.0367 -57.6676 -37.6676 -32.0367 -57.6676 -37.6676 -36.0183 -57.6676 -36.0183 -37.6676 -57.6676 -32.0367 -37.6676 -57.6676 32.0367 -36.0183 -57.6676 36.0183 -32.0367 -52.0367 40 32.0367 -52.0367 40 37.6676 -52.0367 37.6676 40 -52.0367 32.0367 40 -52.0367 -32.0367 37.6676 -52.0367 -37.6676 32.0367 -52.0367 -40 -32.0367 -52.0367 -40 -37.6676 -52.0367 -37.6676 -40 -52.0367 -32.0367 -40 -52.0367 32.0367 -37.6676 -52.0367 37.6676 -32.0367 52.0367 40 32.0367 52.0367 40 37.6676 52.0367 37.6676 40 52.0367 32.0367 40 52.0367 -32.0367 37.6676 52.0367 -37.6676 32.0367 52.0367 -40 -32.0367 52.0367 -40 -37.6676 52.0367 -37.6676 -40 52.0367 -32.0367 -40 52.0367 32.0367 -37.6676 52.0367 37.6676 -32.0367 57.6676 37.6676 32.0367 57.6676 37.6676 36.0183 57.6676 36.0183 37.6676 57.6676 32.0367 37.6676 57.6676 -32.0367 36.0183 57.6676 -36.0183 32.0367 57.6676 -37.6676 -32.0367 57.6676 -37.6676 -36.0183 57.6676 -36.0183 -37.6676 57.6676 -32.0367 -37.6676 57.6676 32.0367 -36.0183 57.6676 36.0183 -32.0367 60 32.0367 32.0367 60 32.0367 32.0367 60 -32.0367 -32.0367 60 -32.0367 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | -0.0785213 -0.921027 0.381502 0.0785213 -0.921027 0.381502 0 -0.382683 0.92388 0 0 1 0 0.382683 0.92388 -0.0785213 0.921027 0.381502 0.0785213 0.921027 0.381502 0.156558 -0.912487 0.377964 0.357407 -0.357407 0.862856 0.382683 0 0.92388 0.357406 0.357407 0.862856 0.156558 0.912487 0.377964 0.381502 -0.921027 0.0785213 0.377964 -0.912487 0.156558 0.862856 -0.357407 0.357407 0.92388 0 0.382683 0.862856 0.357407 0.357407 0.377964 0.912487 0.156558 0.381502 0.921027 0.0785213 0.381502 -0.921027 -0.0785213 0.92388 -0.382683 -0 1 0 -0 0.92388 0.382683 -0 0.381502 0.921027 -0.0785213 0.377964 -0.912487 -0.156558 0.862856 -0.357407 -0.357407 0.92388 0 -0.382683 0.862856 0.357407 -0.357407 0.377964 0.912487 -0.156558 0.0785213 -0.921027 -0.381502 0.156558 -0.912487 -0.377964 0.357406 -0.357407 -0.862856 0.382683 0 -0.92388 0.357407 0.357407 -0.862856 0.156558 0.912487 -0.377964 0.0785213 0.921027 -0.381502 -0.0785213 -0.921027 -0.381502 0 -0.382683 -0.92388 0 0 -1 0 0.382683 -0.92388 -0.0785213 0.921027 -0.381502 -0.156558 -0.912487 -0.377964 -0.357407 -0.357407 -0.862856 -0.382683 0 -0.92388 -0.357406 0.357407 -0.862856 -0.156558 0.912487 -0.377964 -0.381502 -0.921027 -0.0785213 -0.377964 -0.912487 -0.156558 -0.862856 -0.357407 -0.357407 -0.92388 0 -0.382683 -0.862856 0.357407 -0.357407 -0.377964 0.912487 -0.156558 -0.381502 0.921027 -0.0785213 -0.381502 -0.921027 0.0785213 -0.92388 -0.382683 -0 -1 0 -0 -0.92388 0.382683 -0 -0.381502 0.921027 0.0785213 -0.377964 -0.912487 0.156558 -0.862856 -0.357407 0.357407 -0.92388 0 0.382683 -0.862856 0.357407 0.357407 -0.377964 0.912487 0.156558 -0.156558 -0.912487 0.377964 -0.357406 -0.357407 0.862856 -0.382683 0 0.92388 -0.357407 0.357407 0.862856 -0.156558 0.912487 0.377964 0 1 -0 0 -1 -0 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 0 0 0 0.0484494 0.836663 0.0484494 0.836663 0 0 0.0968987 0.836663 0.0968987 0 0.903101 0.836663 0.903101 0 0.951551 0.836663 0.951551 0 1 0.836663 1 0.877497 0 0.918331 0.0484494 0.918331 0.0968987 0.918331 0.903101 0.918331 0.951551 0.877497 1 0.959166 0 1 0.0484494 1 0.0968987 1 0.903101 1 0.951551 0.959166 1 1 1 1 0 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 194 |

1 1 3 5 1 2 4 0 1 0 0 0 5 2 2 17 2 5 16 2 4 4 2 1 17 3 5 29 3 7 28 3 6 16 3 4 29 4 7 41 4 9 40 4 8 28 4 6 41 6 9 53 6 11 52 5 10 40 5 8 6 7 13 5 1 2 1 1 12 6 8 13 18 8 14 17 8 5 5 8 2 18 9 14 30 9 15 29 9 7 17 9 5 30 10 15 42 10 16 41 10 9 29 10 7 42 11 16 53 6 17 41 6 9 7 12 19 6 13 13 1 12 18 7 14 19 19 14 20 18 14 14 6 14 13 19 15 20 31 15 21 30 15 15 18 15 14 31 16 21 43 16 22 42 16 16 30 16 15 43 18 22 53 18 23 42 17 16 2 19 3 8 19 2 7 12 1 1 12 0 8 20 2 20 20 5 19 20 4 7 20 1 20 21 5 32 21 7 31 21 6 19 21 4 32 22 7 44 22 9 43 22 8 31 22 6 44 23 9 54 23 11 53 18 10 43 18 8 9 24 13 8 19 2 2 19 12 9 25 13 21 25 14 20 25 5 8 25 2 21 26 14 33 26 15 32 26 7 20 26 5 33 27 15 45 27 16 44 27 9 32 27 7 45 28 16 54 23 17 44 23 9 10 29 19 9 30 13 2 29 18 10 31 19 22 31 20 21 31 14 9 31 13 22 32 20 34 32 21 33 32 15 21 32 14 34 33 21 46 33 22 45 33 16 33 33 15 46 35 22 54 35 23 45 34 16 3 36 3 11 36 2 10 29 1 2 29 0 11 37 2 23 37 5 22 37 4 10 37 1 23 38 5 35 38 7 34 38 6 22 38 4 35 39 7 47 39 9 46 39 8 34 39 6 47 40 9 55 40 11 54 35 10 46 35 8 12 41 13 11 36 2 3 36 12 12 42 13 24 42 14 23 42 5 11 42 2 24 43 14 36 43 15 35 43 7 23 43 5 36 44 15 48 44 16 47 44 9 35 44 7 48 45 16 55 40 17 47 40 9 13 46 19 12 47 13 3 46 18 13 48 19 25 48 20 24 48 14 12 48 13 25 49 20 37 49 21 36 49 15 24 49 14 37 50 21 49 50 22 48 50 16 36 50 15 49 52 22 55 52 23 48 51 16 0 53 3 14 53 2 13 46 1 3 46 0 14 54 2 26 54 5 25 54 4 13 54 1 26 55 5 38 55 7 37 55 6 25 55 4 38 56 7 50 56 9 49 56 8 37 56 6 50 57 9 52 57 11 55 52 10 49 52 8 15 58 13 14 53 2 0 53 12 15 59 13 27 59 14 26 59 5 14 59 2 27 60 14 39 60 15 38 60 7 26 60 5 39 61 15 51 61 16 50 61 9 38 61 7 51 62 16 52 57 17 50 57 9 4 0 19 15 63 13 0 0 18 4 64 19 16 64 20 27 64 14 15 64 13 16 65 20 28 65 21 39 65 15 27 65 14 28 66 21 40 66 22 51 66 16 39 66 15 40 5 22 52 5 23 51 67 16 53 68 25 54 68 24 55 68 10 52 68 0 3 69 0 2 69 25 1 69 24 0 69 10

195 |
196 |
197 |
198 | 199 | 200 | 201 | -18.4211 -34.5 62.1092 18.4211 -34.5 62.1092 18.4211 -34.5 -62.1092 -18.4211 -34.5 -62.1092 -18.4211 -33.1589 65.3469 18.4211 -33.1589 65.3469 20.7105 -33.1589 64.3986 21.6589 -33.1589 62.1092 21.6589 -33.1589 -62.1092 20.7105 -33.1589 -64.3986 18.4211 -33.1589 -65.3469 -18.4211 -33.1589 -65.3469 -20.7105 -33.1589 -64.3986 -21.6589 -33.1589 -62.1092 -21.6589 -33.1589 62.1092 -20.7105 -33.1589 64.3986 -18.4211 -29.9211 66.6881 18.4211 -29.9211 66.6881 21.6589 -29.9211 65.3469 23 -29.9211 62.1092 23 -29.9211 -62.1092 21.6589 -29.9211 -65.3469 18.4211 -29.9211 -66.6881 -18.4211 -29.9211 -66.6881 -21.6589 -29.9211 -65.3469 -23 -29.9211 -62.1092 -23 -29.9211 62.1092 -21.6589 -29.9211 65.3469 -18.4211 29.9211 66.6881 18.4211 29.9211 66.6881 21.6589 29.9211 65.3469 23 29.9211 62.1092 23 29.9211 -62.1092 21.6589 29.9211 -65.3469 18.4211 29.9211 -66.6881 -18.4211 29.9211 -66.6881 -21.6589 29.9211 -65.3469 -23 29.9211 -62.1092 -23 29.9211 62.1092 -21.6589 29.9211 65.3469 -18.4211 33.1589 65.3469 18.4211 33.1589 65.3469 20.7105 33.1589 64.3986 21.6589 33.1589 62.1092 21.6589 33.1589 -62.1092 20.7105 33.1589 -64.3986 18.4211 33.1589 -65.3469 -18.4211 33.1589 -65.3469 -20.7105 33.1589 -64.3986 -21.6589 33.1589 -62.1092 -21.6589 33.1589 62.1092 -20.7105 33.1589 64.3986 -18.4211 34.5 62.1092 18.4211 34.5 62.1092 18.4211 34.5 -62.1092 -18.4211 34.5 -62.1092 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | -0.0785212 -0.921027 0.381502 0.0785212 -0.921027 0.381502 0 -0.382683 0.92388 0 0 1 0 0.382683 0.92388 -0.0785212 0.921027 0.381502 0.0785212 0.921027 0.381502 0.156558 -0.912487 0.377965 0.357406 -0.357406 0.862857 0.382683 0 0.92388 0.357406 0.357406 0.862857 0.156558 0.912487 0.377965 0.381502 -0.921027 0.0785213 0.377965 -0.912487 0.156558 0.862856 -0.357407 0.357407 0.923879 0 0.382684 0.862856 0.357406 0.357407 0.377965 0.912487 0.156558 0.381502 0.921027 0.0785213 0.381502 -0.921027 -0.0785213 0.92388 -0.382683 -0 1 0 -0 0.92388 0.382683 -0 0.381502 0.921027 -0.0785213 0.377965 -0.912487 -0.156558 0.862856 -0.357406 -0.357407 0.923879 0 -0.382684 0.862856 0.357407 -0.357407 0.377965 0.912487 -0.156558 0.0785212 -0.921027 -0.381502 0.156558 -0.912487 -0.377965 0.357406 -0.357406 -0.862857 0.382683 0 -0.92388 0.357406 0.357406 -0.862857 0.156558 0.912487 -0.377965 0.0785212 0.921027 -0.381502 -0.0785212 -0.921027 -0.381502 0 -0.382683 -0.92388 0 0 -1 0 0.382683 -0.92388 -0.0785212 0.921027 -0.381502 -0.156558 -0.912487 -0.377965 -0.357406 -0.357406 -0.862857 -0.382683 0 -0.92388 -0.357406 0.357406 -0.862857 -0.156558 0.912487 -0.377965 -0.381502 -0.921027 -0.0785213 -0.377965 -0.912487 -0.156558 -0.862856 -0.357407 -0.357407 -0.923879 0 -0.382684 -0.862856 0.357406 -0.357407 -0.377965 0.912487 -0.156558 -0.381502 0.921027 -0.0785213 -0.381502 -0.921027 0.0785213 -0.92388 -0.382683 -0 -1 0 -0 -0.92388 0.382683 -0 -0.381502 0.921027 0.0785213 -0.377965 -0.912487 0.156558 -0.862856 -0.357406 0.357407 -0.923879 0 0.382684 -0.862856 0.357407 0.357407 -0.377965 0.912487 0.156558 -0.156558 -0.912487 0.377965 -0.357406 -0.357406 0.862857 -0.382683 0 0.92388 -0.357406 0.357406 0.862857 -0.156558 0.912487 0.377965 0 1 -0 0 -1 -0 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 0 0 0 0.0484494 0.836663 0.0484494 0.836663 0 0 0.0968987 0.836663 0.0968987 0 0.903101 0.836663 0.903101 0 0.951551 0.836663 0.951551 0 1 0.836663 1 0.877497 0 0.918331 0.0484494 0.918331 0.0968987 0.918331 0.903101 0.918331 0.951551 0.877497 1 0.959166 0 1 0.0484494 1 0.0968987 1 0.903101 1 0.951551 0.959166 1 0.945267 0.0484494 0.945267 0 0.945267 0.0968987 0.945267 0.903101 0.945267 0.951551 0.945267 1 0.95895 0 0.972633 0.0484494 0.972633 0.0968987 0.972633 0.903101 0.972633 0.951551 0.95895 1 0.986317 0 0.986317 1 1 1 1 0 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 237 |

1 1 3 5 1 2 4 0 1 0 0 0 5 2 2 17 2 5 16 2 4 4 2 1 17 3 5 29 3 7 28 3 6 16 3 4 29 4 7 41 4 9 40 4 8 28 4 6 41 6 9 53 6 11 52 5 10 40 5 8 6 7 13 5 1 2 1 1 12 6 8 13 18 8 14 17 8 5 5 8 2 18 9 14 30 9 15 29 9 7 17 9 5 30 10 15 42 10 16 41 10 9 29 10 7 42 11 16 53 6 17 41 6 9 7 12 19 6 13 13 1 12 18 7 14 19 19 14 20 18 14 14 6 14 13 19 15 20 31 15 21 30 15 15 18 15 14 31 16 21 43 16 22 42 16 16 30 16 15 43 18 22 53 18 23 42 17 16 2 19 25 8 19 24 7 12 1 1 12 0 8 20 24 20 20 26 19 20 4 7 20 1 20 21 26 32 21 27 31 21 6 19 21 4 32 22 27 44 22 28 43 22 8 31 22 6 44 23 28 54 23 29 53 18 10 43 18 8 9 24 31 8 19 24 2 19 30 9 25 31 21 25 32 20 25 26 8 25 24 21 26 32 33 26 33 32 26 27 20 26 26 33 27 33 45 27 34 44 27 28 32 27 27 45 28 34 54 23 35 44 23 28 10 29 19 9 30 31 2 29 36 10 31 19 22 31 20 21 31 32 9 31 31 22 32 20 34 32 21 33 32 33 21 32 32 34 33 21 46 33 22 45 33 34 33 33 33 46 35 22 54 35 37 45 34 34 3 36 3 11 36 2 10 29 1 2 29 0 11 37 2 23 37 5 22 37 4 10 37 1 23 38 5 35 38 7 34 38 6 22 38 4 35 39 7 47 39 9 46 39 8 34 39 6 47 40 9 55 40 11 54 35 10 46 35 8 12 41 13 11 36 2 3 36 12 12 42 13 24 42 14 23 42 5 11 42 2 24 43 14 36 43 15 35 43 7 23 43 5 36 44 15 48 44 16 47 44 9 35 44 7 48 45 16 55 40 17 47 40 9 13 46 19 12 47 13 3 46 18 13 48 19 25 48 20 24 48 14 12 48 13 25 49 20 37 49 21 36 49 15 24 49 14 37 50 21 49 50 22 48 50 16 36 50 15 49 52 22 55 52 23 48 51 16 0 53 25 14 53 24 13 46 1 3 46 0 14 54 24 26 54 26 25 54 4 13 54 1 26 55 26 38 55 27 37 55 6 25 55 4 38 56 27 50 56 28 49 56 8 37 56 6 50 57 28 52 57 29 55 52 10 49 52 8 15 58 31 14 53 24 0 53 30 15 59 31 27 59 32 26 59 26 14 59 24 27 60 32 39 60 33 38 60 27 26 60 26 39 61 33 51 61 34 50 61 28 38 61 27 51 62 34 52 57 35 50 57 28 4 0 19 15 63 31 0 0 36 4 64 19 16 64 20 27 64 32 15 64 31 16 65 20 28 65 21 39 65 33 27 65 32 28 66 21 40 66 22 51 66 34 39 66 33 40 5 22 52 5 37 51 67 34 53 68 39 54 68 38 55 68 10 52 68 0 3 69 0 2 69 39 1 69 38 0 69 10

238 |
239 |
240 |
241 | 242 | 243 | 244 | -40 -80.5 40 40 -80.5 40 40 -80.5 -40 -40 -80.5 -40 -40 -77.5711 47.0711 40 -77.5711 47.0711 45 -77.5711 45 47.0711 -77.5711 40 47.0711 -77.5711 -40 45 -77.5711 -45 40 -77.5711 -47.0711 -40 -77.5711 -47.0711 -45 -77.5711 -45 -47.0711 -77.5711 -40 -47.0711 -77.5711 40 -45 -77.5711 45 -40 -70.5 50 40 -70.5 50 47.0711 -70.5 47.0711 50 -70.5 40 50 -70.5 -40 47.0711 -70.5 -47.0711 40 -70.5 -50 -40 -70.5 -50 -47.0711 -70.5 -47.0711 -50 -70.5 -40 -50 -70.5 40 -47.0711 -70.5 47.0711 -40 70.5 50 40 70.5 50 47.0711 70.5 47.0711 50 70.5 40 50 70.5 -40 47.0711 70.5 -47.0711 40 70.5 -50 -40 70.5 -50 -47.0711 70.5 -47.0711 -50 70.5 -40 -50 70.5 40 -47.0711 70.5 47.0711 -40 77.5711 47.0711 40 77.5711 47.0711 45 77.5711 45 47.0711 77.5711 40 47.0711 77.5711 -40 45 77.5711 -45 40 77.5711 -47.0711 -40 77.5711 -47.0711 -45 77.5711 -45 -47.0711 77.5711 -40 -47.0711 77.5711 40 -45 77.5711 45 -40 80.5 40 40 80.5 40 40 80.5 -40 -40 80.5 -40 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | -0.0785214 -0.921027 0.381502 0.0785214 -0.921027 0.381502 0 -0.382683 0.92388 0 0 1 0 0.382683 0.92388 -0.0785214 0.921027 0.381502 0.0785214 0.921027 0.381502 0.156558 -0.912487 0.377964 0.357407 -0.357407 0.862856 0.382683 0 0.92388 0.357407 0.357407 0.862856 0.156558 0.912487 0.377964 0.381502 -0.921027 0.0785214 0.377964 -0.912487 0.156558 0.862856 -0.357407 0.357407 0.92388 0 0.382683 0.862856 0.357407 0.357407 0.377964 0.912487 0.156558 0.381502 0.921027 0.0785214 0.381502 -0.921027 -0.0785214 0.92388 -0.382683 -0 1 0 -0 0.92388 0.382683 -0 0.381502 0.921027 -0.0785214 0.377964 -0.912487 -0.156558 0.862856 -0.357407 -0.357407 0.92388 0 -0.382683 0.862856 0.357407 -0.357407 0.377964 0.912487 -0.156558 0.0785214 -0.921027 -0.381502 0.156558 -0.912487 -0.377964 0.357407 -0.357407 -0.862856 0.382683 0 -0.92388 0.357407 0.357407 -0.862856 0.156558 0.912487 -0.377964 0.0785214 0.921027 -0.381502 -0.0785214 -0.921027 -0.381502 0 -0.382683 -0.92388 0 0 -1 0 0.382683 -0.92388 -0.0785214 0.921027 -0.381502 -0.156558 -0.912487 -0.377964 -0.357407 -0.357407 -0.862856 -0.382683 0 -0.92388 -0.357407 0.357407 -0.862856 -0.156558 0.912487 -0.377964 -0.381502 -0.921027 -0.0785214 -0.377964 -0.912487 -0.156558 -0.862856 -0.357407 -0.357407 -0.92388 0 -0.382683 -0.862856 0.357407 -0.357407 -0.377964 0.912487 -0.156558 -0.381502 0.921027 -0.0785214 -0.381502 -0.921027 0.0785214 -0.92388 -0.382683 -0 -1 0 -0 -0.92388 0.382683 -0 -0.381502 0.921027 0.0785214 -0.377964 -0.912487 0.156558 -0.862856 -0.357407 0.357407 -0.92388 0 0.382683 -0.862856 0.357407 0.357407 -0.377964 0.912487 0.156558 -0.156558 -0.912487 0.377964 -0.357407 -0.357407 0.862856 -0.382683 0 0.92388 -0.357407 0.357407 0.862856 -0.156558 0.912487 0.377964 0 1 -0 0 -1 -0 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 0 0 0 0.0455526 0.835876 0.0455526 0.835876 0 0 0.0911051 0.835876 0.0911051 0 0.908895 0.835876 0.908895 0 0.954447 0.835876 0.954447 0 1 0.835876 1 0.876907 0 0.917938 0.0455526 0.917938 0.0911051 0.917938 0.908895 0.917938 0.954447 0.876907 1 0.958969 0 1 0.0455526 1 0.0911051 1 0.908895 1 0.954447 0.958969 1 1 1 1 0 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 4 4 4 3 4 4 4 3 3 4 4 4 3 4 4 280 |

1 1 3 5 1 2 4 0 1 0 0 0 5 2 2 17 2 5 16 2 4 4 2 1 17 3 5 29 3 7 28 3 6 16 3 4 29 4 7 41 4 9 40 4 8 28 4 6 41 6 9 53 6 11 52 5 10 40 5 8 6 7 13 5 1 2 1 1 12 6 8 13 18 8 14 17 8 5 5 8 2 18 9 14 30 9 15 29 9 7 17 9 5 30 10 15 42 10 16 41 10 9 29 10 7 42 11 16 53 6 17 41 6 9 7 12 19 6 13 13 1 12 18 7 14 19 19 14 20 18 14 14 6 14 13 19 15 20 31 15 21 30 15 15 18 15 14 31 16 21 43 16 22 42 16 16 30 16 15 43 18 22 53 18 23 42 17 16 2 19 3 8 19 2 7 12 1 1 12 0 8 20 2 20 20 5 19 20 4 7 20 1 20 21 5 32 21 7 31 21 6 19 21 4 32 22 7 44 22 9 43 22 8 31 22 6 44 23 9 54 23 11 53 18 10 43 18 8 9 24 13 8 19 2 2 19 12 9 25 13 21 25 14 20 25 5 8 25 2 21 26 14 33 26 15 32 26 7 20 26 5 33 27 15 45 27 16 44 27 9 32 27 7 45 28 16 54 23 17 44 23 9 10 29 19 9 30 13 2 29 18 10 31 19 22 31 20 21 31 14 9 31 13 22 32 20 34 32 21 33 32 15 21 32 14 34 33 21 46 33 22 45 33 16 33 33 15 46 35 22 54 35 23 45 34 16 3 36 3 11 36 2 10 29 1 2 29 0 11 37 2 23 37 5 22 37 4 10 37 1 23 38 5 35 38 7 34 38 6 22 38 4 35 39 7 47 39 9 46 39 8 34 39 6 47 40 9 55 40 11 54 35 10 46 35 8 12 41 13 11 36 2 3 36 12 12 42 13 24 42 14 23 42 5 11 42 2 24 43 14 36 43 15 35 43 7 23 43 5 36 44 15 48 44 16 47 44 9 35 44 7 48 45 16 55 40 17 47 40 9 13 46 19 12 47 13 3 46 18 13 48 19 25 48 20 24 48 14 12 48 13 25 49 20 37 49 21 36 49 15 24 49 14 37 50 21 49 50 22 48 50 16 36 50 15 49 52 22 55 52 23 48 51 16 0 53 3 14 53 2 13 46 1 3 46 0 14 54 2 26 54 5 25 54 4 13 54 1 26 55 5 38 55 7 37 55 6 25 55 4 38 56 7 50 56 9 49 56 8 37 56 6 50 57 9 52 57 11 55 52 10 49 52 8 15 58 13 14 53 2 0 53 12 15 59 13 27 59 14 26 59 5 14 59 2 27 60 14 39 60 15 38 60 7 26 60 5 39 61 15 51 61 16 50 61 9 38 61 7 51 62 16 52 57 17 50 57 9 4 0 19 15 63 13 0 0 18 4 64 19 16 64 20 27 64 14 15 64 13 16 65 20 28 65 21 39 65 15 27 65 14 28 66 21 40 66 22 51 66 16 39 66 15 40 5 22 52 5 23 51 67 16 53 68 25 54 68 24 55 68 10 52 68 0 3 69 0 2 69 25 1 69 24 0 69 10

281 |
282 |
283 |
284 | 285 | 286 | 287 | 0 -50 0 0 50 0 10 -50 -0 10 -50 -0 10 50 -0 10 50 -0 7.07107 -50 -7.07107 7.07107 -50 -7.07107 7.07107 50 -7.07107 7.07107 50 -7.07107 6.12323e-16 -50 -10 6.12323e-16 -50 -10 6.12323e-16 50 -10 6.12323e-16 50 -10 -7.07107 -50 -7.07107 -7.07107 -50 -7.07107 -7.07107 50 -7.07107 -7.07107 50 -7.07107 -10 -50 -1.22465e-15 -10 -50 -1.22465e-15 -10 50 -1.22465e-15 -10 50 -1.22465e-15 -7.07107 -50 7.07107 -7.07107 -50 7.07107 -7.07107 50 7.07107 -7.07107 50 7.07107 -1.83697e-15 -50 10 -1.83697e-15 -50 10 -1.83697e-15 50 10 -1.83697e-15 50 10 7.07107 -50 7.07107 7.07107 -50 7.07107 7.07107 50 7.07107 7.07107 50 7.07107 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 0 -1 -0 1 0 -0 0.707107 0 -0.707107 0 1 -0 0 0 -1 -0.707107 0 -0.707107 -1 0 -0 -0.707107 0 0.707107 0 0 1 0.707107 0 0.707107 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 0.5 0.5 0 0.5 0.146447 0.853553 0 0 0 1 0.125 1 0.125 0 1 0.5 0.853553 0.853553 0.5 1 0.25 1 0.25 0 0.375 1 0.375 0 0.5 0 0.853553 0.146447 0.625 1 0.625 0 0.146447 0.146447 0.75 1 0.75 0 0.875 1 0.875 0 1 1 1 0 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 323 |

6 0 2 2 0 1 0 0 0 7 2 6 8 2 5 4 1 4 3 1 3 9 3 8 1 3 0 5 3 7 10 0 9 6 0 2 0 0 0 11 4 11 12 4 10 8 2 5 7 2 6 13 3 9 1 3 0 9 3 8 14 0 8 10 0 9 0 0 0 15 5 13 16 5 12 12 4 10 11 4 11 17 3 2 1 3 0 13 3 9 18 0 7 14 0 8 0 0 0 19 6 14 20 6 9 16 5 12 15 5 13 21 3 1 1 3 0 17 3 2 22 0 15 18 0 7 0 0 0 23 7 17 24 7 16 20 6 9 19 6 14 25 3 18 1 3 0 21 3 1 26 0 14 22 0 15 0 0 0 27 8 20 28 8 19 24 7 16 23 7 17 29 3 14 1 3 0 25 3 18 30 0 18 26 0 14 0 0 0 31 9 22 32 9 21 28 8 19 27 8 20 33 3 15 1 3 0 29 3 14 2 0 1 30 0 18 0 0 0 3 1 24 4 1 23 32 9 21 31 9 22 5 3 7 1 3 0 33 3 15

324 |
325 |
326 |
327 | 328 | 329 | 330 | -50 0 50 50 0 50 -50 0 -50 50 0 -50 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 0 1 -0 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 0 0 0 1 1 1 1 0 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 4 366 |

1 0 3 3 0 2 2 0 1 0 0 0

367 |
368 |
369 |
370 |
371 | 372 | 373 | 374 | -23.7123 210.709 24.9106 375 | 0 1 0 -0 376 | 1 0 0 0 377 | 0 0 1 -0 378 | 1 1 1 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 3.20043 174.503 2.82738 391 | 0 1 0 -0 392 | 1 0 0 0 393 | 0 0 1 -0 394 | 1 1 1 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 0 180 -0 407 | 0 1 0 -0 408 | 1 0 0 0 409 | 0 0 1 -0 410 | 1 1 1 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 0 50 -0 423 | 0 1 0 -0 424 | 1 0 0 0 425 | 0 0 1 -0 426 | 1 1 1 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 0 0 -0 439 | 0 1 0 -0 440 | 1 0 0 0 441 | 0 0 1 -0 442 | 1 1 1 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 |
459 | -------------------------------------------------------------------------------- /examples/scene/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /gamepad-controls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gamepad controls for A-Frame. 3 | * 4 | * For more information about the Gamepad API, see: 5 | * https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API 6 | */ 7 | 8 | var GamepadButton = require('./lib/GamepadButton'), 9 | GamepadButtonEvent = require('./lib/GamepadButtonEvent'); 10 | 11 | var MAX_DELTA = 200, // ms 12 | PI_2 = Math.PI / 2; 13 | 14 | var JOYSTICK_EPS = 0.2; 15 | 16 | module.exports = { 17 | 18 | /******************************************************************* 19 | * Statics 20 | */ 21 | 22 | GamepadButton: GamepadButton, 23 | 24 | /******************************************************************* 25 | * Schema 26 | */ 27 | 28 | schema: { 29 | // Controller 0-3 30 | controller: { default: 0, oneOf: [0, 1, 2, 3] }, 31 | 32 | // Enable/disable features 33 | enabled: { default: true }, 34 | movementEnabled: { default: true }, 35 | lookEnabled: { default: true }, 36 | flyEnabled: { default: false }, 37 | invertAxisY: { default: false }, 38 | 39 | // Constants 40 | easing: { default: 20 }, 41 | acceleration: { default: 65 }, 42 | sensitivity: { default: 0.04 }, 43 | 44 | // Control axes 45 | pitchAxis: { default: 'x', oneOf: [ 'x', 'y', 'z' ] }, 46 | yawAxis: { default: 'y', oneOf: [ 'x', 'y', 'z' ] }, 47 | rollAxis: { default: 'z', oneOf: [ 'x', 'y', 'z' ] }, 48 | 49 | // Debugging 50 | debug: { default: false } 51 | }, 52 | 53 | /******************************************************************* 54 | * Core 55 | */ 56 | 57 | /** 58 | * Called once when component is attached. Generally for initial setup. 59 | */ 60 | init: function () { 61 | // Movement 62 | this.velocity = new THREE.Vector3(0, 0, 0); 63 | this.direction = new THREE.Vector3(0, 0, 0); 64 | 65 | // Rotation 66 | this.pitch = new THREE.Object3D(); 67 | this.yaw = new THREE.Object3D(); 68 | this.yaw.position.y = 10; 69 | this.yaw.add(this.pitch); 70 | 71 | // Button state 72 | this.buttons = {}; 73 | 74 | if (!this.getGamepad()) { 75 | console.warn( 76 | 'Gamepad #%d not found. Connect controller and press any button to continue.', 77 | this.data.controller 78 | ); 79 | } 80 | }, 81 | 82 | /** 83 | * Called on each iteration of main render loop. 84 | */ 85 | tick: function (t, dt) { 86 | this.updateRotation(dt); 87 | this.updatePosition(dt); 88 | this.updateButtonState(); 89 | }, 90 | 91 | /******************************************************************* 92 | * Movement 93 | */ 94 | 95 | updatePosition: function (dt) { 96 | var data = this.data; 97 | var acceleration = data.acceleration; 98 | var easing = data.easing; 99 | var velocity = this.velocity; 100 | var rollAxis = data.rollAxis; 101 | var pitchAxis = data.pitchAxis; 102 | var el = this.el; 103 | var gamepad = this.getGamepad(); 104 | 105 | // If data has changed or FPS is too low 106 | // we reset the velocity 107 | if (dt > MAX_DELTA) { 108 | velocity[rollAxis] = 0; 109 | velocity[pitchAxis] = 0; 110 | return; 111 | } 112 | 113 | velocity[rollAxis] -= velocity[rollAxis] * easing * dt / 1000; 114 | velocity[pitchAxis] -= velocity[pitchAxis] * easing * dt / 1000; 115 | 116 | var position = el.getAttribute('position'); 117 | 118 | if (data.enabled && data.movementEnabled && gamepad) { 119 | var dpad = this.getDpad(), 120 | inputX = dpad.x || this.getJoystick(0).x, 121 | inputY = dpad.y || this.getJoystick(0).y; 122 | if (Math.abs(inputX) > JOYSTICK_EPS) { 123 | velocity[pitchAxis] += inputX * acceleration * dt / 1000; 124 | } 125 | if (Math.abs(inputY) > JOYSTICK_EPS) { 126 | velocity[rollAxis] += inputY * acceleration * dt / 1000; 127 | } 128 | } 129 | 130 | var movementVector = this.getMovementVector(dt); 131 | 132 | el.object3D.translateX(movementVector.x); 133 | el.object3D.translateY(movementVector.y); 134 | el.object3D.translateZ(movementVector.z); 135 | 136 | el.setAttribute('position', { 137 | x: position.x + movementVector.x, 138 | y: position.y + movementVector.y, 139 | z: position.z + movementVector.z 140 | }); 141 | }, 142 | 143 | getMovementVector: function (dt) { 144 | if (this._getMovementVector) { 145 | return this._getMovementVector(dt); 146 | } 147 | 148 | var euler = new THREE.Euler(0, 0, 0, 'YXZ'), 149 | rotation = new THREE.Vector3(); 150 | 151 | this._getMovementVector = function (dt) { 152 | rotation.copy(this.el.getAttribute('rotation')); 153 | this.direction.copy(this.velocity); 154 | this.direction.multiplyScalar(dt / 1000); 155 | if (!rotation) { return this.direction; } 156 | if (!this.data.flyEnabled) { rotation.x = 0; } 157 | euler.set( 158 | THREE.Math.degToRad(rotation.x), 159 | THREE.Math.degToRad(rotation.y), 160 | 0 161 | ); 162 | this.direction.applyEuler(euler); 163 | return this.direction; 164 | }; 165 | 166 | return this._getMovementVector(dt); 167 | }, 168 | 169 | /******************************************************************* 170 | * Rotation 171 | */ 172 | 173 | updateRotation: function () { 174 | if (this._updateRotation) { 175 | return this._updateRotation(); 176 | } 177 | 178 | var initialRotation = new THREE.Vector3(), 179 | prevInitialRotation = new THREE.Vector3(), 180 | prevFinalRotation = new THREE.Vector3(); 181 | 182 | var tCurrent, 183 | tLastLocalActivity = 0, 184 | tLastExternalActivity = 0; 185 | 186 | var ROTATION_EPS = 0.0001, 187 | DEBOUNCE = 500; 188 | 189 | this._updateRotation = function () { 190 | if (!this.data.lookEnabled || !this.getGamepad()) { 191 | return; 192 | } 193 | 194 | tCurrent = Date.now(); 195 | initialRotation.copy(this.el.getAttribute('rotation') || initialRotation); 196 | 197 | // If initial rotation for this frame is different from last frame, and 198 | // doesn't match last gamepad state, assume an external component is 199 | // active on this element. 200 | if (initialRotation.distanceToSquared(prevInitialRotation) > ROTATION_EPS 201 | && initialRotation.distanceToSquared(prevFinalRotation) > ROTATION_EPS) { 202 | prevInitialRotation.copy(initialRotation); 203 | tLastExternalActivity = tCurrent; 204 | return; 205 | } 206 | 207 | prevInitialRotation.copy(initialRotation); 208 | 209 | // If external controls have been active in last 500ms, wait. 210 | if (tCurrent - tLastExternalActivity < DEBOUNCE) { 211 | return; 212 | } 213 | 214 | var lookVector = this.getJoystick(1); 215 | if (Math.abs(lookVector.x) <= JOYSTICK_EPS) lookVector.x = 0; 216 | if (Math.abs(lookVector.y) <= JOYSTICK_EPS) lookVector.y = 0; 217 | if (this.data.invertAxisY) lookVector.y = -lookVector.y; 218 | 219 | // If external controls have been active more recently than gamepad, 220 | // and gamepad hasn't moved, don't overwrite the existing rotation. 221 | if (tLastExternalActivity > tLastLocalActivity && !lookVector.lengthSq()) { 222 | return; 223 | } 224 | 225 | lookVector.multiplyScalar(this.data.sensitivity); 226 | this.yaw.rotation.y -= lookVector.x; 227 | this.pitch.rotation.x -= lookVector.y; 228 | this.pitch.rotation.x = Math.max(-PI_2, Math.min(PI_2, this.pitch.rotation.x)); 229 | 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 | 239 | return this._updateRotation(); 240 | }, 241 | 242 | /******************************************************************* 243 | * Button events 244 | */ 245 | 246 | updateButtonState: function () { 247 | var gamepad = this.getGamepad(); 248 | if (this.data.enabled && gamepad) { 249 | 250 | // Fire DOM events for button state changes. 251 | for (var i = 0; i < gamepad.buttons.length; i++) { 252 | if (gamepad.buttons[i].pressed && !this.buttons[i]) { 253 | this.emit(new GamepadButtonEvent('gamepadbuttondown', i, gamepad.buttons[i])); 254 | } else if (!gamepad.buttons[i].pressed && this.buttons[i]) { 255 | this.emit(new GamepadButtonEvent('gamepadbuttonup', i, gamepad.buttons[i])); 256 | } 257 | this.buttons[i] = gamepad.buttons[i].pressed; 258 | } 259 | 260 | } else if (Object.keys(this.buttons)) { 261 | // Reset state if controls are disabled or controller is lost. 262 | this.buttons = {}; 263 | } 264 | }, 265 | 266 | emit: function (event) { 267 | // Emit original event. 268 | this.el.emit(event.type, event); 269 | 270 | // Emit convenience event, identifying button index. 271 | this.el.emit( 272 | event.type + ':' + event.index, 273 | new GamepadButtonEvent(event.type, event.index, event) 274 | ); 275 | }, 276 | 277 | /******************************************************************* 278 | * Gamepad state 279 | */ 280 | 281 | /** 282 | * Returns the Gamepad instance attached to the component. If connected, 283 | * a proxy-controls component may provide access to Gamepad input from a 284 | * remote device. 285 | * 286 | * @return {Gamepad} 287 | */ 288 | getGamepad: function () { 289 | var localGamepad = navigator.getGamepads 290 | && navigator.getGamepads()[this.data.controller], 291 | proxyControls = this.el.sceneEl.components['proxy-controls'], 292 | proxyGamepad = proxyControls && proxyControls.isConnected() 293 | && proxyControls.getGamepad(this.data.controller); 294 | return proxyGamepad || localGamepad; 295 | }, 296 | 297 | /** 298 | * Returns the state of the given button. 299 | * @param {number} index The button (0-N) for which to find state. 300 | * @return {GamepadButton} 301 | */ 302 | getButton: function (index) { 303 | return this.getGamepad().buttons[index]; 304 | }, 305 | 306 | /** 307 | * Returns state of the given axis. Axes are labelled 0-N, where 0-1 will 308 | * represent X/Y on the first joystick, and 2-3 X/Y on the second. 309 | * @param {number} index The axis (0-N) for which to find state. 310 | * @return {number} On the interval [-1,1]. 311 | */ 312 | getAxis: function (index) { 313 | return this.getGamepad().axes[index]; 314 | }, 315 | 316 | /** 317 | * Returns the state of the given joystick (0 or 1) as a THREE.Vector2. 318 | * @param {number} id The joystick (0, 1) for which to find state. 319 | * @return {THREE.Vector2} 320 | */ 321 | getJoystick: function (index) { 322 | var gamepad = this.getGamepad(); 323 | switch (index) { 324 | case 0: return new THREE.Vector2(gamepad.axes[0], gamepad.axes[1]); 325 | case 1: return new THREE.Vector2(gamepad.axes[2], gamepad.axes[3]); 326 | default: throw new Error('Unexpected joystick index "%d".', index); 327 | } 328 | }, 329 | 330 | /** 331 | * Returns the state of the dpad as a THREE.Vector2. 332 | * @return {THREE.Vector2} 333 | */ 334 | getDpad: function () { 335 | var gamepad = this.getGamepad(); 336 | if (!gamepad.buttons[GamepadButton.DPAD_RIGHT]) { 337 | return new THREE.Vector2(); 338 | } 339 | return new THREE.Vector2( 340 | (gamepad.buttons[GamepadButton.DPAD_RIGHT].pressed ? 1 : 0) 341 | + (gamepad.buttons[GamepadButton.DPAD_LEFT].pressed ? -1 : 0), 342 | (gamepad.buttons[GamepadButton.DPAD_UP].pressed ? -1 : 0) 343 | + (gamepad.buttons[GamepadButton.DPAD_DOWN].pressed ? 1 : 0) 344 | ); 345 | }, 346 | 347 | /** 348 | * Returns true if the gamepad is currently connected to the system. 349 | * @return {boolean} 350 | */ 351 | isConnected: function () { 352 | var gamepad = this.getGamepad(); 353 | return !!(gamepad && gamepad.connected); 354 | }, 355 | 356 | /** 357 | * Returns a string containing some information about the controller. Result 358 | * may vary across browsers, for a given controller. 359 | * @return {string} 360 | */ 361 | getID: function () { 362 | return this.getGamepad().id; 363 | } 364 | }; 365 | -------------------------------------------------------------------------------- /lib/GamepadButton.js: -------------------------------------------------------------------------------- 1 | module.exports = Object.assign(function GamepadButton () {}, { 2 | FACE_1: 0, 3 | FACE_2: 1, 4 | FACE_3: 2, 5 | FACE_4: 3, 6 | 7 | L_SHOULDER_1: 4, 8 | R_SHOULDER_1: 5, 9 | L_SHOULDER_2: 6, 10 | R_SHOULDER_2: 7, 11 | 12 | SELECT: 8, 13 | START: 9, 14 | 15 | DPAD_UP: 12, 16 | DPAD_DOWN: 13, 17 | DPAD_LEFT: 14, 18 | DPAD_RIGHT: 15, 19 | 20 | VENDOR: 16, 21 | }); 22 | -------------------------------------------------------------------------------- /lib/GamepadButtonEvent.js: -------------------------------------------------------------------------------- 1 | function GamepadButtonEvent (type, index, details) { 2 | this.type = type; 3 | this.index = index; 4 | this.pressed = details.pressed; 5 | this.value = details.value; 6 | } 7 | 8 | module.exports = GamepadButtonEvent; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aframe-gamepad-controls", 3 | "version": "0.6.0", 4 | "description": "Gamepad controls for A-Frame.", 5 | "main": "gamepad-controls.js", 6 | "config": { 7 | "demo_port": 8000 8 | }, 9 | "scripts": { 10 | "dev": "budo browser.js:bundle.js --dir examples --port $npm_package_config_demo_port --live", 11 | "dist": "webpack browser.js dist/aframe-gamepad-controls.js && webpack -p browser.js dist/aframe-gamepad-controls.min.js", 12 | "postpublish": "npm run dist", 13 | "test": "karma start ./tests/karma.conf.js", 14 | "preversion": "karma start ./tests/karma.conf.js --single-run", 15 | "version": "npm run dist && git add -A dist", 16 | "postversion": "git push && git push --tags && npm publish" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/donmccurdy/aframe-gamepad-controls.git" 21 | }, 22 | "keywords": [ 23 | "aframe", 24 | "aframe-component", 25 | "layout", 26 | "aframe-vr", 27 | "vr", 28 | "aframe-layout", 29 | "mozvr", 30 | "webvr", 31 | "gamepad", 32 | "controller", 33 | "joystick" 34 | ], 35 | "author": "Don McCurdy ", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/donmccurdy/aframe-gamepad-controls/issues" 39 | }, 40 | "homepage": "https://github.com/donmccurdy/aframe-gamepad-controls#readme", 41 | "dependencies": {}, 42 | "peerDependencies": { 43 | "aframe": ">=0.5.0" 44 | }, 45 | "devDependencies": { 46 | "aframe": ">=0.5.0", 47 | "browserify": "^12.0.1", 48 | "browserify-css": "^0.8.3", 49 | "budo": "^7.1.0", 50 | "chai": "^3.4.1", 51 | "chai-shallow-deep-equal": "^1.3.0", 52 | "karma": "^0.13.15", 53 | "karma-browserify": "^4.4.2", 54 | "karma-chai-shallow-deep-equal": "0.0.4", 55 | "karma-firefox-launcher": "^0.1.7", 56 | "karma-mocha": "^0.2.1", 57 | "karma-mocha-reporter": "^1.1.3", 58 | "karma-sinon-chai": "^1.1.0", 59 | "mocha": "^2.3.4", 60 | "webpack": "^1.12.9" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tests/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.jshintrc", 3 | "globals" : { 4 | "it" : false, 5 | "describe" : false, 6 | "expect" : false, 7 | "beforeEach" : false, 8 | "afterEach" : false, 9 | "assert" : false, 10 | "spyOn" : false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/__init.test.js: -------------------------------------------------------------------------------- 1 | /* global sinon, setup, teardown */ 2 | 3 | /** 4 | * __init.test.js is run before every test case. 5 | */ 6 | window.debug = true; 7 | 8 | var AScene = require('aframe').AScene; 9 | 10 | beforeEach(function () { 11 | this.sinon = sinon.sandbox.create(); 12 | // Stub to not create a WebGL context since Travis CI runs headless. 13 | this.sinon.stub(AScene.prototype, 'attachedCallback'); 14 | }); 15 | 16 | afterEach(function () { 17 | // Clean up any attached elements. 18 | ['canvas', 'a-assets', 'a-scene'].forEach(function (tagName) { 19 | var els = document.querySelectorAll(tagName); 20 | for (var i = 0; i < els.length; i++) { 21 | els[i].parentNode.removeChild(els[i]); 22 | } 23 | }); 24 | AScene.scene = null; 25 | 26 | this.sinon.restore(); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/gamepad-controls.test.js: -------------------------------------------------------------------------------- 1 | var AFRAME = require('aframe'); 2 | var component = require('../gamepad-controls'); 3 | var entityFactory = require('./helpers').entityFactory; 4 | 5 | AFRAME.registerComponent('gamepad-controls', component); 6 | 7 | describe('Gamepad Controls', function () { 8 | 9 | /******************************************************************* 10 | * Setup 11 | */ 12 | 13 | var GamepadButton = component.GamepadButton, 14 | EPS = 1e-6; 15 | 16 | var gamepad, ctrl; 17 | 18 | beforeEach(function () { 19 | // Mock gamepad 20 | gamepad = { 21 | axes: [0, 0, 0, 0], 22 | buttons: '.................'.split('.').map(function () { 23 | return { pressed: 0, value: 0 }; 24 | }), 25 | connected: true, 26 | id: 'test-gamepad' 27 | }; 28 | this.sinon.stub(navigator, 'getGamepads').returns([gamepad]); 29 | }); 30 | 31 | beforeEach(function (done) { 32 | this.el = entityFactory(); 33 | this.el.setAttribute('gamepad-controls', ''); 34 | this.el.addEventListener('loaded', function () { 35 | ctrl = this.el.components['gamepad-controls']; 36 | done(); 37 | }.bind(this)); 38 | }); 39 | 40 | /******************************************************************* 41 | * Tests 42 | */ 43 | 44 | describe('Accessors', function () { 45 | it('detects a gamepad', function () { 46 | expect(ctrl.getGamepad()).to.equal(gamepad); 47 | expect(ctrl.isConnected()).to.be.true; 48 | }); 49 | 50 | it('returns gamepad ID', function () { 51 | expect(ctrl.getID()).to.equal('test-gamepad'); 52 | }); 53 | 54 | it('returns joystick state', function () { 55 | var joystick0 = ctrl.getJoystick(0); 56 | expect(joystick0.x).to.equal(0); 57 | expect(joystick0.y).to.equal(0); 58 | }); 59 | 60 | it('returns dpad state', function () { 61 | var dpad = ctrl.getDpad(); 62 | expect(dpad.x).to.equal(0); 63 | expect(dpad.y).to.equal(0); 64 | }); 65 | }); 66 | 67 | describe('Movement', function () { 68 | it('moves object up/down/right/left with stick0', function () { 69 | gamepad.axes = [0, 1, 0, 0]; 70 | ctrl.updatePosition(100); 71 | expect(this.el.object3D.position.z).to.equal(0.65); 72 | 73 | gamepad.axes = [1, 0, 0, 0]; 74 | ctrl.updatePosition(1); 75 | expect(Math.abs(this.el.object3D.position.x - 0.000065)).to.be.below(EPS); 76 | expect(Math.abs(this.el.object3D.position.z - 0.65637)).to.be.below(EPS); 77 | }); 78 | 79 | it('moves object up/down/right/left with dpad', function () { 80 | gamepad.buttons[GamepadButton.DPAD_DOWN] = {pressed: true, value: 1}; 81 | ctrl.updatePosition(100); 82 | expect(this.el.object3D.position.z).to.equal(0.65); 83 | 84 | gamepad.buttons[GamepadButton.DPAD_DOWN] = {pressed: false, value: 0}; 85 | gamepad.buttons[GamepadButton.DPAD_RIGHT] = {pressed: true, value: 1}; 86 | ctrl.updatePosition(1); 87 | expect(Math.abs(this.el.object3D.position.x - 0.000065)).to.be.below(EPS); 88 | expect(Math.abs(this.el.object3D.position.z - 0.65637)).to.be.below(EPS); 89 | }); 90 | }); 91 | 92 | describe.skip('Rotation', function () { 93 | it('supports lookEnabled:auto (default)', function () { 94 | // Look controls should be enabled only when VR mode is active AND 95 | // there is a 'look-controls' instance on the element. 96 | this.el.setAttribute('gamepad-controls', ''); 97 | expect(ctrl.isLookEnabled()).to.be.true; 98 | this.el.components['look-controls'] = true; 99 | expect(ctrl.isLookEnabled()).to.be.true; 100 | document.fullscreen = true; 101 | expect(ctrl.isLookEnabled()).to.be.false; 102 | delete this.el.components['look-controls']; 103 | expect(ctrl.isLookEnabled()).to.be.true; 104 | }); 105 | 106 | it('supports lookEnabled:true', function () { 107 | this.el.setAttribute('gamepad-controls', 'lookEnabled: true'); 108 | expect(ctrl.isLookEnabled()).to.be.true; 109 | this.el.components['look-controls'] = true; 110 | document.fullscreen = true; 111 | expect(ctrl.isLookEnabled()).to.be.true; 112 | delete this.el.components['look-controls']; 113 | }); 114 | 115 | it('supports lookEnabled:false', function () { 116 | this.el.setAttribute('gamepad-controls', 'lookEnabled: false'); 117 | expect(ctrl.isLookEnabled()).to.be.false; 118 | }); 119 | }); 120 | 121 | describe('Movement + Rotation', function () { 122 | it('moves relative to post-rotation heading', function () { 123 | this.el.setAttribute('rotation', {x: 0, y: 90, z: 0}); 124 | gamepad.buttons[GamepadButton.DPAD_DOWN] = {pressed: true, value: 1}; 125 | ctrl.updatePosition(100); 126 | expect(this.el.object3D.position.z).to.be.within(-EPS, +EPS); 127 | expect(Math.abs(this.el.object3D.position.x - 0.65)).to.be.below(EPS); 128 | }); 129 | }); 130 | 131 | }); 132 | -------------------------------------------------------------------------------- /tests/helpers.js: -------------------------------------------------------------------------------- 1 | /* global suite */ 2 | 3 | /** 4 | * Helper method to create a scene, create an entity, add entity to scene, 5 | * add scene to document. 6 | * 7 | * @returns {object} An `` element. 8 | */ 9 | module.exports.entityFactory = function () { 10 | var scene = document.createElement('a-scene'); 11 | var entity = document.createElement('a-entity'); 12 | scene.appendChild(entity); 13 | document.body.appendChild(scene); 14 | return entity; 15 | }; 16 | 17 | /** 18 | * Creates and attaches a mixin element (and an `` element if necessary). 19 | * 20 | * @param {string} id - ID of mixin. 21 | * @param {object} obj - Map of component names to attribute values. 22 | * @returns {object} An attached `` element. 23 | */ 24 | module.exports.mixinFactory = function (id, obj) { 25 | var mixinEl = document.createElement('a-mixin'); 26 | mixinEl.setAttribute('id', id); 27 | Object.keys(obj).forEach(function (componentName) { 28 | mixinEl.setAttribute(componentName, obj[componentName]); 29 | }); 30 | 31 | var assetsEl = document.querySelector('a-assets'); 32 | if (!assetsEl) { 33 | assetsEl = document.createElement('a-assets'); 34 | document.body.appendChild(assetsEl); 35 | } 36 | assetsEl.appendChild(mixinEl); 37 | 38 | return mixinEl; 39 | }; 40 | 41 | /** 42 | * Test that is only run locally and is skipped on CI. 43 | */ 44 | module.exports.getSkipCISuite = function () { 45 | if (window.__env__.TEST_ENV === 'ci') { 46 | return suite.skip; 47 | } else { 48 | return suite; 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /tests/karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = function (config) { 3 | config.set({ 4 | basePath: '../', 5 | browserify: { 6 | paths: ['./'] 7 | }, 8 | browsers: ['firefox_latest'], 9 | customLaunchers: { 10 | firefox_latest: { 11 | base: 'FirefoxNightly', 12 | prefs: { /* empty */ } 13 | } 14 | }, 15 | client: { 16 | captureConsole: true, 17 | mocha: {ui: 'bdd'} 18 | }, 19 | envPreprocessor: [ 20 | 'TEST_ENV' 21 | ], 22 | files: [ 23 | 'tests/**/*.test.js', 24 | ], 25 | frameworks: ['mocha', 'sinon-chai', 'chai-shallow-deep-equal', 'browserify'], 26 | preprocessors: { 27 | 'tests/**/*.js': ['browserify'] 28 | }, 29 | reporters: ['mocha'] 30 | }); 31 | }; 32 | --------------------------------------------------------------------------------