UNPKG

leapjs

Version:

JavaScript client for the Leap Motion Controller

1,481 lines (1,371 loc) 222 kB
;(function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s<n.length;s++)i(n[s]);return i})({1:[function(require,module,exports){ /*! * LeapJS v0.4.3 * http://github.com/leapmotion/leapjs/ * * Copyright 2013 LeapMotion, Inc. and other contributors * Released under the BSD-2-Clause license * http://github.com/leapmotion/leapjs/blob/master/LICENSE.txt */ },{}],2:[function(require,module,exports){ var CircularBuffer = module.exports = function(size) { this.pos = 0; this._buf = []; this.size = size; } CircularBuffer.prototype.get = function(i) { if (i == undefined) i = 0; if (i >= this.size) return undefined; if (i >= this._buf.length) return undefined; return this._buf[(this.pos - i - 1) % this.size]; } CircularBuffer.prototype.push = function(o) { this._buf[this.pos % this.size] = o; return this.pos++; } },{}],3:[function(require,module,exports){ var chooseProtocol = require('../protocol').chooseProtocol , EventEmitter = require('events').EventEmitter , _ = require('underscore'); var BaseConnection = module.exports = function(opts) { this.opts = _.defaults(opts || {}, { host : '127.0.0.1', enableGestures: false, port: 6437, background: false, requestProtocolVersion: 4 }); this.host = this.opts.host; this.port = this.opts.port; this.protocolVersionVerified = false; this.on('ready', function() { this.enableGestures(this.opts.enableGestures); this.setBackground(this.opts.background); }); } BaseConnection.prototype.getUrl = function() { return "ws://" + this.host + ":" + this.port + "/v" + this.opts.requestProtocolVersion + ".json"; } BaseConnection.prototype.setBackground = function(state) { this.opts.background = state; if (this.protocol && this.protocol.sendBackground && this.background !== this.opts.background) { this.background = this.opts.background; this.protocol.sendBackground(this, this.opts.background); } } BaseConnection.prototype.handleOpen = function() { if (!this.connected) { this.connected = true; this.emit('connect'); } } BaseConnection.prototype.enableGestures = function(enabled) { this.gesturesEnabled = enabled ? true : false; this.send(this.protocol.encode({"enableGestures": this.gesturesEnabled})); } BaseConnection.prototype.handleClose = function(code, reason) { if (!this.connected) return; this.disconnect(); // 1001 - an active connection is closed // 1006 - cannot connect if (code === 1001 && this.opts.requestProtocolVersion > 1) { if (this.protocolVersionVerified) { this.protocolVersionVerified = false; }else{ this.opts.requestProtocolVersion--; } } this.startReconnection(); } BaseConnection.prototype.startReconnection = function() { var connection = this; this.reconnectionTimer = setInterval(function() { connection.reconnect() }, 1000); } BaseConnection.prototype.disconnect = function() { if (!this.socket) return; this.socket.close(); delete this.socket; delete this.protocol; delete this.background; // This is not persisted when reconnecting to the web socket server if (this.connected) { this.connected = false; this.emit('disconnect'); } return true; } BaseConnection.prototype.reconnect = function() { if (this.connected) { clearInterval(this.reconnectionTimer); } else { this.disconnect(); this.connect(); } } BaseConnection.prototype.handleData = function(data) { var message = JSON.parse(data); var messageEvent; if (this.protocol === undefined) { messageEvent = this.protocol = chooseProtocol(message); this.protocolVersionVerified = true; this.emit('ready'); } else { messageEvent = this.protocol(message); } this.emit(messageEvent.type, messageEvent); } BaseConnection.prototype.connect = function() { if (this.socket) return; this.socket = this.setupSocket(); return true; } BaseConnection.prototype.send = function(data) { this.socket.send(data); } BaseConnection.prototype.reportFocus = function(state) { if (this.focusedState === state) return; this.focusedState = state; this.emit(this.focusedState ? 'focus' : 'blur'); if (this.protocol && this.protocol.sendFocused) { this.protocol.sendFocused(this, this.focusedState); } } _.extend(BaseConnection.prototype, EventEmitter.prototype); },{"../protocol":13,"events":19,"underscore":22}],4:[function(require,module,exports){ var BaseConnection = module.exports = require('./base') , _ = require('underscore'); var BrowserConnection = module.exports = function(opts) { BaseConnection.call(this, opts); var connection = this; this.on('ready', function() { connection.startFocusLoop(); }) this.on('disconnect', function() { connection.stopFocusLoop(); }) } _.extend(BrowserConnection.prototype, BaseConnection.prototype); BrowserConnection.prototype.setupSocket = function() { var connection = this; var socket = new WebSocket(this.getUrl()); socket.onopen = function() { connection.handleOpen(); }; socket.onclose = function(data) { connection.handleClose(data['code'], data['reason']); }; socket.onmessage = function(message) { connection.handleData(message.data) }; return socket; } BrowserConnection.prototype.startFocusLoop = function() { if (this.focusDetectorTimer) return; var connection = this; var propertyName = null; if (typeof document.hidden !== "undefined") { propertyName = "hidden"; } else if (typeof document.mozHidden !== "undefined") { propertyName = "mozHidden"; } else if (typeof document.msHidden !== "undefined") { propertyName = "msHidden"; } else if (typeof document.webkitHidden !== "undefined") { propertyName = "webkitHidden"; } else { propertyName = undefined; } if (connection.windowVisible === undefined) { connection.windowVisible = propertyName === undefined ? true : document[propertyName] === false; } var focusListener = window.addEventListener('focus', function(e) { connection.windowVisible = true; updateFocusState(); }); var blurListener = window.addEventListener('blur', function(e) { connection.windowVisible = false; updateFocusState(); }); this.on('disconnect', function() { window.removeEventListener('focus', focusListener); window.removeEventListener('blur', blurListener); }); var updateFocusState = function() { var isVisible = propertyName === undefined ? true : document[propertyName] === false; connection.reportFocus(isVisible && connection.windowVisible); } this.focusDetectorTimer = setInterval(updateFocusState, 100); } BrowserConnection.prototype.stopFocusLoop = function() { if (!this.focusDetectorTimer) return; clearTimeout(this.focusDetectorTimer); delete this.focusDetectorTimer; } },{"./base":3,"underscore":22}],5:[function(require,module,exports){ var process=require("__browserify_process");var Frame = require('./frame') , Hand = require('./hand') , Pointable = require('./pointable') , CircularBuffer = require("./circular_buffer") , Pipeline = require("./pipeline") , EventEmitter = require('events').EventEmitter , gestureListener = require('./gesture').gestureListener , _ = require('underscore'); /** * Constructs a Controller object. * * When creating a Controller object, you may optionally pass in options * to set the host , set the port, enable gestures, or select the frame event type. * * ```javascript * var controller = new Leap.Controller({ * host: '127.0.0.1', * port: 6437, * enableGestures: true, * frameEventName: 'animationFrame' * }); * ``` * * @class Controller * @memberof Leap * @classdesc * The Controller class is your main interface to the Leap Motion Controller. * * Create an instance of this Controller class to access frames of tracking data * and configuration information. Frame data can be polled at any time using the * [Controller.frame]{@link Leap.Controller#frame}() function. Call frame() or frame(0) to get the most recent * frame. Set the history parameter to a positive integer to access previous frames. * A controller stores up to 60 frames in its frame history. * * Polling is an appropriate strategy for applications which already have an * intrinsic update loop, such as a game. */ var Controller = module.exports = function(opts) { var inNode = (typeof(process) !== 'undefined' && process.versions && process.versions.node), controller = this; opts = _.defaults(opts || {}, { inNode: inNode }); this.inNode = opts.inNode; opts = _.defaults(opts || {}, { frameEventName: this.useAnimationLoop() ? 'animationFrame' : 'deviceFrame', suppressAnimationLoop: !this.useAnimationLoop(), loopWhileDisconnected: false, useAllPlugins: false }); this.animationFrameRequested = false; this.onAnimationFrame = function() { controller.emit('animationFrame', controller.lastConnectionFrame); if (controller.loopWhileDisconnected && (controller.connection.focusedState || controller.connection.opts.background) ){ window.requestAnimationFrame(controller.onAnimationFrame); }else{ controller.animationFrameRequested = false; } } this.suppressAnimationLoop = opts.suppressAnimationLoop; this.loopWhileDisconnected = opts.loopWhileDisconnected; this.frameEventName = opts.frameEventName; this.useAllPlugins = opts.useAllPlugins; this.history = new CircularBuffer(200); this.lastFrame = Frame.Invalid; this.lastValidFrame = Frame.Invalid; this.lastConnectionFrame = Frame.Invalid; this.accumulatedGestures = []; if (opts.connectionType === undefined) { this.connectionType = (this.inBrowser() ? require('./connection/browser') : require('./connection/node')); } else { this.connectionType = opts.connectionType; } this.connection = new this.connectionType(opts); this.plugins = {}; this._pluginPipelineSteps = {}; this._pluginExtendedMethods = {}; if (opts.useAllPlugins) this.useRegisteredPlugins(); this.setupConnectionEvents(); } Controller.prototype.gesture = function(type, cb) { var creator = gestureListener(this, type); if (cb !== undefined) { creator.stop(cb); } return creator; } /* * @returns the controller */ Controller.prototype.setBackground = function(state) { this.connection.setBackground(state); return this; } Controller.prototype.inBrowser = function() { return !this.inNode; } Controller.prototype.useAnimationLoop = function() { return this.inBrowser() && !this.inBackgroundPage(); } Controller.prototype.inBackgroundPage = function(){ // http://developer.chrome.com/extensions/extension#method-getBackgroundPage return (typeof(chrome) !== "undefined") && chrome.extension && chrome.extension.getBackgroundPage && (chrome.extension.getBackgroundPage() === window) } /* * @returns the controller */ Controller.prototype.connect = function() { this.connection.connect(); return this; } Controller.prototype.runAnimationLoop = function(){ if (!this.suppressAnimationLoop && !this.animationFrameRequested) { this.animationFrameRequested = true; window.requestAnimationFrame(this.onAnimationFrame); } } /* * @returns the controller */ Controller.prototype.disconnect = function() { this.connection.disconnect(); return this; } /** * Returns a frame of tracking data from the Leap. * * Use the optional history parameter to specify which frame to retrieve. * Call frame() or frame(0) to access the most recent frame; call frame(1) to * access the previous frame, and so on. If you use a history value greater * than the number of stored frames, then the controller returns an invalid frame. * * @method frame * @memberof Leap.Controller.prototype * @param {number} history The age of the frame to return, counting backwards from * the most recent frame (0) into the past and up to the maximum age (59). * @returns {Leap.Frame} The specified frame; or, if no history * parameter is specified, the newest frame. If a frame is not available at * the specified history position, an invalid Frame is returned. */ Controller.prototype.frame = function(num) { return this.history.get(num) || Frame.Invalid; } Controller.prototype.loop = function(callback) { switch (callback.length) { case 1: this.on(this.frameEventName, callback); break; case 2: var controller = this; var scheduler = null; var immediateRunnerCallback = function(frame) { callback(frame, function() { if (controller.lastFrame != frame) { immediateRunnerCallback(controller.lastFrame); } else { controller.once(controller.frameEventName, immediateRunnerCallback); } }); } this.once(this.frameEventName, immediateRunnerCallback); break; } return this.connect(); } Controller.prototype.addStep = function(step) { if (!this.pipeline) this.pipeline = new Pipeline(this); this.pipeline.addStep(step); } // this is run on every deviceFrame Controller.prototype.processFrame = function(frame) { if (frame.gestures) { this.accumulatedGestures = this.accumulatedGestures.concat(frame.gestures); } // lastConnectionFrame is used by the animation loop this.lastConnectionFrame = frame; this.runAnimationLoop(); this.emit('deviceFrame', frame); } // on a this.deviceEventName (usually 'animationFrame' in browsers), this emits a 'frame' Controller.prototype.processFinishedFrame = function(frame) { this.lastFrame = frame; if (frame.valid) { this.lastValidFrame = frame; } frame.controller = this; frame.historyIdx = this.history.push(frame); if (frame.gestures) { frame.gestures = this.accumulatedGestures; this.accumulatedGestures = []; for (var gestureIdx = 0; gestureIdx != frame.gestures.length; gestureIdx++) { this.emit("gesture", frame.gestures[gestureIdx], frame); } } if (this.pipeline) { frame = this.pipeline.run(frame); if (!frame) frame = Frame.Invalid; } this.emit('frame', frame); } Controller.prototype.setupConnectionEvents = function() { var controller = this; this.connection.on('frame', function(frame) { controller.processFrame(frame); }); this.on(this.frameEventName, function(frame) { controller.processFinishedFrame(frame); }); // Delegate connection events this.connection.on('disconnect', function() { controller.emit('disconnect'); }); this.connection.on('ready', function() { controller.emit('ready'); }); this.connection.on('connect', function() { controller.emit('connect'); }); this.connection.on('focus', function() { controller.emit('focus'); controller.runAnimationLoop(); }); this.connection.on('blur', function() { controller.emit('blur') }); this.connection.on('protocol', function(protocol) { controller.emit('protocol', protocol); }); this.connection.on('deviceConnect', function(evt) { controller.emit(evt.state ? 'deviceConnected' : 'deviceDisconnected'); }); } Controller._pluginFactories = {}; /* * Registers a plugin, making is accessible to controller.use later on. * * @member plugin * @memberof Leap.Controller.prototype * @param {String} name The name of the plugin (usually camelCase). * @param {function} factory A factory method which will return an instance of a plugin. * The factory receives an optional hash of options, passed in via controller.use. * * Valid keys for the object include frame, hand, finger, tool, and pointable. The value * of each key can be either a function or an object. If given a function, that function * will be called once for every instance of the object, with that instance injected as an * argument. This allows decoration of objects with additional data: * * ```javascript * Leap.Controller.plugin('testPlugin', function(options){ * return { * frame: function(frame){ * frame.foo = 'bar'; * } * } * }); * ``` * * When hand is used, the callback is called for every hand in `frame.hands`. Note that * hand objects are recreated with every new frame, so that data saved on the hand will not * persist. * * ```javascript * Leap.Controller.plugin('testPlugin', function(){ * return { * hand: function(hand){ * console.log('testPlugin running on hand ' + hand.id); * } * } * }); * ``` * * A factory can return an object to add custom functionality to Frames, Hands, or Pointables. * The methods are added directly to the object's prototype. Finger and Tool cannot be used here, Pointable * must be used instead. * This is encouraged for calculations which may not be necessary on every frame. * Memoization is also encouraged, for cases where the method may be called many times per frame by the application. * * ```javascript * // This plugin allows hand.usefulData() to be called later. * Leap.Controller.plugin('testPlugin', function(){ * return { * hand: { * usefulData: function(){ * console.log('usefulData on hand', this.id); * // memoize the results on to the hand, preventing repeat work: * this.x || this.x = someExpensiveCalculation(); * return this.x; * } * } * } * }); * * Note that the factory pattern allows encapsulation for every plugin instance. * * ```javascript * Leap.Controller.plugin('testPlugin', function(options){ * options || options = {} * options.center || options.center = [0,0,0] * * privatePrintingMethod = function(){ * console.log('privatePrintingMethod - options', options); * } * * return { * pointable: { * publicPrintingMethod: function(){ * privatePrintingMethod(); * } * } * } * }); * */ Controller.plugin = function(pluginName, factory) { if (this._pluginFactories[pluginName]) { throw "Plugin \"" + pluginName + "\" already registered"; } return this._pluginFactories[pluginName] = factory; }; /* * Returns a list of registered plugins. * @returns {Array} Plugin Factories. */ Controller.plugins = function() { return _.keys(this._pluginFactories); }; /* * Begin using a registered plugin. The plugin's functionality will be added to all frames * returned by the controller (and/or added to the objects within the frame). * - The order of plugin execution inside the loop will match the order in which use is called by the application. * - The plugin be run for both deviceFrames and animationFrames. * * If called a second time, the options will be merged with those of the already instantiated plugin. * * @method use * @memberOf Leap.Controller.prototype * @param pluginName * @param {Hash} Options to be passed to the plugin's factory. * @returns the controller */ Controller.prototype.use = function(pluginName, options) { var functionOrHash, pluginFactory, key, pluginInstance, klass; pluginFactory = (typeof pluginName == 'function') ? pluginName : Controller._pluginFactories[pluginName]; if (!pluginFactory) { throw 'Leap Plugin ' + pluginName + ' not found.'; } options || (options = {}); if (this.plugins[pluginName]){ _.extend(this.plugins[pluginName], options) return this; } this.plugins[pluginName] = options; pluginInstance = pluginFactory.call(this, options); for (key in pluginInstance) { functionOrHash = pluginInstance[key]; if (typeof functionOrHash === 'function') { if (!this.pipeline) this.pipeline = new Pipeline(this); if (!this._pluginPipelineSteps[pluginName]) this._pluginPipelineSteps[pluginName] = []; this._pluginPipelineSteps[pluginName].push( this.pipeline.addWrappedStep(key, functionOrHash) ); } else { if (!this._pluginExtendedMethods[pluginName]) this._pluginExtendedMethods[pluginName] = []; switch (key) { case 'frame': klass = Frame break; case 'hand': klass = Hand break; case 'pointable': klass = Pointable break; default: throw pluginName + ' specifies invalid object type "' + key + '" for prototypical extension' } _.extend(klass.prototype, functionOrHash); _.extend(klass.Invalid, functionOrHash); this._pluginExtendedMethods[pluginName].push([klass, functionOrHash]) } } return this; }; /* * Stop using a used plugin. This will remove any of the plugin's pipeline methods (those called on every frame) * and remove any methods which extend frame-object prototypes. * * @method stopUsing * @memberOf Leap.Controller.prototype * @param pluginName * @returns the controller */ Controller.prototype.stopUsing = function (pluginName) { var steps = this._pluginPipelineSteps[pluginName], extMethodHashes = this._pluginExtendedMethods[pluginName], i = 0, klass, extMethodHash; if (!this.plugins[pluginName]) return; if (steps) { for (i = 0; i < steps.length; i++) { this.pipeline.removeStep(steps[i]); } } if (extMethodHashes){ for (i = 0; i < extMethodHashes.length; i++){ klass = extMethodHashes[i][0] extMethodHash = extMethodHashes[i][1] for (var methodName in extMethodHash) { delete klass.prototype[methodName] delete klass.Invalid[methodName] } } } delete this.plugins[pluginName] return this; } Controller.prototype.useRegisteredPlugins = function(){ for (var plugin in Controller._pluginFactories){ this.use(plugin); } } _.extend(Controller.prototype, EventEmitter.prototype); },{"./circular_buffer":2,"./connection/browser":4,"./connection/node":18,"./frame":6,"./gesture":7,"./hand":8,"./pipeline":11,"./pointable":12,"__browserify_process":20,"events":19,"underscore":22}],6:[function(require,module,exports){ var Hand = require("./hand") , Pointable = require("./pointable") , createGesture = require("./gesture").createGesture , glMatrix = require("gl-matrix") , mat3 = glMatrix.mat3 , vec3 = glMatrix.vec3 , InteractionBox = require("./interaction_box") , _ = require("underscore"); /** * Constructs a Frame object. * * Frame instances created with this constructor are invalid. * Get valid Frame objects by calling the * [Controller.frame]{@link Leap.Controller#frame}() function. *<C-D-Space> * @class Frame * @memberof Leap * @classdesc * The Frame class represents a set of hand and finger tracking data detected * in a single frame. * * The Leap detects hands, fingers and tools within the tracking area, reporting * their positions, orientations and motions in frames at the Leap frame rate. * * Access Frame objects using the [Controller.frame]{@link Leap.Controller#frame}() function. */ var Frame = module.exports = function(data) { /** * Reports whether this Frame instance is valid. * * A valid Frame is one generated by the Controller object that contains * tracking data for all detected entities. An invalid Frame contains no * actual tracking data, but you can call its functions without risk of a * undefined object exception. The invalid Frame mechanism makes it more * convenient to track individual data across the frame history. For example, * you can invoke: * * ```javascript * var finger = controller.frame(n).finger(fingerID); * ``` * * for an arbitrary Frame history value, "n", without first checking whether * frame(n) returned a null object. (You should still check that the * returned Finger instance is valid.) * * @member valid * @memberof Leap.Frame.prototype * @type {Boolean} */ this.valid = true; /** * A unique ID for this Frame. Consecutive frames processed by the Leap * have consecutive increasing values. * @member id * @memberof Leap.Frame.prototype * @type {String} */ this.id = data.id; /** * The frame capture time in microseconds elapsed since the Leap started. * @member timestamp * @memberof Leap.Frame.prototype * @type {number} */ this.timestamp = data.timestamp; /** * The list of Hand objects detected in this frame, given in arbitrary order. * The list can be empty if no hands are detected. * * @member hands[] * @memberof Leap.Frame.prototype * @type {Leap.Hand} */ this.hands = []; this.handsMap = {}; /** * The list of Pointable objects (fingers and tools) detected in this frame, * given in arbitrary order. The list can be empty if no fingers or tools are * detected. * * @member pointables[] * @memberof Leap.Frame.prototype * @type {Leap.Pointable} */ this.pointables = []; /** * The list of Tool objects detected in this frame, given in arbitrary order. * The list can be empty if no tools are detected. * * @member tools[] * @memberof Leap.Frame.prototype * @type {Leap.Pointable} */ this.tools = []; /** * The list of Finger objects detected in this frame, given in arbitrary order. * The list can be empty if no fingers are detected. * @member fingers[] * @memberof Leap.Frame.prototype * @type {Leap.Pointable} */ this.fingers = []; /** * The InteractionBox associated with the current frame. * * @member interactionBox * @memberof Leap.Frame.prototype * @type {Leap.InteractionBox} */ if (data.interactionBox) { this.interactionBox = new InteractionBox(data.interactionBox); } this.gestures = []; this.pointablesMap = {}; this._translation = data.t; this._rotation = _.flatten(data.r); this._scaleFactor = data.s; this.data = data; this.type = 'frame'; // used by event emitting this.currentFrameRate = data.currentFrameRate; var handMap = {}; for (var handIdx = 0, handCount = data.hands.length; handIdx != handCount; handIdx++) { var hand = new Hand(data.hands[handIdx]); hand.frame = this; this.hands.push(hand); this.handsMap[hand.id] = hand; handMap[hand.id] = handIdx; } for (var pointableIdx = 0, pointableCount = data.pointables.length; pointableIdx != pointableCount; pointableIdx++) { var pointable = new Pointable(data.pointables[pointableIdx]); pointable.frame = this; this.pointables.push(pointable); this.pointablesMap[pointable.id] = pointable; (pointable.tool ? this.tools : this.fingers).push(pointable); if (pointable.handId !== undefined && handMap.hasOwnProperty(pointable.handId)) { var hand = this.hands[handMap[pointable.handId]]; hand.pointables.push(pointable); (pointable.tool ? hand.tools : hand.fingers).push(pointable); } } if (data.gestures) { /** * The list of Gesture objects detected in this frame, given in arbitrary order. * The list can be empty if no gestures are detected. * * Circle and swipe gestures are updated every frame. Tap gestures * only appear in the list for a single frame. * @member gestures[] * @memberof Leap.Frame.prototype * @type {Leap.Gesture} */ for (var gestureIdx = 0, gestureCount = data.gestures.length; gestureIdx != gestureCount; gestureIdx++) { this.gestures.push(createGesture(data.gestures[gestureIdx])); } } } /** * The tool with the specified ID in this frame. * * Use the Frame tool() function to retrieve a tool from * this frame using an ID value obtained from a previous frame. * This function always returns a Pointable object, but if no tool * with the specified ID is present, an invalid Pointable object is returned. * * Note that ID values persist across frames, but only until tracking of a * particular object is lost. If tracking of a tool is lost and subsequently * regained, the new Pointable object representing that tool may have a * different ID than that representing the tool in an earlier frame. * * @method tool * @memberof Leap.Frame.prototype * @param {String} id The ID value of a Tool object from a previous frame. * @returns {Leap.Pointable} The tool with the * matching ID if one exists in this frame; otherwise, an invalid Pointable object * is returned. */ Frame.prototype.tool = function(id) { var pointable = this.pointable(id); return pointable.tool ? pointable : Pointable.Invalid; } /** * The Pointable object with the specified ID in this frame. * * Use the Frame pointable() function to retrieve the Pointable object from * this frame using an ID value obtained from a previous frame. * This function always returns a Pointable object, but if no finger or tool * with the specified ID is present, an invalid Pointable object is returned. * * Note that ID values persist across frames, but only until tracking of a * particular object is lost. If tracking of a finger or tool is lost and subsequently * regained, the new Pointable object representing that finger or tool may have * a different ID than that representing the finger or tool in an earlier frame. * * @method pointable * @memberof Leap.Frame.prototype * @param {String} id The ID value of a Pointable object from a previous frame. * @returns {Leap.Pointable} The Pointable object with * the matching ID if one exists in this frame; * otherwise, an invalid Pointable object is returned. */ Frame.prototype.pointable = function(id) { return this.pointablesMap[id] || Pointable.Invalid; } /** * The finger with the specified ID in this frame. * * Use the Frame finger() function to retrieve the finger from * this frame using an ID value obtained from a previous frame. * This function always returns a Finger object, but if no finger * with the specified ID is present, an invalid Pointable object is returned. * * Note that ID values persist across frames, but only until tracking of a * particular object is lost. If tracking of a finger is lost and subsequently * regained, the new Pointable object representing that physical finger may have * a different ID than that representing the finger in an earlier frame. * * @method finger * @memberof Leap.Frame.prototype * @param {String} id The ID value of a finger from a previous frame. * @returns {Leap.Pointable} The finger with the * matching ID if one exists in this frame; otherwise, an invalid Pointable * object is returned. */ Frame.prototype.finger = function(id) { var pointable = this.pointable(id); return !pointable.tool ? pointable : Pointable.Invalid; } /** * The Hand object with the specified ID in this frame. * * Use the Frame hand() function to retrieve the Hand object from * this frame using an ID value obtained from a previous frame. * This function always returns a Hand object, but if no hand * with the specified ID is present, an invalid Hand object is returned. * * Note that ID values persist across frames, but only until tracking of a * particular object is lost. If tracking of a hand is lost and subsequently * regained, the new Hand object representing that physical hand may have * a different ID than that representing the physical hand in an earlier frame. * * @method hand * @memberof Leap.Frame.prototype * @param {String} id The ID value of a Hand object from a previous frame. * @returns {Leap.Hand} The Hand object with the matching * ID if one exists in this frame; otherwise, an invalid Hand object is returned. */ Frame.prototype.hand = function(id) { return this.handsMap[id] || Hand.Invalid; } /** * The angle of rotation around the rotation axis derived from the overall * rotational motion between the current frame and the specified frame. * * The returned angle is expressed in radians measured clockwise around * the rotation axis (using the right-hand rule) between the start and end frames. * The value is always between 0 and pi radians (0 and 180 degrees). * * The Leap derives frame rotation from the relative change in position and * orientation of all objects detected in the field of view. * * If either this frame or sinceFrame is an invalid Frame object, then the * angle of rotation is zero. * * @method rotationAngle * @memberof Leap.Frame.prototype * @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation. * @param {number[]} [axis] The axis to measure rotation around. * @returns {number} A positive value containing the heuristically determined * rotational change between the current frame and that specified in the sinceFrame parameter. */ Frame.prototype.rotationAngle = function(sinceFrame, axis) { if (!this.valid || !sinceFrame.valid) return 0.0; var rot = this.rotationMatrix(sinceFrame); var cs = (rot[0] + rot[4] + rot[8] - 1.0)*0.5 var angle = Math.acos(cs); angle = isNaN(angle) ? 0.0 : angle; if (axis !== undefined) { var rotAxis = this.rotationAxis(sinceFrame); angle *= vec3.dot(rotAxis, vec3.normalize(vec3.create(), axis)); } return angle; } /** * The axis of rotation derived from the overall rotational motion between * the current frame and the specified frame. * * The returned direction vector is normalized. * * The Leap derives frame rotation from the relative change in position and * orientation of all objects detected in the field of view. * * If either this frame or sinceFrame is an invalid Frame object, or if no * rotation is detected between the two frames, a zero vector is returned. * * @method rotationAxis * @memberof Leap.Frame.prototype * @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation. * @returns {number[]} A normalized direction vector representing the axis of the heuristically determined * rotational change between the current frame and that specified in the sinceFrame parameter. */ Frame.prototype.rotationAxis = function(sinceFrame) { if (!this.valid || !sinceFrame.valid) return vec3.create(); return vec3.normalize(vec3.create(), [ this._rotation[7] - sinceFrame._rotation[5], this._rotation[2] - sinceFrame._rotation[6], this._rotation[3] - sinceFrame._rotation[1] ]); } /** * The transform matrix expressing the rotation derived from the overall * rotational motion between the current frame and the specified frame. * * The Leap derives frame rotation from the relative change in position and * orientation of all objects detected in the field of view. * * If either this frame or sinceFrame is an invalid Frame object, then * this method returns an identity matrix. * * @method rotationMatrix * @memberof Leap.Frame.prototype * @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation. * @returns {number[]} A transformation matrix containing the heuristically determined * rotational change between the current frame and that specified in the sinceFrame parameter. */ Frame.prototype.rotationMatrix = function(sinceFrame) { if (!this.valid || !sinceFrame.valid) return mat3.create(); var transpose = mat3.transpose(mat3.create(), this._rotation) return mat3.multiply(mat3.create(), sinceFrame._rotation, transpose); } /** * The scale factor derived from the overall motion between the current frame and the specified frame. * * The scale factor is always positive. A value of 1.0 indicates no scaling took place. * Values between 0.0 and 1.0 indicate contraction and values greater than 1.0 indicate expansion. * * The Leap derives scaling from the relative inward or outward motion of all * objects detected in the field of view (independent of translation and rotation). * * If either this frame or sinceFrame is an invalid Frame object, then this method returns 1.0. * * @method scaleFactor * @memberof Leap.Frame.prototype * @param {Leap.Frame} sinceFrame The starting frame for computing the relative scaling. * @returns {number} A positive value representing the heuristically determined * scaling change ratio between the current frame and that specified in the sinceFrame parameter. */ Frame.prototype.scaleFactor = function(sinceFrame) { if (!this.valid || !sinceFrame.valid) return 1.0; return Math.exp(this._scaleFactor - sinceFrame._scaleFactor); } /** * The change of position derived from the overall linear motion between the * current frame and the specified frame. * * The returned translation vector provides the magnitude and direction of the * movement in millimeters. * * The Leap derives frame translation from the linear motion of all objects * detected in the field of view. * * If either this frame or sinceFrame is an invalid Frame object, then this * method returns a zero vector. * * @method translation * @memberof Leap.Frame.prototype * @param {Leap.Frame} sinceFrame The starting frame for computing the relative translation. * @returns {number[]} A vector representing the heuristically determined change in * position of all objects between the current frame and that specified in the sinceFrame parameter. */ Frame.prototype.translation = function(sinceFrame) { if (!this.valid || !sinceFrame.valid) return vec3.create(); return vec3.subtract(vec3.create(), this._translation, sinceFrame._translation); } /** * A string containing a brief, human readable description of the Frame object. * * @method toString * @memberof Leap.Frame.prototype * @returns {String} A brief description of this frame. */ Frame.prototype.toString = function() { var str = "Frame [ id:"+this.id+" | timestamp:"+this.timestamp+" | Hand count:("+this.hands.length+") | Pointable count:("+this.pointables.length+")"; if (this.gestures) str += " | Gesture count:("+this.gestures.length+")"; str += " ]"; return str; } /** * Returns a JSON-formatted string containing the hands, pointables and gestures * in this frame. * * @method dump * @memberof Leap.Frame.prototype * @returns {String} A JSON-formatted string. */ Frame.prototype.dump = function() { var out = ''; out += "Frame Info:<br/>"; out += this.toString(); out += "<br/><br/>Hands:<br/>" for (var handIdx = 0, handCount = this.hands.length; handIdx != handCount; handIdx++) { out += " "+ this.hands[handIdx].toString() + "<br/>"; } out += "<br/><br/>Pointables:<br/>"; for (var pointableIdx = 0, pointableCount = this.pointables.length; pointableIdx != pointableCount; pointableIdx++) { out += " "+ this.pointables[pointableIdx].toString() + "<br/>"; } if (this.gestures) { out += "<br/><br/>Gestures:<br/>"; for (var gestureIdx = 0, gestureCount = this.gestures.length; gestureIdx != gestureCount; gestureIdx++) { out += " "+ this.gestures[gestureIdx].toString() + "<br/>"; } } out += "<br/><br/>Raw JSON:<br/>"; out += JSON.stringify(this.data); return out; } /** * An invalid Frame object. * * You can use this invalid Frame in comparisons testing * whether a given Frame instance is valid or invalid. (You can also check the * [Frame.valid]{@link Leap.Frame#valid} property.) * * @static * @type {Leap.Frame} * @name Invalid * @memberof Leap.Frame */ Frame.Invalid = { valid: false, hands: [], fingers: [], tools: [], gestures: [], pointables: [], pointable: function() { return Pointable.Invalid }, finger: function() { return Pointable.Invalid }, hand: function() { return Hand.Invalid }, toString: function() { return "invalid frame" }, dump: function() { return this.toString() }, rotationAngle: function() { return 0.0; }, rotationMatrix: function() { return mat3.create(); }, rotationAxis: function() { return vec3.create(); }, scaleFactor: function() { return 1.0; }, translation: function() { return vec3.create(); } }; },{"./gesture":7,"./hand":8,"./interaction_box":10,"./pointable":12,"gl-matrix":21,"underscore":22}],7:[function(require,module,exports){ var glMatrix = require("gl-matrix") , vec3 = glMatrix.vec3 , EventEmitter = require('events').EventEmitter , _ = require('underscore'); /** * Constructs a new Gesture object. * * An uninitialized Gesture object is considered invalid. Get valid instances * of the Gesture class, which will be one of the Gesture subclasses, from a * Frame object. * * @class Gesture * @abstract * @memberof Leap * @classdesc * The Gesture class represents a recognized movement by the user. * * The Leap watches the activity within its field of view for certain movement * patterns typical of a user gesture or command. For example, a movement from side to * side with the hand can indicate a swipe gesture, while a finger poking forward * can indicate a screen tap gesture. * * When the Leap recognizes a gesture, it assigns an ID and adds a * Gesture object to the frame gesture list. For continuous gestures, which * occur over many frames, the Leap updates the gesture by adding * a Gesture object having the same ID and updated properties in each * subsequent frame. * * **Important:** Recognition for each type of gesture must be enabled; * otherwise **no gestures are recognized or reported**. * * Subclasses of Gesture define the properties for the specific movement patterns * recognized by the Leap. * * The Gesture subclasses for include: * * * CircleGesture -- A circular movement by a finger. * * SwipeGesture -- A straight line movement by the hand with fingers extended. * * ScreenTapGesture -- A forward tapping movement by a finger. * * KeyTapGesture -- A downward tapping movement by a finger. * * Circle and swipe gestures are continuous and these objects can have a * state of start, update, and stop. * * The screen tap gesture is a discrete gesture. The Leap only creates a single * ScreenTapGesture object appears for each tap and it always has a stop state. * * Get valid Gesture instances from a Frame object. You can get a list of gestures * from the Frame gestures array. You can also use the Frame gesture() method * to find a gesture in the current frame using an ID value obtained in a * previous frame. * * Gesture objects can be invalid. For example, when you get a gesture by ID * using Frame.gesture(), and there is no gesture with that ID in the current * frame, then gesture() returns an Invalid Gesture object (rather than a null * value). Always check object validity in situations where a gesture might be * invalid. */ var createGesture = exports.createGesture = function(data) { var gesture; switch (data.type) { case 'circle': gesture = new CircleGesture(data); break; case 'swipe': gesture = new SwipeGesture(data); break; case 'screenTap': gesture = new ScreenTapGesture(data); break; case 'keyTap': gesture = new KeyTapGesture(data); break; default: throw "unkown gesture type"; } /** * The gesture ID. * * All Gesture objects belonging to the same recognized movement share the * same ID value. Use the ID value with the Frame::gesture() method to * find updates related to this Gesture object in subsequent frames. * * @member id * @memberof Leap.Gesture.prototype * @type {number} */ gesture.id = data.id; /** * The list of hands associated with this Gesture, if any. * * If no hands are related to this gesture, the list is empty. * * @member handIds * @memberof Leap.Gesture.prototype * @type {Array} */ gesture.handIds = data.handIds; /** * The list of fingers and tools associated with this Gesture, if any. * * If no Pointable objects are related to this gesture, the list is empty. * * @member pointableIds * @memberof Leap.Gesture.prototype * @type {Array} */ gesture.pointableIds = data.pointableIds; /** * The elapsed duration of the recognized movement up to the * frame containing this Gesture object, in microseconds. * * The duration reported for the first Gesture in the sequence (with the * start state) will typically be a small positive number since * the movement must progress far enough for the Leap to recognize it as * an intentional gesture. * * @member duration * @memberof Leap.Gesture.prototype * @type {number} */ gesture.duration = data.duration; /** * The gesture ID. * * Recognized movements occur over time and have a beginning, a middle, * and an end. The 'state()' attribute reports where in that sequence this * Gesture object falls. * * Possible values for the state field are: * * * start * * update * * stop * * @member state * @memberof Leap.Gesture.prototype * @type {String} */ gesture.state = data.state; /** * The gesture type. * * Possible values for the type field are: * * * circle * * swipe * * screenTap * * keyTap * * @member type * @memberof Leap.Gesture.prototype * @type {String} */ gesture.type = data.type; return gesture; } /* * Returns a builder object, which uses method chaining for gesture callback binding. */ var gestureListener = exports.gestureListener = function(controller, type) { var handlers = {}; var gestureMap = {}; controller.on('gesture', function(gesture, frame) { if (gesture.type == type) { if (gesture.state == "start" || gesture.state == "stop") { if (gestureMap[gesture.id] === undefined) { var gestureTracker = new Gesture(gesture, frame); gestureMap[gesture.id] = gestureTracker; _.each(handlers, function(cb, name) { gestureTracker.on(name, cb); }); } } gestureMap[gesture.id].update(gesture, frame); if (gesture.state == "stop") { delete gestureMap[gesture.id]; } } }); var builder = { start: function(cb) { handlers['start'] = cb; return builder; }, stop: function(cb) { handlers['stop'] = cb; return builder; }, complete: function(cb) { handlers['stop'] = cb; return builder; }, update: function(cb) { handlers['update'] = cb; return builder; } } return builder; } var Gesture = exports.Gesture = function(gesture, frame) { this.gestures = [gesture]; this.frames = [frame]; } Gesture.prototype.update = function(gesture, frame) { this.lastGesture = gesture; this.lastFrame = frame; this.gestures.push(gesture); this.frames.push(frame); this.emit(gesture.state, this); } Gesture.prototype.translation = function() { return vec3.subtract(vec3.create(), this.lastGesture.startPosition, this.lastGesture.position); } _.extend(Gesture.prototype, EventEmitter.prototype); /** * Constructs a new CircleGesture object. * * An uninitialized CircleGesture object is considered invalid. Get valid instances * of the CircleGesture class from a Frame object. * * @class CircleGesture * @memberof Leap * @augments Leap.Gesture * @classdesc * The CircleGesture classes represents a circular finger movement. * * A circle movement is recognized when the tip of a finger draws a circle * within the Leap field of view. * * ![CircleGesture](images/Leap_Gesture_Circle.png) * * Circle gestures are continuous. The CircleGesture objects for the gesture have * three possible states: * * * start -- The circle gesture has just started. The movement has * progressed far enough for the recognizer to classify it as a circle. * * update -- The circle gesture is continuing. * * stop -- The circle gesture is finished. */ var CircleGesture = function(data) { /** * The center point of the circle within the Leap frame of reference. * * @member center * @memberof Leap.CircleGesture.prototype * @type {number[]} */ this.center = data.center; /** * The normal vector for the circle being traced. * * If you draw the circle clockwise, the normal vector points in the same * general direction as the pointable object drawing the circle. If you draw * the circle counterclockwise, the normal points back toward the * pointable. If the angle between the normal and the pointable object * drawing the circle is less than 90 degrees, then the circle is clockwise. * * ```javascript * var clockwiseness; * if (circle.pointable.direction.angleTo(circle.normal) <= PI/4) { * clockwiseness = "clockwise"; * } * else * { * clockwiseness = "counterclockwise"; * } * ``` * * @member normal * @memberof Leap.CircleGesture.prototype * @type {number[]} */ this.normal = data.normal; /** * The number of times the finger tip has traversed the circle. * * Progress is reported as a positive number of the number. For example, * a progress value of .5 indicates that the finger has gone halfway * around, while a value of 3 indicates that the finger has gone around * the the circle three times. * * Progress starts where the circle gesture began. Since the circle * must be partially formed before the Leap can recognize it, progress * will be greater than zero when a circle gesture first appears in the * frame. * * @member progress * @memberof Leap.CircleGesture.prototype * @type {number} */ this.progress = data.progress; /** * The radius of the circle in mm. * * @member radius * @memberof Leap.CircleGesture.prototype * @type {number} */ this.radius = data.radius; } CircleGesture.prototype.toString = function() { return "CircleGesture ["+JSON.stringify(this)+"]"; } /** * Constructs a new SwipeGesture object. * * An uninitialized SwipeGesture object is considered invalid. Get valid instances * of the SwipeGesture class from a Frame object. * * @class SwipeGesture * @memberof Leap * @augments Leap.Gesture * @classdesc * The SwipeGesture class represents a swiping motion of a finger or tool. * * ![SwipeGesture](images/Leap_Gesture_Swipe.png) * * Swipe gestures are continuous. */ var SwipeGesture = function(data) { /** * The starting position within the Leap frame of * reference, in mm.