├── .gitignore ├── utils ├── data-plotter │ ├── screenshot.png │ ├── index.html │ └── LeapDataPlotter.js ├── leap-plugins-0.1.12-utils.min.js └── leap-plugins-0.1.12-utils.js ├── test ├── package.json ├── node.coffee └── versionCheck.html ├── index.js ├── README_DOCS.md ├── bower.json ├── main ├── hand-entry │ ├── index.html │ ├── leap.hand-entry.coffee │ └── leap.hand-entry.js ├── version-check │ ├── leap.version-check.coffee │ └── leap.version-check.js ├── screen-position │ ├── index.html │ ├── leap.screen-position.coffee │ └── leap.screen-position.js ├── hand-hold │ ├── leap.hand-hold.coffee │ └── leap.hand-hold.js ├── bone-hand │ ├── index.html │ ├── leap.bone-hand.coffee │ ├── leap.bone-hand.js │ └── lib │ │ └── OrbitControls.js ├── transform │ ├── leap.transform.js │ ├── leap.transform.coffee │ └── index.html └── leap-plugins-0.1.12.min.js ├── package.json ├── CHANGELOG ├── README.md ├── Gruntfile.coffee ├── CONTRIBUTORS └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules -------------------------------------------------------------------------------- /utils/data-plotter/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leapmotion/leapjs-plugins/HEAD/utils/data-plotter/screenshot.png -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leapjs-plugins-test", 3 | "dependencies": { 4 | "leapjs": "git://github.com/leapmotion/leapjs.git#0.4.1" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/node.coffee: -------------------------------------------------------------------------------- 1 | # to run this: coffee node.coffee -n 2 | 3 | leap = require('leapjs') 4 | plugins = require('../main/leapjs-plugins-0.1.1.js') 5 | 6 | controller = new leap.Controller(inNode: true) 7 | 8 | controller.use(plugins.handHold) 9 | 10 | console.log controller._pluginPipelineSteps 11 | console.log controller._pluginExtendedMethods -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | Leap = require('leapjs') 2 | require('./main/hand-entry/leap.hand-entry.js') 3 | require('./main/hand-hold/leap.hand-hold.js') 4 | require('./main/bone-hand/leap.bone-hand.js') 5 | require('./main/playback/leap.playback-0.2.1.js') 6 | require('./main/screen-position/leap.screen-position.js') 7 | require('./main/transform/leap.transform.js') 8 | module.exports = true 9 | -------------------------------------------------------------------------------- /README_DOCS.md: -------------------------------------------------------------------------------- 1 | http://jekyllrb.com/docs/installation/ 2 | 3 | gem install jekyll 4 | 5 | Merge master in to get latest plugin versions, then re-run 6 | 7 | ### to do local work, 8 | 9 | jekyll serve --watch --baseurl= 10 | 11 | Open your server at http://0.0.0.0:4000 12 | 13 | ### before you deploy 14 | 15 | jekyll serve --watch 16 | 17 | 18 | This will keep the root url /leapjs-plugins/ 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/versionCheck.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Version Check 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leapjs-plugins", 3 | "main": "index.js", 4 | "version": "0.1.12", 5 | "homepage": "https://github.com/leapmotion/leapjs-plugins", 6 | "description": "This repository holds a collection of independent plugins which extend the functionality of LeapJS itself.", 7 | "keywords": [ 8 | "Leap", 9 | "LeapMotion", 10 | "Plugins" 11 | ], 12 | "license": "Apache License V2", 13 | "ignore": [ 14 | "**/.*", 15 | "test", 16 | "Gruntfile.coffee" 17 | ], 18 | "dependencies": { 19 | "leapjs": ">0.4.x" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /main/hand-entry/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

11 | Open inspector to see logged events. 12 |

