├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── browser.js ├── dist ├── aframe-keyboard-controls.js └── aframe-keyboard-controls.min.js ├── examples ├── assets │ ├── 321103__nsstudios__blip1.wav │ ├── peach-gradient-1.jpg │ ├── tex │ │ └── shadow-circle.png │ ├── tree1.dae │ └── tree2.dae └── index.html ├── keyboard-controls.js ├── lib └── keyboard.polyfill.js ├── package.json └── tests ├── .jshintrc ├── __init.test.js ├── helpers.js ├── karma.conf.js └── keyboard-controls.test.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 `keyboard-controls` Component 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 | Keyboard controls for A-Frame. 6 | 7 | ## Overview 8 | 9 | Bindings and events for keyboard controls on an A-Frame VR scene. When combined with [ProxyControls.js](https://proxy-controls.donmccurdy.com) (with the [proxy-controls](https://github.com/donmccurdy/aframe-proxy-controls) component for A-Frame), can also receive remote keyboard input by WebRTC. 10 | 11 | ## Usage 12 | 13 | Movement + keyboard events: 14 | 15 | ```html 16 | 17 | 19 | 20 | 21 | ``` 22 | 23 | Keyboard events only (no movement): 24 | 25 | ```html 26 | 27 | 29 | 30 | 31 | ``` 32 | 33 | The full list of options can be seen in 34 | [`keyboard-controls.js`](keyboard-controls.js). 35 | 36 | ## Usage + Remote Device 37 | 38 | [ProxyControls.js ⇢ Docs](http://localhost:3000/#/docs#remote-device) 39 | 40 | Example: 41 | 42 | ```html 43 | 44 | 46 | 47 | 48 | ``` 49 | 50 | ## Check Keyboard State 51 | 52 | To check the pressed/unpressed state of a given [Keyboard.code](https://w3c.github.io/uievents-code/#code-value-tables), use the `isPressed()` method: 53 | 54 | ```javascript 55 | var keyboardControls = el.components['keyboard-controls']; 56 | keyboardControls.isPressed('ArrowLeft'); 57 | ``` 58 | 59 | ## Events 60 | 61 | `keyboard-controls` comes with a polyfill guaranteeing support for [KeyboardEvent.key](https://www.w3.org/TR/DOM-Level-3-Events-key/) and [KeyboardEvent.code](https://w3c.github.io/uievents-code/). When a `keydown` or `keyup` event is detected, an extra event is created with the `code` attached. Example usage: 62 | 63 | ```html 64 | 67 | 68 | ``` 69 | 70 | A complete list of `code` values may be found [here](https://w3c.github.io/uievents-code/#code-value-tables). 71 | 72 | ## Known Issues 73 | 74 | In OS X, pressing the Command/Meta (⌘) key blocks all other key events. For example, pressing `A`, pressing `⌘`, releasing `A`, and then releasing `⌘` would create a `keydown:KeyA` event, but no `keyup:KeyA`. Because of this, I do not recommend using the Command/Meta key in your apps. 75 | -------------------------------------------------------------------------------- /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('keyboard-controls', require('./keyboard-controls')); 10 | 11 | }(window.AFRAME)); 12 | -------------------------------------------------------------------------------- /dist/aframe-keyboard-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('keyboard-controls', __webpack_require__(1)); 56 | 57 | }(window.AFRAME)); 58 | 59 | 60 | /***/ }, 61 | /* 1 */ 62 | /***/ function(module, exports, __webpack_require__) { 63 | 64 | __webpack_require__(2); 65 | 66 | var MAX_DELTA = 200, 67 | PROXY_FLAG = '__keyboard-controls-proxy'; 68 | 69 | var KeyboardEvent = window.KeyboardEvent; 70 | 71 | /** 72 | * Keyboard Controls component. 73 | * 74 | * Bind keyboard events to components, or control your entities with the WASD keys. 75 | * 76 | * @namespace wasd-controls 77 | * @param {number} [easing=20] - How fast the movement decelerates. If you hold the 78 | * keys the entity moves and if you release it will stop. Easing simulates friction. 79 | * @param {number} [acceleration=65] - Determines the acceleration given 80 | * to the entity when pressing the keys. 81 | * @param {number} [angularAcceleration=Math.PI*0.25] - Determines the angular 82 | * acceleration given to the entity when pressing the keys. Only applied when 83 | * mode == 'fps'. Measured in Radians. 84 | * @param {bool} [enabled=true] - To completely enable or disable the controls 85 | * @param {string} [mode='default'] - 86 | * 'default' enforces the direction of the movement to stick to the plane 87 | * where the entity started off. 88 | * 'fps' extends 'default' by enabling "First Person Shooter" controls: W/S = 89 | * Forward/Back, Q/E = Strafe left/right, A/D = Rotate left/right 90 | * 'fly' enables 6 degrees of freedom as a diver underwater or a plane flying. 91 | * @param {string} [rollAxis='z'] - The front-to-back axis. 92 | * @param {string} [pitchAxis='x'] - The left-to-right axis. 93 | * @param {bool} [rollAxisInverted=false] - Roll axis is inverted 94 | * @param {bool} [pitchAxisInverted=false] - Pitch axis is inverted 95 | * @param {bool} [yawAxisInverted=false] - Yaw axis is inverted. Used when 96 | * mode == 'fps' 97 | */ 98 | module.exports = { 99 | schema: { 100 | easing: { default: 20 }, 101 | acceleration: { default: 65 }, 102 | angularAcceleration: { default: Math.PI / 4 }, 103 | enabled: { default: true }, 104 | mode: { default: 'default', oneOf: ['default', 'fly', 'fps']}, 105 | rollAxis: { default: 'z', oneOf: [ 'x', 'y', 'z' ] }, 106 | pitchAxis: { default: 'x', oneOf: [ 'x', 'y', 'z' ] }, 107 | rollAxisInverted: { default: false }, 108 | rollAxisEnabled: { default: true }, 109 | pitchAxisInverted: { default: false }, 110 | pitchAxisEnabled: { default: true }, 111 | yawAxisInverted: { default: false }, 112 | debug: { default: false } 113 | }, 114 | 115 | init: function () { 116 | this.velocity = new THREE.Vector3(); 117 | this.angularVelocity = 0; 118 | this.localKeys = {}; 119 | this.listeners = { 120 | keydown: this.onKeyDown.bind(this), 121 | keyup: this.onKeyUp.bind(this), 122 | blur: this.onBlur.bind(this) 123 | }; 124 | this.attachEventListeners(); 125 | }, 126 | 127 | /******************************************************************* 128 | * Movement 129 | */ 130 | 131 | tick: (function () { 132 | var upVector = new THREE.Vector3(0, 1, 0); 133 | var rotation = new THREE.Euler(0, 0, 0, 'YXZ'); 134 | return function (t, dt) { 135 | var data = this.data; 136 | var acceleration = data.acceleration; 137 | var angularAcceleration = data.angularAcceleration; 138 | var easing = data.easing; 139 | var velocity = this.velocity; 140 | var keys = this.getKeys(); 141 | var movementVector; 142 | var pitchAxis = data.pitchAxis; 143 | var rollAxis = data.rollAxis; 144 | var pitchSign = data.pitchAxisInverted ? -1 : 1; 145 | var rollSign = data.rollAxisInverted ? -1 : 1; 146 | var yawSign = data.yawAxisInverted ? 1 : -1; 147 | var el = this.el; 148 | var strafeLeft = data.mode === 'fps' ? ['KeyQ', 'ArrowLeft'] : ['KeyA', 'ArrowLeft']; 149 | var strafeRight = data.mode === 'fps' ? ['KeyE', 'ArrowRight'] : ['KeyD', 'ArrowRight']; 150 | 151 | // If data changed or FPS too low, reset velocity. 152 | if (isNaN(dt) || dt > MAX_DELTA) { 153 | velocity[pitchAxis] = 0; 154 | velocity[rollAxis] = 0; 155 | this.angularVelocity = 0; 156 | return; 157 | } 158 | 159 | velocity[pitchAxis] -= velocity[pitchAxis] * easing * dt / 1000; 160 | velocity[rollAxis] -= velocity[rollAxis] * easing * dt / 1000; 161 | this.angularVelocity -= this.angularVelocity * easing * dt / 1000; 162 | 163 | var position = el.getAttribute('position'); 164 | 165 | if (data.enabled) { 166 | if (data.pitchAxisEnabled) { 167 | if (keys[strafeLeft[0]] || keys[strafeLeft[1]]) { 168 | velocity[pitchAxis] -= pitchSign * acceleration * dt / 1000; 169 | } 170 | if (keys[strafeRight[0]] || keys[strafeRight[1]]) { 171 | velocity[pitchAxis] += pitchSign * acceleration * dt / 1000; 172 | } 173 | } 174 | if (data.rollAxisEnabled) { 175 | if (keys.KeyW || keys.ArrowUp) { 176 | velocity[rollAxis] -= rollSign * acceleration * dt / 1000; 177 | } 178 | if (keys.KeyS || keys.ArrowDown) { 179 | velocity[rollAxis] += rollSign * acceleration * dt / 1000; 180 | } 181 | } 182 | if (data.mode === 'fps') { 183 | if (keys.KeyA) { 184 | this.angularVelocity -= yawSign * angularAcceleration * dt / 1000; 185 | } 186 | if (keys.KeyD) { 187 | this.angularVelocity += yawSign * angularAcceleration * dt / 1000; 188 | } 189 | } 190 | } 191 | 192 | if (data.mode === 'fps') { 193 | this.rotateOnAxis(rotation, upVector, this.angularVelocity); 194 | 195 | el.setAttribute('rotation', { 196 | x: THREE.Math.radToDeg(rotation.x), 197 | y: THREE.Math.radToDeg(rotation.y), 198 | z: THREE.Math.radToDeg(rotation.z) 199 | }); 200 | } 201 | 202 | movementVector = this.getMovementVector(dt); 203 | el.object3D.translateX(movementVector.x); 204 | el.object3D.translateY(movementVector.y); 205 | el.object3D.translateZ(movementVector.z); 206 | 207 | el.setAttribute('position', { 208 | x: position.x + movementVector.x, 209 | y: position.y + movementVector.y, 210 | z: position.z + movementVector.z 211 | }); 212 | }; 213 | })(), 214 | 215 | 216 | rotateOnAxis: (function () { 217 | 218 | var quaternion = new THREE.Quaternion(); 219 | var eulerAsQuaternion = new THREE.Quaternion(); 220 | 221 | return function (euler, axis, angle) { 222 | quaternion.setFromAxisAngle(axis, angle); 223 | eulerAsQuaternion.setFromEuler(euler); 224 | eulerAsQuaternion.multiply(quaternion); 225 | euler.setFromQuaternion(eulerAsQuaternion, euler.order); 226 | }; 227 | 228 | })(), 229 | 230 | getMovementVector: (function () { 231 | var direction = new THREE.Vector3(0, 0, 0); 232 | var rotation = new THREE.Euler(0, 0, 0, 'YXZ'); 233 | return function (dt) { 234 | var velocity = this.velocity; 235 | var elRotation = this.el.getAttribute('rotation'); 236 | direction.copy(velocity); 237 | direction.multiplyScalar(dt / 1000); 238 | if (!elRotation) { return direction; } 239 | if (this.data.mode !== 'fly') { elRotation.x = 0; } 240 | rotation.set(THREE.Math.degToRad(elRotation.x), 241 | THREE.Math.degToRad(elRotation.y), 0); 242 | direction.applyEuler(rotation); 243 | return direction; 244 | }; 245 | })(), 246 | 247 | /******************************************************************* 248 | * Events 249 | */ 250 | 251 | play: function () { 252 | this.attachEventListeners(); 253 | }, 254 | 255 | pause: function () { 256 | this.removeEventListeners(); 257 | }, 258 | 259 | remove: function () { 260 | this.pause(); 261 | }, 262 | 263 | attachEventListeners: function () { 264 | window.addEventListener('keydown', this.listeners.keydown, false); 265 | window.addEventListener('keyup', this.listeners.keyup, false); 266 | window.addEventListener('blur', this.listeners.blur, false); 267 | }, 268 | 269 | removeEventListeners: function () { 270 | window.removeEventListener('keydown', this.listeners.keydown); 271 | window.removeEventListener('keyup', this.listeners.keyup); 272 | window.removeEventListener('blur', this.listeners.blur); 273 | }, 274 | 275 | onKeyDown: function (event) { 276 | this.localKeys[event.code] = true; 277 | this.emit(event); 278 | }, 279 | 280 | onKeyUp: function (event) { 281 | delete this.localKeys[event.code]; 282 | this.emit(event); 283 | }, 284 | 285 | onBlur: function () { 286 | for (var code in this.localKeys) { 287 | if (this.localKeys.hasOwnProperty(code)) { 288 | delete this.localKeys[code]; 289 | } 290 | } 291 | }, 292 | 293 | emit: function (event) { 294 | // TODO - keydown only initially? 295 | // TODO - where the f is the spacebar 296 | 297 | // Emit original event. 298 | if (PROXY_FLAG in event) { 299 | // TODO - Method never triggered. 300 | this.el.emit(event.type, event); 301 | } 302 | 303 | // Emit convenience event, identifying key. 304 | this.el.emit(event.type + ':' + event.code, new KeyboardEvent(event.type, event)); 305 | if (this.data.debug) console.log(event.type + ':' + event.code); 306 | }, 307 | 308 | /******************************************************************* 309 | * Accessors 310 | */ 311 | 312 | isPressed: function (code) { 313 | return code in this.getKeys(); 314 | }, 315 | 316 | getKeys: function () { 317 | if (this.isProxied()) { 318 | return this.el.sceneEl.components['proxy-controls'].getKeyboard(); 319 | } 320 | return this.localKeys; 321 | }, 322 | 323 | isProxied: function () { 324 | var proxyControls = this.el.sceneEl.components['proxy-controls']; 325 | return proxyControls && proxyControls.isConnected(); 326 | } 327 | 328 | }; 329 | 330 | 331 | /***/ }, 332 | /* 2 */ 333 | /***/ function(module, exports) { 334 | 335 | /** 336 | * Polyfill for the additional KeyboardEvent properties defined in the D3E and 337 | * D4E draft specifications, by @inexorabletash. 338 | * 339 | * See: https://github.com/inexorabletash/polyfill 340 | */ 341 | (function(global) { 342 | var nativeKeyboardEvent = ('KeyboardEvent' in global); 343 | if (!nativeKeyboardEvent) 344 | global.KeyboardEvent = function KeyboardEvent() { throw TypeError('Illegal constructor'); }; 345 | 346 | global.KeyboardEvent.DOM_KEY_LOCATION_STANDARD = 0x00; // Default or unknown location 347 | global.KeyboardEvent.DOM_KEY_LOCATION_LEFT = 0x01; // e.g. Left Alt key 348 | global.KeyboardEvent.DOM_KEY_LOCATION_RIGHT = 0x02; // e.g. Right Alt key 349 | global.KeyboardEvent.DOM_KEY_LOCATION_NUMPAD = 0x03; // e.g. Numpad 0 or + 350 | 351 | var STANDARD = window.KeyboardEvent.DOM_KEY_LOCATION_STANDARD, 352 | LEFT = window.KeyboardEvent.DOM_KEY_LOCATION_LEFT, 353 | RIGHT = window.KeyboardEvent.DOM_KEY_LOCATION_RIGHT, 354 | NUMPAD = window.KeyboardEvent.DOM_KEY_LOCATION_NUMPAD; 355 | 356 | //-------------------------------------------------------------------- 357 | // 358 | // Utilities 359 | // 360 | //-------------------------------------------------------------------- 361 | 362 | function contains(s, ss) { return String(s).indexOf(ss) !== -1; } 363 | 364 | var os = (function() { 365 | if (contains(navigator.platform, 'Win')) { return 'win'; } 366 | if (contains(navigator.platform, 'Mac')) { return 'mac'; } 367 | if (contains(navigator.platform, 'CrOS')) { return 'cros'; } 368 | if (contains(navigator.platform, 'Linux')) { return 'linux'; } 369 | if (contains(navigator.userAgent, 'iPad') || contains(navigator.platform, 'iPod') || contains(navigator.platform, 'iPhone')) { return 'ios'; } 370 | return ''; 371 | } ()); 372 | 373 | var browser = (function() { 374 | if (contains(navigator.userAgent, 'Chrome/')) { return 'chrome'; } 375 | if (contains(navigator.vendor, 'Apple')) { return 'safari'; } 376 | if (contains(navigator.userAgent, 'MSIE')) { return 'ie'; } 377 | if (contains(navigator.userAgent, 'Gecko/')) { return 'moz'; } 378 | if (contains(navigator.userAgent, 'Opera/')) { return 'opera'; } 379 | return ''; 380 | } ()); 381 | 382 | var browser_os = browser + '-' + os; 383 | 384 | function mergeIf(baseTable, select, table) { 385 | if (browser_os === select || browser === select || os === select) { 386 | Object.keys(table).forEach(function(keyCode) { 387 | baseTable[keyCode] = table[keyCode]; 388 | }); 389 | } 390 | } 391 | 392 | function remap(o, key) { 393 | var r = {}; 394 | Object.keys(o).forEach(function(k) { 395 | var item = o[k]; 396 | if (key in item) { 397 | r[item[key]] = item; 398 | } 399 | }); 400 | return r; 401 | } 402 | 403 | function invert(o) { 404 | var r = {}; 405 | Object.keys(o).forEach(function(k) { 406 | r[o[k]] = k; 407 | }); 408 | return r; 409 | } 410 | 411 | //-------------------------------------------------------------------- 412 | // 413 | // Generic Mappings 414 | // 415 | //-------------------------------------------------------------------- 416 | 417 | // "keyInfo" is a dictionary: 418 | // code: string - name from DOM Level 3 KeyboardEvent code Values 419 | // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3Events-code.html 420 | // location (optional): number - one of the DOM_KEY_LOCATION values 421 | // keyCap (optional): string - keyboard label in en-US locale 422 | // USB code Usage ID from page 0x07 unless otherwise noted (Informative) 423 | 424 | // Map of keyCode to keyInfo 425 | var keyCodeToInfoTable = { 426 | // 0x01 - VK_LBUTTON 427 | // 0x02 - VK_RBUTTON 428 | 0x03: { code: 'Cancel' }, // [USB: 0x9b] char \x0018 ??? (Not in D3E) 429 | // 0x04 - VK_MBUTTON 430 | // 0x05 - VK_XBUTTON1 431 | // 0x06 - VK_XBUTTON2 432 | 0x06: { code: 'Help' }, // [USB: 0x75] ??? 433 | // 0x07 - undefined 434 | 0x08: { code: 'Backspace' }, // [USB: 0x2a] Labelled Delete on Macintosh keyboards. 435 | 0x09: { code: 'Tab' }, // [USB: 0x2b] 436 | // 0x0A-0x0B - reserved 437 | 0X0C: { code: 'Clear' }, // [USB: 0x9c] NumPad Center (Not in D3E) 438 | 0X0D: { code: 'Enter' }, // [USB: 0x28] 439 | // 0x0E-0x0F - undefined 440 | 441 | 0x10: { code: 'Shift' }, 442 | 0x11: { code: 'Control' }, 443 | 0x12: { code: 'Alt' }, 444 | 0x13: { code: 'Pause' }, // [USB: 0x48] 445 | 0x14: { code: 'CapsLock' }, // [USB: 0x39] 446 | 0x15: { code: 'KanaMode' }, // [USB: 0x88] - "HangulMode" for Korean layout 447 | 0x16: { code: 'HangulMode' }, // [USB: 0x90] 0x15 as well in MSDN VK table ??? 448 | 0x17: { code: 'JunjaMode' }, // (Not in D3E) 449 | 0x18: { code: 'FinalMode' }, // (Not in D3E) 450 | 0x19: { code: 'KanjiMode' }, // [USB: 0x91] - "HanjaMode" for Korean layout 451 | // 0x1A - undefined 452 | 0x1B: { code: 'Escape' }, // [USB: 0x29] 453 | 0x1C: { code: 'Convert' }, // [USB: 0x8a] 454 | 0x1D: { code: 'NonConvert' }, // [USB: 0x8b] 455 | 0x1E: { code: 'Accept' }, // (Not in D3E) 456 | 0x1F: { code: 'ModeChange' }, // (Not in D3E) 457 | 458 | 0x20: { code: 'Space' }, // [USB: 0x2c] 459 | 0x21: { code: 'PageUp' }, // [USB: 0x4b] 460 | 0x22: { code: 'PageDown' }, // [USB: 0x4e] 461 | 0x23: { code: 'End' }, // [USB: 0x4d] 462 | 0x24: { code: 'Home' }, // [USB: 0x4a] 463 | 0x25: { code: 'ArrowLeft' }, // [USB: 0x50] 464 | 0x26: { code: 'ArrowUp' }, // [USB: 0x52] 465 | 0x27: { code: 'ArrowRight' }, // [USB: 0x4f] 466 | 0x28: { code: 'ArrowDown' }, // [USB: 0x51] 467 | 0x29: { code: 'Select' }, // (Not in D3E) 468 | 0x2A: { code: 'Print' }, // (Not in D3E) 469 | 0x2B: { code: 'Execute' }, // [USB: 0x74] (Not in D3E) 470 | 0x2C: { code: 'PrintScreen' }, // [USB: 0x46] 471 | 0x2D: { code: 'Insert' }, // [USB: 0x49] 472 | 0x2E: { code: 'Delete' }, // [USB: 0x4c] 473 | 0x2F: { code: 'Help' }, // [USB: 0x75] ??? 474 | 475 | 0x30: { code: 'Digit0', keyCap: '0' }, // [USB: 0x27] 0) 476 | 0x31: { code: 'Digit1', keyCap: '1' }, // [USB: 0x1e] 1! 477 | 0x32: { code: 'Digit2', keyCap: '2' }, // [USB: 0x1f] 2@ 478 | 0x33: { code: 'Digit3', keyCap: '3' }, // [USB: 0x20] 3# 479 | 0x34: { code: 'Digit4', keyCap: '4' }, // [USB: 0x21] 4$ 480 | 0x35: { code: 'Digit5', keyCap: '5' }, // [USB: 0x22] 5% 481 | 0x36: { code: 'Digit6', keyCap: '6' }, // [USB: 0x23] 6^ 482 | 0x37: { code: 'Digit7', keyCap: '7' }, // [USB: 0x24] 7& 483 | 0x38: { code: 'Digit8', keyCap: '8' }, // [USB: 0x25] 8* 484 | 0x39: { code: 'Digit9', keyCap: '9' }, // [USB: 0x26] 9( 485 | // 0x3A-0x40 - undefined 486 | 487 | 0x41: { code: 'KeyA', keyCap: 'a' }, // [USB: 0x04] 488 | 0x42: { code: 'KeyB', keyCap: 'b' }, // [USB: 0x05] 489 | 0x43: { code: 'KeyC', keyCap: 'c' }, // [USB: 0x06] 490 | 0x44: { code: 'KeyD', keyCap: 'd' }, // [USB: 0x07] 491 | 0x45: { code: 'KeyE', keyCap: 'e' }, // [USB: 0x08] 492 | 0x46: { code: 'KeyF', keyCap: 'f' }, // [USB: 0x09] 493 | 0x47: { code: 'KeyG', keyCap: 'g' }, // [USB: 0x0a] 494 | 0x48: { code: 'KeyH', keyCap: 'h' }, // [USB: 0x0b] 495 | 0x49: { code: 'KeyI', keyCap: 'i' }, // [USB: 0x0c] 496 | 0x4A: { code: 'KeyJ', keyCap: 'j' }, // [USB: 0x0d] 497 | 0x4B: { code: 'KeyK', keyCap: 'k' }, // [USB: 0x0e] 498 | 0x4C: { code: 'KeyL', keyCap: 'l' }, // [USB: 0x0f] 499 | 0x4D: { code: 'KeyM', keyCap: 'm' }, // [USB: 0x10] 500 | 0x4E: { code: 'KeyN', keyCap: 'n' }, // [USB: 0x11] 501 | 0x4F: { code: 'KeyO', keyCap: 'o' }, // [USB: 0x12] 502 | 503 | 0x50: { code: 'KeyP', keyCap: 'p' }, // [USB: 0x13] 504 | 0x51: { code: 'KeyQ', keyCap: 'q' }, // [USB: 0x14] 505 | 0x52: { code: 'KeyR', keyCap: 'r' }, // [USB: 0x15] 506 | 0x53: { code: 'KeyS', keyCap: 's' }, // [USB: 0x16] 507 | 0x54: { code: 'KeyT', keyCap: 't' }, // [USB: 0x17] 508 | 0x55: { code: 'KeyU', keyCap: 'u' }, // [USB: 0x18] 509 | 0x56: { code: 'KeyV', keyCap: 'v' }, // [USB: 0x19] 510 | 0x57: { code: 'KeyW', keyCap: 'w' }, // [USB: 0x1a] 511 | 0x58: { code: 'KeyX', keyCap: 'x' }, // [USB: 0x1b] 512 | 0x59: { code: 'KeyY', keyCap: 'y' }, // [USB: 0x1c] 513 | 0x5A: { code: 'KeyZ', keyCap: 'z' }, // [USB: 0x1d] 514 | 0x5B: { code: 'OSLeft', location: LEFT }, // [USB: 0xe3] 515 | 0x5C: { code: 'OSRight', location: RIGHT }, // [USB: 0xe7] 516 | 0x5D: { code: 'ContextMenu' }, // [USB: 0x65] Context Menu 517 | // 0x5E - reserved 518 | 0x5F: { code: 'Standby' }, // [USB: 0x82] Sleep 519 | 520 | 0x60: { code: 'Numpad0', keyCap: '0', location: NUMPAD }, // [USB: 0x62] 521 | 0x61: { code: 'Numpad1', keyCap: '1', location: NUMPAD }, // [USB: 0x59] 522 | 0x62: { code: 'Numpad2', keyCap: '2', location: NUMPAD }, // [USB: 0x5a] 523 | 0x63: { code: 'Numpad3', keyCap: '3', location: NUMPAD }, // [USB: 0x5b] 524 | 0x64: { code: 'Numpad4', keyCap: '4', location: NUMPAD }, // [USB: 0x5c] 525 | 0x65: { code: 'Numpad5', keyCap: '5', location: NUMPAD }, // [USB: 0x5d] 526 | 0x66: { code: 'Numpad6', keyCap: '6', location: NUMPAD }, // [USB: 0x5e] 527 | 0x67: { code: 'Numpad7', keyCap: '7', location: NUMPAD }, // [USB: 0x5f] 528 | 0x68: { code: 'Numpad8', keyCap: '8', location: NUMPAD }, // [USB: 0x60] 529 | 0x69: { code: 'Numpad9', keyCap: '9', location: NUMPAD }, // [USB: 0x61] 530 | 0x6A: { code: 'NumpadMultiply', keyCap: '*', location: NUMPAD }, // [USB: 0x55] 531 | 0x6B: { code: 'NumpadAdd', keyCap: '+', location: NUMPAD }, // [USB: 0x57] 532 | 0x6C: { code: 'NumpadComma', keyCap: ',', location: NUMPAD }, // [USB: 0x85] 533 | 0x6D: { code: 'NumpadSubtract', keyCap: '-', location: NUMPAD }, // [USB: 0x56] 534 | 0x6E: { code: 'NumpadDecimal', keyCap: '.', location: NUMPAD }, // [USB: 0x63] 535 | 0x6F: { code: 'NumpadDivide', keyCap: '/', location: NUMPAD }, // [USB: 0x54] 536 | 537 | 0x70: { code: 'F1' }, // [USB: 0x3a] 538 | 0x71: { code: 'F2' }, // [USB: 0x3b] 539 | 0x72: { code: 'F3' }, // [USB: 0x3c] 540 | 0x73: { code: 'F4' }, // [USB: 0x3d] 541 | 0x74: { code: 'F5' }, // [USB: 0x3e] 542 | 0x75: { code: 'F6' }, // [USB: 0x3f] 543 | 0x76: { code: 'F7' }, // [USB: 0x40] 544 | 0x77: { code: 'F8' }, // [USB: 0x41] 545 | 0x78: { code: 'F9' }, // [USB: 0x42] 546 | 0x79: { code: 'F10' }, // [USB: 0x43] 547 | 0x7A: { code: 'F11' }, // [USB: 0x44] 548 | 0x7B: { code: 'F12' }, // [USB: 0x45] 549 | 0x7C: { code: 'F13' }, // [USB: 0x68] 550 | 0x7D: { code: 'F14' }, // [USB: 0x69] 551 | 0x7E: { code: 'F15' }, // [USB: 0x6a] 552 | 0x7F: { code: 'F16' }, // [USB: 0x6b] 553 | 554 | 0x80: { code: 'F17' }, // [USB: 0x6c] 555 | 0x81: { code: 'F18' }, // [USB: 0x6d] 556 | 0x82: { code: 'F19' }, // [USB: 0x6e] 557 | 0x83: { code: 'F20' }, // [USB: 0x6f] 558 | 0x84: { code: 'F21' }, // [USB: 0x70] 559 | 0x85: { code: 'F22' }, // [USB: 0x71] 560 | 0x86: { code: 'F23' }, // [USB: 0x72] 561 | 0x87: { code: 'F24' }, // [USB: 0x73] 562 | // 0x88-0x8F - unassigned 563 | 564 | 0x90: { code: 'NumLock', location: NUMPAD }, // [USB: 0x53] 565 | 0x91: { code: 'ScrollLock' }, // [USB: 0x47] 566 | // 0x92-0x96 - OEM specific 567 | // 0x97-0x9F - unassigned 568 | 569 | // NOTE: 0xA0-0xA5 usually mapped to 0x10-0x12 in browsers 570 | 0xA0: { code: 'ShiftLeft', location: LEFT }, // [USB: 0xe1] 571 | 0xA1: { code: 'ShiftRight', location: RIGHT }, // [USB: 0xe5] 572 | 0xA2: { code: 'ControlLeft', location: LEFT }, // [USB: 0xe0] 573 | 0xA3: { code: 'ControlRight', location: RIGHT }, // [USB: 0xe4] 574 | 0xA4: { code: 'AltLeft', location: LEFT }, // [USB: 0xe2] 575 | 0xA5: { code: 'AltRight', location: RIGHT }, // [USB: 0xe6] 576 | 577 | 0xA6: { code: 'BrowserBack' }, // [USB: 0x0c/0x0224] 578 | 0xA7: { code: 'BrowserForward' }, // [USB: 0x0c/0x0225] 579 | 0xA8: { code: 'BrowserRefresh' }, // [USB: 0x0c/0x0227] 580 | 0xA9: { code: 'BrowserStop' }, // [USB: 0x0c/0x0226] 581 | 0xAA: { code: 'BrowserSearch' }, // [USB: 0x0c/0x0221] 582 | 0xAB: { code: 'BrowserFavorites' }, // [USB: 0x0c/0x0228] 583 | 0xAC: { code: 'BrowserHome' }, // [USB: 0x0c/0x0222] 584 | 0xAD: { code: 'VolumeMute' }, // [USB: 0x7f] 585 | 0xAE: { code: 'VolumeDown' }, // [USB: 0x81] 586 | 0xAF: { code: 'VolumeUp' }, // [USB: 0x80] 587 | 588 | 0xB0: { code: 'MediaTrackNext' }, // [USB: 0x0c/0x00b5] 589 | 0xB1: { code: 'MediaTrackPrevious' }, // [USB: 0x0c/0x00b6] 590 | 0xB2: { code: 'MediaStop' }, // [USB: 0x0c/0x00b7] 591 | 0xB3: { code: 'MediaPlayPause' }, // [USB: 0x0c/0x00cd] 592 | 0xB4: { code: 'LaunchMail' }, // [USB: 0x0c/0x018a] 593 | 0xB5: { code: 'MediaSelect' }, 594 | 0xB6: { code: 'LaunchApp1' }, 595 | 0xB7: { code: 'LaunchApp2' }, 596 | // 0xB8-0xB9 - reserved 597 | 0xBA: { code: 'Semicolon', keyCap: ';' }, // [USB: 0x33] ;: (US Standard 101) 598 | 0xBB: { code: 'Equal', keyCap: '=' }, // [USB: 0x2e] =+ 599 | 0xBC: { code: 'Comma', keyCap: ',' }, // [USB: 0x36] ,< 600 | 0xBD: { code: 'Minus', keyCap: '-' }, // [USB: 0x2d] -_ 601 | 0xBE: { code: 'Period', keyCap: '.' }, // [USB: 0x37] .> 602 | 0xBF: { code: 'Slash', keyCap: '/' }, // [USB: 0x38] /? (US Standard 101) 603 | 604 | 0xC0: { code: 'Backquote', keyCap: '`' }, // [USB: 0x35] `~ (US Standard 101) 605 | // 0xC1-0xCF - reserved 606 | 607 | // 0xD0-0xD7 - reserved 608 | // 0xD8-0xDA - unassigned 609 | 0xDB: { code: 'BracketLeft', keyCap: '[' }, // [USB: 0x2f] [{ (US Standard 101) 610 | 0xDC: { code: 'Backslash', keyCap: '\\' }, // [USB: 0x31] \| (US Standard 101) 611 | 0xDD: { code: 'BracketRight', keyCap: ']' }, // [USB: 0x30] ]} (US Standard 101) 612 | 0xDE: { code: 'Quote', keyCap: '\'' }, // [USB: 0x34] '" (US Standard 101) 613 | // 0xDF - miscellaneous/varies 614 | 615 | // 0xE0 - reserved 616 | // 0xE1 - OEM specific 617 | 0xE2: { code: 'IntlBackslash', keyCap: '\\' }, // [USB: 0x64] \| (UK Standard 102) 618 | // 0xE3-0xE4 - OEM specific 619 | 0xE5: { code: 'Process' }, // (Not in D3E) 620 | // 0xE6 - OEM specific 621 | // 0xE7 - VK_PACKET 622 | // 0xE8 - unassigned 623 | // 0xE9-0xEF - OEM specific 624 | 625 | // 0xF0-0xF5 - OEM specific 626 | 0xF6: { code: 'Attn' }, // [USB: 0x9a] (Not in D3E) 627 | 0xF7: { code: 'CrSel' }, // [USB: 0xa3] (Not in D3E) 628 | 0xF8: { code: 'ExSel' }, // [USB: 0xa4] (Not in D3E) 629 | 0xF9: { code: 'EraseEof' }, // (Not in D3E) 630 | 0xFA: { code: 'Play' }, // (Not in D3E) 631 | 0xFB: { code: 'ZoomToggle' }, // (Not in D3E) 632 | // 0xFC - VK_NONAME - reserved 633 | // 0xFD - VK_PA1 634 | 0xFE: { code: 'Clear' } // [USB: 0x9c] (Not in D3E) 635 | }; 636 | 637 | // No legacy keyCode, but listed in D3E: 638 | 639 | // code: usb 640 | // 'IntlHash': 0x070032, 641 | // 'IntlRo': 0x070087, 642 | // 'IntlYen': 0x070089, 643 | // 'NumpadBackspace': 0x0700bb, 644 | // 'NumpadClear': 0x0700d8, 645 | // 'NumpadClearEntry': 0x0700d9, 646 | // 'NumpadMemoryAdd': 0x0700d3, 647 | // 'NumpadMemoryClear': 0x0700d2, 648 | // 'NumpadMemoryRecall': 0x0700d1, 649 | // 'NumpadMemoryStore': 0x0700d0, 650 | // 'NumpadMemorySubtract': 0x0700d4, 651 | // 'NumpadParenLeft': 0x0700b6, 652 | // 'NumpadParenRight': 0x0700b7, 653 | 654 | //-------------------------------------------------------------------- 655 | // 656 | // Browser/OS Specific Mappings 657 | // 658 | //-------------------------------------------------------------------- 659 | 660 | mergeIf(keyCodeToInfoTable, 661 | 'moz', { 662 | 0x3B: { code: 'Semicolon', keyCap: ';' }, // [USB: 0x33] ;: (US Standard 101) 663 | 0x3D: { code: 'Equal', keyCap: '=' }, // [USB: 0x2e] =+ 664 | 0x6B: { code: 'Equal', keyCap: '=' }, // [USB: 0x2e] =+ 665 | 0x6D: { code: 'Minus', keyCap: '-' }, // [USB: 0x2d] -_ 666 | 0xBB: { code: 'NumpadAdd', keyCap: '+', location: NUMPAD }, // [USB: 0x57] 667 | 0xBD: { code: 'NumpadSubtract', keyCap: '-', location: NUMPAD } // [USB: 0x56] 668 | }); 669 | 670 | mergeIf(keyCodeToInfoTable, 671 | 'moz-mac', { 672 | 0x0C: { code: 'NumLock', location: NUMPAD }, // [USB: 0x53] 673 | 0xAD: { code: 'Minus', keyCap: '-' } // [USB: 0x2d] -_ 674 | }); 675 | 676 | mergeIf(keyCodeToInfoTable, 677 | 'moz-win', { 678 | 0xAD: { code: 'Minus', keyCap: '-' } // [USB: 0x2d] -_ 679 | }); 680 | 681 | mergeIf(keyCodeToInfoTable, 682 | 'chrome-mac', { 683 | 0x5D: { code: 'OSRight', location: RIGHT } // [USB: 0xe7] 684 | }); 685 | 686 | // Windows via Bootcamp (!) 687 | if (0) { 688 | mergeIf(keyCodeToInfoTable, 689 | 'chrome-win', { 690 | 0xC0: { code: 'Quote', keyCap: '\'' }, // [USB: 0x34] '" (US Standard 101) 691 | 0xDE: { code: 'Backslash', keyCap: '\\' }, // [USB: 0x31] \| (US Standard 101) 692 | 0xDF: { code: 'Backquote', keyCap: '`' } // [USB: 0x35] `~ (US Standard 101) 693 | }); 694 | 695 | mergeIf(keyCodeToInfoTable, 696 | 'ie', { 697 | 0xC0: { code: 'Quote', keyCap: '\'' }, // [USB: 0x34] '" (US Standard 101) 698 | 0xDE: { code: 'Backslash', keyCap: '\\' }, // [USB: 0x31] \| (US Standard 101) 699 | 0xDF: { code: 'Backquote', keyCap: '`' } // [USB: 0x35] `~ (US Standard 101) 700 | }); 701 | } 702 | 703 | mergeIf(keyCodeToInfoTable, 704 | 'safari', { 705 | 0x03: { code: 'Enter' }, // [USB: 0x28] old Safari 706 | 0x19: { code: 'Tab' } // [USB: 0x2b] old Safari for Shift+Tab 707 | }); 708 | 709 | mergeIf(keyCodeToInfoTable, 710 | 'ios', { 711 | 0x0A: { code: 'Enter', location: STANDARD } // [USB: 0x28] 712 | }); 713 | 714 | mergeIf(keyCodeToInfoTable, 715 | 'safari-mac', { 716 | 0x5B: { code: 'OSLeft', location: LEFT }, // [USB: 0xe3] 717 | 0x5D: { code: 'OSRight', location: RIGHT }, // [USB: 0xe7] 718 | 0xE5: { code: 'KeyQ', keyCap: 'Q' } // [USB: 0x14] On alternate presses, Ctrl+Q sends this 719 | }); 720 | 721 | //-------------------------------------------------------------------- 722 | // 723 | // Identifier Mappings 724 | // 725 | //-------------------------------------------------------------------- 726 | 727 | // Cases where newer-ish browsers send keyIdentifier which can be 728 | // used to disambiguate keys. 729 | 730 | // keyIdentifierTable[keyIdentifier] -> keyInfo 731 | 732 | var keyIdentifierTable = {}; 733 | if ('cros' === os) { 734 | keyIdentifierTable['U+00A0'] = { code: 'ShiftLeft', location: LEFT }; 735 | keyIdentifierTable['U+00A1'] = { code: 'ShiftRight', location: RIGHT }; 736 | keyIdentifierTable['U+00A2'] = { code: 'ControlLeft', location: LEFT }; 737 | keyIdentifierTable['U+00A3'] = { code: 'ControlRight', location: RIGHT }; 738 | keyIdentifierTable['U+00A4'] = { code: 'AltLeft', location: LEFT }; 739 | keyIdentifierTable['U+00A5'] = { code: 'AltRight', location: RIGHT }; 740 | } 741 | if ('chrome-mac' === browser_os) { 742 | keyIdentifierTable['U+0010'] = { code: 'ContextMenu' }; 743 | } 744 | if ('safari-mac' === browser_os) { 745 | keyIdentifierTable['U+0010'] = { code: 'ContextMenu' }; 746 | } 747 | if ('ios' === os) { 748 | // These only generate keyup events 749 | keyIdentifierTable['U+0010'] = { code: 'Function' }; 750 | 751 | keyIdentifierTable['U+001C'] = { code: 'ArrowLeft' }; 752 | keyIdentifierTable['U+001D'] = { code: 'ArrowRight' }; 753 | keyIdentifierTable['U+001E'] = { code: 'ArrowUp' }; 754 | keyIdentifierTable['U+001F'] = { code: 'ArrowDown' }; 755 | 756 | keyIdentifierTable['U+0001'] = { code: 'Home' }; // [USB: 0x4a] Fn + ArrowLeft 757 | keyIdentifierTable['U+0004'] = { code: 'End' }; // [USB: 0x4d] Fn + ArrowRight 758 | keyIdentifierTable['U+000B'] = { code: 'PageUp' }; // [USB: 0x4b] Fn + ArrowUp 759 | keyIdentifierTable['U+000C'] = { code: 'PageDown' }; // [USB: 0x4e] Fn + ArrowDown 760 | } 761 | 762 | //-------------------------------------------------------------------- 763 | // 764 | // Location Mappings 765 | // 766 | //-------------------------------------------------------------------- 767 | 768 | // Cases where newer-ish browsers send location/keyLocation which 769 | // can be used to disambiguate keys. 770 | 771 | // locationTable[location][keyCode] -> keyInfo 772 | var locationTable = []; 773 | locationTable[LEFT] = { 774 | 0x10: { code: 'ShiftLeft', location: LEFT }, // [USB: 0xe1] 775 | 0x11: { code: 'ControlLeft', location: LEFT }, // [USB: 0xe0] 776 | 0x12: { code: 'AltLeft', location: LEFT } // [USB: 0xe2] 777 | }; 778 | locationTable[RIGHT] = { 779 | 0x10: { code: 'ShiftRight', location: RIGHT }, // [USB: 0xe5] 780 | 0x11: { code: 'ControlRight', location: RIGHT }, // [USB: 0xe4] 781 | 0x12: { code: 'AltRight', location: RIGHT } // [USB: 0xe6] 782 | }; 783 | locationTable[NUMPAD] = { 784 | 0x0D: { code: 'NumpadEnter', location: NUMPAD } // [USB: 0x58] 785 | }; 786 | 787 | mergeIf(locationTable[NUMPAD], 'moz', { 788 | 0x6D: { code: 'NumpadSubtract', location: NUMPAD }, // [USB: 0x56] 789 | 0x6B: { code: 'NumpadAdd', location: NUMPAD } // [USB: 0x57] 790 | }); 791 | mergeIf(locationTable[LEFT], 'moz-mac', { 792 | 0xE0: { code: 'OSLeft', location: LEFT } // [USB: 0xe3] 793 | }); 794 | mergeIf(locationTable[RIGHT], 'moz-mac', { 795 | 0xE0: { code: 'OSRight', location: RIGHT } // [USB: 0xe7] 796 | }); 797 | mergeIf(locationTable[RIGHT], 'moz-win', { 798 | 0x5B: { code: 'OSRight', location: RIGHT } // [USB: 0xe7] 799 | }); 800 | 801 | 802 | mergeIf(locationTable[RIGHT], 'mac', { 803 | 0x5D: { code: 'OSRight', location: RIGHT } // [USB: 0xe7] 804 | }); 805 | 806 | mergeIf(locationTable[NUMPAD], 'chrome-mac', { 807 | 0x0C: { code: 'NumLock', location: NUMPAD } // [USB: 0x53] 808 | }); 809 | 810 | mergeIf(locationTable[NUMPAD], 'safari-mac', { 811 | 0x0C: { code: 'NumLock', location: NUMPAD }, // [USB: 0x53] 812 | 0xBB: { code: 'NumpadAdd', location: NUMPAD }, // [USB: 0x57] 813 | 0xBD: { code: 'NumpadSubtract', location: NUMPAD }, // [USB: 0x56] 814 | 0xBE: { code: 'NumpadDecimal', location: NUMPAD }, // [USB: 0x63] 815 | 0xBF: { code: 'NumpadDivide', location: NUMPAD } // [USB: 0x54] 816 | }); 817 | 818 | 819 | //-------------------------------------------------------------------- 820 | // 821 | // Key Values 822 | // 823 | //-------------------------------------------------------------------- 824 | 825 | // Mapping from `code` values to `key` values. Values defined at: 826 | // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3Events-key.html 827 | // Entries are only provided when `key` differs from `code`. If 828 | // printable, `shiftKey` has the shifted printable character. This 829 | // assumes US Standard 101 layout 830 | 831 | var codeToKeyTable = { 832 | // Modifier Keys 833 | ShiftLeft: { key: 'Shift' }, 834 | ShiftRight: { key: 'Shift' }, 835 | ControlLeft: { key: 'Control' }, 836 | ControlRight: { key: 'Control' }, 837 | AltLeft: { key: 'Alt' }, 838 | AltRight: { key: 'Alt' }, 839 | OSLeft: { key: 'OS' }, 840 | OSRight: { key: 'OS' }, 841 | 842 | // Whitespace Keys 843 | NumpadEnter: { key: 'Enter' }, 844 | Space: { key: ' ' }, 845 | 846 | // Printable Keys 847 | Digit0: { key: '0', shiftKey: ')' }, 848 | Digit1: { key: '1', shiftKey: '!' }, 849 | Digit2: { key: '2', shiftKey: '@' }, 850 | Digit3: { key: '3', shiftKey: '#' }, 851 | Digit4: { key: '4', shiftKey: '$' }, 852 | Digit5: { key: '5', shiftKey: '%' }, 853 | Digit6: { key: '6', shiftKey: '^' }, 854 | Digit7: { key: '7', shiftKey: '&' }, 855 | Digit8: { key: '8', shiftKey: '*' }, 856 | Digit9: { key: '9', shiftKey: '(' }, 857 | KeyA: { key: 'a', shiftKey: 'A' }, 858 | KeyB: { key: 'b', shiftKey: 'B' }, 859 | KeyC: { key: 'c', shiftKey: 'C' }, 860 | KeyD: { key: 'd', shiftKey: 'D' }, 861 | KeyE: { key: 'e', shiftKey: 'E' }, 862 | KeyF: { key: 'f', shiftKey: 'F' }, 863 | KeyG: { key: 'g', shiftKey: 'G' }, 864 | KeyH: { key: 'h', shiftKey: 'H' }, 865 | KeyI: { key: 'i', shiftKey: 'I' }, 866 | KeyJ: { key: 'j', shiftKey: 'J' }, 867 | KeyK: { key: 'k', shiftKey: 'K' }, 868 | KeyL: { key: 'l', shiftKey: 'L' }, 869 | KeyM: { key: 'm', shiftKey: 'M' }, 870 | KeyN: { key: 'n', shiftKey: 'N' }, 871 | KeyO: { key: 'o', shiftKey: 'O' }, 872 | KeyP: { key: 'p', shiftKey: 'P' }, 873 | KeyQ: { key: 'q', shiftKey: 'Q' }, 874 | KeyR: { key: 'r', shiftKey: 'R' }, 875 | KeyS: { key: 's', shiftKey: 'S' }, 876 | KeyT: { key: 't', shiftKey: 'T' }, 877 | KeyU: { key: 'u', shiftKey: 'U' }, 878 | KeyV: { key: 'v', shiftKey: 'V' }, 879 | KeyW: { key: 'w', shiftKey: 'W' }, 880 | KeyX: { key: 'x', shiftKey: 'X' }, 881 | KeyY: { key: 'y', shiftKey: 'Y' }, 882 | KeyZ: { key: 'z', shiftKey: 'Z' }, 883 | Numpad0: { key: '0' }, 884 | Numpad1: { key: '1' }, 885 | Numpad2: { key: '2' }, 886 | Numpad3: { key: '3' }, 887 | Numpad4: { key: '4' }, 888 | Numpad5: { key: '5' }, 889 | Numpad6: { key: '6' }, 890 | Numpad7: { key: '7' }, 891 | Numpad8: { key: '8' }, 892 | Numpad9: { key: '9' }, 893 | NumpadMultiply: { key: '*' }, 894 | NumpadAdd: { key: '+' }, 895 | NumpadComma: { key: ',' }, 896 | NumpadSubtract: { key: '-' }, 897 | NumpadDecimal: { key: '.' }, 898 | NumpadDivide: { key: '/' }, 899 | Semicolon: { key: ';', shiftKey: ':' }, 900 | Equal: { key: '=', shiftKey: '+' }, 901 | Comma: { key: ',', shiftKey: '<' }, 902 | Minus: { key: '-', shiftKey: '_' }, 903 | Period: { key: '.', shiftKey: '>' }, 904 | Slash: { key: '/', shiftKey: '?' }, 905 | Backquote: { key: '`', shiftKey: '~' }, 906 | BracketLeft: { key: '[', shiftKey: '{' }, 907 | Backslash: { key: '\\', shiftKey: '|' }, 908 | BracketRight: { key: ']', shiftKey: '}' }, 909 | Quote: { key: '\'', shiftKey: '"' }, 910 | IntlBackslash: { key: '\\', shiftKey: '|' } 911 | }; 912 | 913 | mergeIf(codeToKeyTable, 'mac', { 914 | OSLeft: { key: 'Meta' }, 915 | OSRight: { key: 'Meta' } 916 | }); 917 | 918 | // Corrections for 'key' names in older browsers (e.g. FF36-) 919 | // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key#Key_values 920 | var keyFixTable = { 921 | Esc: 'Escape', 922 | Nonconvert: 'NonConvert', 923 | Left: 'ArrowLeft', 924 | Up: 'ArrowUp', 925 | Right: 'ArrowRight', 926 | Down: 'ArrowDown', 927 | Del: 'Delete', 928 | Menu: 'ContextMenu', 929 | MediaNextTrack: 'MediaTrackNext', 930 | MediaPreviousTrack: 'MediaTrackPrevious', 931 | SelectMedia: 'MediaSelect', 932 | HalfWidth: 'Hankaku', 933 | FullWidth: 'Zenkaku', 934 | RomanCharacters: 'Romaji', 935 | Crsel: 'CrSel', 936 | Exsel: 'ExSel', 937 | Zoom: 'ZoomToggle' 938 | }; 939 | 940 | //-------------------------------------------------------------------- 941 | // 942 | // Exported Functions 943 | // 944 | //-------------------------------------------------------------------- 945 | 946 | 947 | var codeTable = remap(keyCodeToInfoTable, 'code'); 948 | 949 | try { 950 | var nativeLocation = nativeKeyboardEvent && ('location' in new KeyboardEvent('')); 951 | } catch (_) {} 952 | 953 | function keyInfoForEvent(event) { 954 | var keyCode = 'keyCode' in event ? event.keyCode : 'which' in event ? event.which : 0; 955 | 956 | var keyInfo = (function(){ 957 | if (nativeLocation || 'keyLocation' in event) { 958 | var location = nativeLocation ? event.location : event.keyLocation; 959 | if (location && keyCode in locationTable[location]) { 960 | return locationTable[location][keyCode]; 961 | } 962 | } 963 | if ('keyIdentifier' in event && event.keyIdentifier in keyIdentifierTable) { 964 | return keyIdentifierTable[event.keyIdentifier]; 965 | } 966 | if (keyCode in keyCodeToInfoTable) { 967 | return keyCodeToInfoTable[keyCode]; 968 | } 969 | return null; 970 | }()); 971 | 972 | // TODO: Track these down and move to general tables 973 | if (0) { 974 | // TODO: Map these for newerish browsers? 975 | // TODO: iOS only? 976 | // TODO: Override with more common keyIdentifier name? 977 | switch (event.keyIdentifier) { 978 | case 'U+0010': keyInfo = { code: 'Function' }; break; 979 | case 'U+001C': keyInfo = { code: 'ArrowLeft' }; break; 980 | case 'U+001D': keyInfo = { code: 'ArrowRight' }; break; 981 | case 'U+001E': keyInfo = { code: 'ArrowUp' }; break; 982 | case 'U+001F': keyInfo = { code: 'ArrowDown' }; break; 983 | } 984 | } 985 | 986 | if (!keyInfo) 987 | return null; 988 | 989 | var key = (function() { 990 | var entry = codeToKeyTable[keyInfo.code]; 991 | if (!entry) return keyInfo.code; 992 | return (event.shiftKey && 'shiftKey' in entry) ? entry.shiftKey : entry.key; 993 | }()); 994 | 995 | return { 996 | code: keyInfo.code, 997 | key: key, 998 | location: keyInfo.location, 999 | keyCap: keyInfo.keyCap 1000 | }; 1001 | } 1002 | 1003 | function queryKeyCap(code, locale) { 1004 | code = String(code); 1005 | if (!codeTable.hasOwnProperty(code)) return 'Undefined'; 1006 | if (locale && String(locale).toLowerCase() !== 'en-us') throw Error('Unsupported locale'); 1007 | var keyInfo = codeTable[code]; 1008 | return keyInfo.keyCap || keyInfo.code || 'Undefined'; 1009 | } 1010 | 1011 | if ('KeyboardEvent' in global && 'defineProperty' in Object) { 1012 | (function() { 1013 | function define(o, p, v) { 1014 | if (p in o) return; 1015 | Object.defineProperty(o, p, v); 1016 | } 1017 | 1018 | define(KeyboardEvent.prototype, 'code', { get: function() { 1019 | var keyInfo = keyInfoForEvent(this); 1020 | return keyInfo ? keyInfo.code : ''; 1021 | }}); 1022 | 1023 | // Fix for nonstandard `key` values (FF36-) 1024 | if ('key' in KeyboardEvent.prototype) { 1025 | var desc = Object.getOwnPropertyDescriptor(KeyboardEvent.prototype, 'key'); 1026 | Object.defineProperty(KeyboardEvent.prototype, 'key', { get: function() { 1027 | var key = desc.get.call(this); 1028 | return keyFixTable.hasOwnProperty(key) ? keyFixTable[key] : key; 1029 | }}); 1030 | } 1031 | 1032 | define(KeyboardEvent.prototype, 'key', { get: function() { 1033 | var keyInfo = keyInfoForEvent(this); 1034 | return (keyInfo && 'key' in keyInfo) ? keyInfo.key : 'Unidentified'; 1035 | }}); 1036 | 1037 | define(KeyboardEvent.prototype, 'location', { get: function() { 1038 | var keyInfo = keyInfoForEvent(this); 1039 | return (keyInfo && 'location' in keyInfo) ? keyInfo.location : STANDARD; 1040 | }}); 1041 | 1042 | define(KeyboardEvent.prototype, 'locale', { get: function() { 1043 | return ''; 1044 | }}); 1045 | }()); 1046 | } 1047 | 1048 | if (!('queryKeyCap' in global.KeyboardEvent)) 1049 | global.KeyboardEvent.queryKeyCap = queryKeyCap; 1050 | 1051 | // Helper for IE8- 1052 | global.identifyKey = function(event) { 1053 | if ('code' in event) 1054 | return; 1055 | 1056 | var keyInfo = keyInfoForEvent(event); 1057 | event.code = keyInfo ? keyInfo.code : ''; 1058 | event.key = (keyInfo && 'key' in keyInfo) ? keyInfo.key : 'Unidentified'; 1059 | event.location = ('location' in event) ? event.location : 1060 | ('keyLocation' in event) ? event.keyLocation : 1061 | (keyInfo && 'location' in keyInfo) ? keyInfo.location : STANDARD; 1062 | event.locale = ''; 1063 | }; 1064 | 1065 | } (window)); 1066 | 1067 | 1068 | /***/ } 1069 | /******/ ]); -------------------------------------------------------------------------------- /dist/aframe-keyboard-controls.min.js: -------------------------------------------------------------------------------- 1 | !function(e){function o(i){if(t[i])return t[i].exports;var a=t[i]={exports:{},id:i,loaded:!1};return e[i].call(a.exports,a,a.exports,o),a.loaded=!0,a.exports}var t={};return o.m=e,o.c=t,o.p="",o(0)}([function(e,o,t){!function(e){return e?void(e.aframeCore||e).registerComponent("keyboard-controls",t(1)):void console.error("Component attempted to register before AFRAME was available.")}(window.AFRAME)},function(e,o,t){t(2);var i=200,a="__keyboard-controls-proxy",c=window.KeyboardEvent;e.exports={schema:{easing:{"default":20},acceleration:{"default":65},angularAcceleration:{"default":Math.PI/4},enabled:{"default":!0},mode:{"default":"default",oneOf:["default","fly","fps"]},rollAxis:{"default":"z",oneOf:["x","y","z"]},pitchAxis:{"default":"x",oneOf:["x","y","z"]},rollAxisInverted:{"default":!1},rollAxisEnabled:{"default":!0},pitchAxisInverted:{"default":!1},pitchAxisEnabled:{"default":!0},yawAxisInverted:{"default":!1},debug:{"default":!1}},init:function(){this.velocity=new THREE.Vector3,this.angularVelocity=0,this.localKeys={},this.listeners={keydown:this.onKeyDown.bind(this),keyup:this.onKeyUp.bind(this),blur:this.onBlur.bind(this)},this.attachEventListeners()},tick:function(){var e=new THREE.Vector3(0,1,0),o=new THREE.Euler(0,0,0,"YXZ");return function(t,a){var c,n=this.data,d=n.acceleration,r=n.angularAcceleration,y=n.easing,l=this.velocity,s=this.getKeys(),u=n.pitchAxis,p=n.rollAxis,k=n.pitchAxisInverted?-1:1,f=n.rollAxisInverted?-1:1,h=n.yawAxisInverted?1:-1,K=this.el,m="fps"===n.mode?["KeyQ","ArrowLeft"]:["KeyA","ArrowLeft"],C="fps"===n.mode?["KeyE","ArrowRight"]:["KeyD","ArrowRight"];if(isNaN(a)||a>i)return l[u]=0,l[p]=0,void(this.angularVelocity=0);l[u]-=l[u]*y*a/1e3,l[p]-=l[p]*y*a/1e3,this.angularVelocity-=this.angularVelocity*y*a/1e3;var g=K.getAttribute("position");n.enabled&&(n.pitchAxisEnabled&&((s[m[0]]||s[m[1]])&&(l[u]-=k*d*a/1e3),(s[C[0]]||s[C[1]])&&(l[u]+=k*d*a/1e3)),n.rollAxisEnabled&&((s.KeyW||s.ArrowUp)&&(l[p]-=f*d*a/1e3),(s.KeyS||s.ArrowDown)&&(l[p]+=f*d*a/1e3)),"fps"===n.mode&&(s.KeyA&&(this.angularVelocity-=h*r*a/1e3),s.KeyD&&(this.angularVelocity+=h*r*a/1e3))),"fps"===n.mode&&(this.rotateOnAxis(o,e,this.angularVelocity),K.setAttribute("rotation",{x:THREE.Math.radToDeg(o.x),y:THREE.Math.radToDeg(o.y),z:THREE.Math.radToDeg(o.z)})),c=this.getMovementVector(a),K.object3D.translateX(c.x),K.object3D.translateY(c.y),K.object3D.translateZ(c.z),K.setAttribute("position",{x:g.x+c.x,y:g.y+c.y,z:g.z+c.z})}}(),rotateOnAxis:function(){var e=new THREE.Quaternion,o=new THREE.Quaternion;return function(t,i,a){e.setFromAxisAngle(i,a),o.setFromEuler(t),o.multiply(e),t.setFromQuaternion(o,t.order)}}(),getMovementVector:function(){var e=new THREE.Vector3(0,0,0),o=new THREE.Euler(0,0,0,"YXZ");return function(t){var i=this.velocity,a=this.el.getAttribute("rotation");return e.copy(i),e.multiplyScalar(t/1e3),a?("fly"!==this.data.mode&&(a.x=0),o.set(THREE.Math.degToRad(a.x),THREE.Math.degToRad(a.y),0),e.applyEuler(o),e):e}}(),play:function(){this.attachEventListeners()},pause:function(){this.removeEventListeners()},remove:function(){this.pause()},attachEventListeners:function(){window.addEventListener("keydown",this.listeners.keydown,!1),window.addEventListener("keyup",this.listeners.keyup,!1),window.addEventListener("blur",this.listeners.blur,!1)},removeEventListeners:function(){window.removeEventListener("keydown",this.listeners.keydown),window.removeEventListener("keyup",this.listeners.keyup),window.removeEventListener("blur",this.listeners.blur)},onKeyDown:function(e){this.localKeys[e.code]=!0,this.emit(e)},onKeyUp:function(e){delete this.localKeys[e.code],this.emit(e)},onBlur:function(){for(var e in this.localKeys)this.localKeys.hasOwnProperty(e)&&delete this.localKeys[e]},emit:function(e){a in e&&this.el.emit(e.type,e),this.el.emit(e.type+":"+e.code,new c(e.type,e)),this.data.debug&&console.log(e.type+":"+e.code)},isPressed:function(e){return e in this.getKeys()},getKeys:function(){return this.isProxied()?this.el.sceneEl.components["proxy-controls"].getKeyboard():this.localKeys},isProxied:function(){var e=this.el.sceneEl.components["proxy-controls"];return e&&e.isConnected()}}},function(e,o){!function(e){function o(e,o){return-1!==String(e).indexOf(o)}function t(e,o,t){(p===o||u===o||s===o)&&Object.keys(t).forEach(function(o){e[o]=t[o]})}function i(e,o){var t={};return Object.keys(e).forEach(function(i){var a=e[i];o in a&&(t[a[o]]=a)}),t}function a(e){var o="keyCode"in e?e.keyCode:"which"in e?e.which:0,t=function(){if(g||"keyLocation"in e){var t=g?e.location:e.keyLocation;if(t&&o in h[t])return h[t][o]}return"keyIdentifier"in e&&e.keyIdentifier in f?f[e.keyIdentifier]:o in k?k[o]:null}();if(!t)return null;var i=function(){var o=K[t.code];return o?e.shiftKey&&"shiftKey"in o?o.shiftKey:o.key:t.code}();return{code:t.code,key:i,location:t.location,keyCap:t.keyCap}}function c(e,o){if(e=String(e),!C.hasOwnProperty(e))return"Undefined";if(o&&"en-us"!==String(o).toLowerCase())throw Error("Unsupported locale");var t=C[e];return t.keyCap||t.code||"Undefined"}var n="KeyboardEvent"in e;n||(e.KeyboardEvent=function(){throw TypeError("Illegal constructor")}),e.KeyboardEvent.DOM_KEY_LOCATION_STANDARD=0,e.KeyboardEvent.DOM_KEY_LOCATION_LEFT=1,e.KeyboardEvent.DOM_KEY_LOCATION_RIGHT=2,e.KeyboardEvent.DOM_KEY_LOCATION_NUMPAD=3;var d=window.KeyboardEvent.DOM_KEY_LOCATION_STANDARD,r=window.KeyboardEvent.DOM_KEY_LOCATION_LEFT,y=window.KeyboardEvent.DOM_KEY_LOCATION_RIGHT,l=window.KeyboardEvent.DOM_KEY_LOCATION_NUMPAD,s=function(){return o(navigator.platform,"Win")?"win":o(navigator.platform,"Mac")?"mac":o(navigator.platform,"CrOS")?"cros":o(navigator.platform,"Linux")?"linux":o(navigator.userAgent,"iPad")||o(navigator.platform,"iPod")||o(navigator.platform,"iPhone")?"ios":""}(),u=function(){return o(navigator.userAgent,"Chrome/")?"chrome":o(navigator.vendor,"Apple")?"safari":o(navigator.userAgent,"MSIE")?"ie":o(navigator.userAgent,"Gecko/")?"moz":o(navigator.userAgent,"Opera/")?"opera":""}(),p=u+"-"+s,k={3:{code:"Cancel"},6:{code:"Help"},8:{code:"Backspace"},9:{code:"Tab"},12:{code:"Clear"},13:{code:"Enter"},16:{code:"Shift"},17:{code:"Control"},18:{code:"Alt"},19:{code:"Pause"},20:{code:"CapsLock"},21:{code:"KanaMode"},22:{code:"HangulMode"},23:{code:"JunjaMode"},24:{code:"FinalMode"},25:{code:"KanjiMode"},27:{code:"Escape"},28:{code:"Convert"},29:{code:"NonConvert"},30:{code:"Accept"},31:{code:"ModeChange"},32:{code:"Space"},33:{code:"PageUp"},34:{code:"PageDown"},35:{code:"End"},36:{code:"Home"},37:{code:"ArrowLeft"},38:{code:"ArrowUp"},39:{code:"ArrowRight"},40:{code:"ArrowDown"},41:{code:"Select"},42:{code:"Print"},43:{code:"Execute"},44:{code:"PrintScreen"},45:{code:"Insert"},46:{code:"Delete"},47:{code:"Help"},48:{code:"Digit0",keyCap:"0"},49:{code:"Digit1",keyCap:"1"},50:{code:"Digit2",keyCap:"2"},51:{code:"Digit3",keyCap:"3"},52:{code:"Digit4",keyCap:"4"},53:{code:"Digit5",keyCap:"5"},54:{code:"Digit6",keyCap:"6"},55:{code:"Digit7",keyCap:"7"},56:{code:"Digit8",keyCap:"8"},57:{code:"Digit9",keyCap:"9"},65:{code:"KeyA",keyCap:"a"},66:{code:"KeyB",keyCap:"b"},67:{code:"KeyC",keyCap:"c"},68:{code:"KeyD",keyCap:"d"},69:{code:"KeyE",keyCap:"e"},70:{code:"KeyF",keyCap:"f"},71:{code:"KeyG",keyCap:"g"},72:{code:"KeyH",keyCap:"h"},73:{code:"KeyI",keyCap:"i"},74:{code:"KeyJ",keyCap:"j"},75:{code:"KeyK",keyCap:"k"},76:{code:"KeyL",keyCap:"l"},77:{code:"KeyM",keyCap:"m"},78:{code:"KeyN",keyCap:"n"},79:{code:"KeyO",keyCap:"o"},80:{code:"KeyP",keyCap:"p"},81:{code:"KeyQ",keyCap:"q"},82:{code:"KeyR",keyCap:"r"},83:{code:"KeyS",keyCap:"s"},84:{code:"KeyT",keyCap:"t"},85:{code:"KeyU",keyCap:"u"},86:{code:"KeyV",keyCap:"v"},87:{code:"KeyW",keyCap:"w"},88:{code:"KeyX",keyCap:"x"},89:{code:"KeyY",keyCap:"y"},90:{code:"KeyZ",keyCap:"z"},91:{code:"OSLeft",location:r},92:{code:"OSRight",location:y},93:{code:"ContextMenu"},95:{code:"Standby"},96:{code:"Numpad0",keyCap:"0",location:l},97:{code:"Numpad1",keyCap:"1",location:l},98:{code:"Numpad2",keyCap:"2",location:l},99:{code:"Numpad3",keyCap:"3",location:l},100:{code:"Numpad4",keyCap:"4",location:l},101:{code:"Numpad5",keyCap:"5",location:l},102:{code:"Numpad6",keyCap:"6",location:l},103:{code:"Numpad7",keyCap:"7",location:l},104:{code:"Numpad8",keyCap:"8",location:l},105:{code:"Numpad9",keyCap:"9",location:l},106:{code:"NumpadMultiply",keyCap:"*",location:l},107:{code:"NumpadAdd",keyCap:"+",location:l},108:{code:"NumpadComma",keyCap:",",location:l},109:{code:"NumpadSubtract",keyCap:"-",location:l},110:{code:"NumpadDecimal",keyCap:".",location:l},111:{code:"NumpadDivide",keyCap:"/",location:l},112:{code:"F1"},113:{code:"F2"},114:{code:"F3"},115:{code:"F4"},116:{code:"F5"},117:{code:"F6"},118:{code:"F7"},119:{code:"F8"},120:{code:"F9"},121:{code:"F10"},122:{code:"F11"},123:{code:"F12"},124:{code:"F13"},125:{code:"F14"},126:{code:"F15"},127:{code:"F16"},128:{code:"F17"},129:{code:"F18"},130:{code:"F19"},131:{code:"F20"},132:{code:"F21"},133:{code:"F22"},134:{code:"F23"},135:{code:"F24"},144:{code:"NumLock",location:l},145:{code:"ScrollLock"},160:{code:"ShiftLeft",location:r},161:{code:"ShiftRight",location:y},162:{code:"ControlLeft",location:r},163:{code:"ControlRight",location:y},164:{code:"AltLeft",location:r},165:{code:"AltRight",location:y},166:{code:"BrowserBack"},167:{code:"BrowserForward"},168:{code:"BrowserRefresh"},169:{code:"BrowserStop"},170:{code:"BrowserSearch"},171:{code:"BrowserFavorites"},172:{code:"BrowserHome"},173:{code:"VolumeMute"},174:{code:"VolumeDown"},175:{code:"VolumeUp"},176:{code:"MediaTrackNext"},177:{code:"MediaTrackPrevious"},178:{code:"MediaStop"},179:{code:"MediaPlayPause"},180:{code:"LaunchMail"},181:{code:"MediaSelect"},182:{code:"LaunchApp1"},183:{code:"LaunchApp2"},186:{code:"Semicolon",keyCap:";"},187:{code:"Equal",keyCap:"="},188:{code:"Comma",keyCap:","},189:{code:"Minus",keyCap:"-"},190:{code:"Period",keyCap:"."},191:{code:"Slash",keyCap:"/"},192:{code:"Backquote",keyCap:"`"},219:{code:"BracketLeft",keyCap:"["},220:{code:"Backslash",keyCap:"\\"},221:{code:"BracketRight",keyCap:"]"},222:{code:"Quote",keyCap:"'"},226:{code:"IntlBackslash",keyCap:"\\"},229:{code:"Process"},246:{code:"Attn"},247:{code:"CrSel"},248:{code:"ExSel"},249:{code:"EraseEof"},250:{code:"Play"},251:{code:"ZoomToggle"},254:{code:"Clear"}};t(k,"moz",{59:{code:"Semicolon",keyCap:";"},61:{code:"Equal",keyCap:"="},107:{code:"Equal",keyCap:"="},109:{code:"Minus",keyCap:"-"},187:{code:"NumpadAdd",keyCap:"+",location:l},189:{code:"NumpadSubtract",keyCap:"-",location:l}}),t(k,"moz-mac",{12:{code:"NumLock",location:l},173:{code:"Minus",keyCap:"-"}}),t(k,"moz-win",{173:{code:"Minus",keyCap:"-"}}),t(k,"chrome-mac",{93:{code:"OSRight",location:y}}),t(k,"safari",{3:{code:"Enter"},25:{code:"Tab"}}),t(k,"ios",{10:{code:"Enter",location:d}}),t(k,"safari-mac",{91:{code:"OSLeft",location:r},93:{code:"OSRight",location:y},229:{code:"KeyQ",keyCap:"Q"}});var f={};"cros"===s&&(f["U+00A0"]={code:"ShiftLeft",location:r},f["U+00A1"]={code:"ShiftRight",location:y},f["U+00A2"]={code:"ControlLeft",location:r},f["U+00A3"]={code:"ControlRight",location:y},f["U+00A4"]={code:"AltLeft",location:r},f["U+00A5"]={code:"AltRight",location:y}),"chrome-mac"===p&&(f["U+0010"]={code:"ContextMenu"}),"safari-mac"===p&&(f["U+0010"]={code:"ContextMenu"}),"ios"===s&&(f["U+0010"]={code:"Function"},f["U+001C"]={code:"ArrowLeft"},f["U+001D"]={code:"ArrowRight"},f["U+001E"]={code:"ArrowUp"},f["U+001F"]={code:"ArrowDown"},f["U+0001"]={code:"Home"},f["U+0004"]={code:"End"},f["U+000B"]={code:"PageUp"},f["U+000C"]={code:"PageDown"});var h=[];h[r]={16:{code:"ShiftLeft",location:r},17:{code:"ControlLeft",location:r},18:{code:"AltLeft",location:r}},h[y]={16:{code:"ShiftRight",location:y},17:{code:"ControlRight",location:y},18:{code:"AltRight",location:y}},h[l]={13:{code:"NumpadEnter",location:l}},t(h[l],"moz",{109:{code:"NumpadSubtract",location:l},107:{code:"NumpadAdd",location:l}}),t(h[r],"moz-mac",{224:{code:"OSLeft",location:r}}),t(h[y],"moz-mac",{224:{code:"OSRight",location:y}}),t(h[y],"moz-win",{91:{code:"OSRight",location:y}}),t(h[y],"mac",{93:{code:"OSRight",location:y}}),t(h[l],"chrome-mac",{12:{code:"NumLock",location:l}}),t(h[l],"safari-mac",{12:{code:"NumLock",location:l},187:{code:"NumpadAdd",location:l},189:{code:"NumpadSubtract",location:l},190:{code:"NumpadDecimal",location:l},191:{code:"NumpadDivide",location:l}});var K={ShiftLeft:{key:"Shift"},ShiftRight:{key:"Shift"},ControlLeft:{key:"Control"},ControlRight:{key:"Control"},AltLeft:{key:"Alt"},AltRight:{key:"Alt"},OSLeft:{key:"OS"},OSRight:{key:"OS"},NumpadEnter:{key:"Enter"},Space:{key:" "},Digit0:{key:"0",shiftKey:")"},Digit1:{key:"1",shiftKey:"!"},Digit2:{key:"2",shiftKey:"@"},Digit3:{key:"3",shiftKey:"#"},Digit4:{key:"4",shiftKey:"$"},Digit5:{key:"5",shiftKey:"%"},Digit6:{key:"6",shiftKey:"^"},Digit7:{key:"7",shiftKey:"&"},Digit8:{key:"8",shiftKey:"*"},Digit9:{key:"9",shiftKey:"("},KeyA:{key:"a",shiftKey:"A"},KeyB:{key:"b",shiftKey:"B"},KeyC:{key:"c",shiftKey:"C"},KeyD:{key:"d",shiftKey:"D"},KeyE:{key:"e",shiftKey:"E"},KeyF:{key:"f",shiftKey:"F"},KeyG:{key:"g",shiftKey:"G"},KeyH:{key:"h",shiftKey:"H"},KeyI:{key:"i",shiftKey:"I"},KeyJ:{key:"j",shiftKey:"J"},KeyK:{key:"k",shiftKey:"K"},KeyL:{key:"l",shiftKey:"L"},KeyM:{key:"m",shiftKey:"M"},KeyN:{key:"n",shiftKey:"N"},KeyO:{key:"o",shiftKey:"O"},KeyP:{key:"p",shiftKey:"P"},KeyQ:{key:"q",shiftKey:"Q"},KeyR:{key:"r",shiftKey:"R"},KeyS:{key:"s",shiftKey:"S"},KeyT:{key:"t",shiftKey:"T"},KeyU:{key:"u",shiftKey:"U"},KeyV:{key:"v",shiftKey:"V"},KeyW:{key:"w",shiftKey:"W"},KeyX:{key:"x",shiftKey:"X"},KeyY:{key:"y",shiftKey:"Y"},KeyZ:{key:"z",shiftKey:"Z"},Numpad0:{key:"0"},Numpad1:{key:"1"},Numpad2:{key:"2"},Numpad3:{key:"3"},Numpad4:{key:"4"},Numpad5:{key:"5"},Numpad6:{key:"6"},Numpad7:{key:"7"},Numpad8:{key:"8"},Numpad9:{key:"9"},NumpadMultiply:{key:"*"},NumpadAdd:{key:"+"},NumpadComma:{key:","},NumpadSubtract:{key:"-"},NumpadDecimal:{key:"."},NumpadDivide:{key:"/"},Semicolon:{key:";",shiftKey:":"},Equal:{key:"=",shiftKey:"+"},Comma:{key:",",shiftKey:"<"},Minus:{key:"-",shiftKey:"_"},Period:{key:".",shiftKey:">"},Slash:{key:"/",shiftKey:"?"},Backquote:{key:"`",shiftKey:"~"},BracketLeft:{key:"[",shiftKey:"{"},Backslash:{key:"\\",shiftKey:"|"},BracketRight:{key:"]",shiftKey:"}"},Quote:{key:"'",shiftKey:'"'},IntlBackslash:{key:"\\",shiftKey:"|"}};t(K,"mac",{OSLeft:{key:"Meta"},OSRight:{key:"Meta"}});var m={Esc:"Escape",Nonconvert:"NonConvert",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Menu:"ContextMenu",MediaNextTrack:"MediaTrackNext",MediaPreviousTrack:"MediaTrackPrevious",SelectMedia:"MediaSelect",HalfWidth:"Hankaku",FullWidth:"Zenkaku",RomanCharacters:"Romaji",Crsel:"CrSel",Exsel:"ExSel",Zoom:"ZoomToggle"},C=i(k,"code");try{var g=n&&"location"in new KeyboardEvent("")}catch(v){}"KeyboardEvent"in e&&"defineProperty"in Object&&!function(){function e(e,o,t){o in e||Object.defineProperty(e,o,t)}if(e(KeyboardEvent.prototype,"code",{get:function(){var e=a(this);return e?e.code:""}}),"key"in KeyboardEvent.prototype){var o=Object.getOwnPropertyDescriptor(KeyboardEvent.prototype,"key");Object.defineProperty(KeyboardEvent.prototype,"key",{get:function(){var e=o.get.call(this);return m.hasOwnProperty(e)?m[e]:e}})}e(KeyboardEvent.prototype,"key",{get:function(){var e=a(this);return e&&"key"in e?e.key:"Unidentified"}}),e(KeyboardEvent.prototype,"location",{get:function(){var e=a(this);return e&&"location"in e?e.location:d}}),e(KeyboardEvent.prototype,"locale",{get:function(){return""}})}(),"queryKeyCap"in e.KeyboardEvent||(e.KeyboardEvent.queryKeyCap=c),e.identifyKey=function(e){if(!("code"in e)){var o=a(e);e.code=o?o.code:"",e.key=o&&"key"in o?o.key:"Unidentified",e.location="location"in e?e.location:"keyLocation"in e?e.keyLocation:o&&"location"in o?o.location:d,e.locale=""}}}(window)}]); -------------------------------------------------------------------------------- /examples/assets/321103__nsstudios__blip1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/aframe-keyboard-controls/f62aab778a12eebda9aac38d8b67564161302214/examples/assets/321103__nsstudios__blip1.wav -------------------------------------------------------------------------------- /examples/assets/peach-gradient-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/aframe-keyboard-controls/f62aab778a12eebda9aac38d8b67564161302214/examples/assets/peach-gradient-1.jpg -------------------------------------------------------------------------------- /examples/assets/tex/shadow-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/aframe-keyboard-controls/f62aab778a12eebda9aac38d8b67564161302214/examples/assets/tex/shadow-circle.png -------------------------------------------------------------------------------- /examples/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/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/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 43 | 44 | 45 | 46 | 47 | 57 |