13 | 14 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leapjs-plugins", 3 | "version": "0.1.12", 4 | "description": "A collection of useful plugins for LeapJS", 5 | "main": "main/leapjs-plugins-0.1.12.js", 6 | "dependencies": { 7 | "three": "^0.69.0" 8 | }, 9 | "devDependencies": { 10 | "coffee-script": ">=1.7.1", 11 | "grunt-contrib-concat": "~0.3.0", 12 | "grunt-contrib-uglify": "~0.3.2", 13 | "grunt-contrib-coffee": "~0.8.2", 14 | "grunt-contrib-watch": "~0.6.1", 15 | "grunt-string-replace": "~0.2.7", 16 | "grunt-banner": "git://github.com/pehrlich/grunt-banner.git", 17 | "grunt-contrib-clean": "~0.5.0", 18 | "grunt": "~0.4.2", 19 | "grunt-bump": "0.0.13", 20 | "load-grunt-tasks": "~0.4.0" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git://github.com/leapmotion/leapjs-plugins.git" 25 | }, 26 | "keywords": [ 27 | "leap", 28 | "leapjs", 29 | "plugins", 30 | "extensions", 31 | "controller" 32 | ], 33 | "author": "pehrlich", 34 | "license": "Apache-2.0", 35 | "bugs": { 36 | "url": "http://github.com/leapmotion/leapjs-plugins/issues" 37 | }, 38 | "homepage": "http://leapmotion.github.io/leapjs-plugins/docs" 39 | } 40 | -------------------------------------------------------------------------------- /main/version-check/leap.version-check.coffee: -------------------------------------------------------------------------------- 1 | versionCheck = (scope)-> 2 | scope.alert ||= false 3 | scope.requiredProtocolVersion ||= 6 4 | scope.disconnect ||= true 5 | 6 | if (typeof Leap != 'undefined') && Leap.Controller 7 | if Leap.version.minor < 5 && Leap.version.dot < 4 8 | console.warn("LeapJS Version Check plugin incompatible with LeapJS pre 0.4.4") 9 | 10 | 11 | @on 'ready', -> 12 | required = scope.requiredProtocolVersion 13 | current = @connection.opts.requestProtocolVersion 14 | 15 | if current < required 16 | message = "Protocol Version too old. v#{required} required, v#{current} available." 17 | 18 | if scope.disconnect 19 | @disconnect() 20 | message += " Disconnecting." 21 | 22 | console.warn message 23 | 24 | if scope.alert 25 | alert("Your Leap Software version is out of date. Visit http://www.leapmotion.com/setup to update") 26 | 27 | @emit('versionCheck.outdated', { 28 | required: required 29 | current: current 30 | disconnect: scope.disconnect 31 | }) 32 | 33 | {} 34 | 35 | 36 | if (typeof Leap != 'undefined') && Leap.Controller 37 | Leap.Controller.plugin 'versionCheck', versionCheck 38 | else if (typeof module != 'undefined') 39 | module.exports.versionCheck = versionCheck 40 | else 41 | throw 'leap.js not included' -------------------------------------------------------------------------------- /main/version-check/leap.version-check.js: -------------------------------------------------------------------------------- 1 | //CoffeeScript generated from main/version-check/leap.version-check.coffee 2 | (function() { 3 | var versionCheck; 4 | 5 | versionCheck = function(scope) { 6 | scope.alert || (scope.alert = false); 7 | scope.requiredProtocolVersion || (scope.requiredProtocolVersion = 6); 8 | scope.disconnect || (scope.disconnect = true); 9 | if ((typeof Leap !== 'undefined') && Leap.Controller) { 10 | if (Leap.version.minor < 5 && Leap.version.dot < 4) { 11 | console.warn("LeapJS Version Check plugin incompatible with LeapJS pre 0.4.4"); 12 | } 13 | } 14 | this.on('ready', function() { 15 | var current, message, required; 16 | required = scope.requiredProtocolVersion; 17 | current = this.connection.opts.requestProtocolVersion; 18 | if (current < required) { 19 | message = "Protocol Version too old. v" + required + " required, v" + current + " available."; 20 | if (scope.disconnect) { 21 | this.disconnect(); 22 | message += " Disconnecting."; 23 | } 24 | console.warn(message); 25 | if (scope.alert) { 26 | alert("Your Leap Software version is out of date. Visit http://www.leapmotion.com/setup to update"); 27 | } 28 | return this.emit('versionCheck.outdated', { 29 | required: required, 30 | current: current, 31 | disconnect: scope.disconnect 32 | }); 33 | } 34 | }); 35 | return {}; 36 | }; 37 | 38 | if ((typeof Leap !== 'undefined') && Leap.Controller) { 39 | Leap.Controller.plugin('versionCheck', versionCheck); 40 | } else if (typeof module !== 'undefined') { 41 | module.exports.versionCheck = versionCheck; 42 | } else { 43 | throw 'leap.js not included'; 44 | } 45 | 46 | }).call(this); 47 | -------------------------------------------------------------------------------- /main/hand-entry/leap.hand-entry.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Emits controller events when a hand enters of leaves the frame 3 | "handLost" and "handFound" 4 | Each event also includes the hand object, which will be invalid for the handLost event. 5 | ### 6 | 7 | handEntry = -> 8 | activeHandIds = [] 9 | 10 | if Leap.version.major == 0 && Leap.version.minor < 5 11 | console.warn "The hand entry plugin requires LeapJS 0.5.0 or newer." 12 | 13 | # Note that for multiple devices, this would blink the still connected device's hands out of recognition for a moment. :-/ 14 | @on "deviceStopped", -> 15 | `for (var i = 0, len = activeHandIds.length; i < len; i++){ 16 | id = activeHandIds[i]; 17 | activeHandIds.splice(i, 1); 18 | // this gets executed before the current frame is added to the history. 19 | this.emit('handLost', this.lastConnectionFrame.hand(id)) 20 | i--; 21 | len--; 22 | }` 23 | return 24 | 25 | { 26 | frame: (frame)-> 27 | newValidHandIds = frame.hands.map (hand)-> hand.id 28 | 29 | `for (var i = 0, len = activeHandIds.length; i < len; i++){ 30 | id = activeHandIds[i]; 31 | if( newValidHandIds.indexOf(id) == -1){ 32 | activeHandIds.splice(i, 1); 33 | // this gets executed before the current frame is added to the history. 34 | this.emit('handLost', this.frame(1).hand(id)); 35 | i--; 36 | len--; 37 | } 38 | }` 39 | 40 | for id in newValidHandIds 41 | if activeHandIds.indexOf(id) == -1 42 | activeHandIds.push id 43 | @emit('handFound', frame.hand(id)) 44 | } 45 | 46 | 47 | 48 | if (typeof Leap != 'undefined') && Leap.Controller 49 | Leap.Controller.plugin 'handEntry', handEntry 50 | else if (typeof module != 'undefined') 51 | module.exports.handEntry = handEntry 52 | else 53 | throw 'leap.js not included' -------------------------------------------------------------------------------- /main/hand-entry/leap.hand-entry.js: -------------------------------------------------------------------------------- 1 | //CoffeeScript generated from main/hand-entry/leap.hand-entry.coffee 2 | /* 3 | Emits controller events when a hand enters of leaves the frame 4 | "handLost" and "handFound" 5 | Each event also includes the hand object, which will be invalid for the handLost event. 6 | */ 7 | 8 | 9 | (function() { 10 | var handEntry; 11 | 12 | handEntry = function() { 13 | var activeHandIds; 14 | activeHandIds = []; 15 | if (Leap.version.major === 0 && Leap.version.minor < 5) { 16 | console.warn("The hand entry plugin requires LeapJS 0.5.0 or newer."); 17 | } 18 | this.on("deviceStopped", function() { 19 | for (var i = 0, len = activeHandIds.length; i < len; i++){ 20 | id = activeHandIds[i]; 21 | activeHandIds.splice(i, 1); 22 | // this gets executed before the current frame is added to the history. 23 | this.emit('handLost', this.lastConnectionFrame.hand(id)) 24 | i--; 25 | len--; 26 | }; 27 | }); 28 | return { 29 | frame: function(frame) { 30 | var id, newValidHandIds, _i, _len, _results; 31 | newValidHandIds = frame.hands.map(function(hand) { 32 | return hand.id; 33 | }); 34 | for (var i = 0, len = activeHandIds.length; i < len; i++){ 35 | id = activeHandIds[i]; 36 | if( newValidHandIds.indexOf(id) == -1){ 37 | activeHandIds.splice(i, 1); 38 | // this gets executed before the current frame is added to the history. 39 | this.emit('handLost', this.frame(1).hand(id)); 40 | i--; 41 | len--; 42 | } 43 | }; 44 | _results = []; 45 | for (_i = 0, _len = newValidHandIds.length; _i < _len; _i++) { 46 | id = newValidHandIds[_i]; 47 | if (activeHandIds.indexOf(id) === -1) { 48 | activeHandIds.push(id); 49 | _results.push(this.emit('handFound', frame.hand(id))); 50 | } else { 51 | _results.push(void 0); 52 | } 53 | } 54 | return _results; 55 | } 56 | }; 57 | }; 58 | 59 | if ((typeof Leap !== 'undefined') && Leap.Controller) { 60 | Leap.Controller.plugin('handEntry', handEntry); 61 | } else if (typeof module !== 'undefined') { 62 | module.exports.handEntry = handEntry; 63 | } else { 64 | throw 'leap.js not included'; 65 | } 66 | 67 | }).call(this); 68 | -------------------------------------------------------------------------------- /main/screen-position/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 38 | 39 | 40 | 41 | 42 | 43 |
44 |
45 | 46 | 47 |
48 | 49 |
50 | 51 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /main/hand-hold/leap.hand-hold.coffee: -------------------------------------------------------------------------------- 1 | handHold = -> 2 | interFrameData = {} 3 | 4 | # like jQuery: accepts a hash to set, or a key and value to set, or a key to read. 5 | dataFn = (prefix, hashOrKey, value)-> 6 | interFrameData[prefix + @id] ||= [] 7 | dict = interFrameData[prefix + @id] 8 | 9 | if value != undefined 10 | dict[hashOrKey] = value 11 | 12 | else if( ({}).toString.call(hashOrKey) == '[object String]') 13 | 14 | return dict[hashOrKey] 15 | 16 | else 17 | for key, value of hashOrKey 18 | if value == undefined 19 | delete dict[key] 20 | else 21 | dict[key] = value 22 | { 23 | hand: { 24 | # like jQuery: accepts a hash to set, or a key and value to set, or a key to read. 25 | # 26 | # set: 27 | # hand.data('color', 'blue') 28 | # -> 'blue' 29 | # 30 | # get: 31 | # hand.data('color') 32 | # -> 'blue' 33 | # 34 | # use defaults: 35 | # otherHand.data('color', {default: 'green'}) 36 | # -> 'green' 37 | # 38 | # value will be stored after first call with default: 39 | # otherHand.data('color') 40 | # -> 'green' 41 | data: (hashOrKey, value)-> 42 | dataFn.call(this, 'h', hashOrKey, value) 43 | , 44 | 45 | # Give the hand an object to hold, which will be returned by .holding() 46 | # if no object is passed, the hand will try and hold whatever it is hovering over. 47 | hold: (object)-> 48 | if object 49 | @data(holding: object) 50 | else 51 | @hold @hovering() 52 | 53 | holding: -> 54 | @data('holding') 55 | 56 | release: -> 57 | release = @data('holding') 58 | @data(holding: undefined) 59 | release 60 | 61 | # Saves a method which will be used to calculate what a hand is hovering over. 62 | hoverFn: (getHover)-> 63 | @data(getHover: getHover) 64 | 65 | # Returns what the hand is currently hovering over 66 | # This does not persist between frames 67 | hovering: -> 68 | if getHover = @data('getHover') 69 | @_hovering ||= getHover.call(this) 70 | 71 | } 72 | pointable: { 73 | data: (hashOrKey, value)-> 74 | dataFn.call(this, 'p', hashOrKey, value) 75 | } 76 | } 77 | 78 | if (typeof Leap != 'undefined') && Leap.Controller 79 | Leap.Controller.plugin 'handHold', handHold 80 | else if (typeof module != 'undefined') 81 | module.exports.handHold = handHold 82 | else 83 | throw 'leap.js not included' -------------------------------------------------------------------------------- /main/hand-hold/leap.hand-hold.js: -------------------------------------------------------------------------------- 1 | //CoffeeScript generated from main/hand-hold/leap.hand-hold.coffee 2 | (function() { 3 | var handHold; 4 | 5 | handHold = function() { 6 | var dataFn, interFrameData; 7 | interFrameData = {}; 8 | dataFn = function(prefix, hashOrKey, value) { 9 | var dict, key, _name, _results; 10 | interFrameData[_name = prefix + this.id] || (interFrameData[_name] = []); 11 | dict = interFrameData[prefix + this.id]; 12 | if (value !== void 0) { 13 | return dict[hashOrKey] = value; 14 | } else if ({}.toString.call(hashOrKey) === '[object String]') { 15 | return dict[hashOrKey]; 16 | } else { 17 | _results = []; 18 | for (key in hashOrKey) { 19 | value = hashOrKey[key]; 20 | if (value === void 0) { 21 | _results.push(delete dict[key]); 22 | } else { 23 | _results.push(dict[key] = value); 24 | } 25 | } 26 | return _results; 27 | } 28 | }; 29 | return { 30 | hand: { 31 | data: function(hashOrKey, value) { 32 | return dataFn.call(this, 'h', hashOrKey, value); 33 | }, 34 | hold: function(object) { 35 | if (object) { 36 | return this.data({ 37 | holding: object 38 | }); 39 | } else { 40 | return this.hold(this.hovering()); 41 | } 42 | }, 43 | holding: function() { 44 | return this.data('holding'); 45 | }, 46 | release: function() { 47 | var release; 48 | release = this.data('holding'); 49 | this.data({ 50 | holding: void 0 51 | }); 52 | return release; 53 | }, 54 | hoverFn: function(getHover) { 55 | return this.data({ 56 | getHover: getHover 57 | }); 58 | }, 59 | hovering: function() { 60 | var getHover; 61 | if (getHover = this.data('getHover')) { 62 | return this._hovering || (this._hovering = getHover.call(this)); 63 | } 64 | } 65 | }, 66 | pointable: { 67 | data: function(hashOrKey, value) { 68 | return dataFn.call(this, 'p', hashOrKey, value); 69 | } 70 | } 71 | }; 72 | }; 73 | 74 | if ((typeof Leap !== 'undefined') && Leap.Controller) { 75 | Leap.Controller.plugin('handHold', handHold); 76 | } else if (typeof module !== 'undefined') { 77 | module.exports.handHold = handHold; 78 | } else { 79 | throw 'leap.js not included'; 80 | } 81 | 82 | }).call(this); 83 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | # 0.1.11 2 | - NPM support 3 | - [boneHand] Add default shadowing and shadow camera. (yay) 4 | - [boneHand] Add callbacks for `onMeshCreated`, `onMeshUsed`, and `traverse` method. 5 | - [boneHand] Fix issue where `render` method is required. 6 | - [boneHand] Add `renderDepth` to meshes - prevents flicker (aka "transparency bouncing") 7 | - [boneHand] Name the bone meshes 8 | - [boneHand] Fix bug where color options would be ignored 9 | - [boneHand] Allow width and height to be passed in to default renderer. 10 | - [transform,boneHand] Add "desktop" mode - this sets units to meters, but leaves the coordinate system un-rotated. 11 | - [transform] subtle CPU optimize 12 | 13 | 14 | 15 | # 0.1.10 16 | - [transform] Do not allow duplicate transformations (important for LeapJS 0.6.4+ with playback, which can emit the same frame multiple times) 17 | - [boneHand] Automatically scale the view frustum based upon scene scale. 18 | - [boneHand] Render after leapmotion pipeline runs, expose scope.render method 19 | - [boneHand] Expose HandMesh on scope 20 | - [boneHand] Fix issue where including plugins would depend on THREE.js 21 | - [boneHand] Fix issue where handMesh would not be cleaned up on hand lost 22 | - [boneHand] fix issue where deferred scene creation would result in a one hand maximum 23 | 24 | # 0.1.9 25 | - [bone hand] Added bone hand plugin. 26 | - [bone hand] Optimized performance for new hands entering the scene. 27 | - [transform] Added preset VR mode 28 | - [transform] Add effectiveParent option 29 | - [transform] Add arm support, bone lengths, bone bases 30 | - [transform] Fix issue where rotations would be scaled 31 | - [playback, plotter] Minor speedup, removing errant console assertions. 32 | - [meta] Added grunt watch. 33 | 34 | # 0.1.8 35 | - Add LeapDataPlotter utility 36 | - Fix issue where bone tip position would not be transformed. 37 | - Fix issue where playback would require globally accessible controller. 38 | 39 | # 0.1.7 40 | - Transform plugin 41 | - resumeOnHandLost option added to playback 42 | - Fix an issue in playback which would cause the "connect leap" icon to be displayed incorrectly. 43 | 44 | # 0.1.6.1 45 | - A couple of fixes for ie-compatibility with playback and hand-hold plugins 46 | - Screen position now uses palm/tip position in by default, rather than their stabalized counterparts 47 | 48 | # 0.1.6 49 | - Fix issue where hand data would incorrectly stash data, or values could not be nulled 50 | 51 | # 0.1.5 52 | - Allow hand-hold to accept default values 53 | - Upgrade playback to v0.2.0, now with better animations and 60% better file compression 54 | - Playback will no longer crash when used in the header 55 | - Version check has a more descriptive warning 56 | 57 | # 0.1.4 58 | - Add playback 59 | - Add Version check plugin 60 | - Fix issue where multiple hands in frame on device-disconnect would misfire handLost 61 | - Fix issue in hand-entry where handFound would not fire after reconnect 62 | 63 | # 0.1.3 64 | - Compatibility with leapjs 0.4.2 (plugin pipeline now on animationFrames) 65 | 66 | # 0.1.2 67 | - Include lost hand data in handLost event 68 | - Add z-depth to screenPosition 69 | 70 | # 0.1.1 71 | - Proximity Alert Plugin 72 | - Better Comments on the tops of js files 73 | - ScreenPosition now uses a top left origin rather than a bottom right one. 74 | - Node support -------------------------------------------------------------------------------- /main/screen-position/leap.screen-position.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | Adds the "screenPosition" method by default to hands and pointables. This returns a vec3 (an array of length 3) 3 | with [x,y,z] screen coordinates indicating where the hand is, originating from the bottom left. 4 | This method can accept an optional vec3, allowing it to convert any arbitrary vec3 of coordinates. 5 | 6 | Custom positioning methods can be passed in, allowing different scaling techniques, 7 | e.g., http://msdn.microsoft.com/en-us/library/windows/hardware/gg463319.aspx (Pointer Ballistics) 8 | Here we scale based upon the interaction box and screen size: 9 | 10 | options: 11 | scale, scaleX, and scaleY. They all default to 1. 12 | verticalOffset: in pixels. This number is added to the returned Y value. Defaults to 0. 13 | 14 | 15 | 16 | controller.use 'screenPosition', { 17 | method: (positionVec3)-> 18 | Arguments for Leap.vec3 are (out, a, b) 19 | [ 20 | Leap.vec3.subtract(positionVec3, positionVec3, @frame.interactionBox.center) 21 | Leap.vec3.divide(positionVec3, positionVec3, @frame.interactionBox.size) 22 | Leap.vec3.multiply(positionVec3, positionVec3, [document.body.offsetWidth, document.body.offsetHeight, 0]) 23 | ] 24 | } 25 | More info on vec3 can be found, here: http://glmatrix.net/docs/2.2.0/symbols/vec3.html 26 | ### 27 | screenPosition = (options = {})-> 28 | # instance of extension should be tied to instance of hand 29 | # positioning can be one of a series of predefined position identifiers, or a custom method. 30 | options.positioning ||= 'absolute' 31 | options.scale ||= 1 32 | options.scaleX ||= 1 33 | options.scaleY ||= 1 34 | options.scaleZ ||= 1 35 | options.verticalOffset ||= 0 # pixels 36 | baseScale = 6 37 | baseVerticalOffset = -100 38 | 39 | positioningMethods = { 40 | absolute: (positionVec3)-> 41 | [ 42 | (window.innerWidth / 2) + (positionVec3[0] * baseScale * options.scale * options.scaleX), 43 | window.innerHeight + baseVerticalOffset + options.verticalOffset - 44 | (positionVec3[1] * baseScale * options.scale * options.scaleY), 45 | (positionVec3[2] * baseScale * options.scale * options.scaleZ) 46 | ] 47 | } 48 | 49 | position = (vec3, memoize = false)-> 50 | # Note that "@" (a hand/finger/etc) is remade for every frame. 51 | screenPositionVec3 = if typeof options.positioning == 'function' 52 | options.positioning.call(@, vec3) 53 | else 54 | positioningMethods[options.positioning].call(@, vec3) 55 | 56 | if memoize 57 | @screenPositionVec3 = screenPositionVec3 58 | 59 | screenPositionVec3 60 | 61 | 62 | { 63 | hand: { 64 | # screenPosition will use the stabilized position by default, or allow any array of [x,y,z] to be passed in. 65 | screenPosition: (vec3)-> 66 | position.call(@, vec3 || @palmPosition, !vec3) 67 | } 68 | pointable: { 69 | screenPosition: (vec3)-> 70 | position.call(@, vec3 || @tipPosition, !vec3) 71 | } 72 | } 73 | 74 | 75 | if (typeof Leap != 'undefined') && Leap.Controller 76 | Leap.Controller.plugin 'screenPosition', screenPosition 77 | else if (typeof module != 'undefined') 78 | module.exports.screenPosition = screenPosition 79 | else 80 | throw 'leap.js not included' 81 | -------------------------------------------------------------------------------- /main/screen-position/leap.screen-position.js: -------------------------------------------------------------------------------- 1 | //CoffeeScript generated from main/screen-position/leap.screen-position.coffee 2 | /* 3 | Adds the "screenPosition" method by default to hands and pointables. This returns a vec3 (an array of length 3) 4 | with [x,y,z] screen coordinates indicating where the hand is, originating from the bottom left. 5 | This method can accept an optional vec3, allowing it to convert any arbitrary vec3 of coordinates. 6 | 7 | Custom positioning methods can be passed in, allowing different scaling techniques, 8 | e.g., http://msdn.microsoft.com/en-us/library/windows/hardware/gg463319.aspx (Pointer Ballistics) 9 | Here we scale based upon the interaction box and screen size: 10 | 11 | options: 12 | scale, scaleX, and scaleY. They all default to 1. 13 | verticalOffset: in pixels. This number is added to the returned Y value. Defaults to 0. 14 | 15 | 16 | 17 | controller.use 'screenPosition', { 18 | method: (positionVec3)-> 19 | Arguments for Leap.vec3 are (out, a, b) 20 | [ 21 | Leap.vec3.subtract(positionVec3, positionVec3, @frame.interactionBox.center) 22 | Leap.vec3.divide(positionVec3, positionVec3, @frame.interactionBox.size) 23 | Leap.vec3.multiply(positionVec3, positionVec3, [document.body.offsetWidth, document.body.offsetHeight, 0]) 24 | ] 25 | } 26 | More info on vec3 can be found, here: http://glmatrix.net/docs/2.2.0/symbols/vec3.html 27 | */ 28 | 29 | 30 | (function() { 31 | var screenPosition; 32 | 33 | screenPosition = function(options) { 34 | var baseScale, baseVerticalOffset, position, positioningMethods; 35 | if (options == null) { 36 | options = {}; 37 | } 38 | options.positioning || (options.positioning = 'absolute'); 39 | options.scale || (options.scale = 1); 40 | options.scaleX || (options.scaleX = 1); 41 | options.scaleY || (options.scaleY = 1); 42 | options.scaleZ || (options.scaleZ = 1); 43 | options.verticalOffset || (options.verticalOffset = 0); 44 | baseScale = 6; 45 | baseVerticalOffset = -100; 46 | positioningMethods = { 47 | absolute: function(positionVec3) { 48 | return [(window.innerWidth / 2) + (positionVec3[0] * baseScale * options.scale * options.scaleX), window.innerHeight + baseVerticalOffset + options.verticalOffset - (positionVec3[1] * baseScale * options.scale * options.scaleY), positionVec3[2] * baseScale * options.scale * options.scaleZ]; 49 | } 50 | }; 51 | position = function(vec3, memoize) { 52 | var screenPositionVec3; 53 | if (memoize == null) { 54 | memoize = false; 55 | } 56 | screenPositionVec3 = typeof options.positioning === 'function' ? options.positioning.call(this, vec3) : positioningMethods[options.positioning].call(this, vec3); 57 | if (memoize) { 58 | this.screenPositionVec3 = screenPositionVec3; 59 | } 60 | return screenPositionVec3; 61 | }; 62 | return { 63 | hand: { 64 | screenPosition: function(vec3) { 65 | return position.call(this, vec3 || this.palmPosition, !vec3); 66 | } 67 | }, 68 | pointable: { 69 | screenPosition: function(vec3) { 70 | return position.call(this, vec3 || this.tipPosition, !vec3); 71 | } 72 | } 73 | }; 74 | }; 75 | 76 | if ((typeof Leap !== 'undefined') && Leap.Controller) { 77 | Leap.Controller.plugin('screenPosition', screenPosition); 78 | } else if (typeof module !== 'undefined') { 79 | module.exports.screenPosition = screenPosition; 80 | } else { 81 | throw 'leap.js not included'; 82 | } 83 | 84 | }).call(this); 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LeapJS Plugins 2 | ============== 3 | 4 | This repository holds a collection of independent plugins which extend the functionality of LeapJS itself. 5 | 6 | **`leap-plugins.js`** is a collection of amazing plugins to get you started quickly. 7 | 8 | - **[Hand Entry](http://leapmotion.github.io/leapjs-plugins/docs/#hand-entry)** Emit events when a hand enters of leaves the field of view. 9 | - **[Hand Hold](http://leapmotion.github.io/leapjs-plugins/docs/#hand-hold)** Save data on to hands or fingers which will be persisted between frames. 10 | - **[Screen Position](http://leapmotion.github.io/leapjs-plugins/docs/#screen-position)** Get the on-screen position of any Leap-space point. 11 | - **[Version Check](http://leapmotion.github.io/leapjs-plugins/docs/#version-check)** Ensure a minimum protocol version when running your app. 12 | - **[Playback](http://leapmotion.github.io/leapjs-plugins/docs/#playback)** Record hand-data from the Leap, compress it, and use it to animate your app. 13 | - **[Transform](http://leapmotion.github.io/leapjs-plugins/main/transform/)** Translate, rotate, and scale Leap Motion data. Easily. 14 | - **[Bone Hand](http://leapmotion.github.io/leapjs-plugins/main/bone-hand/)** Drop THREE.js Hands in to any scene, or quick-start with the default scene. 15 | 16 | **`leap-plugins-utils.js`** explores what can be done with LeapJS Plugins. 17 | 18 | - **[LeapDataPlotter](http://leapmotion.github.io/leapjs-plugins/utils/data-plotter/)** Allows super trivial plotting of streaming data. 19 | 20 | **Other libraries** 21 | - **[Widgets](https://github.com/leapmotion/leapjs-widgets)** 3D input elements - buttons and planes. 22 | - **[Rigged Hand](https://github.com/leapmotion/leapjs-rigged-hand)** Easily add virtual 3d hands to any web page with THREE.js. 23 | 24 | ## Download 25 | 26 | [developer.leapmotion.com/downloads/javascript#plugins](https://developer.leapmotion.com/downloads/javascript#plugins) 27 | 28 | ## Usage 29 | 30 | Include LeapJS >= 0.4.0 and either javascript file of an individual plugin or a collection. 31 | Configure your controller to use the plugin, and that functionality will be available to you. 32 | See [hand-entry](http://leapmotion.github.io/leapjs-plugins/docs/index.html#hand-entry) for docs on hand-entry itself. 33 | 34 | ```html 35 | 36 | 37 | 38 | 42 | ``` 43 | 44 | - Download [from the CDN](http://developer.leapmotion.com/leapjs/plugins). 45 | - Each plugin is individually documented, with demo, on the gh-pages [docs site](http://leapmotion.github.io/leapjs-plugins/docs/). 46 | - See [making plugins](http://github.com/leapmotion/leapjs/wiki/plugins) on the leapjs wiki. 47 | 48 | 49 | ## Examples 50 | 51 | Examples are available on the [developer gallery](http://developer.leapmotion.com/gallery/tags/javascript) live editor 52 | and in subfolders here of individual plugins. 53 | 54 | To run them on localhost, you'll need a web server to resolve asset paths. 55 | 56 | ```bash 57 | > python -m SimpleHTTPServer 58 | ``` 59 | 60 | 61 | Contributing 62 | =============== 63 | 64 | #### Open an issue! 65 | - https://github.com/leapmotion/leapjs-plugins/issues 66 | 67 | #### Open a pull request! 68 | 69 | - Read up on [Making Plugins](https://github.com/leapmotion/leapjs/wiki/Plugins#plugin-development), then: 70 | - Make a fork, name your branch, add your plugin or fix. 71 | - Add your name, email, and github account to the CONTRIBUTORS.txt list, thereby agreeing to the terms and conditions of the Contributor License Agreement. 72 | - Open a Pull Request. If your information is not in the CONTRIBUTORS file, your pull request will not be reviewed. 73 | -------------------------------------------------------------------------------- /utils/leap-plugins-0.1.12-utils.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * LeapJS-Plugins Extra - v0.1.12 - 2016-11-16 3 | * http://github.com/leapmotion/leapjs-plugins/ 4 | * 5 | * Copyright 2016 LeapMotion, Inc 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | !function(a,b){"function"==typeof define&&define.amd?define([],b):a.LeapDataPlotter=b()}(this,function(){var a,b,c=["#900","#090","#009","#990","#909","#099"],d=0;return a=function(a){this.options=a||(a={}),this.seriesHash={},this.series=[],this.init(a.el)},a.prototype.init=function(a){if(a)var b=a;else{var b=document.createElement("canvas");b.className="leap-data-plotter",document.body.appendChild(b)}this.canvas=b,this.context=b.getContext("2d"),this.rescale()},a.prototype.rescale=function(){var a=getComputedStyle(this.canvas),b=parseInt(a.width,10),c=parseInt(a.height,10);this.width=b,this.height=c;var d=window.devicePixelRatio||1,e=this.context.webkitBackingStorePixelRatio||this.context.mozBackingStorePixelRatio||this.context.msBackingStorePixelRatio||this.context.oBackingStorePixelRatio||this.context.backingStorePixelRatio||1,f=d/e;if(d!==e){var g=this.canvas.width,h=this.canvas.height;this.canvas.width=g*f,this.canvas.height=h*f,this.canvas.style.width=g+"px",this.canvas.style.height=h+"px",this.context.scale(f,f)}this.clear(),this.draw()},a.prototype.plot=function(a,b,c){if(c||(c={}),b.length)for(var d=0,e=120;d122?97:e)this.getTimeSeries(a+"."+String.fromCharCode(e),c).push(b[d],{pointColor:c.pointColor});else this.getTimeSeries(a,c).push(b,{pointColor:c.pointColor})},a.prototype.getTimeSeries=function(a,c){var d=this.seriesHash[a];if(!d){var e=this.getOptions(a);for(key in c)e[key]=c[key];d=new b(e),this.series.push(d),this.seriesHash[a]=d}return d},a.prototype.getOptions=function(a){var b=d;d=(d+1)%c.length;var e=this.series.length,f=e?this.series[e-1].y+50:0;return{y:f,width:this.width,color:c[b],name:a}},a.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)},a.prototype.draw=function(){var a=this.context;this.series.forEach(function(b){b.draw(a)})},a.prototype.update=function(){this.clear(),this.draw()},b=function(a){a=a||{},this.x=a.x||0,this.y=a.y||0,this.precision=a.precision||5,this.units=a.units||"",this.width=a.width||1e3,this.height=a.height||50,this.length=a.length||600,this.color=a.color||"#000",this.name=a.name||"",this.frameHandler=a.frameHandler,this.max=-(1/0),this.min=1/0,this.data=[],this.pointColors=[]},b.prototype.push=function(a,b){return this.data.push(a),this.data.length>=this.length&&this.data.shift(),b&&b.pointColor&&(this.pointColors.push(b.pointColor),this.pointColors.length>=this.length&&this.pointColors.shift()),this},b.prototype.draw=function(a){var b=this,c=(this.width-10)/(this.length-1),d=-(this.height-10)/(this.max-this.min),e=5,f=(this.max-this.min)*d+10;a.save(),a.strokeRect(this.x,this.y,this.width,this.height),a.translate(this.x,this.y+this.height-e),a.strokeStyle=this.color,a.beginPath();var g=-(1/0),h=1/0;this.data.forEach(function(e,f){e>g&&(g=e),h>e&&(h=e),isNaN(e)?(a.stroke(),a.beginPath()):(a.lineTo(f*c,(e-b.min)*d),b.pointColors[f]&&b.pointColors[f]!=b.pointColors[f-1]&&(a.stroke(),a.strokeStyle=b.pointColors[f],a.beginPath(),a.lineTo(f*c,(e-b.min)*d)))}),a.stroke(),a.fillText(this.name,e,f),a.fillText(this.data[this.data.length-1].toPrecision(this.precision)+this.units,e,0),a.textAlign="end",a.fillText(this.min.toPrecision(this.precision)+this.units,this.width-e,0),a.fillText(this.max.toPrecision(this.precision)+this.units,this.width-e,f),a.textAlign="left",a.restore(),this.min=h,this.max=g},a}); -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (grunt) -> 2 | 3 | fs = require('fs'); 4 | 5 | 6 | filename = "leap-plugins-<%= pkg.version %>" 7 | banner = (project)-> 8 | '/* 9 | \n * LeapJS-Plugins ' + project + ' - v<%= pkg.version %> - <%= grunt.template.today(\"yyyy-mm-dd\") %> 10 | \n * http://github.com/leapmotion/leapjs-plugins/ 11 | \n * 12 | \n * Copyright <%= grunt.template.today(\"yyyy\") %> LeapMotion, Inc 13 | \n * 14 | \n * Licensed under the Apache License, Version 2.0 (the "License"); 15 | \n * you may not use this file except in compliance with the License. 16 | \n * You may obtain a copy of the License at 17 | \n * 18 | \n * http://www.apache.org/licenses/LICENSE-2.0 19 | \n * 20 | \n * Unless required by applicable law or agreed to in writing, software 21 | \n * distributed under the License is distributed on an "AS IS" BASIS, 22 | \n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | \n * See the License for the specific language governing permissions and 24 | \n * limitations under the License. 25 | \n * 26 | \n */ 27 | \n' 28 | 29 | # https://github.com/gruntjs/grunt/issues/315 30 | 31 | grunt.initConfig 32 | pkg: grunt.file.readJSON("package.json") 33 | 34 | 'string-replace': 35 | main: 36 | files: { 37 | 'main/': 'main/**/*.html' 38 | './': 'bower.json' 39 | } 40 | options: { 41 | replacements: [ 42 | # bower.json 43 | { 44 | pattern: /"version": ".*"/, 45 | replacement: '"version": "<%= pkg.version %>"' 46 | }, 47 | # examples 48 | { 49 | pattern: /leap-plugins.*\.js/, 50 | replacement: filename + '.js' 51 | } 52 | ] 53 | } 54 | 55 | coffee: 56 | main: 57 | files: [{ 58 | expand: true 59 | cwd: 'main/' 60 | src: '**/*.coffee' 61 | dest: 'main/' 62 | rename: (task, path, options)-> 63 | task + path.replace('.coffee', '.js') 64 | }] 65 | utils: 66 | files: [{ 67 | expand: true 68 | cwd: 'util/' 69 | src: '**/*.coffee' 70 | dest: 'utils/' 71 | rename: (task, path, options)-> 72 | task + path.replace('.coffee', '.js') 73 | }] 74 | 75 | usebanner: 76 | coffeeMessagesMain: 77 | options: 78 | banner: (file) -> 79 | coffeePath = file.replace('.js', '.coffee') 80 | if fs.existsSync(coffeePath) 81 | "//CoffeeScript generated from #{coffeePath}" 82 | else 83 | null 84 | src: "main/**/*.js" 85 | coffeeMessagesUtils: 86 | options: 87 | banner: (file) -> 88 | coffeePath = file.replace('.js', '.coffee') 89 | if fs.existsSync(coffeePath) 90 | "//CoffeeScript generated from #{coffeePath}" 91 | else 92 | null 93 | src: "utils/**/*.js" 94 | 95 | licenseMain: 96 | options: 97 | banner: banner('') 98 | src: "main/#{filename}.js" 99 | licenseUtils: 100 | options: 101 | banner: banner('Extra') 102 | src: "exras/#{filename}.js" 103 | 104 | clean: 105 | main: 106 | src: ["main/leap-plugins-*.js"] 107 | utils: 108 | src: ["utils/leap-plugins-*.js"] 109 | 110 | concat: 111 | main: 112 | src: 'main/*/*.js' 113 | dest: "main/#{filename}.js" 114 | utils: 115 | src: 'utils/**/*.js' 116 | dest: "utils/#{filename}-utils.js" 117 | 118 | uglify: 119 | main: 120 | options: 121 | banner: banner('') 122 | src: "main/#{filename}.js" 123 | dest: "main/#{filename}.min.js" 124 | utils: 125 | options: 126 | banner: banner('Extra') 127 | src: "utils/#{filename}-utils.js" 128 | dest: "utils/#{filename}-utils.min.js" 129 | 130 | bump: 131 | options: 132 | pushTo: 'master' 133 | 134 | 135 | watch: 136 | files: ['main/**/*.coffee', 'utils/**/*.coffee'], 137 | options: 138 | atBegin: true 139 | tasks: [ 140 | "string-replace", 141 | "coffee", 142 | "usebanner:coffeeMessagesMain", 143 | "usebanner:coffeeMessagesUtils", 144 | "clean", 145 | "concat", 146 | "usebanner:licenseMain", 147 | "usebanner:licenseUtils", 148 | "uglify" 149 | ] 150 | 151 | 152 | require('load-grunt-tasks')(grunt); 153 | 154 | grunt.registerTask "default", [ 155 | "string-replace", 156 | "coffee", 157 | "usebanner:coffeeMessagesMain", 158 | "usebanner:coffeeMessagesUtils", 159 | "clean", 160 | "concat", 161 | "usebanner:licenseMain", 162 | "usebanner:licenseUtils", 163 | "uglify" 164 | ] -------------------------------------------------------------------------------- /main/bone-hand/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 40 | 41 | 42 | 43 | 44 | View Source 45 | 46 |

Bone Hand

47 |

48 | Insert hand for live demo. 49 |

50 |

51 | Allows adding a hand to your THREE.js scene in one line. 52 |

53 |

54 | Creates a default scene for you, when time is of the essence. 55 |

56 |

 57 |   // At the simplest:
 58 |   Leap.loop()
 59 |     .use('boneHand', {
 60 |       targetEl: document.body,
 61 |       arm: true
 62 |     });
 63 | 
64 | 65 | 66 |

Options

67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 |
NameDefaultDescription
sceneundefined 78 |

Accepts a THREE.scene. If undefined, a default scene will be created with some defaults (transparent canvas, three lights, mm units), 79 | and rendered on every animation frame. If null, the plugin will wait for the scene to be set explicitly at a future point. 80 |

81 |

With the default scene, the renderer, camera, and scene will all be made available on the plugin scope. E.g., `myController.plugins.boneHand.scene`

82 |
targetElundefinedAccepts an element, such as `document.body`.
boneScale1 / 6The radius of the bones relative to the length of the proximal bone on the middle finger
jointScale1 / 5The radius of the bones relative to the length of the proximal bone on the middle finger
boneColor(new THREE.Color).setHex(0xffffff)
jointColor(new THREE.Color).setHex(0x5daa00)
110 | 111 |

Addendum

112 | 113 |

114 | THREE.js shadows are fully supported. 115 |

116 | 122 | 123 |

124 | See the source for details. scope.light.shadowCameraVisible is a nice tool to know about. 125 |

126 | 127 | 128 | 129 | 179 | 180 | -------------------------------------------------------------------------------- /utils/data-plotter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Leap Data Plotter 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 54 | 55 | 72 | 73 | 74 | 75 | 76 | 77 |

Leap Data Plotter

78 | 79 |

80 | Allows easy super trivial plotting of streaming data. 81 |

82 |

83 | Dependencies: none. RequireJS-compatible. 84 |

85 | 86 | 91 | 92 |
93 | 94 |
95 |

Live Example

96 |

97 | (insert hand) 98 |

99 | 100 | 101 | 102 |

Screenshot

103 | 104 | 105 |
106 | 107 | 108 |

Usage:

109 | 110 | 111 |
112 | 
113 |   // call this once per page, after the DOM is ready.
114 |   window.plotter = new LeapDataPlotter();
115 | 
116 | 
117 |   Leap.loop(function(frame){
118 |     var hand = frame.hands[0];
119 |     if (!hand) return;
120 | 
121 | 
122 |     // call this once per frame per plot
123 |     plotter.plot('height', hand.palmPosition[1], {
124 |       precision: 3,
125 |       units: 'mm'
126 |     });
127 | 
128 | 
129 |     plotter.plot('y velocity', hand.palmVelocity[1], {
130 |       precision: 4,
131 |       units: 'mm/s'
132 |     });
133 | 
134 | 
135 |     // call this once per frame
136 |     plotter.update()
137 | 
138 |   });
139 | 
140 | 
141 | 
142 | 143 | 144 | 145 |

LeapDataPlotter:

146 | 147 |

148 | Calling plot will add a canvas to the page. The canvas will simply be appended to the body of `document.body`, 149 | with a classname of `leap-data-plotter`. Alternatively, you can pass in your own canvas element: 150 |

151 | 152 |
153 | 
154 |   // basic
155 |   window.plotter = new LeapDataPlotter();
156 | 
157 |   // custom canvas
158 |   window.plotter = new LeapDataPlotter({
159 |     el: document.getElementById('my_canvas')
160 |   });
161 | 
162 | 
163 | 
164 | 165 |

LeapDataPlotter#plot(id, data, opts)

166 | 167 | 168 | 169 | 170 | 174 | 175 | 176 | 177 | 180 | 181 | 182 | 183 | 211 | 212 |
id 171 | The name of the graph, persistent between frames 172 |
- The plot automatically resizes based upon input data 173 |
data 178 | The datapoint to be added to the plot 179 |
opts 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 |
precisiondecimal places to display in outputs [default: 5]
unitsString suffix to print after the numeric output [default: '']
lengthThe number of data points which fit on to a graph. [default: 600]
width[default: canvas width]
height[default: 50]
colorColor of plotted line. Hex string. [default: auto]
210 |
213 | 214 | 215 |

LeapDataPlotter#update()

216 | 217 |

218 | Redraws the canvas, updating the plot. Nothing will be visible without this being called. 219 |

220 |
221 | 222 |

Authors

223 | 224 |

225 | Thanks to @nashira for authoring this library. 226 |

227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /main/transform/leap.transform.js: -------------------------------------------------------------------------------- 1 | //CoffeeScript generated from main/transform/leap.transform.coffee 2 | (function() { 3 | Leap.plugin('transform', function(scope) { 4 | var noop, transformDirections, transformMat4Implicit0, transformPositions, transformWithMatrices, _directionTransform; 5 | if (scope == null) { 6 | scope = {}; 7 | } 8 | noop = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; 9 | _directionTransform = new THREE.Matrix4; 10 | if (scope.vr === true) { 11 | this.setOptimizeHMD(true); 12 | scope.quaternion = (new THREE.Quaternion).setFromRotationMatrix((new THREE.Matrix4).set(-1, 0, 0, 0, 0, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 1)); 13 | scope.scale = 0.001; 14 | scope.position = new THREE.Vector3(0, 0, -0.08); 15 | } 16 | if (scope.vr === 'desktop') { 17 | scope.scale = 0.001; 18 | } 19 | scope.getTransform = function(hand) { 20 | var matrix; 21 | if (scope.matrix) { 22 | matrix = typeof scope.matrix === 'function' ? scope.matrix(hand) : scope.matrix; 23 | if (window['THREE'] && matrix instanceof THREE.Matrix4) { 24 | return matrix.elements; 25 | } else { 26 | return matrix; 27 | } 28 | } else if (scope.position || scope.quaternion || scope.scale) { 29 | _directionTransform.set.apply(_directionTransform, noop); 30 | if (scope.quaternion) { 31 | _directionTransform.makeRotationFromQuaternion(typeof scope.quaternion === 'function' ? scope.quaternion(hand) : scope.quaternion); 32 | } 33 | if (scope.position) { 34 | _directionTransform.setPosition(typeof scope.position === 'function' ? scope.position(hand) : scope.position); 35 | } 36 | return _directionTransform.elements; 37 | } else { 38 | return noop; 39 | } 40 | }; 41 | scope.getScale = function(hand) { 42 | if (!isNaN(scope.scale)) { 43 | scope.scale = new THREE.Vector3(scope.scale, scope.scale, scope.scale); 44 | } 45 | if (typeof scope.scale === 'function') { 46 | return scope.scale(hand); 47 | } else { 48 | return scope.scale; 49 | } 50 | }; 51 | transformPositions = function(matrix, vec3s) { 52 | var vec3, _i, _len, _results; 53 | _results = []; 54 | for (_i = 0, _len = vec3s.length; _i < _len; _i++) { 55 | vec3 = vec3s[_i]; 56 | if (vec3) { 57 | _results.push(Leap.vec3.transformMat4(vec3, vec3, matrix)); 58 | } else { 59 | _results.push(void 0); 60 | } 61 | } 62 | return _results; 63 | }; 64 | transformMat4Implicit0 = function(out, a, m) { 65 | var x, y, z; 66 | x = a[0]; 67 | y = a[1]; 68 | z = a[2]; 69 | out[0] = m[0] * x + m[4] * y + m[8] * z; 70 | out[1] = m[1] * x + m[5] * y + m[9] * z; 71 | out[2] = m[2] * x + m[6] * y + m[10] * z; 72 | return out; 73 | }; 74 | transformDirections = function(matrix, vec3s) { 75 | var vec3, _i, _len, _results; 76 | _results = []; 77 | for (_i = 0, _len = vec3s.length; _i < _len; _i++) { 78 | vec3 = vec3s[_i]; 79 | if (vec3) { 80 | _results.push(transformMat4Implicit0(vec3, vec3, matrix)); 81 | } else { 82 | _results.push(void 0); 83 | } 84 | } 85 | return _results; 86 | }; 87 | transformWithMatrices = function(hand, transform, scale) { 88 | var finger, scalarScale, _i, _j, _len, _len1, _ref, _ref1; 89 | transformDirections(transform, [hand.direction, hand.palmNormal, hand.palmVelocity, hand.arm.basis[0], hand.arm.basis[1], hand.arm.basis[2]]); 90 | _ref = hand.fingers; 91 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 92 | finger = _ref[_i]; 93 | transformDirections(transform, [finger.direction, finger.metacarpal.basis[0], finger.metacarpal.basis[1], finger.metacarpal.basis[2], finger.proximal.basis[0], finger.proximal.basis[1], finger.proximal.basis[2], finger.medial.basis[0], finger.medial.basis[1], finger.medial.basis[2], finger.distal.basis[0], finger.distal.basis[1], finger.distal.basis[2]]); 94 | } 95 | Leap.glMatrix.mat4.scale(transform, transform, scale); 96 | transformPositions(transform, [hand.palmPosition, hand.stabilizedPalmPosition, hand.sphereCenter, hand.arm.nextJoint, hand.arm.prevJoint]); 97 | _ref1 = hand.fingers; 98 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { 99 | finger = _ref1[_j]; 100 | transformPositions(transform, [finger.carpPosition, finger.mcpPosition, finger.pipPosition, finger.dipPosition, finger.distal.nextJoint, finger.tipPosition]); 101 | } 102 | scalarScale = (scale[0] + scale[1] + scale[2]) / 3; 103 | return hand.arm.width *= scalarScale; 104 | }; 105 | return { 106 | frame: function(frame) { 107 | var finger, hand, len, _i, _j, _len, _len1, _ref, _ref1, _results; 108 | if (!frame.valid || frame.data.transformed) { 109 | return; 110 | } 111 | frame.data.transformed = true; 112 | _ref = frame.hands; 113 | _results = []; 114 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 115 | hand = _ref[_i]; 116 | transformWithMatrices(hand, scope.getTransform(hand), (scope.getScale(hand) || new THREE.Vector3(1, 1, 1)).toArray()); 117 | if (scope.effectiveParent) { 118 | transformWithMatrices(hand, scope.effectiveParent.matrixWorld.elements, scope.effectiveParent.scale.toArray()); 119 | } 120 | len = null; 121 | _ref1 = hand.fingers; 122 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { 123 | finger = _ref1[_j]; 124 | len = Leap.vec3.create(); 125 | Leap.vec3.sub(len, finger.mcpPosition, finger.carpPosition); 126 | finger.metacarpal.length = Leap.vec3.length(len); 127 | Leap.vec3.sub(len, finger.pipPosition, finger.mcpPosition); 128 | finger.proximal.length = Leap.vec3.length(len); 129 | Leap.vec3.sub(len, finger.dipPosition, finger.pipPosition); 130 | finger.medial.length = Leap.vec3.length(len); 131 | Leap.vec3.sub(len, finger.tipPosition, finger.dipPosition); 132 | finger.distal.length = Leap.vec3.length(len); 133 | } 134 | Leap.vec3.sub(len, hand.arm.prevJoint, hand.arm.nextJoint); 135 | _results.push(hand.arm.length = Leap.vec3.length(len)); 136 | } 137 | return _results; 138 | } 139 | }; 140 | }); 141 | 142 | }).call(this); 143 | -------------------------------------------------------------------------------- /main/transform/leap.transform.coffee: -------------------------------------------------------------------------------- 1 | # Allows arbitrary transforms to be easily applied to hands in the Leap Frame 2 | # This requires THREE.js 3 | 4 | # configuration: 5 | # if transform is set, all other properties will be ignored 6 | # transform: a THREE.Matrix4 directly. This can be either an array of 16-length, or a THREE.matrix4 7 | # quaternion: a THREE.Quaternion 8 | # position: a THREE.Vector3 9 | # scale: a THREE.Vector3 or a number. 10 | 11 | 12 | Leap.plugin 'transform', (scope = {})-> 13 | noop = [ 14 | 1, 0, 0, 0, 15 | 0, 1, 0, 0, 16 | 0, 0, 1, 0, 17 | 0, 0, 0, 1 18 | ] 19 | _directionTransform = new THREE.Matrix4 20 | 21 | if scope.vr == true 22 | 23 | this.setOptimizeHMD(true) 24 | 25 | # This matrix flips the x, y, and z axis. 26 | scope.quaternion = (new THREE.Quaternion).setFromRotationMatrix( 27 | (new THREE.Matrix4).set( 28 | -1, 0, 0, 0, 29 | 0, 0, -1, 0, 30 | 0, -1, 0, 0, 31 | 0, 0, 0, 1 32 | ) 33 | ) 34 | 35 | # Scales to meters. 36 | scope.scale = 0.001 37 | 38 | scope.position = new THREE.Vector3(0,0,-0.08) 39 | 40 | if scope.vr == 'desktop' 41 | scope.scale = 0.001 42 | 43 | # no scale 44 | scope.getTransform = (hand)-> 45 | if scope.matrix 46 | matrix = if typeof scope.matrix == 'function' then scope.matrix(hand) else scope.matrix 47 | 48 | if window['THREE'] && matrix instanceof THREE.Matrix4 49 | return matrix.elements 50 | else 51 | return matrix 52 | 53 | else if scope.position || scope.quaternion || scope.scale 54 | _directionTransform.set.apply(_directionTransform, noop) 55 | 56 | if scope.quaternion 57 | _directionTransform.makeRotationFromQuaternion( 58 | if typeof scope.quaternion == 'function' then scope.quaternion(hand) else scope.quaternion 59 | ) 60 | 61 | if scope.position 62 | _directionTransform.setPosition( 63 | if typeof scope.position == 'function' then scope.position(hand) else scope.position 64 | ) 65 | 66 | return _directionTransform.elements 67 | 68 | else 69 | return noop 70 | 71 | 72 | scope.getScale = (hand)-> 73 | if !isNaN(scope.scale) 74 | scope.scale = new THREE.Vector3(scope.scale, scope.scale, scope.scale) 75 | 76 | return if typeof scope.scale == 'function' then scope.scale(hand) else scope.scale 77 | 78 | 79 | # implicitly appends 1 to the vec3s, applying both translation and rotation 80 | transformPositions = (matrix, vec3s)-> 81 | for vec3 in vec3s 82 | if vec3 # some recordings may not have all fields 83 | Leap.vec3.transformMat4(vec3, vec3, matrix) 84 | 85 | transformMat4Implicit0 = (out, a, m) -> 86 | x = a[0] 87 | y = a[1] 88 | z = a[2] 89 | 90 | out[0] = m[0] * x + m[4] * y + m[8] * z 91 | out[1] = m[1] * x + m[5] * y + m[9] * z 92 | out[2] = m[2] * x + m[6] * y + m[10] * z 93 | return out 94 | 95 | # appends 0 to the vec3s, applying only rotation 96 | transformDirections = (matrix, vec3s)-> 97 | for vec3 in vec3s 98 | if vec3 # some recordings may not have all fields 99 | transformMat4Implicit0(vec3, vec3, matrix) 100 | 101 | # expects a hand, an array mat4 and an array scale. 102 | transformWithMatrices = (hand, transform, scale) -> 103 | transformDirections( 104 | transform, 105 | [ 106 | hand.direction, 107 | hand.palmNormal, 108 | hand.palmVelocity, 109 | hand.arm.basis[0], 110 | hand.arm.basis[1], 111 | hand.arm.basis[2], 112 | ] 113 | ) 114 | 115 | for finger in hand.fingers 116 | transformDirections( 117 | transform, 118 | [ 119 | finger.direction, 120 | finger.metacarpal.basis[0], 121 | finger.metacarpal.basis[1], 122 | finger.metacarpal.basis[2], 123 | finger.proximal.basis[0], 124 | finger.proximal.basis[1], 125 | finger.proximal.basis[2], 126 | finger.medial.basis[0], 127 | finger.medial.basis[1], 128 | finger.medial.basis[2], 129 | finger.distal.basis[0], 130 | finger.distal.basis[1], 131 | finger.distal.basis[2] 132 | ] 133 | ) 134 | 135 | Leap.glMatrix.mat4.scale(transform, transform, scale) 136 | 137 | transformPositions( 138 | transform, 139 | [ 140 | hand.palmPosition, 141 | hand.stabilizedPalmPosition, 142 | hand.sphereCenter, 143 | hand.arm.nextJoint, 144 | hand.arm.prevJoint 145 | ] 146 | ) 147 | 148 | for finger in hand.fingers 149 | transformPositions( 150 | transform, 151 | [ 152 | finger.carpPosition, 153 | finger.mcpPosition, 154 | finger.pipPosition, 155 | finger.dipPosition, 156 | finger.distal.nextJoint, 157 | finger.tipPosition 158 | ] 159 | ) 160 | 161 | scalarScale = ( scale[0] + scale[1] + scale[2] ) / 3; 162 | hand.arm.width *= scalarScale 163 | 164 | # todo - expose function to transform a frame 165 | { 166 | frame: (frame)-> 167 | 168 | return if !frame.valid || frame.data.transformed 169 | 170 | frame.data.transformed = true 171 | 172 | for hand in frame.hands 173 | 174 | transformWithMatrices(hand, scope.getTransform(hand), (scope.getScale(hand) || new THREE.Vector3(1,1,1)).toArray() ) 175 | 176 | if scope.effectiveParent 177 | # as long as parent doesn't have scale, we're good. 178 | # could refactor to extract scale from mat4 and do the two separately 179 | # e.g., decompose in to pos/rot/scale, recompose from pos/rot/defaultScale 180 | transformWithMatrices( hand, scope.effectiveParent.matrixWorld.elements, scope.effectiveParent.scale.toArray() ) 181 | 182 | len = null 183 | for finger in hand.fingers 184 | # recalculate lengths 185 | len = Leap.vec3.create() 186 | Leap.vec3.sub(len, finger.mcpPosition, finger.carpPosition) 187 | finger.metacarpal.length = Leap.vec3.length(len) 188 | 189 | Leap.vec3.sub(len, finger.pipPosition, finger.mcpPosition) 190 | finger.proximal.length = Leap.vec3.length(len) 191 | 192 | Leap.vec3.sub(len, finger.dipPosition, finger.pipPosition) 193 | finger.medial.length = Leap.vec3.length(len) 194 | 195 | Leap.vec3.sub(len, finger.tipPosition, finger.dipPosition) 196 | finger.distal.length = Leap.vec3.length(len) 197 | 198 | Leap.vec3.sub(len, hand.arm.prevJoint, hand.arm.nextJoint) 199 | hand.arm.length = Leap.vec3.length(len) 200 | 201 | } 202 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Leap Motion Individual Contributor License Agreement, v1.0 2 | 3 | In order to clarify the intellectual property license granted with 4 | Contributions from any person or entity, Leap Motion, Inc. ("Leap 5 | Motion") must have a Contributor License Agreement ("CLA") on file that 6 | has been signed by each Contributor, indicating agreement to the license 7 | terms below. This license is for your protection as a Contributor as 8 | well as the protection of Leap Motion; it does not change your rights to 9 | use your own Contributions for any other purpose. 10 | 11 | If you wish to submit a pull request, please read the terms of this agreement 12 | and indicate your acceptance of it by adding your name, email, and github account name 13 | to the bottom of this file. Pull requests submitted without accepting this agreement 14 | will not be reviewed. 15 | 16 | By adding your information to this file, you accept and agree to the following 17 | terms and conditions for Your present and future Contributions submitted to 18 | Leap Motion. Except for the license granted herein to Leap Motion and recipients of 19 | software distributed by Leap Motion, You reserve all right, title, and interest 20 | in and to Your Contributions. 21 | 22 | 1. Definitions. 23 | 24 | "You" (or "Your") shall mean the copyright owner or legal entity 25 | authorized by the copyright owner that is making this Agreement with 26 | Leap Motion. 27 | 28 | For legal entities, the entity making a Contribution and all other 29 | entities that control, are controlled by, or are under common control 30 | with that entity are considered to be a single Contributor. 31 | 32 | For the purposes of this definition, "control" means (i) the power, 33 | direct or indirect, to cause the direction or management of such entity, 34 | whether by contract or otherwise, or (ii) ownership of fifty percent 35 | (50%) or more of the outstanding shares, or (iii) beneficial ownership 36 | of such entity. 37 | 38 | “Contribution" shall mean any original work of authorship, including 39 | any modifications or additions to an existing work, that is 40 | intentionally submitted by You to Leap Motion for inclusion in, or 41 | documentation of, any of the products owned or managed by Leap Motion 42 | (the "Work"). For the purposes of this definition, "submitted" means any 43 | form of electronic, verbal, or written communication sent to Leap Motion 44 | or its representatives, including but not limited to communication on 45 | electronic mailing lists, source code control systems, and issue 46 | tracking systems that are managed by, or on behalf of, Leap Motion for 47 | the purpose of discussing and improving the Work, but excluding 48 | communication that is conspicuously marked or otherwise designated in 49 | writing by You as "Not a Contribution." 50 | 51 | 2. Grant of Copyright License. 52 | 53 | Subject to the terms and conditions of this Agreement, You hereby grant 54 | to Leap Motion and to recipients of software distributed by Leap Motion 55 | a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 56 | irrevocable copyright license to reproduce, prepare derivative works of, 57 | publicly display, publicly perform, sublicense, and distribute Your 58 | Contributions and such derivative works. 59 | 60 | 3. Grant of Patent License. 61 | 62 | Subject to the terms and conditions of this Agreement, You hereby grant 63 | to Leap Motion and to recipients of software distributed by Leap Motion 64 | a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable (except as stated in this section) patent license to make, 66 | have made, use, offer to sell, import, and otherwise transfer the Work, 67 | where such license applies only to those patent claims licensable by You 68 | that are necessarily infringed by Your Contribution(s) alone or by 69 | combination of Your Contribution(s) with the Work to which such 70 | Contribution(s) was submitted. If any entity institutes patent 71 | litigation against You or any other entity (including a cross-claim or 72 | counterclaim in a lawsuit) alleging that your Contribution, or the Work 73 | to which you have contributed, constitutes direct or contributory patent 74 | infringement, then any patent licenses granted to that entity under this 75 | Agreement for that Contribution or Work shall terminate as of the date 76 | such litigation is filed. 77 | 78 | 4. You represent that you are legally entitled to grant the above 79 | license. 80 | 81 | If your employer(s) has rights to intellectual property that you create 82 | that includes your Contributions, you represent that you have received 83 | permission to make Contributions on behalf of that employer, that your 84 | employer has waived such rights for your Contributions to Leap Motion, 85 | or that your employer has executed a separate Corporate CLA with Leap 86 | Motion. 87 | 88 | 5. You represent that each of Your Contributions is Your original 89 | creation (see section 7 for submissions on behalf of others). 90 | 91 | You represent that Your Contribution submissions include complete 92 | details of any third-party license or other restriction (including, but 93 | not limited to, related patents and trademarks) of which you are 94 | personally aware and which are associated with any part of Your 95 | Contributions. 96 | 97 | 6. You are not expected to provide support for Your Contributions, 98 | except to the extent You desire to provide support. You may provide 99 | support for free, for a fee, or not at all. Unless required by 100 | applicable law or agreed to in writing, You provide Your Contributions 101 | on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 102 | either express or implied, including, without limitation, any warranties 103 | or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS 104 | FOR A PARTICULAR PURPOSE. 105 | 106 | 7. Should You wish to submit work that is not Your original creation, 107 | You may submit it to Leap Motion separately from any Contribution, 108 | identifying the complete details of its source and of any license or 109 | other restriction (including, but not limited to, related patents, 110 | trademarks, and license agreements) of which you are personally aware, 111 | and conspicuously marking the work as "Submitted on behalf of a 112 | third-party: [[]named here]". 113 | 114 | 8. You agree to notify Leap Motion of any facts or circumstances of 115 | which you become aware that would make these representations inaccurate 116 | in any respect. 117 | 118 | ====================================================== 119 | 120 | 1. Peter Ehrlich 121 | Github account: pehrlich 122 | Email: pehrlich@leapmotion.com 123 | 124 | 2. Max Sills 125 | Github account: MaxSillsLM 126 | Email: max@leapmotion.com 127 | 128 | 3. Paul Mandel 129 | Github account: paulmand3l 130 | Email: pmandel@leapmotion.com 131 | 132 | 4. Robin Böhm 133 | Github account: robinboehm 134 | Email: robinboehm@gmail.com 135 | 136 | 5. Martin Naumann 137 | Github account: AVGP 138 | Email: martin@geekonaut.de 139 | 140 | 6. Wenchen Li (Neo) 141 | Github account: neolwc 142 | Email: neolwc@gmail.com 143 | -------------------------------------------------------------------------------- /main/transform/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Leap Transform Plugin 6 | 7 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | View Source 54 | 55 |