Mode: Default. (<Space> to change)

58 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /keyboard-controls.js: -------------------------------------------------------------------------------- 1 | require('./lib/keyboard.polyfill'); 2 | 3 | var MAX_DELTA = 200, 4 | PROXY_FLAG = '__keyboard-controls-proxy'; 5 | 6 | var KeyboardEvent = window.KeyboardEvent; 7 | 8 | /** 9 | * Keyboard Controls component. 10 | * 11 | * Bind keyboard events to components, or control your entities with the WASD keys. 12 | * 13 | * @namespace wasd-controls 14 | * @param {number} [easing=20] - How fast the movement decelerates. If you hold the 15 | * keys the entity moves and if you release it will stop. Easing simulates friction. 16 | * @param {number} [acceleration=65] - Determines the acceleration given 17 | * to the entity when pressing the keys. 18 | * @param {number} [angularAcceleration=Math.PI*0.25] - Determines the angular 19 | * acceleration given to the entity when pressing the keys. Only applied when 20 | * mode == 'fps'. Measured in Radians. 21 | * @param {bool} [enabled=true] - To completely enable or disable the controls 22 | * @param {string} [mode='default'] - 23 | * 'default' enforces the direction of the movement to stick to the plane 24 | * where the entity started off. 25 | * 'fps' extends 'default' by enabling "First Person Shooter" controls: W/S = 26 | * Forward/Back, Q/E = Strafe left/right, A/D = Rotate left/right 27 | * 'fly' enables 6 degrees of freedom as a diver underwater or a plane flying. 28 | * @param {string} [rollAxis='z'] - The front-to-back axis. 29 | * @param {string} [pitchAxis='x'] - The left-to-right axis. 30 | * @param {bool} [rollAxisInverted=false] - Roll axis is inverted 31 | * @param {bool} [pitchAxisInverted=false] - Pitch axis is inverted 32 | * @param {bool} [yawAxisInverted=false] - Yaw axis is inverted. Used when 33 | * mode == 'fps' 34 | */ 35 | module.exports = { 36 | schema: { 37 | easing: { default: 20 }, 38 | acceleration: { default: 65 }, 39 | angularAcceleration: { default: Math.PI / 4 }, 40 | enabled: { default: true }, 41 | mode: { default: 'default', oneOf: ['default', 'fly', 'fps']}, 42 | rollAxis: { default: 'z', oneOf: [ 'x', 'y', 'z' ] }, 43 | pitchAxis: { default: 'x', oneOf: [ 'x', 'y', 'z' ] }, 44 | rollAxisInverted: { default: false }, 45 | rollAxisEnabled: { default: true }, 46 | pitchAxisInverted: { default: false }, 47 | pitchAxisEnabled: { default: true }, 48 | yawAxisInverted: { default: false }, 49 | debug: { default: false } 50 | }, 51 | 52 | init: function () { 53 | this.velocity = new THREE.Vector3(); 54 | this.angularVelocity = 0; 55 | this.localKeys = {}; 56 | this.listeners = { 57 | keydown: this.onKeyDown.bind(this), 58 | keyup: this.onKeyUp.bind(this), 59 | blur: this.onBlur.bind(this) 60 | }; 61 | this.attachEventListeners(); 62 | }, 63 | 64 | /******************************************************************* 65 | * Movement 66 | */ 67 | 68 | tick: (function () { 69 | var upVector = new THREE.Vector3(0, 1, 0); 70 | var rotation = new THREE.Euler(0, 0, 0, 'YXZ'); 71 | return function (t, dt) { 72 | var data = this.data; 73 | var acceleration = data.acceleration; 74 | var angularAcceleration = data.angularAcceleration; 75 | var easing = data.easing; 76 | var velocity = this.velocity; 77 | var keys = this.getKeys(); 78 | var movementVector; 79 | var pitchAxis = data.pitchAxis; 80 | var rollAxis = data.rollAxis; 81 | var pitchSign = data.pitchAxisInverted ? -1 : 1; 82 | var rollSign = data.rollAxisInverted ? -1 : 1; 83 | var yawSign = data.yawAxisInverted ? 1 : -1; 84 | var el = this.el; 85 | var strafeLeft = data.mode === 'fps' ? ['KeyQ', 'ArrowLeft'] : ['KeyA', 'ArrowLeft']; 86 | var strafeRight = data.mode === 'fps' ? ['KeyE', 'ArrowRight'] : ['KeyD', 'ArrowRight']; 87 | 88 | // If data changed or FPS too low, reset velocity. 89 | if (isNaN(dt) || dt > MAX_DELTA) { 90 | velocity[pitchAxis] = 0; 91 | velocity[rollAxis] = 0; 92 | this.angularVelocity = 0; 93 | return; 94 | } 95 | 96 | velocity[pitchAxis] -= velocity[pitchAxis] * easing * dt / 1000; 97 | velocity[rollAxis] -= velocity[rollAxis] * easing * dt / 1000; 98 | this.angularVelocity -= this.angularVelocity * easing * dt / 1000; 99 | 100 | var position = el.getAttribute('position'); 101 | 102 | if (data.enabled) { 103 | if (data.pitchAxisEnabled) { 104 | if (keys[strafeLeft[0]] || keys[strafeLeft[1]]) { 105 | velocity[pitchAxis] -= pitchSign * acceleration * dt / 1000; 106 | } 107 | if (keys[strafeRight[0]] || keys[strafeRight[1]]) { 108 | velocity[pitchAxis] += pitchSign * acceleration * dt / 1000; 109 | } 110 | } 111 | if (data.rollAxisEnabled) { 112 | if (keys.KeyW || keys.ArrowUp) { 113 | velocity[rollAxis] -= rollSign * acceleration * dt / 1000; 114 | } 115 | if (keys.KeyS || keys.ArrowDown) { 116 | velocity[rollAxis] += rollSign * acceleration * dt / 1000; 117 | } 118 | } 119 | if (data.mode === 'fps') { 120 | if (keys.KeyA) { 121 | this.angularVelocity -= yawSign * angularAcceleration * dt / 1000; 122 | } 123 | if (keys.KeyD) { 124 | this.angularVelocity += yawSign * angularAcceleration * dt / 1000; 125 | } 126 | } 127 | } 128 | 129 | if (data.mode === 'fps') { 130 | this.rotateOnAxis(rotation, upVector, this.angularVelocity); 131 | 132 | el.setAttribute('rotation', { 133 | x: THREE.Math.radToDeg(rotation.x), 134 | y: THREE.Math.radToDeg(rotation.y), 135 | z: THREE.Math.radToDeg(rotation.z) 136 | }); 137 | } 138 | 139 | movementVector = this.getMovementVector(dt); 140 | el.object3D.translateX(movementVector.x); 141 | el.object3D.translateY(movementVector.y); 142 | el.object3D.translateZ(movementVector.z); 143 | 144 | el.setAttribute('position', { 145 | x: position.x + movementVector.x, 146 | y: position.y + movementVector.y, 147 | z: position.z + movementVector.z 148 | }); 149 | }; 150 | })(), 151 | 152 | 153 | rotateOnAxis: (function () { 154 | 155 | var quaternion = new THREE.Quaternion(); 156 | var eulerAsQuaternion = new THREE.Quaternion(); 157 | 158 | return function (euler, axis, angle) { 159 | quaternion.setFromAxisAngle(axis, angle); 160 | eulerAsQuaternion.setFromEuler(euler); 161 | eulerAsQuaternion.multiply(quaternion); 162 | euler.setFromQuaternion(eulerAsQuaternion, euler.order); 163 | }; 164 | 165 | })(), 166 | 167 | getMovementVector: (function () { 168 | var direction = new THREE.Vector3(0, 0, 0); 169 | var rotation = new THREE.Euler(0, 0, 0, 'YXZ'); 170 | return function (dt) { 171 | var velocity = this.velocity; 172 | var elRotation = this.el.getAttribute('rotation'); 173 | direction.copy(velocity); 174 | direction.multiplyScalar(dt / 1000); 175 | if (!elRotation) { return direction; } 176 | if (this.data.mode !== 'fly') { elRotation.x = 0; } 177 | rotation.set(THREE.Math.degToRad(elRotation.x), 178 | THREE.Math.degToRad(elRotation.y), 0); 179 | direction.applyEuler(rotation); 180 | return direction; 181 | }; 182 | })(), 183 | 184 | /******************************************************************* 185 | * Events 186 | */ 187 | 188 | play: function () { 189 | this.attachEventListeners(); 190 | }, 191 | 192 | pause: function () { 193 | this.removeEventListeners(); 194 | }, 195 | 196 | remove: function () { 197 | this.pause(); 198 | }, 199 | 200 | attachEventListeners: function () { 201 | window.addEventListener('keydown', this.listeners.keydown, false); 202 | window.addEventListener('keyup', this.listeners.keyup, false); 203 | window.addEventListener('blur', this.listeners.blur, false); 204 | }, 205 | 206 | removeEventListeners: function () { 207 | window.removeEventListener('keydown', this.listeners.keydown); 208 | window.removeEventListener('keyup', this.listeners.keyup); 209 | window.removeEventListener('blur', this.listeners.blur); 210 | }, 211 | 212 | onKeyDown: function (event) { 213 | this.localKeys[event.code] = true; 214 | this.emit(event); 215 | }, 216 | 217 | onKeyUp: function (event) { 218 | delete this.localKeys[event.code]; 219 | this.emit(event); 220 | }, 221 | 222 | onBlur: function () { 223 | for (var code in this.localKeys) { 224 | if (this.localKeys.hasOwnProperty(code)) { 225 | delete this.localKeys[code]; 226 | } 227 | } 228 | }, 229 | 230 | emit: function (event) { 231 | // TODO - keydown only initially? 232 | // TODO - where the f is the spacebar 233 | 234 | // Emit original event. 235 | if (PROXY_FLAG in event) { 236 | // TODO - Method never triggered. 237 | this.el.emit(event.type, event); 238 | } 239 | 240 | // Emit convenience event, identifying key. 241 | this.el.emit(event.type + ':' + event.code, new KeyboardEvent(event.type, event)); 242 | if (this.data.debug) console.log(event.type + ':' + event.code); 243 | }, 244 | 245 | /******************************************************************* 246 | * Accessors 247 | */ 248 | 249 | isPressed: function (code) { 250 | return code in this.getKeys(); 251 | }, 252 | 253 | getKeys: function () { 254 | if (this.isProxied()) { 255 | return this.el.sceneEl.components['proxy-controls'].getKeyboard(); 256 | } 257 | return this.localKeys; 258 | }, 259 | 260 | isProxied: function () { 261 | var proxyControls = this.el.sceneEl.components['proxy-controls']; 262 | return proxyControls && proxyControls.isConnected(); 263 | } 264 | 265 | }; 266 | -------------------------------------------------------------------------------- /lib/keyboard.polyfill.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Polyfill for the additional KeyboardEvent properties defined in the D3E and 3 | * D4E draft specifications, by @inexorabletash. 4 | * 5 | * See: https://github.com/inexorabletash/polyfill 6 | */ 7 | (function(global) { 8 | var nativeKeyboardEvent = ('KeyboardEvent' in global); 9 | if (!nativeKeyboardEvent) 10 | global.KeyboardEvent = function KeyboardEvent() { throw TypeError('Illegal constructor'); }; 11 | 12 | global.KeyboardEvent.DOM_KEY_LOCATION_STANDARD = 0x00; // Default or unknown location 13 | global.KeyboardEvent.DOM_KEY_LOCATION_LEFT = 0x01; // e.g. Left Alt key 14 | global.KeyboardEvent.DOM_KEY_LOCATION_RIGHT = 0x02; // e.g. Right Alt key 15 | global.KeyboardEvent.DOM_KEY_LOCATION_NUMPAD = 0x03; // e.g. Numpad 0 or + 16 | 17 | var STANDARD = window.KeyboardEvent.DOM_KEY_LOCATION_STANDARD, 18 | LEFT = window.KeyboardEvent.DOM_KEY_LOCATION_LEFT, 19 | RIGHT = window.KeyboardEvent.DOM_KEY_LOCATION_RIGHT, 20 | NUMPAD = window.KeyboardEvent.DOM_KEY_LOCATION_NUMPAD; 21 | 22 | //-------------------------------------------------------------------- 23 | // 24 | // Utilities 25 | // 26 | //-------------------------------------------------------------------- 27 | 28 | function contains(s, ss) { return String(s).indexOf(ss) !== -1; } 29 | 30 | var os = (function() { 31 | if (contains(navigator.platform, 'Win')) { return 'win'; } 32 | if (contains(navigator.platform, 'Mac')) { return 'mac'; } 33 | if (contains(navigator.platform, 'CrOS')) { return 'cros'; } 34 | if (contains(navigator.platform, 'Linux')) { return 'linux'; } 35 | if (contains(navigator.userAgent, 'iPad') || contains(navigator.platform, 'iPod') || contains(navigator.platform, 'iPhone')) { return 'ios'; } 36 | return ''; 37 | } ()); 38 | 39 | var browser = (function() { 40 | if (contains(navigator.userAgent, 'Chrome/')) { return 'chrome'; } 41 | if (contains(navigator.vendor, 'Apple')) { return 'safari'; } 42 | if (contains(navigator.userAgent, 'MSIE')) { return 'ie'; } 43 | if (contains(navigator.userAgent, 'Gecko/')) { return 'moz'; } 44 | if (contains(navigator.userAgent, 'Opera/')) { return 'opera'; } 45 | return ''; 46 | } ()); 47 | 48 | var browser_os = browser + '-' + os; 49 | 50 | function mergeIf(baseTable, select, table) { 51 | if (browser_os === select || browser === select || os === select) { 52 | Object.keys(table).forEach(function(keyCode) { 53 | baseTable[keyCode] = table[keyCode]; 54 | }); 55 | } 56 | } 57 | 58 | function remap(o, key) { 59 | var r = {}; 60 | Object.keys(o).forEach(function(k) { 61 | var item = o[k]; 62 | if (key in item) { 63 | r[item[key]] = item; 64 | } 65 | }); 66 | return r; 67 | } 68 | 69 | function invert(o) { 70 | var r = {}; 71 | Object.keys(o).forEach(function(k) { 72 | r[o[k]] = k; 73 | }); 74 | return r; 75 | } 76 | 77 | //-------------------------------------------------------------------- 78 | // 79 | // Generic Mappings 80 | // 81 | //-------------------------------------------------------------------- 82 | 83 | // "keyInfo" is a dictionary: 84 | // code: string - name from DOM Level 3 KeyboardEvent code Values 85 | // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3Events-code.html 86 | // location (optional): number - one of the DOM_KEY_LOCATION values 87 | // keyCap (optional): string - keyboard label in en-US locale 88 | // USB code Usage ID from page 0x07 unless otherwise noted (Informative) 89 | 90 | // Map of keyCode to keyInfo 91 | var keyCodeToInfoTable = { 92 | // 0x01 - VK_LBUTTON 93 | // 0x02 - VK_RBUTTON 94 | 0x03: { code: 'Cancel' }, // [USB: 0x9b] char \x0018 ??? (Not in D3E) 95 | // 0x04 - VK_MBUTTON 96 | // 0x05 - VK_XBUTTON1 97 | // 0x06 - VK_XBUTTON2 98 | 0x06: { code: 'Help' }, // [USB: 0x75] ??? 99 | // 0x07 - undefined 100 | 0x08: { code: 'Backspace' }, // [USB: 0x2a] Labelled Delete on Macintosh keyboards. 101 | 0x09: { code: 'Tab' }, // [USB: 0x2b] 102 | // 0x0A-0x0B - reserved 103 | 0X0C: { code: 'Clear' }, // [USB: 0x9c] NumPad Center (Not in D3E) 104 | 0X0D: { code: 'Enter' }, // [USB: 0x28] 105 | // 0x0E-0x0F - undefined 106 | 107 | 0x10: { code: 'Shift' }, 108 | 0x11: { code: 'Control' }, 109 | 0x12: { code: 'Alt' }, 110 | 0x13: { code: 'Pause' }, // [USB: 0x48] 111 | 0x14: { code: 'CapsLock' }, // [USB: 0x39] 112 | 0x15: { code: 'KanaMode' }, // [USB: 0x88] - "HangulMode" for Korean layout 113 | 0x16: { code: 'HangulMode' }, // [USB: 0x90] 0x15 as well in MSDN VK table ??? 114 | 0x17: { code: 'JunjaMode' }, // (Not in D3E) 115 | 0x18: { code: 'FinalMode' }, // (Not in D3E) 116 | 0x19: { code: 'KanjiMode' }, // [USB: 0x91] - "HanjaMode" for Korean layout 117 | // 0x1A - undefined 118 | 0x1B: { code: 'Escape' }, // [USB: 0x29] 119 | 0x1C: { code: 'Convert' }, // [USB: 0x8a] 120 | 0x1D: { code: 'NonConvert' }, // [USB: 0x8b] 121 | 0x1E: { code: 'Accept' }, // (Not in D3E) 122 | 0x1F: { code: 'ModeChange' }, // (Not in D3E) 123 | 124 | 0x20: { code: 'Space' }, // [USB: 0x2c] 125 | 0x21: { code: 'PageUp' }, // [USB: 0x4b] 126 | 0x22: { code: 'PageDown' }, // [USB: 0x4e] 127 | 0x23: { code: 'End' }, // [USB: 0x4d] 128 | 0x24: { code: 'Home' }, // [USB: 0x4a] 129 | 0x25: { code: 'ArrowLeft' }, // [USB: 0x50] 130 | 0x26: { code: 'ArrowUp' }, // [USB: 0x52] 131 | 0x27: { code: 'ArrowRight' }, // [USB: 0x4f] 132 | 0x28: { code: 'ArrowDown' }, // [USB: 0x51] 133 | 0x29: { code: 'Select' }, // (Not in D3E) 134 | 0x2A: { code: 'Print' }, // (Not in D3E) 135 | 0x2B: { code: 'Execute' }, // [USB: 0x74] (Not in D3E) 136 | 0x2C: { code: 'PrintScreen' }, // [USB: 0x46] 137 | 0x2D: { code: 'Insert' }, // [USB: 0x49] 138 | 0x2E: { code: 'Delete' }, // [USB: 0x4c] 139 | 0x2F: { code: 'Help' }, // [USB: 0x75] ??? 140 | 141 | 0x30: { code: 'Digit0', keyCap: '0' }, // [USB: 0x27] 0) 142 | 0x31: { code: 'Digit1', keyCap: '1' }, // [USB: 0x1e] 1! 143 | 0x32: { code: 'Digit2', keyCap: '2' }, // [USB: 0x1f] 2@ 144 | 0x33: { code: 'Digit3', keyCap: '3' }, // [USB: 0x20] 3# 145 | 0x34: { code: 'Digit4', keyCap: '4' }, // [USB: 0x21] 4$ 146 | 0x35: { code: 'Digit5', keyCap: '5' }, // [USB: 0x22] 5% 147 | 0x36: { code: 'Digit6', keyCap: '6' }, // [USB: 0x23] 6^ 148 | 0x37: { code: 'Digit7', keyCap: '7' }, // [USB: 0x24] 7& 149 | 0x38: { code: 'Digit8', keyCap: '8' }, // [USB: 0x25] 8* 150 | 0x39: { code: 'Digit9', keyCap: '9' }, // [USB: 0x26] 9( 151 | // 0x3A-0x40 - undefined 152 | 153 | 0x41: { code: 'KeyA', keyCap: 'a' }, // [USB: 0x04] 154 | 0x42: { code: 'KeyB', keyCap: 'b' }, // [USB: 0x05] 155 | 0x43: { code: 'KeyC', keyCap: 'c' }, // [USB: 0x06] 156 | 0x44: { code: 'KeyD', keyCap: 'd' }, // [USB: 0x07] 157 | 0x45: { code: 'KeyE', keyCap: 'e' }, // [USB: 0x08] 158 | 0x46: { code: 'KeyF', keyCap: 'f' }, // [USB: 0x09] 159 | 0x47: { code: 'KeyG', keyCap: 'g' }, // [USB: 0x0a] 160 | 0x48: { code: 'KeyH', keyCap: 'h' }, // [USB: 0x0b] 161 | 0x49: { code: 'KeyI', keyCap: 'i' }, // [USB: 0x0c] 162 | 0x4A: { code: 'KeyJ', keyCap: 'j' }, // [USB: 0x0d] 163 | 0x4B: { code: 'KeyK', keyCap: 'k' }, // [USB: 0x0e] 164 | 0x4C: { code: 'KeyL', keyCap: 'l' }, // [USB: 0x0f] 165 | 0x4D: { code: 'KeyM', keyCap: 'm' }, // [USB: 0x10] 166 | 0x4E: { code: 'KeyN', keyCap: 'n' }, // [USB: 0x11] 167 | 0x4F: { code: 'KeyO', keyCap: 'o' }, // [USB: 0x12] 168 | 169 | 0x50: { code: 'KeyP', keyCap: 'p' }, // [USB: 0x13] 170 | 0x51: { code: 'KeyQ', keyCap: 'q' }, // [USB: 0x14] 171 | 0x52: { code: 'KeyR', keyCap: 'r' }, // [USB: 0x15] 172 | 0x53: { code: 'KeyS', keyCap: 's' }, // [USB: 0x16] 173 | 0x54: { code: 'KeyT', keyCap: 't' }, // [USB: 0x17] 174 | 0x55: { code: 'KeyU', keyCap: 'u' }, // [USB: 0x18] 175 | 0x56: { code: 'KeyV', keyCap: 'v' }, // [USB: 0x19] 176 | 0x57: { code: 'KeyW', keyCap: 'w' }, // [USB: 0x1a] 177 | 0x58: { code: 'KeyX', keyCap: 'x' }, // [USB: 0x1b] 178 | 0x59: { code: 'KeyY', keyCap: 'y' }, // [USB: 0x1c] 179 | 0x5A: { code: 'KeyZ', keyCap: 'z' }, // [USB: 0x1d] 180 | 0x5B: { code: 'OSLeft', location: LEFT }, // [USB: 0xe3] 181 | 0x5C: { code: 'OSRight', location: RIGHT }, // [USB: 0xe7] 182 | 0x5D: { code: 'ContextMenu' }, // [USB: 0x65] Context Menu 183 | // 0x5E - reserved 184 | 0x5F: { code: 'Standby' }, // [USB: 0x82] Sleep 185 | 186 | 0x60: { code: 'Numpad0', keyCap: '0', location: NUMPAD }, // [USB: 0x62] 187 | 0x61: { code: 'Numpad1', keyCap: '1', location: NUMPAD }, // [USB: 0x59] 188 | 0x62: { code: 'Numpad2', keyCap: '2', location: NUMPAD }, // [USB: 0x5a] 189 | 0x63: { code: 'Numpad3', keyCap: '3', location: NUMPAD }, // [USB: 0x5b] 190 | 0x64: { code: 'Numpad4', keyCap: '4', location: NUMPAD }, // [USB: 0x5c] 191 | 0x65: { code: 'Numpad5', keyCap: '5', location: NUMPAD }, // [USB: 0x5d] 192 | 0x66: { code: 'Numpad6', keyCap: '6', location: NUMPAD }, // [USB: 0x5e] 193 | 0x67: { code: 'Numpad7', keyCap: '7', location: NUMPAD }, // [USB: 0x5f] 194 | 0x68: { code: 'Numpad8', keyCap: '8', location: NUMPAD }, // [USB: 0x60] 195 | 0x69: { code: 'Numpad9', keyCap: '9', location: NUMPAD }, // [USB: 0x61] 196 | 0x6A: { code: 'NumpadMultiply', keyCap: '*', location: NUMPAD }, // [USB: 0x55] 197 | 0x6B: { code: 'NumpadAdd', keyCap: '+', location: NUMPAD }, // [USB: 0x57] 198 | 0x6C: { code: 'NumpadComma', keyCap: ',', location: NUMPAD }, // [USB: 0x85] 199 | 0x6D: { code: 'NumpadSubtract', keyCap: '-', location: NUMPAD }, // [USB: 0x56] 200 | 0x6E: { code: 'NumpadDecimal', keyCap: '.', location: NUMPAD }, // [USB: 0x63] 201 | 0x6F: { code: 'NumpadDivide', keyCap: '/', location: NUMPAD }, // [USB: 0x54] 202 | 203 | 0x70: { code: 'F1' }, // [USB: 0x3a] 204 | 0x71: { code: 'F2' }, // [USB: 0x3b] 205 | 0x72: { code: 'F3' }, // [USB: 0x3c] 206 | 0x73: { code: 'F4' }, // [USB: 0x3d] 207 | 0x74: { code: 'F5' }, // [USB: 0x3e] 208 | 0x75: { code: 'F6' }, // [USB: 0x3f] 209 | 0x76: { code: 'F7' }, // [USB: 0x40] 210 | 0x77: { code: 'F8' }, // [USB: 0x41] 211 | 0x78: { code: 'F9' }, // [USB: 0x42] 212 | 0x79: { code: 'F10' }, // [USB: 0x43] 213 | 0x7A: { code: 'F11' }, // [USB: 0x44] 214 | 0x7B: { code: 'F12' }, // [USB: 0x45] 215 | 0x7C: { code: 'F13' }, // [USB: 0x68] 216 | 0x7D: { code: 'F14' }, // [USB: 0x69] 217 | 0x7E: { code: 'F15' }, // [USB: 0x6a] 218 | 0x7F: { code: 'F16' }, // [USB: 0x6b] 219 | 220 | 0x80: { code: 'F17' }, // [USB: 0x6c] 221 | 0x81: { code: 'F18' }, // [USB: 0x6d] 222 | 0x82: { code: 'F19' }, // [USB: 0x6e] 223 | 0x83: { code: 'F20' }, // [USB: 0x6f] 224 | 0x84: { code: 'F21' }, // [USB: 0x70] 225 | 0x85: { code: 'F22' }, // [USB: 0x71] 226 | 0x86: { code: 'F23' }, // [USB: 0x72] 227 | 0x87: { code: 'F24' }, // [USB: 0x73] 228 | // 0x88-0x8F - unassigned 229 | 230 | 0x90: { code: 'NumLock', location: NUMPAD }, // [USB: 0x53] 231 | 0x91: { code: 'ScrollLock' }, // [USB: 0x47] 232 | // 0x92-0x96 - OEM specific 233 | // 0x97-0x9F - unassigned 234 | 235 | // NOTE: 0xA0-0xA5 usually mapped to 0x10-0x12 in browsers 236 | 0xA0: { code: 'ShiftLeft', location: LEFT }, // [USB: 0xe1] 237 | 0xA1: { code: 'ShiftRight', location: RIGHT }, // [USB: 0xe5] 238 | 0xA2: { code: 'ControlLeft', location: LEFT }, // [USB: 0xe0] 239 | 0xA3: { code: 'ControlRight', location: RIGHT }, // [USB: 0xe4] 240 | 0xA4: { code: 'AltLeft', location: LEFT }, // [USB: 0xe2] 241 | 0xA5: { code: 'AltRight', location: RIGHT }, // [USB: 0xe6] 242 | 243 | 0xA6: { code: 'BrowserBack' }, // [USB: 0x0c/0x0224] 244 | 0xA7: { code: 'BrowserForward' }, // [USB: 0x0c/0x0225] 245 | 0xA8: { code: 'BrowserRefresh' }, // [USB: 0x0c/0x0227] 246 | 0xA9: { code: 'BrowserStop' }, // [USB: 0x0c/0x0226] 247 | 0xAA: { code: 'BrowserSearch' }, // [USB: 0x0c/0x0221] 248 | 0xAB: { code: 'BrowserFavorites' }, // [USB: 0x0c/0x0228] 249 | 0xAC: { code: 'BrowserHome' }, // [USB: 0x0c/0x0222] 250 | 0xAD: { code: 'VolumeMute' }, // [USB: 0x7f] 251 | 0xAE: { code: 'VolumeDown' }, // [USB: 0x81] 252 | 0xAF: { code: 'VolumeUp' }, // [USB: 0x80] 253 | 254 | 0xB0: { code: 'MediaTrackNext' }, // [USB: 0x0c/0x00b5] 255 | 0xB1: { code: 'MediaTrackPrevious' }, // [USB: 0x0c/0x00b6] 256 | 0xB2: { code: 'MediaStop' }, // [USB: 0x0c/0x00b7] 257 | 0xB3: { code: 'MediaPlayPause' }, // [USB: 0x0c/0x00cd] 258 | 0xB4: { code: 'LaunchMail' }, // [USB: 0x0c/0x018a] 259 | 0xB5: { code: 'MediaSelect' }, 260 | 0xB6: { code: 'LaunchApp1' }, 261 | 0xB7: { code: 'LaunchApp2' }, 262 | // 0xB8-0xB9 - reserved 263 | 0xBA: { code: 'Semicolon', keyCap: ';' }, // [USB: 0x33] ;: (US Standard 101) 264 | 0xBB: { code: 'Equal', keyCap: '=' }, // [USB: 0x2e] =+ 265 | 0xBC: { code: 'Comma', keyCap: ',' }, // [USB: 0x36] ,< 266 | 0xBD: { code: 'Minus', keyCap: '-' }, // [USB: 0x2d] -_ 267 | 0xBE: { code: 'Period', keyCap: '.' }, // [USB: 0x37] .> 268 | 0xBF: { code: 'Slash', keyCap: '/' }, // [USB: 0x38] /? (US Standard 101) 269 | 270 | 0xC0: { code: 'Backquote', keyCap: '`' }, // [USB: 0x35] `~ (US Standard 101) 271 | // 0xC1-0xCF - reserved 272 | 273 | // 0xD0-0xD7 - reserved 274 | // 0xD8-0xDA - unassigned 275 | 0xDB: { code: 'BracketLeft', keyCap: '[' }, // [USB: 0x2f] [{ (US Standard 101) 276 | 0xDC: { code: 'Backslash', keyCap: '\\' }, // [USB: 0x31] \| (US Standard 101) 277 | 0xDD: { code: 'BracketRight', keyCap: ']' }, // [USB: 0x30] ]} (US Standard 101) 278 | 0xDE: { code: 'Quote', keyCap: '\'' }, // [USB: 0x34] '" (US Standard 101) 279 | // 0xDF - miscellaneous/varies 280 | 281 | // 0xE0 - reserved 282 | // 0xE1 - OEM specific 283 | 0xE2: { code: 'IntlBackslash', keyCap: '\\' }, // [USB: 0x64] \| (UK Standard 102) 284 | // 0xE3-0xE4 - OEM specific 285 | 0xE5: { code: 'Process' }, // (Not in D3E) 286 | // 0xE6 - OEM specific 287 | // 0xE7 - VK_PACKET 288 | // 0xE8 - unassigned 289 | // 0xE9-0xEF - OEM specific 290 | 291 | // 0xF0-0xF5 - OEM specific 292 | 0xF6: { code: 'Attn' }, // [USB: 0x9a] (Not in D3E) 293 | 0xF7: { code: 'CrSel' }, // [USB: 0xa3] (Not in D3E) 294 | 0xF8: { code: 'ExSel' }, // [USB: 0xa4] (Not in D3E) 295 | 0xF9: { code: 'EraseEof' }, // (Not in D3E) 296 | 0xFA: { code: 'Play' }, // (Not in D3E) 297 | 0xFB: { code: 'ZoomToggle' }, // (Not in D3E) 298 | // 0xFC - VK_NONAME - reserved 299 | // 0xFD - VK_PA1 300 | 0xFE: { code: 'Clear' } // [USB: 0x9c] (Not in D3E) 301 | }; 302 | 303 | // No legacy keyCode, but listed in D3E: 304 | 305 | // code: usb 306 | // 'IntlHash': 0x070032, 307 | // 'IntlRo': 0x070087, 308 | // 'IntlYen': 0x070089, 309 | // 'NumpadBackspace': 0x0700bb, 310 | // 'NumpadClear': 0x0700d8, 311 | // 'NumpadClearEntry': 0x0700d9, 312 | // 'NumpadMemoryAdd': 0x0700d3, 313 | // 'NumpadMemoryClear': 0x0700d2, 314 | // 'NumpadMemoryRecall': 0x0700d1, 315 | // 'NumpadMemoryStore': 0x0700d0, 316 | // 'NumpadMemorySubtract': 0x0700d4, 317 | // 'NumpadParenLeft': 0x0700b6, 318 | // 'NumpadParenRight': 0x0700b7, 319 | 320 | //-------------------------------------------------------------------- 321 | // 322 | // Browser/OS Specific Mappings 323 | // 324 | //-------------------------------------------------------------------- 325 | 326 | mergeIf(keyCodeToInfoTable, 327 | 'moz', { 328 | 0x3B: { code: 'Semicolon', keyCap: ';' }, // [USB: 0x33] ;: (US Standard 101) 329 | 0x3D: { code: 'Equal', keyCap: '=' }, // [USB: 0x2e] =+ 330 | 0x6B: { code: 'Equal', keyCap: '=' }, // [USB: 0x2e] =+ 331 | 0x6D: { code: 'Minus', keyCap: '-' }, // [USB: 0x2d] -_ 332 | 0xBB: { code: 'NumpadAdd', keyCap: '+', location: NUMPAD }, // [USB: 0x57] 333 | 0xBD: { code: 'NumpadSubtract', keyCap: '-', location: NUMPAD } // [USB: 0x56] 334 | }); 335 | 336 | mergeIf(keyCodeToInfoTable, 337 | 'moz-mac', { 338 | 0x0C: { code: 'NumLock', location: NUMPAD }, // [USB: 0x53] 339 | 0xAD: { code: 'Minus', keyCap: '-' } // [USB: 0x2d] -_ 340 | }); 341 | 342 | mergeIf(keyCodeToInfoTable, 343 | 'moz-win', { 344 | 0xAD: { code: 'Minus', keyCap: '-' } // [USB: 0x2d] -_ 345 | }); 346 | 347 | mergeIf(keyCodeToInfoTable, 348 | 'chrome-mac', { 349 | 0x5D: { code: 'OSRight', location: RIGHT } // [USB: 0xe7] 350 | }); 351 | 352 | // Windows via Bootcamp (!) 353 | if (0) { 354 | mergeIf(keyCodeToInfoTable, 355 | 'chrome-win', { 356 | 0xC0: { code: 'Quote', keyCap: '\'' }, // [USB: 0x34] '" (US Standard 101) 357 | 0xDE: { code: 'Backslash', keyCap: '\\' }, // [USB: 0x31] \| (US Standard 101) 358 | 0xDF: { code: 'Backquote', keyCap: '`' } // [USB: 0x35] `~ (US Standard 101) 359 | }); 360 | 361 | mergeIf(keyCodeToInfoTable, 362 | 'ie', { 363 | 0xC0: { code: 'Quote', keyCap: '\'' }, // [USB: 0x34] '" (US Standard 101) 364 | 0xDE: { code: 'Backslash', keyCap: '\\' }, // [USB: 0x31] \| (US Standard 101) 365 | 0xDF: { code: 'Backquote', keyCap: '`' } // [USB: 0x35] `~ (US Standard 101) 366 | }); 367 | } 368 | 369 | mergeIf(keyCodeToInfoTable, 370 | 'safari', { 371 | 0x03: { code: 'Enter' }, // [USB: 0x28] old Safari 372 | 0x19: { code: 'Tab' } // [USB: 0x2b] old Safari for Shift+Tab 373 | }); 374 | 375 | mergeIf(keyCodeToInfoTable, 376 | 'ios', { 377 | 0x0A: { code: 'Enter', location: STANDARD } // [USB: 0x28] 378 | }); 379 | 380 | mergeIf(keyCodeToInfoTable, 381 | 'safari-mac', { 382 | 0x5B: { code: 'OSLeft', location: LEFT }, // [USB: 0xe3] 383 | 0x5D: { code: 'OSRight', location: RIGHT }, // [USB: 0xe7] 384 | 0xE5: { code: 'KeyQ', keyCap: 'Q' } // [USB: 0x14] On alternate presses, Ctrl+Q sends this 385 | }); 386 | 387 | //-------------------------------------------------------------------- 388 | // 389 | // Identifier Mappings 390 | // 391 | //-------------------------------------------------------------------- 392 | 393 | // Cases where newer-ish browsers send keyIdentifier which can be 394 | // used to disambiguate keys. 395 | 396 | // keyIdentifierTable[keyIdentifier] -> keyInfo 397 | 398 | var keyIdentifierTable = {}; 399 | if ('cros' === os) { 400 | keyIdentifierTable['U+00A0'] = { code: 'ShiftLeft', location: LEFT }; 401 | keyIdentifierTable['U+00A1'] = { code: 'ShiftRight', location: RIGHT }; 402 | keyIdentifierTable['U+00A2'] = { code: 'ControlLeft', location: LEFT }; 403 | keyIdentifierTable['U+00A3'] = { code: 'ControlRight', location: RIGHT }; 404 | keyIdentifierTable['U+00A4'] = { code: 'AltLeft', location: LEFT }; 405 | keyIdentifierTable['U+00A5'] = { code: 'AltRight', location: RIGHT }; 406 | } 407 | if ('chrome-mac' === browser_os) { 408 | keyIdentifierTable['U+0010'] = { code: 'ContextMenu' }; 409 | } 410 | if ('safari-mac' === browser_os) { 411 | keyIdentifierTable['U+0010'] = { code: 'ContextMenu' }; 412 | } 413 | if ('ios' === os) { 414 | // These only generate keyup events 415 | keyIdentifierTable['U+0010'] = { code: 'Function' }; 416 | 417 | keyIdentifierTable['U+001C'] = { code: 'ArrowLeft' }; 418 | keyIdentifierTable['U+001D'] = { code: 'ArrowRight' }; 419 | keyIdentifierTable['U+001E'] = { code: 'ArrowUp' }; 420 | keyIdentifierTable['U+001F'] = { code: 'ArrowDown' }; 421 | 422 | keyIdentifierTable['U+0001'] = { code: 'Home' }; // [USB: 0x4a] Fn + ArrowLeft 423 | keyIdentifierTable['U+0004'] = { code: 'End' }; // [USB: 0x4d] Fn + ArrowRight 424 | keyIdentifierTable['U+000B'] = { code: 'PageUp' }; // [USB: 0x4b] Fn + ArrowUp 425 | keyIdentifierTable['U+000C'] = { code: 'PageDown' }; // [USB: 0x4e] Fn + ArrowDown 426 | } 427 | 428 | //-------------------------------------------------------------------- 429 | // 430 | // Location Mappings 431 | // 432 | //-------------------------------------------------------------------- 433 | 434 | // Cases where newer-ish browsers send location/keyLocation which 435 | // can be used to disambiguate keys. 436 | 437 | // locationTable[location][keyCode] -> keyInfo 438 | var locationTable = []; 439 | locationTable[LEFT] = { 440 | 0x10: { code: 'ShiftLeft', location: LEFT }, // [USB: 0xe1] 441 | 0x11: { code: 'ControlLeft', location: LEFT }, // [USB: 0xe0] 442 | 0x12: { code: 'AltLeft', location: LEFT } // [USB: 0xe2] 443 | }; 444 | locationTable[RIGHT] = { 445 | 0x10: { code: 'ShiftRight', location: RIGHT }, // [USB: 0xe5] 446 | 0x11: { code: 'ControlRight', location: RIGHT }, // [USB: 0xe4] 447 | 0x12: { code: 'AltRight', location: RIGHT } // [USB: 0xe6] 448 | }; 449 | locationTable[NUMPAD] = { 450 | 0x0D: { code: 'NumpadEnter', location: NUMPAD } // [USB: 0x58] 451 | }; 452 | 453 | mergeIf(locationTable[NUMPAD], 'moz', { 454 | 0x6D: { code: 'NumpadSubtract', location: NUMPAD }, // [USB: 0x56] 455 | 0x6B: { code: 'NumpadAdd', location: NUMPAD } // [USB: 0x57] 456 | }); 457 | mergeIf(locationTable[LEFT], 'moz-mac', { 458 | 0xE0: { code: 'OSLeft', location: LEFT } // [USB: 0xe3] 459 | }); 460 | mergeIf(locationTable[RIGHT], 'moz-mac', { 461 | 0xE0: { code: 'OSRight', location: RIGHT } // [USB: 0xe7] 462 | }); 463 | mergeIf(locationTable[RIGHT], 'moz-win', { 464 | 0x5B: { code: 'OSRight', location: RIGHT } // [USB: 0xe7] 465 | }); 466 | 467 | 468 | mergeIf(locationTable[RIGHT], 'mac', { 469 | 0x5D: { code: 'OSRight', location: RIGHT } // [USB: 0xe7] 470 | }); 471 | 472 | mergeIf(locationTable[NUMPAD], 'chrome-mac', { 473 | 0x0C: { code: 'NumLock', location: NUMPAD } // [USB: 0x53] 474 | }); 475 | 476 | mergeIf(locationTable[NUMPAD], 'safari-mac', { 477 | 0x0C: { code: 'NumLock', location: NUMPAD }, // [USB: 0x53] 478 | 0xBB: { code: 'NumpadAdd', location: NUMPAD }, // [USB: 0x57] 479 | 0xBD: { code: 'NumpadSubtract', location: NUMPAD }, // [USB: 0x56] 480 | 0xBE: { code: 'NumpadDecimal', location: NUMPAD }, // [USB: 0x63] 481 | 0xBF: { code: 'NumpadDivide', location: NUMPAD } // [USB: 0x54] 482 | }); 483 | 484 | 485 | //-------------------------------------------------------------------- 486 | // 487 | // Key Values 488 | // 489 | //-------------------------------------------------------------------- 490 | 491 | // Mapping from `code` values to `key` values. Values defined at: 492 | // https://dvcs.w3.org/hg/dom3events/raw-file/tip/html/DOM3Events-key.html 493 | // Entries are only provided when `key` differs from `code`. If 494 | // printable, `shiftKey` has the shifted printable character. This 495 | // assumes US Standard 101 layout 496 | 497 | var codeToKeyTable = { 498 | // Modifier Keys 499 | ShiftLeft: { key: 'Shift' }, 500 | ShiftRight: { key: 'Shift' }, 501 | ControlLeft: { key: 'Control' }, 502 | ControlRight: { key: 'Control' }, 503 | AltLeft: { key: 'Alt' }, 504 | AltRight: { key: 'Alt' }, 505 | OSLeft: { key: 'OS' }, 506 | OSRight: { key: 'OS' }, 507 | 508 | // Whitespace Keys 509 | NumpadEnter: { key: 'Enter' }, 510 | Space: { key: ' ' }, 511 | 512 | // Printable Keys 513 | Digit0: { key: '0', shiftKey: ')' }, 514 | Digit1: { key: '1', shiftKey: '!' }, 515 | Digit2: { key: '2', shiftKey: '@' }, 516 | Digit3: { key: '3', shiftKey: '#' }, 517 | Digit4: { key: '4', shiftKey: '$' }, 518 | Digit5: { key: '5', shiftKey: '%' }, 519 | Digit6: { key: '6', shiftKey: '^' }, 520 | Digit7: { key: '7', shiftKey: '&' }, 521 | Digit8: { key: '8', shiftKey: '*' }, 522 | Digit9: { key: '9', shiftKey: '(' }, 523 | KeyA: { key: 'a', shiftKey: 'A' }, 524 | KeyB: { key: 'b', shiftKey: 'B' }, 525 | KeyC: { key: 'c', shiftKey: 'C' }, 526 | KeyD: { key: 'd', shiftKey: 'D' }, 527 | KeyE: { key: 'e', shiftKey: 'E' }, 528 | KeyF: { key: 'f', shiftKey: 'F' }, 529 | KeyG: { key: 'g', shiftKey: 'G' }, 530 | KeyH: { key: 'h', shiftKey: 'H' }, 531 | KeyI: { key: 'i', shiftKey: 'I' }, 532 | KeyJ: { key: 'j', shiftKey: 'J' }, 533 | KeyK: { key: 'k', shiftKey: 'K' }, 534 | KeyL: { key: 'l', shiftKey: 'L' }, 535 | KeyM: { key: 'm', shiftKey: 'M' }, 536 | KeyN: { key: 'n', shiftKey: 'N' }, 537 | KeyO: { key: 'o', shiftKey: 'O' }, 538 | KeyP: { key: 'p', shiftKey: 'P' }, 539 | KeyQ: { key: 'q', shiftKey: 'Q' }, 540 | KeyR: { key: 'r', shiftKey: 'R' }, 541 | KeyS: { key: 's', shiftKey: 'S' }, 542 | KeyT: { key: 't', shiftKey: 'T' }, 543 | KeyU: { key: 'u', shiftKey: 'U' }, 544 | KeyV: { key: 'v', shiftKey: 'V' }, 545 | KeyW: { key: 'w', shiftKey: 'W' }, 546 | KeyX: { key: 'x', shiftKey: 'X' }, 547 | KeyY: { key: 'y', shiftKey: 'Y' }, 548 | KeyZ: { key: 'z', shiftKey: 'Z' }, 549 | Numpad0: { key: '0' }, 550 | Numpad1: { key: '1' }, 551 | Numpad2: { key: '2' }, 552 | Numpad3: { key: '3' }, 553 | Numpad4: { key: '4' }, 554 | Numpad5: { key: '5' }, 555 | Numpad6: { key: '6' }, 556 | Numpad7: { key: '7' }, 557 | Numpad8: { key: '8' }, 558 | Numpad9: { key: '9' }, 559 | NumpadMultiply: { key: '*' }, 560 | NumpadAdd: { key: '+' }, 561 | NumpadComma: { key: ',' }, 562 | NumpadSubtract: { key: '-' }, 563 | NumpadDecimal: { key: '.' }, 564 | NumpadDivide: { key: '/' }, 565 | Semicolon: { key: ';', shiftKey: ':' }, 566 | Equal: { key: '=', shiftKey: '+' }, 567 | Comma: { key: ',', shiftKey: '<' }, 568 | Minus: { key: '-', shiftKey: '_' }, 569 | Period: { key: '.', shiftKey: '>' }, 570 | Slash: { key: '/', shiftKey: '?' }, 571 | Backquote: { key: '`', shiftKey: '~' }, 572 | BracketLeft: { key: '[', shiftKey: '{' }, 573 | Backslash: { key: '\\', shiftKey: '|' }, 574 | BracketRight: { key: ']', shiftKey: '}' }, 575 | Quote: { key: '\'', shiftKey: '"' }, 576 | IntlBackslash: { key: '\\', shiftKey: '|' } 577 | }; 578 | 579 | mergeIf(codeToKeyTable, 'mac', { 580 | OSLeft: { key: 'Meta' }, 581 | OSRight: { key: 'Meta' } 582 | }); 583 | 584 | // Corrections for 'key' names in older browsers (e.g. FF36-) 585 | // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key#Key_values 586 | var keyFixTable = { 587 | Esc: 'Escape', 588 | Nonconvert: 'NonConvert', 589 | Left: 'ArrowLeft', 590 | Up: 'ArrowUp', 591 | Right: 'ArrowRight', 592 | Down: 'ArrowDown', 593 | Del: 'Delete', 594 | Menu: 'ContextMenu', 595 | MediaNextTrack: 'MediaTrackNext', 596 | MediaPreviousTrack: 'MediaTrackPrevious', 597 | SelectMedia: 'MediaSelect', 598 | HalfWidth: 'Hankaku', 599 | FullWidth: 'Zenkaku', 600 | RomanCharacters: 'Romaji', 601 | Crsel: 'CrSel', 602 | Exsel: 'ExSel', 603 | Zoom: 'ZoomToggle' 604 | }; 605 | 606 | //-------------------------------------------------------------------- 607 | // 608 | // Exported Functions 609 | // 610 | //-------------------------------------------------------------------- 611 | 612 | 613 | var codeTable = remap(keyCodeToInfoTable, 'code'); 614 | 615 | try { 616 | var nativeLocation = nativeKeyboardEvent && ('location' in new KeyboardEvent('')); 617 | } catch (_) {} 618 | 619 | function keyInfoForEvent(event) { 620 | var keyCode = 'keyCode' in event ? event.keyCode : 'which' in event ? event.which : 0; 621 | 622 | var keyInfo = (function(){ 623 | if (nativeLocation || 'keyLocation' in event) { 624 | var location = nativeLocation ? event.location : event.keyLocation; 625 | if (location && keyCode in locationTable[location]) { 626 | return locationTable[location][keyCode]; 627 | } 628 | } 629 | if ('keyIdentifier' in event && event.keyIdentifier in keyIdentifierTable) { 630 | return keyIdentifierTable[event.keyIdentifier]; 631 | } 632 | if (keyCode in keyCodeToInfoTable) { 633 | return keyCodeToInfoTable[keyCode]; 634 | } 635 | return null; 636 | }()); 637 | 638 | // TODO: Track these down and move to general tables 639 | if (0) { 640 | // TODO: Map these for newerish browsers? 641 | // TODO: iOS only? 642 | // TODO: Override with more common keyIdentifier name? 643 | switch (event.keyIdentifier) { 644 | case 'U+0010': keyInfo = { code: 'Function' }; break; 645 | case 'U+001C': keyInfo = { code: 'ArrowLeft' }; break; 646 | case 'U+001D': keyInfo = { code: 'ArrowRight' }; break; 647 | case 'U+001E': keyInfo = { code: 'ArrowUp' }; break; 648 | case 'U+001F': keyInfo = { code: 'ArrowDown' }; break; 649 | } 650 | } 651 | 652 | if (!keyInfo) 653 | return null; 654 | 655 | var key = (function() { 656 | var entry = codeToKeyTable[keyInfo.code]; 657 | if (!entry) return keyInfo.code; 658 | return (event.shiftKey && 'shiftKey' in entry) ? entry.shiftKey : entry.key; 659 | }()); 660 | 661 | return { 662 | code: keyInfo.code, 663 | key: key, 664 | location: keyInfo.location, 665 | keyCap: keyInfo.keyCap 666 | }; 667 | } 668 | 669 | function queryKeyCap(code, locale) { 670 | code = String(code); 671 | if (!codeTable.hasOwnProperty(code)) return 'Undefined'; 672 | if (locale && String(locale).toLowerCase() !== 'en-us') throw Error('Unsupported locale'); 673 | var keyInfo = codeTable[code]; 674 | return keyInfo.keyCap || keyInfo.code || 'Undefined'; 675 | } 676 | 677 | if ('KeyboardEvent' in global && 'defineProperty' in Object) { 678 | (function() { 679 | function define(o, p, v) { 680 | if (p in o) return; 681 | Object.defineProperty(o, p, v); 682 | } 683 | 684 | define(KeyboardEvent.prototype, 'code', { get: function() { 685 | var keyInfo = keyInfoForEvent(this); 686 | return keyInfo ? keyInfo.code : ''; 687 | }}); 688 | 689 | // Fix for nonstandard `key` values (FF36-) 690 | if ('key' in KeyboardEvent.prototype) { 691 | var desc = Object.getOwnPropertyDescriptor(KeyboardEvent.prototype, 'key'); 692 | Object.defineProperty(KeyboardEvent.prototype, 'key', { get: function() { 693 | var key = desc.get.call(this); 694 | return keyFixTable.hasOwnProperty(key) ? keyFixTable[key] : key; 695 | }}); 696 | } 697 | 698 | define(KeyboardEvent.prototype, 'key', { get: function() { 699 | var keyInfo = keyInfoForEvent(this); 700 | return (keyInfo && 'key' in keyInfo) ? keyInfo.key : 'Unidentified'; 701 | }}); 702 | 703 | define(KeyboardEvent.prototype, 'location', { get: function() { 704 | var keyInfo = keyInfoForEvent(this); 705 | return (keyInfo && 'location' in keyInfo) ? keyInfo.location : STANDARD; 706 | }}); 707 | 708 | define(KeyboardEvent.prototype, 'locale', { get: function() { 709 | return ''; 710 | }}); 711 | }()); 712 | } 713 | 714 | if (!('queryKeyCap' in global.KeyboardEvent)) 715 | global.KeyboardEvent.queryKeyCap = queryKeyCap; 716 | 717 | // Helper for IE8- 718 | global.identifyKey = function(event) { 719 | if ('code' in event) 720 | return; 721 | 722 | var keyInfo = keyInfoForEvent(event); 723 | event.code = keyInfo ? keyInfo.code : ''; 724 | event.key = (keyInfo && 'key' in keyInfo) ? keyInfo.key : 'Unidentified'; 725 | event.location = ('location' in event) ? event.location : 726 | ('keyLocation' in event) ? event.keyLocation : 727 | (keyInfo && 'location' in keyInfo) ? keyInfo.location : STANDARD; 728 | event.locale = ''; 729 | }; 730 | 731 | } (window)); 732 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aframe-keyboard-controls", 3 | "version": "1.3.0", 4 | "description": "Keyboard controls for A-Frame.", 5 | "main": "keyboard-controls.js", 6 | "config": { 7 | "demo_host": "localhost", 8 | "demo_port": 8000 9 | }, 10 | "scripts": { 11 | "dev": "budo browser.js:bundle.js --dir examples --port $npm_package_config_demo_port --host $npm_package_config_demo_host --live", 12 | "dist": "webpack browser.js dist/aframe-keyboard-controls.js && webpack -p browser.js dist/aframe-keyboard-controls.min.js", 13 | "postpublish": "npm run dist", 14 | "test": "karma start ./tests/karma.conf.js", 15 | "preversion": "karma start ./tests/karma.conf.js --single-run", 16 | "version": "npm run dist && git add -A dist", 17 | "postversion": "git push && git push --tags && npm publish" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/donmccurdy/aframe-keyboard-controls.git" 22 | }, 23 | "keywords": [ 24 | "aframe", 25 | "aframe-component", 26 | "aframe-vr", 27 | "vr", 28 | "mozvr", 29 | "webvr", 30 | "keyboard", 31 | "controls", 32 | "wasd", 33 | "input" 34 | ], 35 | "author": "Don McCurdy ", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/donmccurdy/aframe-keyboard-controls/issues" 39 | }, 40 | "homepage": "https://github.com/donmccurdy/aframe-keyboard-controls#readme", 41 | "dependencies": {}, 42 | "devDependencies": { 43 | "aframe-core": "^0.1.3", 44 | "browserify": "^12.0.1", 45 | "browserify-css": "^0.8.3", 46 | "budo": "^7.1.0", 47 | "chai": "^3.4.1", 48 | "chai-shallow-deep-equal": "^1.3.0", 49 | "karma": "^0.13.15", 50 | "karma-browserify": "^4.4.2", 51 | "karma-chai-shallow-deep-equal": "0.0.4", 52 | "karma-firefox-launcher": "^0.1.7", 53 | "karma-mocha": "^0.2.1", 54 | "karma-mocha-reporter": "^1.1.3", 55 | "karma-sinon-chai": "^1.1.0", 56 | "mocha": "^2.3.4", 57 | "webpack": "^1.12.9" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /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-core').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/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 | -------------------------------------------------------------------------------- /tests/keyboard-controls.test.js: -------------------------------------------------------------------------------- 1 | var Aframe = require('aframe-core'); 2 | var component = require('../keyboard-controls'); 3 | var entityFactory = require('./helpers').entityFactory; 4 | 5 | Aframe.registerComponent('keyboard-controls', component); 6 | 7 | describe('Keyboard Controls', function () { 8 | 9 | /******************************************************************* 10 | * Setup 11 | */ 12 | 13 | var EPS = 1e-6; 14 | 15 | var ctrl, 16 | currentTime = 0; 17 | 18 | beforeEach(function () { 19 | // Mock time 20 | currentTime = 0; 21 | this.sinon.stub(window.performance, 'now', function () { return currentTime; }); 22 | }); 23 | 24 | beforeEach(function (done) { 25 | this.el = entityFactory(); 26 | this.el.setAttribute('keyboard-controls', ''); 27 | this.el.addEventListener('loaded', function () { 28 | ctrl = this.el.components['keyboard-controls']; 29 | done(); 30 | }.bind(this)); 31 | }); 32 | 33 | /******************************************************************* 34 | * Tests 35 | */ 36 | 37 | describe('Accessors', function () { 38 | it('is attached to component', function () { 39 | expect(ctrl).to.be.ok; 40 | }); 41 | }); 42 | 43 | }); 44 | --------------------------------------------------------------------------------