Transform Plugin

56 |

57 | Transforms Leap data based off of a rotation matrix or a THREE.js rotation and vectors. 58 |

59 |

60 | Here we use the riggedHand to visualize the transformations, but it is not required. All data in the frame is altered 61 | by the transform plugin. 62 |

63 |

64 | Parameters can be either static objects or methods which transform on every frame. 65 |

66 |

67 | In this way, you can then transform individual hands based upon their id. 68 |

69 | 70 |
71 | 72 |
73 |
74 | 75 |
76 | 77 |
78 |
79 | 80 |
81 | 82 |
83 |
84 | 85 | 87 |
88 |
89 | 90 |
91 | 92 |
93 | 94 |

 95 |   // At the simplest:
 96 |   Leap.loop()
 97 |     .use('transform', {
 98 |       // move 20 cm back.
 99 |       position: new THREE.Vector3(0,0,-200)
100 |     });
101 | 
102 | 103 |
104 | 105 |

Options

106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 141 | 142 |
NameDefaultDescription
VRfalse 117 | Sets scale, position, and rotation transforms for HMD mode. These are: units in meters, 8cm forward, and all x, y, and z axis flipped. Also tells tracking to use head-mounted device mode. 118 |
positionundefinedA THREE.Vector3 position offset vector
quaternionundefinedA THREE.Quaternion rotation
scaleundefinedA THREE.Vector scale vector, or a single scalar to be applied to all three axis.
effectiveParentundefinedA THREE.Object3d, or anything which responds to `matrixWorld` and `scale`. This matrixWorld is applied to the 139 | hand data as well as any transformations specified through the other options. This allows hands to be "attached" 140 | to a camera.
143 | 144 | 145 | 146 | 147 | 148 | 149 | 255 | 256 | 257 | -------------------------------------------------------------------------------- /utils/data-plotter/LeapDataPlotter.js: -------------------------------------------------------------------------------- 1 | // this allows RequireJS without necessitating it. 2 | // see http://bob.yexley.net/umd-javascript-that-runs-anywhere/ 3 | (function (root, factory) { 4 | 5 | if (typeof define === "function" && define.amd) { 6 | define([], factory); 7 | } else { 8 | root.LeapDataPlotter = factory(); 9 | } 10 | 11 | }(this, function () { 12 | 13 | var LeapDataPlotter, TimeSeries; 14 | 15 | var colors = ['#900', '#090', '#009', '#990', '#909', '#099']; 16 | var colorIndex = 0; 17 | 18 | LeapDataPlotter = function (options) { 19 | this.options = options || (options = {}); 20 | this.seriesHash = {}; 21 | this.series = []; 22 | this.init(options.el); 23 | } 24 | 25 | LeapDataPlotter.prototype.init = function(el) { 26 | 27 | if (el){ 28 | var canvas = el; 29 | }else { 30 | var canvas = document.createElement('canvas'); 31 | canvas.className = "leap-data-plotter"; 32 | document.body.appendChild(canvas); 33 | } 34 | 35 | 36 | this.canvas = canvas; 37 | this.context = canvas.getContext('2d'); 38 | 39 | this.rescale(); 40 | } 41 | 42 | // this method must be called any time the canvas changes size. 43 | LeapDataPlotter.prototype.rescale = function(){ 44 | var styles = getComputedStyle(this.canvas); 45 | var windowWidth = parseInt(styles.width, 10); 46 | var windowHeight = parseInt(styles.height, 10); 47 | this.width = windowWidth; 48 | this.height = windowHeight; 49 | 50 | var devicePixelRatio = window.devicePixelRatio || 1; 51 | var backingStoreRatio = this.context.webkitBackingStorePixelRatio || 52 | this.context.mozBackingStorePixelRatio || 53 | this.context.msBackingStorePixelRatio || 54 | this.context.oBackingStorePixelRatio || 55 | this.context.backingStorePixelRatio || 1; 56 | 57 | var ratio = devicePixelRatio / backingStoreRatio; 58 | if (devicePixelRatio !== backingStoreRatio) { 59 | 60 | var oldWidth = this.canvas.width; 61 | var oldHeight = this.canvas.height; 62 | 63 | this.canvas.width = oldWidth * ratio; 64 | this.canvas.height = oldHeight * ratio; 65 | 66 | this.canvas.style.width = oldWidth + 'px'; 67 | this.canvas.style.height = oldHeight + 'px'; 68 | 69 | this.context.scale(ratio, ratio); 70 | } 71 | 72 | this.clear(); 73 | this.draw(); 74 | } 75 | 76 | // pushes a data point on to the plot 77 | // data can either be a number 78 | // or an array [x,y,z], which will be plotted in three graphs. 79 | // options: 80 | // - y: the graph index on which to plot this datapoint 81 | // - color: hex code 82 | // - name: name of the plot 83 | // - precision: how many decimals to show (for max, min, current value) 84 | LeapDataPlotter.prototype.plot = function (id, data, opts) { 85 | // console.assert(!isNaN(data), "No plotting data received"); 86 | 87 | opts || (opts = {}); 88 | 89 | if (data.length) { 90 | 91 | for (var i = 0, c = 120; i < data.length; i++, c=++c>122?97:c) { 92 | this.getTimeSeries( id + '.' + String.fromCharCode(c), opts ) 93 | .push( data[i], {pointColor: opts.pointColor} ); 94 | } 95 | 96 | } else { 97 | 98 | this.getTimeSeries(id, opts) 99 | .push(data, {pointColor: opts.pointColor}); 100 | 101 | } 102 | 103 | } 104 | 105 | LeapDataPlotter.prototype.getTimeSeries = function (id, opts) { 106 | var ts = this.seriesHash[id]; 107 | 108 | if (!ts) { 109 | 110 | var defaultOpts = this.getOptions(id); 111 | for (key in opts){ 112 | defaultOpts[key] = opts[key]; 113 | } 114 | 115 | ts = new TimeSeries(defaultOpts); 116 | this.series.push(ts); 117 | this.seriesHash[id] = ts; 118 | 119 | } 120 | 121 | return ts; 122 | } 123 | 124 | LeapDataPlotter.prototype.getOptions = function (name) { 125 | var c = colorIndex; 126 | colorIndex = (colorIndex + 1) % colors.length; 127 | var len = this.series.length; 128 | var y = len ? this.series[len - 1].y + 50 : 0; 129 | return { 130 | y: y, 131 | width: this.width, 132 | color: colors[c], 133 | name: name 134 | } 135 | } 136 | 137 | LeapDataPlotter.prototype.clear = function() { 138 | this.context.clearRect(0, 0, this.width, this.height); 139 | } 140 | 141 | LeapDataPlotter.prototype.draw = function() { 142 | var context = this.context; 143 | this.series.forEach(function (s) { 144 | s.draw(context); 145 | }); 146 | } 147 | 148 | LeapDataPlotter.prototype.update = function(){ 149 | this.clear(); 150 | this.draw(); 151 | } 152 | 153 | TimeSeries = function (opts) { 154 | opts = opts || {}; 155 | this.x = opts.x || 0; 156 | this.y = opts.y || 0; 157 | this.precision = opts.precision || 5; 158 | this.units = opts.units || ''; 159 | this.width = opts.width || 1000; 160 | this.height = opts.height || 50; 161 | this.length = opts.length || 600; 162 | this.color = opts.color || '#000'; 163 | this.name = opts.name || ""; 164 | this.frameHandler = opts.frameHandler; 165 | 166 | this.max = -Infinity; 167 | this.min = Infinity; 168 | this.data = []; 169 | this.pointColors = []; 170 | } 171 | 172 | TimeSeries.prototype.push = function (value, opts) { 173 | this.data.push(value); 174 | 175 | if (this.data.length >= this.length) { 176 | this.data.shift(); 177 | } 178 | 179 | if (opts && opts.pointColor){ 180 | this.pointColors.push(opts.pointColor); 181 | 182 | // note: this can get out of sync if a point color is not set for every point. 183 | if (this.pointColors.length >= this.length) { 184 | this.pointColors.shift(); 185 | } 186 | } 187 | 188 | return this; 189 | } 190 | 191 | TimeSeries.prototype.draw = function (context) { 192 | var self = this; 193 | var xScale = (this.width - 10) / (this.length - 1); 194 | var yScale = -(this.height - 10) / (this.max - this.min); 195 | 196 | var padding = 5; 197 | var top = (this.max - this.min) * yScale + 10; 198 | 199 | context.save(); 200 | context.strokeRect(this.x, this.y, this.width, this.height); 201 | context.translate(this.x, this.y + this.height - padding); 202 | context.strokeStyle = this.color; 203 | 204 | context.beginPath(); 205 | 206 | var max = -Infinity; 207 | var min = Infinity; 208 | this.data.forEach(function (d, i) { 209 | if (d > max) max = d; 210 | if (d < min) min = d; 211 | 212 | if (isNaN(d)) { 213 | context.stroke(); 214 | context.beginPath(); 215 | } else { 216 | context.lineTo(i * xScale, (d - self.min) * yScale); 217 | if (self.pointColors[i] && (self.pointColors[i] != self.pointColors[i - 1]) ){ 218 | context.stroke(); 219 | context.strokeStyle = self.pointColors[i]; 220 | context.beginPath(); 221 | context.lineTo(i * xScale, (d - self.min) * yScale); 222 | } 223 | } 224 | }); 225 | context.stroke(); 226 | 227 | // draw labels 228 | context.fillText( this.name, padding, top); 229 | context.fillText( this.data[this.data.length - 1].toPrecision(this.precision) + this.units, padding, 0 ); 230 | 231 | context.textAlign="end"; 232 | context.fillText( this.min.toPrecision(this.precision) + this.units, this.width - padding, 0 ); 233 | context.fillText( this.max.toPrecision(this.precision) + this.units, this.width - padding, top ); 234 | context.textAlign="left"; 235 | // end draw labels 236 | 237 | context.restore(); 238 | this.min = min; 239 | this.max = max; 240 | } 241 | 242 | return LeapDataPlotter; 243 | 244 | })); 245 | -------------------------------------------------------------------------------- /utils/leap-plugins-0.1.12-utils.js: -------------------------------------------------------------------------------- 1 | // this allows RequireJS without necessitating it. 2 | // see http://bob.yexley.net/umd-javascript-that-runs-anywhere/ 3 | (function (root, factory) { 4 | 5 | if (typeof define === "function" && define.amd) { 6 | define([], factory); 7 | } else { 8 | root.LeapDataPlotter = factory(); 9 | } 10 | 11 | }(this, function () { 12 | 13 | var LeapDataPlotter, TimeSeries; 14 | 15 | var colors = ['#900', '#090', '#009', '#990', '#909', '#099']; 16 | var colorIndex = 0; 17 | 18 | LeapDataPlotter = function (options) { 19 | this.options = options || (options = {}); 20 | this.seriesHash = {}; 21 | this.series = []; 22 | this.init(options.el); 23 | } 24 | 25 | LeapDataPlotter.prototype.init = function(el) { 26 | 27 | if (el){ 28 | var canvas = el; 29 | }else { 30 | var canvas = document.createElement('canvas'); 31 | canvas.className = "leap-data-plotter"; 32 | document.body.appendChild(canvas); 33 | } 34 | 35 | 36 | this.canvas = canvas; 37 | this.context = canvas.getContext('2d'); 38 | 39 | this.rescale(); 40 | } 41 | 42 | // this method must be called any time the canvas changes size. 43 | LeapDataPlotter.prototype.rescale = function(){ 44 | var styles = getComputedStyle(this.canvas); 45 | var windowWidth = parseInt(styles.width, 10); 46 | var windowHeight = parseInt(styles.height, 10); 47 | this.width = windowWidth; 48 | this.height = windowHeight; 49 | 50 | var devicePixelRatio = window.devicePixelRatio || 1; 51 | var backingStoreRatio = this.context.webkitBackingStorePixelRatio || 52 | this.context.mozBackingStorePixelRatio || 53 | this.context.msBackingStorePixelRatio || 54 | this.context.oBackingStorePixelRatio || 55 | this.context.backingStorePixelRatio || 1; 56 | 57 | var ratio = devicePixelRatio / backingStoreRatio; 58 | if (devicePixelRatio !== backingStoreRatio) { 59 | 60 | var oldWidth = this.canvas.width; 61 | var oldHeight = this.canvas.height; 62 | 63 | this.canvas.width = oldWidth * ratio; 64 | this.canvas.height = oldHeight * ratio; 65 | 66 | this.canvas.style.width = oldWidth + 'px'; 67 | this.canvas.style.height = oldHeight + 'px'; 68 | 69 | this.context.scale(ratio, ratio); 70 | } 71 | 72 | this.clear(); 73 | this.draw(); 74 | } 75 | 76 | // pushes a data point on to the plot 77 | // data can either be a number 78 | // or an array [x,y,z], which will be plotted in three graphs. 79 | // options: 80 | // - y: the graph index on which to plot this datapoint 81 | // - color: hex code 82 | // - name: name of the plot 83 | // - precision: how many decimals to show (for max, min, current value) 84 | LeapDataPlotter.prototype.plot = function (id, data, opts) { 85 | // console.assert(!isNaN(data), "No plotting data received"); 86 | 87 | opts || (opts = {}); 88 | 89 | if (data.length) { 90 | 91 | for (var i = 0, c = 120; i < data.length; i++, c=++c>122?97:c) { 92 | this.getTimeSeries( id + '.' + String.fromCharCode(c), opts ) 93 | .push( data[i], {pointColor: opts.pointColor} ); 94 | } 95 | 96 | } else { 97 | 98 | this.getTimeSeries(id, opts) 99 | .push(data, {pointColor: opts.pointColor}); 100 | 101 | } 102 | 103 | } 104 | 105 | LeapDataPlotter.prototype.getTimeSeries = function (id, opts) { 106 | var ts = this.seriesHash[id]; 107 | 108 | if (!ts) { 109 | 110 | var defaultOpts = this.getOptions(id); 111 | for (key in opts){ 112 | defaultOpts[key] = opts[key]; 113 | } 114 | 115 | ts = new TimeSeries(defaultOpts); 116 | this.series.push(ts); 117 | this.seriesHash[id] = ts; 118 | 119 | } 120 | 121 | return ts; 122 | } 123 | 124 | LeapDataPlotter.prototype.getOptions = function (name) { 125 | var c = colorIndex; 126 | colorIndex = (colorIndex + 1) % colors.length; 127 | var len = this.series.length; 128 | var y = len ? this.series[len - 1].y + 50 : 0; 129 | return { 130 | y: y, 131 | width: this.width, 132 | color: colors[c], 133 | name: name 134 | } 135 | } 136 | 137 | LeapDataPlotter.prototype.clear = function() { 138 | this.context.clearRect(0, 0, this.width, this.height); 139 | } 140 | 141 | LeapDataPlotter.prototype.draw = function() { 142 | var context = this.context; 143 | this.series.forEach(function (s) { 144 | s.draw(context); 145 | }); 146 | } 147 | 148 | LeapDataPlotter.prototype.update = function(){ 149 | this.clear(); 150 | this.draw(); 151 | } 152 | 153 | TimeSeries = function (opts) { 154 | opts = opts || {}; 155 | this.x = opts.x || 0; 156 | this.y = opts.y || 0; 157 | this.precision = opts.precision || 5; 158 | this.units = opts.units || ''; 159 | this.width = opts.width || 1000; 160 | this.height = opts.height || 50; 161 | this.length = opts.length || 600; 162 | this.color = opts.color || '#000'; 163 | this.name = opts.name || ""; 164 | this.frameHandler = opts.frameHandler; 165 | 166 | this.max = -Infinity; 167 | this.min = Infinity; 168 | this.data = []; 169 | this.pointColors = []; 170 | } 171 | 172 | TimeSeries.prototype.push = function (value, opts) { 173 | this.data.push(value); 174 | 175 | if (this.data.length >= this.length) { 176 | this.data.shift(); 177 | } 178 | 179 | if (opts && opts.pointColor){ 180 | this.pointColors.push(opts.pointColor); 181 | 182 | // note: this can get out of sync if a point color is not set for every point. 183 | if (this.pointColors.length >= this.length) { 184 | this.pointColors.shift(); 185 | } 186 | } 187 | 188 | return this; 189 | } 190 | 191 | TimeSeries.prototype.draw = function (context) { 192 | var self = this; 193 | var xScale = (this.width - 10) / (this.length - 1); 194 | var yScale = -(this.height - 10) / (this.max - this.min); 195 | 196 | var padding = 5; 197 | var top = (this.max - this.min) * yScale + 10; 198 | 199 | context.save(); 200 | context.strokeRect(this.x, this.y, this.width, this.height); 201 | context.translate(this.x, this.y + this.height - padding); 202 | context.strokeStyle = this.color; 203 | 204 | context.beginPath(); 205 | 206 | var max = -Infinity; 207 | var min = Infinity; 208 | this.data.forEach(function (d, i) { 209 | if (d > max) max = d; 210 | if (d < min) min = d; 211 | 212 | if (isNaN(d)) { 213 | context.stroke(); 214 | context.beginPath(); 215 | } else { 216 | context.lineTo(i * xScale, (d - self.min) * yScale); 217 | if (self.pointColors[i] && (self.pointColors[i] != self.pointColors[i - 1]) ){ 218 | context.stroke(); 219 | context.strokeStyle = self.pointColors[i]; 220 | context.beginPath(); 221 | context.lineTo(i * xScale, (d - self.min) * yScale); 222 | } 223 | } 224 | }); 225 | context.stroke(); 226 | 227 | // draw labels 228 | context.fillText( this.name, padding, top); 229 | context.fillText( this.data[this.data.length - 1].toPrecision(this.precision) + this.units, padding, 0 ); 230 | 231 | context.textAlign="end"; 232 | context.fillText( this.min.toPrecision(this.precision) + this.units, this.width - padding, 0 ); 233 | context.fillText( this.max.toPrecision(this.precision) + this.units, this.width - padding, top ); 234 | context.textAlign="left"; 235 | // end draw labels 236 | 237 | context.restore(); 238 | this.min = min; 239 | this.max = max; 240 | } 241 | 242 | return LeapDataPlotter; 243 | 244 | })); 245 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | -------------------------------------------------------------------------------- /main/bone-hand/leap.bone-hand.coffee: -------------------------------------------------------------------------------- 1 | scope = null 2 | 3 | THREE = if typeof require != 'undefined' then require('three') else window.THREE 4 | 5 | initScene = (targetEl, scale)-> 6 | 7 | # scene and renderer 8 | 9 | scope.scene = new THREE.Scene() 10 | scope.rendererOps ||= {} 11 | if scope.rendererOps.alpha == undefined then scope.rendererOps.alpha = true 12 | scope.renderer = renderer = new THREE.WebGLRenderer(scope.rendererOps) 13 | 14 | width = scope.width || window.innerWidth 15 | height = scope.height || window.innerHeight 16 | 17 | renderer.setClearColor(0x000000, 0) 18 | renderer.setSize(width, height) 19 | 20 | renderer.domElement.className = "leap-boneHand" 21 | 22 | renderer.shadowMap.enabled = true 23 | renderer.shadowMap.type = THREE.PCFSoftShadowMap 24 | 25 | targetEl.appendChild(renderer.domElement) 26 | 27 | # camera 28 | 29 | near = 1 # 1 mm 30 | far = 10000 # 10 m 31 | 32 | if scale 33 | near *= scale 34 | far *= scale 35 | 36 | scope.camera = camera = new THREE.PerspectiveCamera(45, width / height, near, far) 37 | 38 | camera.position.set(0, 300, 500); 39 | camera.lookAt(new THREE.Vector3(0, 160, 0)); 40 | 41 | scope.scene.add(camera) 42 | 43 | if !scope.width && !scope.height 44 | window.addEventListener 'resize', -> 45 | width = window.innerWidth 46 | height = window.innerHeight 47 | 48 | camera.aspect = width / height 49 | camera.updateProjectionMatrix() 50 | 51 | renderer.setSize( width, height ) 52 | 53 | renderer.render(scope.scene, camera) 54 | , false 55 | 56 | 57 | scope.render ||= (timestamp)-> 58 | renderer.render(scope.scene, scope.camera); 59 | 60 | scope.render() 61 | 62 | baseBoneRotation = null 63 | jointColor = null 64 | boneColor = null 65 | boneScale = null 66 | jointScale = null 67 | boneRadius = null 68 | jointRadius = null 69 | material = null 70 | armTopAndBottomRotation = null 71 | 72 | 73 | 74 | class HandMesh 75 | # when a hand enters the scene, it takes a mesh out of here, or creates a new one 76 | @unusedHandMeshes: [] 77 | 78 | # gets or creates a handmesh 79 | # makes it visible 80 | @get: -> 81 | if HandMesh.unusedHandMeshes.length == 0 82 | handMesh = HandMesh.create() 83 | 84 | handMesh = HandMesh.unusedHandMeshes.pop() 85 | 86 | handMesh.show() 87 | 88 | return handMesh 89 | 90 | # replaces a handMesh in the cache 91 | # makes it invisible. 92 | replace: -> 93 | @hide() 94 | HandMesh.unusedHandMeshes.push(this) 95 | 96 | 97 | # adds hand meshes to the scene 98 | # stores them in unusedhandMesh 99 | @create: -> 100 | mesh = new HandMesh 101 | mesh.setVisibility(false) 102 | HandMesh.unusedHandMeshes.push(mesh) 103 | 104 | 105 | if HandMesh.onMeshCreated 106 | HandMesh.onMeshCreated(mesh) 107 | 108 | return mesh 109 | 110 | constructor: -> 111 | 112 | material = if !isNaN(scope.opacity) 113 | new THREE.MeshPhongMaterial(fog: false, transparent: true, opacity: scope.opacity) 114 | else 115 | new THREE.MeshPhongMaterial(fog: false) 116 | 117 | boneRadius = 40 * boneScale # 40 is typical mm middle finger proximal bone length. This can be anything, as it gets rescaled later. 118 | jointRadius = 40 * jointScale 119 | 120 | @fingerMeshes = [] 121 | for i in [0...5] 122 | finger = [] 123 | boneCount = if i == 0 then 3 else 4 # thumb has one fewer 124 | 125 | for j in [0...boneCount] 126 | # we keep one array with bone and joint meshes, and iterate through it later for position.. easy 127 | 128 | #joint 129 | mesh = new THREE.Mesh( 130 | new THREE.SphereGeometry(jointRadius, 32, 32), 131 | material.clone() 132 | ) 133 | mesh.name = "hand-bone-#{j}" 134 | mesh.material.color.copy(jointColor) 135 | mesh.renderOrder = ((i * 9) + (2 * j) ) / 36 136 | mesh.castShadow = true 137 | scope.scene.add mesh 138 | finger.push mesh 139 | 140 | # bone 141 | # CylinderGeometry(radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded) 142 | mesh = new THREE.Mesh( 143 | new THREE.CylinderGeometry(boneRadius, boneRadius, 40, 32), 144 | material.clone() 145 | ) 146 | mesh.name = "hand-joint-#{j}" 147 | mesh.material.color.copy(boneColor) 148 | mesh.renderOrder = ((i * 9) + (2 * j) + 1 ) / 36 # might fuckup opaque objects? 149 | mesh.castShadow = true 150 | scope.scene.add mesh 151 | finger.push mesh 152 | 153 | 154 | #joint - end cap 155 | mesh = new THREE.Mesh( 156 | new THREE.SphereGeometry(jointRadius, 32, 32), 157 | material.clone() 158 | ) 159 | mesh.material.color.copy(jointColor) 160 | 161 | mesh.castShadow = true 162 | 163 | scope.scene.add mesh 164 | finger.push mesh 165 | 166 | 167 | @fingerMeshes.push(finger) 168 | 169 | 170 | if scope.arm 171 | @armMesh = new THREE.Object3D; 172 | @armBones = [] 173 | @armSpheres = [] 174 | 175 | for i in [0..3] 176 | @armBones.push(new THREE.Mesh( 177 | # CylinderGeometry(radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded) 178 | new THREE.CylinderGeometry(boneRadius, boneRadius, 179 | ( if i < 2 then 1000 else 100 ) 180 | , 32), 181 | material.clone() 182 | )) 183 | @armBones[i].material.color.copy(boneColor) 184 | @armBones[i].castShadow = true 185 | @armBones[i].name= "ArmBone#{i}" 186 | 187 | if i > 1 188 | @armBones[i].quaternion.multiply(armTopAndBottomRotation) 189 | 190 | @armMesh.add(@armBones[i]) 191 | 192 | @armSpheres = [] 193 | for i in [0..3] 194 | @armSpheres.push(new THREE.Mesh( 195 | new THREE.SphereGeometry(jointRadius, 32, 32), 196 | material.clone() 197 | )) 198 | @armSpheres[i].material.color.copy(jointColor) 199 | @armSpheres[i].castShadow = true 200 | @armSpheres[i].name= "ArmSphere#{i}" 201 | @armMesh.add(@armSpheres[i]) 202 | 203 | scope.scene.add(@armMesh); 204 | 205 | traverse: (callback)-> 206 | for i in [0...5] 207 | for mesh in @fingerMeshes[i] 208 | callback(mesh) 209 | 210 | @armMesh && @armMesh.traverse(callback) 211 | 212 | 213 | 214 | # scales the meshes appropriately 215 | scaleTo: (hand)-> 216 | 217 | # bone radius changes with overall hand size, but not joint length 218 | baseScale = hand.middleFinger.proximal.length / @fingerMeshes[2][1].geometry.parameters.height 219 | 220 | for i in [0...5] 221 | finger = hand.fingers[i] 222 | j = 0 223 | # iterate backwards as first bone in the thumb is bunk. 224 | while true 225 | if j == @fingerMeshes[i].length - 1 226 | mesh = @fingerMeshes[i][j] 227 | mesh.scale.set(baseScale, baseScale, baseScale) 228 | break 229 | 230 | bone = finger.bones[ 3 - (j / 2) ] 231 | 232 | # joints 233 | mesh = @fingerMeshes[i][j] 234 | mesh.scale.set(baseScale, baseScale, baseScale) 235 | 236 | j++ 237 | 238 | # bones 239 | mesh = @fingerMeshes[i][j] 240 | fingerBoneLengthScale = bone.length / mesh.geometry.parameters.height 241 | mesh.scale.set(baseScale, fingerBoneLengthScale, baseScale) 242 | j++ 243 | 244 | if scope.arm 245 | armLenScale = hand.arm.length / ( @armBones[0].geometry.parameters.height + @armBones[0].geometry.parameters.radiusTop ) 246 | armWidthScale = hand.arm.width / ( @armBones[2].geometry.parameters.height + @armBones[2].geometry.parameters.radiusTop ) 247 | 248 | for i in [0..3] 249 | @armBones[i].scale.set(baseScale, 250 | ( if i < 2 then armLenScale else armWidthScale ) 251 | , baseScale) 252 | 253 | @armSpheres[i].scale.set(baseScale, baseScale, baseScale) 254 | 255 | boneXOffset = (hand.arm.width / 2) * 0.85 256 | halfArmLength = hand.arm.length / 2 257 | 258 | 259 | # CW from Top center 260 | @armBones[0].position.setX(boneXOffset) # radius 261 | @armBones[1].position.setX(-boneXOffset) # ulna 262 | @armBones[2].position.setY(halfArmLength) 263 | @armBones[3].position.setY(-halfArmLength) 264 | 265 | # CW from TL 266 | @armSpheres[0].position.set( - boneXOffset, halfArmLength, 0) 267 | @armSpheres[1].position.set( boneXOffset, halfArmLength, 0) 268 | @armSpheres[2].position.set( boneXOffset, - halfArmLength, 0) 269 | @armSpheres[3].position.set( - boneXOffset, - halfArmLength, 0) 270 | 271 | 272 | 273 | return this 274 | 275 | # positions and rotates the meshes appropriately 276 | formTo: (hand)-> 277 | for i in [0...5] 278 | finger = hand.fingers[i] 279 | j = 0 280 | while true 281 | 282 | if j == @fingerMeshes[i].length - 1 283 | mesh = @fingerMeshes[i][j] # alternating, joint-bone-joint-bone... 284 | mesh.position.fromArray bone.prevJoint 285 | break 286 | 287 | bone = finger.bones[ 3 - (j / 2) ] 288 | 289 | mesh = @fingerMeshes[i][j] 290 | mesh.position.fromArray bone.nextJoint 291 | ++j 292 | 293 | mesh = @fingerMeshes[i][j] 294 | 295 | mesh.position.fromArray bone.center() 296 | mesh.setRotationFromMatrix (new THREE.Matrix4).fromArray(bone.matrix()) 297 | mesh.quaternion.multiply baseBoneRotation 298 | ++j 299 | 300 | if @armMesh 301 | @armMesh.position.fromArray(hand.arm.center()) 302 | @armMesh.setRotationFromMatrix( 303 | (new THREE.Matrix4).fromArray( hand.arm.matrix() ) 304 | ); 305 | @armMesh.quaternion.multiply(baseBoneRotation) 306 | 307 | return this 308 | 309 | setVisibility: (visible)-> 310 | for i in [0...5] 311 | j = 0 312 | while true 313 | @fingerMeshes[i][j].visible = visible 314 | ++j 315 | 316 | break if j == @fingerMeshes[i].length 317 | 318 | if scope.arm 319 | for i in [0..3] 320 | @armBones[i].visible = visible 321 | @armSpheres[i].visible = visible 322 | 323 | 324 | show: -> 325 | @setVisibility(true) 326 | 327 | hide: -> 328 | @setVisibility(false) 329 | 330 | 331 | 332 | onHand = (hand) -> 333 | return if !scope.scene 334 | 335 | handMesh = hand.data('handMesh') 336 | 337 | # the handFound listener doesn't actually fire if in live mode with hand-in-screen 338 | # we manually check for finger meshes and initialize if necessary 339 | if !handMesh 340 | handMesh = HandMesh.get().scaleTo(hand) 341 | hand.data('handMesh', handMesh) 342 | if HandMesh.onMeshUsed 343 | HandMesh.onMeshUsed(handMesh) 344 | 345 | handMesh.formTo(hand) 346 | 347 | 348 | boneHandLost = (hand) -> 349 | handMesh = hand.data('handMesh') 350 | if handMesh 351 | handMesh.replace() 352 | 353 | handMesh = hand.data('handMesh', null) 354 | 355 | Leap.plugin 'boneHand', (options = {}) -> 356 | # make sure scope is globally available 357 | scope = options 358 | controller = this; 359 | 360 | jointColor = (new THREE.Color).setHex(0x5daa00) 361 | boneColor = (new THREE.Color).setHex(0xffffff) 362 | 363 | scope.boneScale && boneScale = scope.boneScale 364 | scope.jointScale && jointScale = scope.jointScale 365 | 366 | scope.boneColor && boneColor = scope.boneColor 367 | scope.jointColor && jointColor = scope.jointColor 368 | 369 | scope.HandMesh = HandMesh 370 | 371 | # this method enhances scope.scene with camera and lighting 372 | # it is split from initScene so as to allow it being called with non-default scenes. 373 | # expects camera to be added to the scene 374 | scope.addShadowCamera = -> 375 | 376 | scope.light = new THREE.SpotLight( 0xffffff, 1 ) 377 | scope.light.castShadow = true 378 | # scope.light.shadowCameraVisible = true # This makes for excellent debugging 379 | # scope.light.shadowDarkness = 0.8 # THREE.Light: .shadowDarkness has been removed. 380 | scope.light.shadow.mapSize.width = 1024 381 | scope.light.shadow.mapSize.height = 1024 382 | scope.light.shadow.camera.near = 0.5 / 0.001 383 | scope.light.shadow.camera.far = 3 / 0.001 384 | 385 | # fixed hand position.. 386 | scope.light.position.set(0,1000,1000); # up and behind 387 | scope.light.target.position.set(0,0,-1000); # one meter forward 388 | 389 | # see https:#github.com/mrdoob/three.js/issues/2251 390 | scope.camera.add(scope.light.target); 391 | scope.camera.add(scope.light); 392 | 393 | if controller.plugins.transform 394 | 395 | if controller.plugins.transform.getScale() 396 | # this somewhat confusingly-named method also ensures that scale is a Vector3 397 | # for VR, this would be 0.001 m/mm 398 | 399 | scope.light.shadowCameraNear *= controller.plugins.transform.scale.x 400 | scope.light.shadowCameraFar *= controller.plugins.transform.scale.x 401 | scope.light.target.position.multiply(controller.plugins.transform.scale) 402 | scope.light.position.multiply(controller.plugins.transform.scale) 403 | 404 | if controller.plugins.transform.vr == true 405 | scope.camera.position.set(0,0,0) 406 | 407 | if controller.plugins.transform.vr == 'desktop' 408 | scope.camera.position.set(0,0.15,0.3) 409 | 410 | 411 | 412 | 413 | baseBoneRotation = (new THREE.Quaternion).setFromEuler( 414 | new THREE.Euler(Math.PI / 2, 0, 0) 415 | ); 416 | 417 | 418 | 419 | boneScale = 1 / 6 420 | jointScale = 1 / 5 421 | 422 | boneRadius = null 423 | jointRadius = null 424 | 425 | material = null 426 | 427 | armTopAndBottomRotation = (new THREE.Quaternion).setFromEuler( 428 | new THREE.Euler(0, 0, Math.PI / 2) 429 | ); 430 | 431 | 432 | HandMesh.onMeshCreated = (mesh)-> 433 | controller.emit('handMeshCreated', mesh) 434 | 435 | HandMesh.onMeshUsed = (mesh)-> 436 | controller.emit('handMeshUsed', mesh) 437 | 438 | 439 | 440 | 441 | @use('handEntry') 442 | @use('handHold') 443 | 444 | # this allows a null scene to be passed in for delayed-initialization. 445 | if scope.scene == undefined 446 | console.assert(scope.targetEl) 447 | 448 | if @plugins.transform && @plugins.transform.getScale() 449 | scale = @plugins.transform.scale.x # we just grab one value, as they're usually all the same. 450 | 451 | initScene(scope.targetEl, scale) 452 | scope.addShadowCamera() 453 | 454 | 455 | 456 | # Preload two hands 457 | if scope.scene 458 | HandMesh.create() 459 | HandMesh.create() 460 | 461 | # have rendered called by leap, rather than animation frame, to make sure there's no one-frame-delay. 462 | # we wrap this to allow the render method to be replaced 463 | if Leap.version.major == 0 && Leap.version.minor < 7 && Leap.version.dot < 4 464 | console.warn("BoneHand default scene render requires LeapJS > 0.6.3. You're running have #{Leap.version.full}") 465 | 466 | @on 'frameEnd', (timestamp)-> 467 | if scope.render 468 | scope.render(timestamp) 469 | 470 | @on 'handLost', boneHandLost 471 | 472 | 473 | { 474 | 475 | hand: onHand 476 | 477 | } 478 | -------------------------------------------------------------------------------- /main/bone-hand/leap.bone-hand.js: -------------------------------------------------------------------------------- 1 | //CoffeeScript generated from main/bone-hand/leap.bone-hand.coffee 2 | (function() { 3 | var HandMesh, THREE, armTopAndBottomRotation, baseBoneRotation, boneColor, boneHandLost, boneRadius, boneScale, initScene, jointColor, jointRadius, jointScale, material, onHand, scope; 4 | 5 | scope = null; 6 | 7 | THREE = typeof require !== 'undefined' ? require('three') : window.THREE; 8 | 9 | initScene = function(targetEl, scale) { 10 | var camera, far, height, near, renderer, width; 11 | scope.scene = new THREE.Scene(); 12 | scope.rendererOps || (scope.rendererOps = {}); 13 | if (scope.rendererOps.alpha === void 0) { 14 | scope.rendererOps.alpha = true; 15 | } 16 | scope.renderer = renderer = new THREE.WebGLRenderer(scope.rendererOps); 17 | width = scope.width || window.innerWidth; 18 | height = scope.height || window.innerHeight; 19 | renderer.setClearColor(0x000000, 0); 20 | renderer.setSize(width, height); 21 | renderer.domElement.className = "leap-boneHand"; 22 | renderer.shadowMap.enabled = true; 23 | renderer.shadowMap.type = THREE.PCFSoftShadowMap; 24 | targetEl.appendChild(renderer.domElement); 25 | near = 1; 26 | far = 10000; 27 | if (scale) { 28 | near *= scale; 29 | far *= scale; 30 | } 31 | scope.camera = camera = new THREE.PerspectiveCamera(45, width / height, near, far); 32 | camera.position.set(0, 300, 500); 33 | camera.lookAt(new THREE.Vector3(0, 160, 0)); 34 | scope.scene.add(camera); 35 | if (!scope.width && !scope.height) { 36 | window.addEventListener('resize', function() { 37 | width = window.innerWidth; 38 | height = window.innerHeight; 39 | camera.aspect = width / height; 40 | camera.updateProjectionMatrix(); 41 | renderer.setSize(width, height); 42 | return renderer.render(scope.scene, camera); 43 | }, false); 44 | } 45 | scope.render || (scope.render = function(timestamp) { 46 | return renderer.render(scope.scene, scope.camera); 47 | }); 48 | return scope.render(); 49 | }; 50 | 51 | baseBoneRotation = null; 52 | 53 | jointColor = null; 54 | 55 | boneColor = null; 56 | 57 | boneScale = null; 58 | 59 | jointScale = null; 60 | 61 | boneRadius = null; 62 | 63 | jointRadius = null; 64 | 65 | material = null; 66 | 67 | armTopAndBottomRotation = null; 68 | 69 | HandMesh = (function() { 70 | HandMesh.unusedHandMeshes = []; 71 | 72 | HandMesh.get = function() { 73 | var handMesh; 74 | if (HandMesh.unusedHandMeshes.length === 0) { 75 | handMesh = HandMesh.create(); 76 | } 77 | handMesh = HandMesh.unusedHandMeshes.pop(); 78 | handMesh.show(); 79 | return handMesh; 80 | }; 81 | 82 | HandMesh.prototype.replace = function() { 83 | this.hide(); 84 | return HandMesh.unusedHandMeshes.push(this); 85 | }; 86 | 87 | HandMesh.create = function() { 88 | var mesh; 89 | mesh = new HandMesh; 90 | mesh.setVisibility(false); 91 | HandMesh.unusedHandMeshes.push(mesh); 92 | if (HandMesh.onMeshCreated) { 93 | HandMesh.onMeshCreated(mesh); 94 | } 95 | return mesh; 96 | }; 97 | 98 | function HandMesh() { 99 | var boneCount, finger, i, j, mesh, _i, _j, _k, _l; 100 | material = !isNaN(scope.opacity) ? new THREE.MeshPhongMaterial({ 101 | fog: false, 102 | transparent: true, 103 | opacity: scope.opacity 104 | }) : new THREE.MeshPhongMaterial({ 105 | fog: false 106 | }); 107 | boneRadius = 40 * boneScale; 108 | jointRadius = 40 * jointScale; 109 | this.fingerMeshes = []; 110 | for (i = _i = 0; _i < 5; i = ++_i) { 111 | finger = []; 112 | boneCount = i === 0 ? 3 : 4; 113 | for (j = _j = 0; 0 <= boneCount ? _j < boneCount : _j > boneCount; j = 0 <= boneCount ? ++_j : --_j) { 114 | mesh = new THREE.Mesh(new THREE.SphereGeometry(jointRadius, 32, 32), material.clone()); 115 | mesh.name = "hand-bone-" + j; 116 | mesh.material.color.copy(jointColor); 117 | mesh.renderOrder = ((i * 9) + (2 * j)) / 36; 118 | mesh.castShadow = true; 119 | scope.scene.add(mesh); 120 | finger.push(mesh); 121 | mesh = new THREE.Mesh(new THREE.CylinderGeometry(boneRadius, boneRadius, 40, 32), material.clone()); 122 | mesh.name = "hand-joint-" + j; 123 | mesh.material.color.copy(boneColor); 124 | mesh.renderOrder = ((i * 9) + (2 * j) + 1) / 36; 125 | mesh.castShadow = true; 126 | scope.scene.add(mesh); 127 | finger.push(mesh); 128 | } 129 | mesh = new THREE.Mesh(new THREE.SphereGeometry(jointRadius, 32, 32), material.clone()); 130 | mesh.material.color.copy(jointColor); 131 | mesh.castShadow = true; 132 | scope.scene.add(mesh); 133 | finger.push(mesh); 134 | this.fingerMeshes.push(finger); 135 | } 136 | if (scope.arm) { 137 | this.armMesh = new THREE.Object3D; 138 | this.armBones = []; 139 | this.armSpheres = []; 140 | for (i = _k = 0; _k <= 3; i = ++_k) { 141 | this.armBones.push(new THREE.Mesh(new THREE.CylinderGeometry(boneRadius, boneRadius, (i < 2 ? 1000 : 100), 32), material.clone())); 142 | this.armBones[i].material.color.copy(boneColor); 143 | this.armBones[i].castShadow = true; 144 | this.armBones[i].name = "ArmBone" + i; 145 | if (i > 1) { 146 | this.armBones[i].quaternion.multiply(armTopAndBottomRotation); 147 | } 148 | this.armMesh.add(this.armBones[i]); 149 | } 150 | this.armSpheres = []; 151 | for (i = _l = 0; _l <= 3; i = ++_l) { 152 | this.armSpheres.push(new THREE.Mesh(new THREE.SphereGeometry(jointRadius, 32, 32), material.clone())); 153 | this.armSpheres[i].material.color.copy(jointColor); 154 | this.armSpheres[i].castShadow = true; 155 | this.armSpheres[i].name = "ArmSphere" + i; 156 | this.armMesh.add(this.armSpheres[i]); 157 | } 158 | scope.scene.add(this.armMesh); 159 | } 160 | } 161 | 162 | HandMesh.prototype.traverse = function(callback) { 163 | var i, mesh, _i, _j, _len, _ref; 164 | for (i = _i = 0; _i < 5; i = ++_i) { 165 | _ref = this.fingerMeshes[i]; 166 | for (_j = 0, _len = _ref.length; _j < _len; _j++) { 167 | mesh = _ref[_j]; 168 | callback(mesh); 169 | } 170 | } 171 | return this.armMesh && this.armMesh.traverse(callback); 172 | }; 173 | 174 | HandMesh.prototype.scaleTo = function(hand) { 175 | var armLenScale, armWidthScale, baseScale, bone, boneXOffset, finger, fingerBoneLengthScale, halfArmLength, i, j, mesh, _i, _j; 176 | baseScale = hand.middleFinger.proximal.length / this.fingerMeshes[2][1].geometry.parameters.height; 177 | for (i = _i = 0; _i < 5; i = ++_i) { 178 | finger = hand.fingers[i]; 179 | j = 0; 180 | while (true) { 181 | if (j === this.fingerMeshes[i].length - 1) { 182 | mesh = this.fingerMeshes[i][j]; 183 | mesh.scale.set(baseScale, baseScale, baseScale); 184 | break; 185 | } 186 | bone = finger.bones[3 - (j / 2)]; 187 | mesh = this.fingerMeshes[i][j]; 188 | mesh.scale.set(baseScale, baseScale, baseScale); 189 | j++; 190 | mesh = this.fingerMeshes[i][j]; 191 | fingerBoneLengthScale = bone.length / mesh.geometry.parameters.height; 192 | mesh.scale.set(baseScale, fingerBoneLengthScale, baseScale); 193 | j++; 194 | } 195 | } 196 | if (scope.arm) { 197 | armLenScale = hand.arm.length / (this.armBones[0].geometry.parameters.height + this.armBones[0].geometry.parameters.radiusTop); 198 | armWidthScale = hand.arm.width / (this.armBones[2].geometry.parameters.height + this.armBones[2].geometry.parameters.radiusTop); 199 | for (i = _j = 0; _j <= 3; i = ++_j) { 200 | this.armBones[i].scale.set(baseScale, (i < 2 ? armLenScale : armWidthScale), baseScale); 201 | this.armSpheres[i].scale.set(baseScale, baseScale, baseScale); 202 | } 203 | boneXOffset = (hand.arm.width / 2) * 0.85; 204 | halfArmLength = hand.arm.length / 2; 205 | this.armBones[0].position.setX(boneXOffset); 206 | this.armBones[1].position.setX(-boneXOffset); 207 | this.armBones[2].position.setY(halfArmLength); 208 | this.armBones[3].position.setY(-halfArmLength); 209 | this.armSpheres[0].position.set(-boneXOffset, halfArmLength, 0); 210 | this.armSpheres[1].position.set(boneXOffset, halfArmLength, 0); 211 | this.armSpheres[2].position.set(boneXOffset, -halfArmLength, 0); 212 | this.armSpheres[3].position.set(-boneXOffset, -halfArmLength, 0); 213 | } 214 | return this; 215 | }; 216 | 217 | HandMesh.prototype.formTo = function(hand) { 218 | var bone, finger, i, j, mesh, _i; 219 | for (i = _i = 0; _i < 5; i = ++_i) { 220 | finger = hand.fingers[i]; 221 | j = 0; 222 | while (true) { 223 | if (j === this.fingerMeshes[i].length - 1) { 224 | mesh = this.fingerMeshes[i][j]; 225 | mesh.position.fromArray(bone.prevJoint); 226 | break; 227 | } 228 | bone = finger.bones[3 - (j / 2)]; 229 | mesh = this.fingerMeshes[i][j]; 230 | mesh.position.fromArray(bone.nextJoint); 231 | ++j; 232 | mesh = this.fingerMeshes[i][j]; 233 | mesh.position.fromArray(bone.center()); 234 | mesh.setRotationFromMatrix((new THREE.Matrix4).fromArray(bone.matrix())); 235 | mesh.quaternion.multiply(baseBoneRotation); 236 | ++j; 237 | } 238 | } 239 | if (this.armMesh) { 240 | this.armMesh.position.fromArray(hand.arm.center()); 241 | this.armMesh.setRotationFromMatrix((new THREE.Matrix4).fromArray(hand.arm.matrix())); 242 | this.armMesh.quaternion.multiply(baseBoneRotation); 243 | } 244 | return this; 245 | }; 246 | 247 | HandMesh.prototype.setVisibility = function(visible) { 248 | var i, j, _i, _j, _results; 249 | for (i = _i = 0; _i < 5; i = ++_i) { 250 | j = 0; 251 | while (true) { 252 | this.fingerMeshes[i][j].visible = visible; 253 | ++j; 254 | if (j === this.fingerMeshes[i].length) { 255 | break; 256 | } 257 | } 258 | } 259 | if (scope.arm) { 260 | _results = []; 261 | for (i = _j = 0; _j <= 3; i = ++_j) { 262 | this.armBones[i].visible = visible; 263 | _results.push(this.armSpheres[i].visible = visible); 264 | } 265 | return _results; 266 | } 267 | }; 268 | 269 | HandMesh.prototype.show = function() { 270 | return this.setVisibility(true); 271 | }; 272 | 273 | HandMesh.prototype.hide = function() { 274 | return this.setVisibility(false); 275 | }; 276 | 277 | return HandMesh; 278 | 279 | })(); 280 | 281 | onHand = function(hand) { 282 | var handMesh; 283 | if (!scope.scene) { 284 | return; 285 | } 286 | handMesh = hand.data('handMesh'); 287 | if (!handMesh) { 288 | handMesh = HandMesh.get().scaleTo(hand); 289 | hand.data('handMesh', handMesh); 290 | if (HandMesh.onMeshUsed) { 291 | HandMesh.onMeshUsed(handMesh); 292 | } 293 | } 294 | return handMesh.formTo(hand); 295 | }; 296 | 297 | boneHandLost = function(hand) { 298 | var handMesh; 299 | handMesh = hand.data('handMesh'); 300 | if (handMesh) { 301 | handMesh.replace(); 302 | } 303 | return handMesh = hand.data('handMesh', null); 304 | }; 305 | 306 | Leap.plugin('boneHand', function(options) { 307 | var controller, scale; 308 | if (options == null) { 309 | options = {}; 310 | } 311 | scope = options; 312 | controller = this; 313 | jointColor = (new THREE.Color).setHex(0x5daa00); 314 | boneColor = (new THREE.Color).setHex(0xffffff); 315 | scope.boneScale && (boneScale = scope.boneScale); 316 | scope.jointScale && (jointScale = scope.jointScale); 317 | scope.boneColor && (boneColor = scope.boneColor); 318 | scope.jointColor && (jointColor = scope.jointColor); 319 | scope.HandMesh = HandMesh; 320 | scope.addShadowCamera = function() { 321 | scope.light = new THREE.SpotLight(0xffffff, 1); 322 | scope.light.castShadow = true; 323 | scope.light.shadow.mapSize.width = 1024; 324 | scope.light.shadow.mapSize.height = 1024; 325 | scope.light.shadow.camera.near = 0.5 / 0.001; 326 | scope.light.shadow.camera.far = 3 / 0.001; 327 | scope.light.position.set(0, 1000, 1000); 328 | scope.light.target.position.set(0, 0, -1000); 329 | scope.camera.add(scope.light.target); 330 | scope.camera.add(scope.light); 331 | if (controller.plugins.transform) { 332 | if (controller.plugins.transform.getScale()) { 333 | scope.light.shadowCameraNear *= controller.plugins.transform.scale.x; 334 | scope.light.shadowCameraFar *= controller.plugins.transform.scale.x; 335 | scope.light.target.position.multiply(controller.plugins.transform.scale); 336 | scope.light.position.multiply(controller.plugins.transform.scale); 337 | } 338 | if (controller.plugins.transform.vr === true) { 339 | scope.camera.position.set(0, 0, 0); 340 | } 341 | if (controller.plugins.transform.vr === 'desktop') { 342 | return scope.camera.position.set(0, 0.15, 0.3); 343 | } 344 | } 345 | }; 346 | baseBoneRotation = (new THREE.Quaternion).setFromEuler(new THREE.Euler(Math.PI / 2, 0, 0)); 347 | boneScale = 1 / 6; 348 | jointScale = 1 / 5; 349 | boneRadius = null; 350 | jointRadius = null; 351 | material = null; 352 | armTopAndBottomRotation = (new THREE.Quaternion).setFromEuler(new THREE.Euler(0, 0, Math.PI / 2)); 353 | HandMesh.onMeshCreated = function(mesh) { 354 | return controller.emit('handMeshCreated', mesh); 355 | }; 356 | HandMesh.onMeshUsed = function(mesh) { 357 | return controller.emit('handMeshUsed', mesh); 358 | }; 359 | this.use('handEntry'); 360 | this.use('handHold'); 361 | if (scope.scene === void 0) { 362 | console.assert(scope.targetEl); 363 | if (this.plugins.transform && this.plugins.transform.getScale()) { 364 | scale = this.plugins.transform.scale.x; 365 | } 366 | initScene(scope.targetEl, scale); 367 | scope.addShadowCamera(); 368 | } 369 | if (scope.scene) { 370 | HandMesh.create(); 371 | HandMesh.create(); 372 | if (Leap.version.major === 0 && Leap.version.minor < 7 && Leap.version.dot < 4) { 373 | console.warn("BoneHand default scene render requires LeapJS > 0.6.3. You're running have " + Leap.version.full); 374 | } 375 | this.on('frameEnd', function(timestamp) { 376 | if (scope.render) { 377 | return scope.render(timestamp); 378 | } 379 | }); 380 | } 381 | this.on('handLost', boneHandLost); 382 | return { 383 | hand: onHand 384 | }; 385 | }); 386 | 387 | }).call(this); 388 | -------------------------------------------------------------------------------- /main/bone-hand/lib/OrbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | /*global THREE, console */ 9 | 10 | // This set of controls performs orbiting, dollying (zooming), and panning. It maintains 11 | // the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is 12 | // supported. 13 | // 14 | // Orbit - left mouse / touch: one finger move 15 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 16 | // Pan - right mouse, or arrow keys / touch: three finter swipe 17 | // 18 | // This is a drop-in replacement for (most) TrackballControls used in examples. 19 | // That is, include this js file and wherever you see: 20 | // controls = new THREE.TrackballControls( camera ); 21 | // controls.target.z = 150; 22 | // Simple substitute "OrbitControls" and the control should work as-is. 23 | 24 | THREE.OrbitControls = function ( object, domElement ) { 25 | 26 | this.object = object; 27 | this.domElement = ( domElement !== undefined ) ? domElement : document; 28 | 29 | // API 30 | 31 | // Set to false to disable this control 32 | this.enabled = true; 33 | 34 | // "target" sets the location of focus, where the control orbits around 35 | // and where it pans with respect to. 36 | this.target = new THREE.Vector3(); 37 | 38 | // center is old, deprecated; use "target" instead 39 | this.center = this.target; 40 | 41 | // This option actually enables dollying in and out; left as "zoom" for 42 | // backwards compatibility 43 | this.noZoom = false; 44 | this.zoomSpeed = 1.0; 45 | 46 | // Limits to how far you can dolly in and out 47 | this.minDistance = 0; 48 | this.maxDistance = Infinity; 49 | 50 | // Set to true to disable this control 51 | this.noRotate = false; 52 | this.rotateSpeed = 1.0; 53 | 54 | // Set to true to disable this control 55 | this.noPan = false; 56 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 57 | 58 | // Set to true to automatically rotate around the target 59 | this.autoRotate = false; 60 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 61 | 62 | // How far you can orbit vertically, upper and lower limits. 63 | // Range is 0 to Math.PI radians. 64 | this.minPolarAngle = 0; // radians 65 | this.maxPolarAngle = Math.PI; // radians 66 | 67 | // Set to true to disable use of the keys 68 | this.noKeys = false; 69 | 70 | // The four arrow keys 71 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 72 | 73 | //////////// 74 | // internals 75 | 76 | var scope = this; 77 | 78 | var EPS = 0.000001; 79 | 80 | var rotateStart = new THREE.Vector2(); 81 | var rotateEnd = new THREE.Vector2(); 82 | var rotateDelta = new THREE.Vector2(); 83 | 84 | var panStart = new THREE.Vector2(); 85 | var panEnd = new THREE.Vector2(); 86 | var panDelta = new THREE.Vector2(); 87 | var panOffset = new THREE.Vector3(); 88 | 89 | var offset = new THREE.Vector3(); 90 | 91 | var dollyStart = new THREE.Vector2(); 92 | var dollyEnd = new THREE.Vector2(); 93 | var dollyDelta = new THREE.Vector2(); 94 | 95 | var phiDelta = 0; 96 | var thetaDelta = 0; 97 | var scale = 1; 98 | var pan = new THREE.Vector3(); 99 | 100 | var lastPosition = new THREE.Vector3(); 101 | var lastQuaternion = new THREE.Quaternion(); 102 | 103 | var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 104 | 105 | var state = STATE.NONE; 106 | 107 | // for reset 108 | 109 | this.target0 = this.target.clone(); 110 | this.position0 = this.object.position.clone(); 111 | 112 | // so camera.up is the orbit axis 113 | 114 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 115 | var quatInverse = quat.clone().inverse(); 116 | 117 | // events 118 | 119 | var changeEvent = { type: 'change' }; 120 | var startEvent = { type: 'start'}; 121 | var endEvent = { type: 'end'}; 122 | 123 | this.rotateLeft = function ( angle ) { 124 | 125 | if ( angle === undefined ) { 126 | 127 | angle = getAutoRotationAngle(); 128 | 129 | } 130 | 131 | thetaDelta -= angle; 132 | 133 | }; 134 | 135 | this.rotateUp = function ( angle ) { 136 | 137 | if ( angle === undefined ) { 138 | 139 | angle = getAutoRotationAngle(); 140 | 141 | } 142 | 143 | phiDelta -= angle; 144 | 145 | }; 146 | 147 | // pass in distance in world space to move left 148 | this.panLeft = function ( distance ) { 149 | 150 | var te = this.object.matrix.elements; 151 | 152 | // get X column of matrix 153 | panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] ); 154 | panOffset.multiplyScalar( - distance ); 155 | 156 | pan.add( panOffset ); 157 | 158 | }; 159 | 160 | // pass in distance in world space to move up 161 | this.panUp = function ( distance ) { 162 | 163 | var te = this.object.matrix.elements; 164 | 165 | // get Y column of matrix 166 | panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] ); 167 | panOffset.multiplyScalar( distance ); 168 | 169 | pan.add( panOffset ); 170 | 171 | }; 172 | 173 | // pass in x,y of change desired in pixel space, 174 | // right and down are positive 175 | this.pan = function ( deltaX, deltaY ) { 176 | 177 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 178 | 179 | if ( scope.object.fov !== undefined ) { 180 | 181 | // perspective 182 | var position = scope.object.position; 183 | var offset = position.clone().sub( scope.target ); 184 | var targetDistance = offset.length(); 185 | 186 | // half of the fov is center to top of screen 187 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 188 | 189 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 190 | scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight ); 191 | scope.panUp( 2 * deltaY * targetDistance / element.clientHeight ); 192 | 193 | } else if ( scope.object.top !== undefined ) { 194 | 195 | // orthographic 196 | scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth ); 197 | scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight ); 198 | 199 | } else { 200 | 201 | // camera neither orthographic or perspective 202 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 203 | 204 | } 205 | 206 | }; 207 | 208 | this.dollyIn = function ( dollyScale ) { 209 | 210 | if ( dollyScale === undefined ) { 211 | 212 | dollyScale = getZoomScale(); 213 | 214 | } 215 | 216 | scale /= dollyScale; 217 | 218 | }; 219 | 220 | this.dollyOut = function ( dollyScale ) { 221 | 222 | if ( dollyScale === undefined ) { 223 | 224 | dollyScale = getZoomScale(); 225 | 226 | } 227 | 228 | scale *= dollyScale; 229 | 230 | }; 231 | 232 | this.update = function () { 233 | 234 | var position = this.object.position; 235 | 236 | offset.copy( position ).sub( this.target ); 237 | 238 | // rotate offset to "y-axis-is-up" space 239 | offset.applyQuaternion( quat ); 240 | 241 | // angle from z-axis around y-axis 242 | 243 | var theta = Math.atan2( offset.x, offset.z ); 244 | 245 | // angle from y-axis 246 | 247 | var phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); 248 | 249 | if ( this.autoRotate ) { 250 | 251 | this.rotateLeft( getAutoRotationAngle() ); 252 | 253 | } 254 | 255 | theta += thetaDelta; 256 | phi += phiDelta; 257 | 258 | // restrict phi to be between desired limits 259 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); 260 | 261 | // restrict phi to be betwee EPS and PI-EPS 262 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); 263 | 264 | var radius = offset.length() * scale; 265 | 266 | // restrict radius to be between desired limits 267 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); 268 | 269 | // move target to panned location 270 | this.target.add( pan ); 271 | 272 | offset.x = radius * Math.sin( phi ) * Math.sin( theta ); 273 | offset.y = radius * Math.cos( phi ); 274 | offset.z = radius * Math.sin( phi ) * Math.cos( theta ); 275 | 276 | // rotate offset back to "camera-up-vector-is-up" space 277 | offset.applyQuaternion( quatInverse ); 278 | 279 | position.copy( this.target ).add( offset ); 280 | 281 | this.object.lookAt( this.target ); 282 | 283 | thetaDelta = 0; 284 | phiDelta = 0; 285 | scale = 1; 286 | pan.set( 0, 0, 0 ); 287 | 288 | // update condition is: 289 | // min(camera displacement, camera rotation in radians)^2 > EPS 290 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 291 | 292 | if ( lastPosition.distanceToSquared( this.object.position ) > EPS 293 | || 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS ) { 294 | 295 | this.dispatchEvent( changeEvent ); 296 | 297 | lastPosition.copy( this.object.position ); 298 | lastQuaternion.copy (this.object.quaternion ); 299 | 300 | } 301 | 302 | }; 303 | 304 | 305 | this.reset = function () { 306 | 307 | state = STATE.NONE; 308 | 309 | this.target.copy( this.target0 ); 310 | this.object.position.copy( this.position0 ); 311 | 312 | this.update(); 313 | 314 | }; 315 | 316 | function getAutoRotationAngle() { 317 | 318 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 319 | 320 | } 321 | 322 | function getZoomScale() { 323 | 324 | return Math.pow( 0.95, scope.zoomSpeed ); 325 | 326 | } 327 | 328 | function onMouseDown( event ) { 329 | 330 | if ( scope.enabled === false ) return; 331 | event.preventDefault(); 332 | 333 | if ( event.button === 0 ) { 334 | if ( scope.noRotate === true ) return; 335 | 336 | state = STATE.ROTATE; 337 | 338 | rotateStart.set( event.clientX, event.clientY ); 339 | 340 | } else if ( event.button === 1 ) { 341 | if ( scope.noZoom === true ) return; 342 | 343 | state = STATE.DOLLY; 344 | 345 | dollyStart.set( event.clientX, event.clientY ); 346 | 347 | } else if ( event.button === 2 ) { 348 | if ( scope.noPan === true ) return; 349 | 350 | state = STATE.PAN; 351 | 352 | panStart.set( event.clientX, event.clientY ); 353 | 354 | } 355 | 356 | document.addEventListener( 'mousemove', onMouseMove, false ); 357 | document.addEventListener( 'mouseup', onMouseUp, false ); 358 | scope.dispatchEvent( startEvent ); 359 | 360 | } 361 | 362 | function onMouseMove( event ) { 363 | 364 | if ( scope.enabled === false ) return; 365 | 366 | event.preventDefault(); 367 | 368 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 369 | 370 | if ( state === STATE.ROTATE ) { 371 | 372 | if ( scope.noRotate === true ) return; 373 | 374 | rotateEnd.set( event.clientX, event.clientY ); 375 | rotateDelta.subVectors( rotateEnd, rotateStart ); 376 | 377 | // rotating across whole screen goes 360 degrees around 378 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 379 | 380 | // rotating up and down along whole screen attempts to go 360, but limited to 180 381 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 382 | 383 | rotateStart.copy( rotateEnd ); 384 | 385 | } else if ( state === STATE.DOLLY ) { 386 | 387 | if ( scope.noZoom === true ) return; 388 | 389 | dollyEnd.set( event.clientX, event.clientY ); 390 | dollyDelta.subVectors( dollyEnd, dollyStart ); 391 | 392 | if ( dollyDelta.y > 0 ) { 393 | 394 | scope.dollyIn(); 395 | 396 | } else { 397 | 398 | scope.dollyOut(); 399 | 400 | } 401 | 402 | dollyStart.copy( dollyEnd ); 403 | 404 | } else if ( state === STATE.PAN ) { 405 | 406 | if ( scope.noPan === true ) return; 407 | 408 | panEnd.set( event.clientX, event.clientY ); 409 | panDelta.subVectors( panEnd, panStart ); 410 | 411 | scope.pan( panDelta.x, panDelta.y ); 412 | 413 | panStart.copy( panEnd ); 414 | 415 | } 416 | 417 | scope.update(); 418 | 419 | } 420 | 421 | function onMouseUp( /* event */ ) { 422 | 423 | if ( scope.enabled === false ) return; 424 | 425 | document.removeEventListener( 'mousemove', onMouseMove, false ); 426 | document.removeEventListener( 'mouseup', onMouseUp, false ); 427 | scope.dispatchEvent( endEvent ); 428 | state = STATE.NONE; 429 | 430 | } 431 | 432 | function onMouseWheel( event ) { 433 | 434 | if ( scope.enabled === false || scope.noZoom === true ) return; 435 | 436 | event.preventDefault(); 437 | event.stopPropagation(); 438 | 439 | var delta = 0; 440 | 441 | if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9 442 | 443 | delta = event.wheelDelta; 444 | 445 | } else if ( event.detail !== undefined ) { // Firefox 446 | 447 | delta = - event.detail; 448 | 449 | } 450 | 451 | if ( delta > 0 ) { 452 | 453 | scope.dollyOut(); 454 | 455 | } else { 456 | 457 | scope.dollyIn(); 458 | 459 | } 460 | 461 | scope.update(); 462 | scope.dispatchEvent( startEvent ); 463 | scope.dispatchEvent( endEvent ); 464 | 465 | } 466 | 467 | function onKeyDown( event ) { 468 | 469 | if ( scope.enabled === false || scope.noKeys === true || scope.noPan === true ) return; 470 | 471 | switch ( event.keyCode ) { 472 | 473 | case scope.keys.UP: 474 | scope.pan( 0, scope.keyPanSpeed ); 475 | scope.update(); 476 | break; 477 | 478 | case scope.keys.BOTTOM: 479 | scope.pan( 0, - scope.keyPanSpeed ); 480 | scope.update(); 481 | break; 482 | 483 | case scope.keys.LEFT: 484 | scope.pan( scope.keyPanSpeed, 0 ); 485 | scope.update(); 486 | break; 487 | 488 | case scope.keys.RIGHT: 489 | scope.pan( - scope.keyPanSpeed, 0 ); 490 | scope.update(); 491 | break; 492 | 493 | } 494 | 495 | } 496 | 497 | function touchstart( event ) { 498 | 499 | if ( scope.enabled === false ) return; 500 | 501 | switch ( event.touches.length ) { 502 | 503 | case 1: // one-fingered touch: rotate 504 | 505 | if ( scope.noRotate === true ) return; 506 | 507 | state = STATE.TOUCH_ROTATE; 508 | 509 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 510 | break; 511 | 512 | case 2: // two-fingered touch: dolly 513 | 514 | if ( scope.noZoom === true ) return; 515 | 516 | state = STATE.TOUCH_DOLLY; 517 | 518 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 519 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 520 | var distance = Math.sqrt( dx * dx + dy * dy ); 521 | dollyStart.set( 0, distance ); 522 | break; 523 | 524 | case 3: // three-fingered touch: pan 525 | 526 | if ( scope.noPan === true ) return; 527 | 528 | state = STATE.TOUCH_PAN; 529 | 530 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 531 | break; 532 | 533 | default: 534 | 535 | state = STATE.NONE; 536 | 537 | } 538 | 539 | scope.dispatchEvent( startEvent ); 540 | 541 | } 542 | 543 | function touchmove( event ) { 544 | 545 | if ( scope.enabled === false ) return; 546 | 547 | event.preventDefault(); 548 | event.stopPropagation(); 549 | 550 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 551 | 552 | switch ( event.touches.length ) { 553 | 554 | case 1: // one-fingered touch: rotate 555 | 556 | if ( scope.noRotate === true ) return; 557 | if ( state !== STATE.TOUCH_ROTATE ) return; 558 | 559 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 560 | rotateDelta.subVectors( rotateEnd, rotateStart ); 561 | 562 | // rotating across whole screen goes 360 degrees around 563 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 564 | // rotating up and down along whole screen attempts to go 360, but limited to 180 565 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 566 | 567 | rotateStart.copy( rotateEnd ); 568 | 569 | scope.update(); 570 | break; 571 | 572 | case 2: // two-fingered touch: dolly 573 | 574 | if ( scope.noZoom === true ) return; 575 | if ( state !== STATE.TOUCH_DOLLY ) return; 576 | 577 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 578 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 579 | var distance = Math.sqrt( dx * dx + dy * dy ); 580 | 581 | dollyEnd.set( 0, distance ); 582 | dollyDelta.subVectors( dollyEnd, dollyStart ); 583 | 584 | if ( dollyDelta.y > 0 ) { 585 | 586 | scope.dollyOut(); 587 | 588 | } else { 589 | 590 | scope.dollyIn(); 591 | 592 | } 593 | 594 | dollyStart.copy( dollyEnd ); 595 | 596 | scope.update(); 597 | break; 598 | 599 | case 3: // three-fingered touch: pan 600 | 601 | if ( scope.noPan === true ) return; 602 | if ( state !== STATE.TOUCH_PAN ) return; 603 | 604 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 605 | panDelta.subVectors( panEnd, panStart ); 606 | 607 | scope.pan( panDelta.x, panDelta.y ); 608 | 609 | panStart.copy( panEnd ); 610 | 611 | scope.update(); 612 | break; 613 | 614 | default: 615 | 616 | state = STATE.NONE; 617 | 618 | } 619 | 620 | } 621 | 622 | function touchend( /* event */ ) { 623 | 624 | if ( scope.enabled === false ) return; 625 | 626 | scope.dispatchEvent( endEvent ); 627 | state = STATE.NONE; 628 | 629 | } 630 | 631 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 632 | this.domElement.addEventListener( 'mousedown', onMouseDown, false ); 633 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 634 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox 635 | 636 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 637 | this.domElement.addEventListener( 'touchend', touchend, false ); 638 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 639 | 640 | window.addEventListener( 'keydown', onKeyDown, false ); 641 | 642 | // force an update at start 643 | this.update(); 644 | 645 | }; 646 | 647 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 648 | -------------------------------------------------------------------------------- /main/leap-plugins-0.1.12.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * LeapJS-Plugins - v0.1.12 - 2016-11-16 3 | * http://github.com/leapmotion/leapjs-plugins/ 4 | * 5 | * Copyright 2016 LeapMotion, Inc 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | */ 20 | (function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o;o=null,b="undefined"!=typeof require?require("three"):window.THREE,i=function(a,c){var d,e,f,g,h,i;return o.scene=new b.Scene,o.rendererOps||(o.rendererOps={}),void 0===o.rendererOps.alpha&&(o.rendererOps.alpha=!0),o.renderer=h=new b.WebGLRenderer(o.rendererOps),i=o.width||window.innerWidth,f=o.height||window.innerHeight,h.setClearColor(0,0),h.setSize(i,f),h.domElement.className="leap-boneHand",h.shadowMap.enabled=!0,h.shadowMap.type=b.PCFSoftShadowMap,a.appendChild(h.domElement),g=1,e=1e4,c&&(g*=c,e*=c),o.camera=d=new b.PerspectiveCamera(45,i/f,g,e),d.position.set(0,300,500),d.lookAt(new b.Vector3(0,160,0)),o.scene.add(d),o.width||o.height||window.addEventListener("resize",function(){return i=window.innerWidth,f=window.innerHeight,d.aspect=i/f,d.updateProjectionMatrix(),h.setSize(i,f),h.render(o.scene,d)},!1),o.render||(o.render=function(a){return h.render(o.scene,o.camera)}),o.render()},d=null,j=null,e=null,h=null,l=null,g=null,k=null,m=null,c=null,a=function(){function a(){var a,d,f,i,n,p,q,r,s;for(m=isNaN(o.opacity)?new b.MeshPhongMaterial({fog:!1}):new b.MeshPhongMaterial({fog:!1,transparent:!0,opacity:o.opacity}),g=40*h,k=40*l,this.fingerMeshes=[],f=p=0;5>p;f=++p){for(d=[],a=0===f?3:4,i=q=0;a>=0?a>q:q>a;i=a>=0?++q:--q)n=new b.Mesh(new b.SphereGeometry(k,32,32),m.clone()),n.name="hand-bone-"+i,n.material.color.copy(j),n.renderOrder=(9*f+2*i)/36,n.castShadow=!0,o.scene.add(n),d.push(n),n=new b.Mesh(new b.CylinderGeometry(g,g,40,32),m.clone()),n.name="hand-joint-"+i,n.material.color.copy(e),n.renderOrder=(9*f+2*i+1)/36,n.castShadow=!0,o.scene.add(n),d.push(n);n=new b.Mesh(new b.SphereGeometry(k,32,32),m.clone()),n.material.color.copy(j),n.castShadow=!0,o.scene.add(n),d.push(n),this.fingerMeshes.push(d)}if(o.arm){for(this.armMesh=new b.Object3D,this.armBones=[],this.armSpheres=[],f=r=0;3>=r;f=++r)this.armBones.push(new b.Mesh(new b.CylinderGeometry(g,g,2>f?1e3:100,32),m.clone())),this.armBones[f].material.color.copy(e),this.armBones[f].castShadow=!0,this.armBones[f].name="ArmBone"+f,f>1&&this.armBones[f].quaternion.multiply(c),this.armMesh.add(this.armBones[f]);for(this.armSpheres=[],f=s=0;3>=s;f=++s)this.armSpheres.push(new b.Mesh(new b.SphereGeometry(k,32,32),m.clone())),this.armSpheres[f].material.color.copy(j),this.armSpheres[f].castShadow=!0,this.armSpheres[f].name="ArmSphere"+f,this.armMesh.add(this.armSpheres[f]);o.scene.add(this.armMesh)}}return a.unusedHandMeshes=[],a.get=function(){var b;return 0===a.unusedHandMeshes.length&&(b=a.create()),b=a.unusedHandMeshes.pop(),b.show(),b},a.prototype.replace=function(){return this.hide(),a.unusedHandMeshes.push(this)},a.create=function(){var b;return b=new a,b.setVisibility(!1),a.unusedHandMeshes.push(b),a.onMeshCreated&&a.onMeshCreated(b),b},a.prototype.traverse=function(a){var b,c,d,e,f,g;for(b=d=0;5>d;b=++d)for(g=this.fingerMeshes[b],e=0,f=g.length;f>e;e++)c=g[e],a(c);return this.armMesh&&this.armMesh.traverse(a)},a.prototype.scaleTo=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n;for(d=a.middleFinger.proximal.length/this.fingerMeshes[2][1].geometry.parameters.height,j=m=0;5>m;j=++m)for(g=a.fingers[j],k=0;;){if(k===this.fingerMeshes[j].length-1){l=this.fingerMeshes[j][k],l.scale.set(d,d,d);break}e=g.bones[3-k/2],l=this.fingerMeshes[j][k],l.scale.set(d,d,d),k++,l=this.fingerMeshes[j][k],h=e.length/l.geometry.parameters.height,l.scale.set(d,h,d),k++}if(o.arm){for(b=a.arm.length/(this.armBones[0].geometry.parameters.height+this.armBones[0].geometry.parameters.radiusTop),c=a.arm.width/(this.armBones[2].geometry.parameters.height+this.armBones[2].geometry.parameters.radiusTop),j=n=0;3>=n;j=++n)this.armBones[j].scale.set(d,2>j?b:c,d),this.armSpheres[j].scale.set(d,d,d);f=a.arm.width/2*.85,i=a.arm.length/2,this.armBones[0].position.setX(f),this.armBones[1].position.setX(-f),this.armBones[2].position.setY(i),this.armBones[3].position.setY(-i),this.armSpheres[0].position.set(-f,i,0),this.armSpheres[1].position.set(f,i,0),this.armSpheres[2].position.set(f,-i,0),this.armSpheres[3].position.set(-f,-i,0)}return this},a.prototype.formTo=function(a){var c,e,f,g,h,i;for(f=i=0;5>i;f=++i)for(e=a.fingers[f],g=0;;){if(g===this.fingerMeshes[f].length-1){h=this.fingerMeshes[f][g],h.position.fromArray(c.prevJoint);break}c=e.bones[3-g/2],h=this.fingerMeshes[f][g],h.position.fromArray(c.nextJoint),++g,h=this.fingerMeshes[f][g],h.position.fromArray(c.center()),h.setRotationFromMatrix((new b.Matrix4).fromArray(c.matrix())),h.quaternion.multiply(d),++g}return this.armMesh&&(this.armMesh.position.fromArray(a.arm.center()),this.armMesh.setRotationFromMatrix((new b.Matrix4).fromArray(a.arm.matrix())),this.armMesh.quaternion.multiply(d)),this},a.prototype.setVisibility=function(a){var b,c,d,e,f;for(b=d=0;5>d;b=++d)for(c=0;;)if(this.fingerMeshes[b][c].visible=a,++c,c===this.fingerMeshes[b].length)break;if(o.arm){for(f=[],b=e=0;3>=e;b=++e)this.armBones[b].visible=a,f.push(this.armSpheres[b].visible=a);return f}},a.prototype.show=function(){return this.setVisibility(!0)},a.prototype.hide=function(){return this.setVisibility(!1)},a}(),n=function(b){var c;if(o.scene)return c=b.data("handMesh"),c||(c=a.get().scaleTo(b),b.data("handMesh",c),a.onMeshUsed&&a.onMeshUsed(c)),c.formTo(b)},f=function(a){var b;return b=a.data("handMesh"),b&&b.replace(),b=a.data("handMesh",null)},Leap.plugin("boneHand",function(p){var q,r;return null==p&&(p={}),o=p,q=this,j=(new b.Color).setHex(6138368),e=(new b.Color).setHex(16777215),o.boneScale&&(h=o.boneScale),o.jointScale&&(l=o.jointScale),o.boneColor&&(e=o.boneColor),o.jointColor&&(j=o.jointColor),o.HandMesh=a,o.addShadowCamera=function(){return o.light=new b.SpotLight(16777215,1),o.light.castShadow=!0,o.light.shadow.mapSize.width=1024,o.light.shadow.mapSize.height=1024,o.light.shadow.camera.near=500,o.light.shadow.camera.far=3e3,o.light.position.set(0,1e3,1e3),o.light.target.position.set(0,0,-1e3),o.camera.add(o.light.target),o.camera.add(o.light),q.plugins.transform&&(q.plugins.transform.getScale()&&(o.light.shadowCameraNear*=q.plugins.transform.scale.x,o.light.shadowCameraFar*=q.plugins.transform.scale.x,o.light.target.position.multiply(q.plugins.transform.scale),o.light.position.multiply(q.plugins.transform.scale)),q.plugins.transform.vr===!0&&o.camera.position.set(0,0,0),"desktop"===q.plugins.transform.vr)?o.camera.position.set(0,.15,.3):void 0},d=(new b.Quaternion).setFromEuler(new b.Euler(Math.PI/2,0,0)),h=1/6,l=.2,g=null,k=null,m=null,c=(new b.Quaternion).setFromEuler(new b.Euler(0,0,Math.PI/2)),a.onMeshCreated=function(a){return q.emit("handMeshCreated",a)},a.onMeshUsed=function(a){return q.emit("handMeshUsed",a)},this.use("handEntry"),this.use("handHold"),void 0===o.scene&&(console.assert(o.targetEl),this.plugins.transform&&this.plugins.transform.getScale()&&(r=this.plugins.transform.scale.x),i(o.targetEl,r),o.addShadowCamera()),o.scene&&(a.create(),a.create(),0===Leap.version.major&&Leap.version.minor<7&&Leap.version.dot<4&&console.warn("BoneHand default scene render requires LeapJS > 0.6.3. You're running have "+Leap.version.full),this.on("frameEnd",function(a){return o.render?o.render(a):void 0})),this.on("handLost",f),{hand:n}})}).call(this),function(){var a;if(a=function(){var a;return a=[],0===Leap.version.major&&Leap.version.minor<5&&console.warn("The hand entry plugin requires LeapJS 0.5.0 or newer."),this.on("deviceStopped",function(){for(var b=0,c=a.length;c>b;b++)id=a[b],a.splice(b,1),this.emit("handLost",this.lastConnectionFrame.hand(id)),b--,c--}),{frame:function(b){var c,d,e,f,g;d=b.hands.map(function(a){return a.id});for(var h=0,i=a.length;i>h;h++)c=a[h],-1==d.indexOf(c)&&(a.splice(h,1),this.emit("handLost",this.frame(1).hand(c)),h--,i--);for(g=[],e=0,f=d.length;f>e;e++)c=d[e],-1===a.indexOf(c)?(a.push(c),g.push(this.emit("handFound",b.hand(c)))):g.push(void 0);return g}}},"undefined"!=typeof Leap&&Leap.Controller)Leap.Controller.plugin("handEntry",a);else{if("undefined"==typeof module)throw"leap.js not included";module.exports.handEntry=a}}.call(this),function(){var a;if(a=function(){var a,b;return b={},a=function(a,c,d){var e,f,g,h;if(b[g=a+this.id]||(b[g]=[]),e=b[a+this.id],void 0!==d)return e[c]=d;if("[object String]"==={}.toString.call(c))return e[c];h=[];for(f in c)d=c[f],void 0===d?h.push(delete e[f]):h.push(e[f]=d);return h},{hand:{data:function(b,c){return a.call(this,"h",b,c)},hold:function(a){return a?this.data({holding:a}):this.hold(this.hovering())},holding:function(){return this.data("holding")},release:function(){var a;return a=this.data("holding"),this.data({holding:void 0}),a},hoverFn:function(a){return this.data({getHover:a})},hovering:function(){var a;return(a=this.data("getHover"))?this._hovering||(this._hovering=a.call(this)):void 0}},pointable:{data:function(b,c){return a.call(this,"p",b,c)}}}},"undefined"!=typeof Leap&&Leap.Controller)Leap.Controller.plugin("handHold",a);else{if("undefined"==typeof module)throw"leap.js not included";module.exports.handHold=a}}.call(this),function(a,b){"use strict";function c(a){this.options=a||(a={}),this.loading=!1,this.timeBetweenLoops=a.timeBetweenLoops||50,this.packingStructure=["id","timestamp",{hands:[["id","type","direction","palmNormal","palmPosition","palmVelocity","stabilizedPalmPosition","pinchStrength","grabStrength","confidence","armBasis","armWidth","elbow","wrist"]]},{pointables:[["id","direction","handId","length","stabilizedTipPosition","tipPosition","tipVelocity","tool","carpPosition","mcpPosition","pipPosition","dipPosition","btipPosition","bases","type"]]},{interactionBox:["center","size"]}],this.setFrames(a.frames||[])}var d={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",_f:String.fromCharCode,compressToBase64:function(a){if(null==a)return"";var b,c,e,f,g,h,i,j="",k=0;for(a=d.compress(a);k<2*a.length;)k%2==0?(b=a.charCodeAt(k/2)>>8,c=255&a.charCodeAt(k/2),e=k/2+1>8:NaN):(b=255&a.charCodeAt((k-1)/2),(k+1)/2>8,e=255&a.charCodeAt((k+1)/2)):c=e=NaN),k+=3,f=b>>2,g=(3&b)<<4|c>>4,h=(15&c)<<2|e>>6,i=63&e,isNaN(c)?h=i=64:isNaN(e)&&(i=64),j=j+d._keyStr.charAt(f)+d._keyStr.charAt(g)+d._keyStr.charAt(h)+d._keyStr.charAt(i);return j},decompressFromBase64:function(a){if(null==a)return"";var b,c,e,f,g,h,i,j,k="",l=0,m=0,n=d._f;for(a=a.replace(/[^A-Za-z0-9\+\/\=]/g,"");m>4,e=(15&h)<<4|i>>2,f=(3&i)<<6|j,l%2==0?(b=c<<8,64!=i&&(k+=n(b|e)),64!=j&&(b=f<<8)):(k+=n(b|c),64!=i&&(b=e<<8),64!=j&&(k+=n(b|f))),l+=3;return d.decompress(k)},compressToUTF16:function(a){if(null==a)return"";var b,c,e,f="",g=0,h=d._f;for(a=d.compress(a),b=0;b>1)+32),e=(1&c)<<14;break;case 1:f+=h(e+(c>>2)+32),e=(3&c)<<13;break;case 2:f+=h(e+(c>>3)+32),e=(7&c)<<12;break;case 3:f+=h(e+(c>>4)+32),e=(15&c)<<11;break;case 4:f+=h(e+(c>>5)+32),e=(31&c)<<10;break;case 5:f+=h(e+(c>>6)+32),e=(63&c)<<9;break;case 6:f+=h(e+(c>>7)+32),e=(127&c)<<8;break;case 7:f+=h(e+(c>>8)+32),e=(255&c)<<7;break;case 8:f+=h(e+(c>>9)+32),e=(511&c)<<6;break;case 9:f+=h(e+(c>>10)+32),e=(1023&c)<<5;break;case 10:f+=h(e+(c>>11)+32),e=(2047&c)<<4;break;case 11:f+=h(e+(c>>12)+32),e=(4095&c)<<3;break;case 12:f+=h(e+(c>>13)+32),e=(8191&c)<<2;break;case 13:f+=h(e+(c>>14)+32),e=(16383&c)<<1;break;case 14:f+=h(e+(c>>15)+32,(32767&c)+32),g=0}return f+h(e+32)},decompressFromUTF16:function(a){if(null==a)return"";for(var b,c,e="",f=0,g=0,h=d._f;g>14),b=(16383&c)<<2;break;case 2:e+=h(b|c>>13),b=(8191&c)<<3;break;case 3:e+=h(b|c>>12),b=(4095&c)<<4;break;case 4:e+=h(b|c>>11),b=(2047&c)<<5;break;case 5:e+=h(b|c>>10),b=(1023&c)<<6;break;case 6:e+=h(b|c>>9),b=(511&c)<<7;break;case 7:e+=h(b|c>>8),b=(255&c)<<8;break;case 8:e+=h(b|c>>7),b=(127&c)<<9;break;case 9:e+=h(b|c>>6),b=(63&c)<<10;break;case 10:e+=h(b|c>>5),b=(31&c)<<11;break;case 11:e+=h(b|c>>4),b=(15&c)<<12;break;case 12:e+=h(b|c>>3),b=(7&c)<<13;break;case 13:e+=h(b|c>>2),b=(3&c)<<14;break;case 14:e+=h(b|c>>1),b=(1&c)<<15;break;case 15:e+=h(b|c),f=0}g++}return d.decompress(e)},compress:function(a){if(null==a)return"";var b,c,e,f={},g={},h="",i="",j="",k=2,l=3,m=2,n="",o=0,p=0,q=d._f;for(e=0;eb;b++)o<<=1,15==p?(p=0,n+=q(o),o=0):p++;for(c=j.charCodeAt(0),b=0;8>b;b++)o=o<<1|1&c,15==p?(p=0,n+=q(o),o=0):p++,c>>=1}else{for(c=1,b=0;m>b;b++)o=o<<1|c,15==p?(p=0,n+=q(o),o=0):p++,c=0;for(c=j.charCodeAt(0),b=0;16>b;b++)o=o<<1|1&c,15==p?(p=0,n+=q(o),o=0):p++,c>>=1}k--,0==k&&(k=Math.pow(2,m),m++),delete g[j]}else for(c=f[j],b=0;m>b;b++)o=o<<1|1&c,15==p?(p=0,n+=q(o),o=0):p++,c>>=1;k--,0==k&&(k=Math.pow(2,m),m++),f[i]=l++,j=String(h)}if(""!==j){if(Object.prototype.hasOwnProperty.call(g,j)){if(j.charCodeAt(0)<256){for(b=0;m>b;b++)o<<=1,15==p?(p=0,n+=q(o),o=0):p++;for(c=j.charCodeAt(0),b=0;8>b;b++)o=o<<1|1&c,15==p?(p=0,n+=q(o),o=0):p++,c>>=1}else{for(c=1,b=0;m>b;b++)o=o<<1|c,15==p?(p=0,n+=q(o),o=0):p++,c=0;for(c=j.charCodeAt(0),b=0;16>b;b++)o=o<<1|1&c,15==p?(p=0,n+=q(o),o=0):p++,c>>=1}k--,0==k&&(k=Math.pow(2,m),m++),delete g[j]}else for(c=f[j],b=0;m>b;b++)o=o<<1|1&c,15==p?(p=0,n+=q(o),o=0):p++,c>>=1;k--,0==k&&(k=Math.pow(2,m),m++)}for(c=2,b=0;m>b;b++)o=o<<1|1&c,15==p?(p=0,n+=q(o),o=0):p++,c>>=1;for(;;){if(o<<=1,15==p){n+=q(o);break}p++}return n},decompress:function(a){if(null==a)return"";if(""==a)return null;var b,c,e,f,g,h,i,j,k=[],l=4,m=4,n=3,o="",p="",q=d._f,r={string:a,val:a.charCodeAt(0),position:32768,index:1};for(c=0;3>c;c+=1)k[c]=c;for(f=0,h=Math.pow(2,2),i=1;i!=h;)g=r.val&r.position,r.position>>=1,0==r.position&&(r.position=32768,r.val=r.string.charCodeAt(r.index++)),f|=(g>0?1:0)*i,i<<=1;switch(b=f){case 0:for(f=0,h=Math.pow(2,8),i=1;i!=h;)g=r.val&r.position,r.position>>=1,0==r.position&&(r.position=32768,r.val=r.string.charCodeAt(r.index++)),f|=(g>0?1:0)*i,i<<=1;j=q(f);break;case 1:for(f=0,h=Math.pow(2,16),i=1;i!=h;)g=r.val&r.position,r.position>>=1,0==r.position&&(r.position=32768,r.val=r.string.charCodeAt(r.index++)),f|=(g>0?1:0)*i,i<<=1;j=q(f);break;case 2:return""}for(k[3]=j,e=p=j;;){if(r.index>r.string.length)return"";for(f=0,h=Math.pow(2,n),i=1;i!=h;)g=r.val&r.position,r.position>>=1,0==r.position&&(r.position=32768,r.val=r.string.charCodeAt(r.index++)),f|=(g>0?1:0)*i,i<<=1;switch(j=f){case 0:for(f=0,h=Math.pow(2,8),i=1;i!=h;)g=r.val&r.position,r.position>>=1,0==r.position&&(r.position=32768,r.val=r.string.charCodeAt(r.index++)),f|=(g>0?1:0)*i,i<<=1;k[m++]=q(f),j=m-1,l--;break;case 1:for(f=0,h=Math.pow(2,16),i=1;i!=h;)g=r.val&r.position,r.position>>=1,0==r.position&&(r.position=32768,r.val=r.string.charCodeAt(r.index++)),f|=(g>0?1:0)*i,i<<=1;k[m++]=q(f),j=m-1,l--;break;case 2:return p}if(0==l&&(l=Math.pow(2,n),n++),k[j])o=k[j];else{if(j!==m)return null;o=e+e.charAt(0)}p+=o,k[m++]=e+o.charAt(0),l--,e=o,0==l&&(l=Math.pow(2,n),n++)}}};"undefined"!=typeof module&&null!=module&&(module.exports=d),function(){if("undefined"==typeof a.performance&&(a.performance={}),!a.performance.now){var b=Date.now();performance.timing&&performance.timing.navigationStart&&(b=performance.timing.navigationStart),a.performance.now=function(){return Date.now()-b}}}(),c.prototype={setFrames:function(a){this.frameData=a,this.frameIndex=0,this.frameCount=a.length,this.leftCropPosition=0,this.rightCropPosition=this.frameCount,this.setMetaData()},addFrame:function(a){this.frameData.push(a)},currentFrame:function(){return this.frameData[this.frameIndex]},nextFrame:function(){var a=this.frameIndex+1;return a%=this.rightCropPosition||1,a=this.rightCropPosition&&!this.options.loop?(this.frameIndex--,!1):(this.frameIndex=this.frameIndex%(this.rightCropPosition||1),this.frameIndex=this.rightCropPosition?(this.frameIndex=this.frameIndex%(this.rightCropPosition||1),this.frameIndexn;n++){c=i.hands[n];for(var o=0;l>o;o++)b=g[o],e.hands[n][b]&&f.hands[n]&&Leap.vec3.lerp(c[b],e.hands[n][b],f.hands[n][b],a)}for(n=0;k>n;n++)for(d=i.pointables[n],o=0;m>o;o++)b=h[o],e.pointables[n][b]&&f.hands[n]&&Leap.vec3.lerp(d[b],e.pointables[n][b],f.pointables[n][b],0);return i},timeToNextFrame:function(){var a=(this.nextFrame().timestamp-this.currentFrame().timestamp)/1e3;return 0>a&&(a=this.timeBetweenLoops),a},blank:function(){return 0===this.frameData.length},leftCrop:function(){this.leftCropPosition=this.frameIndex},rightCrop:function(){this.rightCropPosition=this.frameIndex},cullFrames:function(a){console.log("cull frames",a),a||(a=1);for(var b=0;bd;d++)a=b[d],c.push(this.packArray(this.packingStructure,a));return c},packArray:function(a,b){for(var c,d=[],e=0,f=a.length;f>e;e++)if(c=a[e],"string"==typeof c)d.push(b[c]);else if("[object Array]"==Object.prototype.toString.call(c))for(var g=0,h=b.length;h>g;g++)d.push(this.packArray(c,b[g]));else{for(var i in c)break;d.push(this.packArray(c[i],b[i]))}return d},unPackFrameData:function(a){for(var b,c=a[0],d=[],e=1,f=a.length;f>e;e++)b=a[e],d.push(this.unPackArray(c,b));return d},unPackArray:function(a,b){for(var c,d={},e=0,f=a.length;f>e;e++)if(c=a[e],"string"==typeof c)d[c]=b[e];else{if("[object Array]"==Object.prototype.toString.call(c)){for(var g=[],h=0,i=b.length;i>h;h++)g.push(this.unPackArray(c,b[h]));return g}for(var j in c)break;d[j]=this.unPackArray(c[j],b[e])}return d},toHash:function(){return this.setMetaData(),{metadata:this.metadata,frames:this.packedFrameData()}},"export":function(a){var b=JSON.stringify(this.toHash());return"json"==a?b:d.compressToBase64(b)},save:function(a){var b;b=this.metadata.title?this.metadata.title.replace(/\s/g,""):"leap-playback-recording",this.metadata.frameRate&&(b+="-"+Math.round(this.metadata.frameRate)+"fps"),"json"===a?saveAs(new Blob([this["export"]("json")],{type:"text/JSON;charset=utf-8"}),b+".json"):saveAs(new Blob([this["export"]("lz")],{type:"application/x-gzip;charset=utf-8"}),b+".json.lz")},decompress:function(a){return d.decompressFromBase64(a)},loaded:function(){return!(!this.frameData||!this.frameData.length)},loadFrameData:function(a){var b=new XMLHttpRequest,c=this.url,d=this;b.onreadystatechange=function(){b.readyState===b.DONE&&(200===b.status||0===b.status?b.responseText?d.finishLoad(b.responseText,a):console.error('Leap Playback: "'+c+'" seems to be unreachable or the file is empty.'):console.error("Leap Playback: Couldn't load \""+c+'" ('+b.status+")"))},b.addEventListener("progress",function(a){if(d.options.loadProgress&&a.lengthComputable){var b=a.loaded/a.total;d.options.loadProgress(d,b,a)}}),this.loading=!0,b.open("GET",c,!0),b.send(null)},finishLoad:function(a,b){var c=this.url;"lz"==c.split(".")[c.split(".").length-1]&&(a=this.decompress(a)),a=JSON.parse(a),2==a.metadata.formatVersion&&(a.frames=this.unPackFrameData(a.frames)),this.metadata=a.metadata,console.log("Recording loaded:",this.metadata),this.loading=!1,b&&b.call(this,a.frames)},loadCompressedRecording:function(a,b){var c=this.decompress(a);c=JSON.parse(c),2==c.metadata.formatVersion&&(c.frames=this.unPackFrameData(c.frames)),this.metadata=c.metadata,console.log("Recording loaded:",this.metadata),this.loading=!1,b&&b.call(this,c.frames)}},function(){function d(a,b){var c=this;b||(b={}),this.options=b,this.recording=b.recording,this.controller=a,this.resetTimers(),this.setupLoops(),this.controller.connection.on("ready",function(){c.setupProtocols()}),this.userHasControl=!1,b.recording&&("[object String]"==Object.prototype.toString.call(b.recording)&&(b.recording={url:b.recording}),this.setRecording(b.recording)),document.addEventListener("DOMContentLoaded",function(a){document.body.addEventListener("keydown",function(a){a.which===c.options.pauseHotkey&&c.toggle()},!1)})}var e='',f=''; 21 | d.prototype={resetTimers:function(){this.timeSinceLastFrame=0,this.lastFrameTime=null},setupLoops:function(){var a=this;this.stepFrameLoop=function(b){"playing"==a.state&&(a.sendFrameAt(b||performance.now()),requestAnimationFrame(a.stepFrameLoop))}},setupProtocols:function(){var a=this;this.stopProtocol=this.controller.connection.protocol,this.playbackProtocol=function(b){var c=a.stopProtocol(b);return c instanceof Leap.Frame?(a.pauseOnHand&&(b.hands.length>0?(a.userHasControl=!0,a.controller.emit("playback.userTakeControl"),a.setGraphic(),a.idle()):0==b.hands.length&&a.userHasControl&&a.resumeOnHandLost&&(a.userHasControl=!1,a.controller.emit("playback.userReleaseControl"),a.setGraphic("wave"))),{type:"playback"}):c},this.recordProtocol=function(b){var c=a.stopProtocol(b);return c instanceof Leap.Frame&&a.recordFrameHandler(b),c};for(var b in this.stopProtocol)this.stopProtocol.hasOwnProperty(b)&&(this.playbackProtocol[b]=this.stopProtocol[b],this.recordProtocol[b]=this.stopProtocol[b]);"playing"==this.state&&(this.controller.connection.protocol=this.playbackProtocol)},sendFrameAt:function(a){this.lastFrameTime&&(a(b=this.recording.timeToNextFrame());)if(this.timeSinceLastFrame-=b,!this.recording.advanceFrame())return this.pause(),void this.controller.emit("playback.playbackFinished",this);this.sendFrame(this.recording.createLerpFrameData(this.timeSinceLastFrame/b))},sendFrame:function(a){if(!a)throw"Frame data not provided";var b=new Leap.Frame(a);return this.controller.processFrame(b),!0},sendImmediateFrame:function(a){if(!a)throw"Frame data not provided";var b=new Leap.Frame(a);return this.controller.processFinishedFrame(b),!0},setFrameIndex:function(a){a!=this.recording.frameIndex&&(this.recording.frameIndex=a%this.recording.frameCount,this.sendFrame(this.recording.currentFrame()))},stop:function(){this.idle(),delete this.recording,this.recording=new c({timeBetweenLoops:this.options.timeBetweenLoops,loop:this.options.loop,requestProtocolVersion:this.controller.connection.opts.requestProtocolVersion,serviceVersion:this.controller.connection.protocol.serviceVersion}),this.controller.emit("playback.stop",this)},pause:function(){this.state="idle",this.hideOverlay(),this.controller.emit("playback.pause",this)},idle:function(){this.state="idle",this.controller.connection.protocol=this.stopProtocol},toggle:function(){"idle"==this.state?this.play():"playing"==this.state&&this.pause()},record:function(){this.clear(),this.stop(),this.state="recording",this.controller.connection.protocol=this.recordProtocol,this.setGraphic("connect"),this.controller.emit("playback.record",this)},clear:function(){if(this.recording&&!this.recording.blank()){var a=this.recording.cloneCurrentFrame();a.hands=[],a.fingers=[],a.pointables=[],a.tools=[],this.sendImmediateFrame(a)}},recordPending:function(){return"recording"==this.state&&this.recording.blank()},isRecording:function(){return"recording"==this.state&&!this.recording.blank()},finishRecording:function(){this.controller.connection.protocol=this.playbackProtocol,this.recording.setFrames(this.recording.frameData),this.controller.emit("playback.recordingFinished",this)},loaded:function(){return this.recording.loaded()},loading:function(){return this.recording.loading},play:function(){if("playing"!==this.state&&!this.loading()&&!this.recording.blank()){this.state="playing",this.controller.connection.protocol=this.playbackProtocol;var a=this;this.controller.connection.removeAllListeners("frame"),this.controller.connection.on("frame",function(b){a.resumeOnHandLost&&a.autoPlay&&"idle"==a.state&&0==b.hands.length&&a.play(),a.controller.processFrame(b)}),this.resetTimers(),this.recording.readyPlay(),this.stepFrameLoop(),this.controller.emit("playback.play",this)}},recordFrameHandler:function(a){this.setGraphic("wave"),a.hands.length>0?(this.recording.addFrame(a),this.hideOverlay()):this.recording.blank()||this.finishRecording()},setRecording:function(a){var b=this;this.pause();var d=function(a){return this.setFrames(a),b.recording!=this?void console.log("recordings changed during load"):(b.autoPlay&&(b.play(),b.pauseOnHand&&!b.controller.streaming()&&b.setGraphic("connect")),void b.controller.emit("playback.recordingSet",this))};return this.recording=a,a instanceof c||(this.recording.__proto__=c.prototype,c.call(this.recording,{timeBetweenLoops:this.options.timeBetweenLoops,loop:this.options.loop,loadProgress:function(a,c,d){b.controller.emit("playback.ajax:progress",a,c,d)}})),this.recording.loaded()?d.call(this.recording,this.recording.frameData):a.url?(this.controller.emit("playback.ajax:begin",this,this.recording),this.recording.loadFrameData(function(a){d.call(this,a),b.controller.emit("playback.ajax:complete",b,this)})):a.compressedRecording&&this.recording.loadCompressedRecording(a.compressedRecording,function(a){d.call(this,a)}),this},hideOverlay:function(){this.overlay&&(this.overlay.style.display="none")},setGraphic:function(a){if(this.overlay&&this.graphicName!=a)switch(this.graphicName=a,a){case"connect":this.overlay.style.display="block",this.overlay.innerHTML=e;break;case"wave":this.overlay.style.display="block",this.overlay.innerHTML=f;break;case b:this.overlay.innerHTML=""}}};var g=function(c){var e=this,f=c.autoPlay;f===b&&(f=!0);var g=c.pauseOnHand;g===b&&(g=!0);var h=c.resumeOnHandLost;h===b&&(h=!0);var i=c.timeBetweenLoops;i===b&&(i=50);var j=c.requiredProtocolVersion,k=c.pauseHotkey;k===b&&(k=32);var l=c.loop;l===b&&(l=!0);var m=c.overlay;m===b&&document.body&&(m=document.createElement("div"),document.body.appendChild(m),m.style.width="100%",m.style.position="absolute",m.style.top="0",m.style.left="-"+a.getComputedStyle(document.body).getPropertyValue("margin"),m.style.padding="10px",m.style.textAlign="center",m.style.fontSize="18px",m.style.opacity="0.8",m.style.display="none",m.style.zIndex="10",m.id="connect-leap",m.style.cursor="pointer",m.addEventListener("click",function(){return this.style.display="none",!1},!1)),c.player=new d(this,{recording:c.recording,loop:l,pauseHotkey:k,timeBetweenLoops:i}),c.player.overlay=m,c.player.pauseOnHand=g,c.player.resumeOnHandLost=h,c.player.requiredProtocolVersion=j,c.player.autoPlay=f;var n=function(){return c.player.pauseOnHand&&e.connection.opts.requestProtocolVersiond;d++)c=b[d],c?f.push(Leap.vec3.transformMat4(c,c,a)):f.push(void 0);return f},d=function(a,b,c){var d,e,f;return d=b[0],e=b[1],f=b[2],a[0]=c[0]*d+c[4]*e+c[8]*f,a[1]=c[1]*d+c[5]*e+c[9]*f,a[2]=c[2]*d+c[6]*e+c[10]*f,a},c=function(a,b){var c,e,f,g;for(g=[],e=0,f=b.length;f>e;e++)c=b[e],c?g.push(d(c,c,a)):g.push(void 0);return g},f=function(a,b,d){var f,g,h,i,j,k,l,m;for(c(b,[a.direction,a.palmNormal,a.palmVelocity,a.arm.basis[0],a.arm.basis[1],a.arm.basis[2]]),l=a.fingers,h=0,j=l.length;j>h;h++)f=l[h],c(b,[f.direction,f.metacarpal.basis[0],f.metacarpal.basis[1],f.metacarpal.basis[2],f.proximal.basis[0],f.proximal.basis[1],f.proximal.basis[2],f.medial.basis[0],f.medial.basis[1],f.medial.basis[2],f.distal.basis[0],f.distal.basis[1],f.distal.basis[2]]);for(Leap.glMatrix.mat4.scale(b,b,d),e(b,[a.palmPosition,a.stabilizedPalmPosition,a.sphereCenter,a.arm.nextJoint,a.arm.prevJoint]),m=a.fingers,i=0,k=m.length;k>i;i++)f=m[i],e(b,[f.carpPosition,f.mcpPosition,f.pipPosition,f.dipPosition,f.distal.nextJoint,f.tipPosition]);return g=(d[0]+d[1]+d[2])/3,a.arm.width*=g},{frame:function(b){var c,d,e,g,h,i,j,k,l,m;if(b.valid&&!b.data.transformed){for(b.data.transformed=!0,k=b.hands,m=[],g=0,i=k.length;i>g;g++){for(d=k[g],f(d,a.getTransform(d),(a.getScale(d)||new THREE.Vector3(1,1,1)).toArray()),a.effectiveParent&&f(d,a.effectiveParent.matrixWorld.elements,a.effectiveParent.scale.toArray()),e=null,l=d.fingers,h=0,j=l.length;j>h;h++)c=l[h],e=Leap.vec3.create(),Leap.vec3.sub(e,c.mcpPosition,c.carpPosition),c.metacarpal.length=Leap.vec3.length(e),Leap.vec3.sub(e,c.pipPosition,c.mcpPosition),c.proximal.length=Leap.vec3.length(e),Leap.vec3.sub(e,c.dipPosition,c.pipPosition),c.medial.length=Leap.vec3.length(e),Leap.vec3.sub(e,c.tipPosition,c.dipPosition),c.distal.length=Leap.vec3.length(e);Leap.vec3.sub(e,d.arm.prevJoint,d.arm.nextJoint),m.push(d.arm.length=Leap.vec3.length(e))}return m}}}})}.call(this),function(){var a;if(a=function(a){return a.alert||(a.alert=!1),a.requiredProtocolVersion||(a.requiredProtocolVersion=6),a.disconnect||(a.disconnect=!0),"undefined"!=typeof Leap&&Leap.Controller&&Leap.version.minor<5&&Leap.version.dot<4&&console.warn("LeapJS Version Check plugin incompatible with LeapJS pre 0.4.4"),this.on("ready",function(){var b,c,d;return d=a.requiredProtocolVersion,b=this.connection.opts.requestProtocolVersion,d>b?(c="Protocol Version too old. v"+d+" required, v"+b+" available.",a.disconnect&&(this.disconnect(),c+=" Disconnecting."),console.warn(c),a.alert&&alert("Your Leap Software version is out of date. Visit http://www.leapmotion.com/setup to update"),this.emit("versionCheck.outdated",{required:d,current:b,disconnect:a.disconnect})):void 0}),{}},"undefined"!=typeof Leap&&Leap.Controller)Leap.Controller.plugin("versionCheck",a);else{if("undefined"==typeof module)throw"leap.js not included";module.exports.versionCheck=a}}.call(this); --------------------------------------------------------------------------------