UNPKG

node-red-contrib-leap-motion

Version:

Node-Red nodes for leap motion

484 lines (467 loc) 14.4 kB
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 "unknown 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.slice(); /** * 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.slice(); /** * 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. * * @member startPosition * @memberof Leap.SwipeGesture.prototype * @type {number[]} */ this.startPosition = data.startPosition; /** * The current swipe position within the Leap frame of * reference, in mm. * * @member position * @memberof Leap.SwipeGesture.prototype * @type {number[]} */ this.position = data.position; /** * The unit direction vector parallel to the swipe motion. * * You can compare the components of the vector to classify the swipe as * appropriate for your application. For example, if you are using swipes * for two dimensional scrolling, you can compare the x and y values to * determine if the swipe is primarily horizontal or vertical. * * @member direction * @memberof Leap.SwipeGesture.prototype * @type {number[]} */ this.direction = data.direction; /** * The speed of the finger performing the swipe gesture in * millimeters per second. * * @member speed * @memberof Leap.SwipeGesture.prototype * @type {number} */ this.speed = data.speed; } SwipeGesture.prototype.toString = function() { return "SwipeGesture ["+JSON.stringify(this)+"]"; } /** * Constructs a new ScreenTapGesture object. * * An uninitialized ScreenTapGesture object is considered invalid. Get valid instances * of the ScreenTapGesture class from a Frame object. * * @class ScreenTapGesture * @memberof Leap * @augments Leap.Gesture * @classdesc * The ScreenTapGesture class represents a tapping gesture by a finger or tool. * * A screen tap gesture is recognized when the tip of a finger pokes forward * and then springs back to approximately the original postion, as if * tapping a vertical screen. The tapping finger must pause briefly before beginning the tap. * * ![ScreenTap](images/Leap_Gesture_Tap2.png) * * ScreenTap gestures are discrete. The ScreenTapGesture object representing a tap always * has the state, STATE_STOP. Only one ScreenTapGesture object is created for each * screen tap gesture recognized. */ var ScreenTapGesture = function(data) { /** * The position where the screen tap is registered. * * @member position * @memberof Leap.ScreenTapGesture.prototype * @type {number[]} */ this.position = data.position; /** * The direction of finger tip motion. * * @member direction * @memberof Leap.ScreenTapGesture.prototype * @type {number[]} */ this.direction = data.direction; /** * The progess value is always 1.0 for a screen tap gesture. * * @member progress * @memberof Leap.ScreenTapGesture.prototype * @type {number} */ this.progress = data.progress; } ScreenTapGesture.prototype.toString = function() { return "ScreenTapGesture ["+JSON.stringify(this)+"]"; } /** * Constructs a new KeyTapGesture object. * * An uninitialized KeyTapGesture object is considered invalid. Get valid instances * of the KeyTapGesture class from a Frame object. * * @class KeyTapGesture * @memberof Leap * @augments Leap.Gesture * @classdesc * The KeyTapGesture class represents a tapping gesture by a finger or tool. * * A key tap gesture is recognized when the tip of a finger rotates down toward the * palm and then springs back to approximately the original postion, as if * tapping. The tapping finger must pause briefly before beginning the tap. * * ![KeyTap](images/Leap_Gesture_Tap.png) * * Key tap gestures are discrete. The KeyTapGesture object representing a tap always * has the state, STATE_STOP. Only one KeyTapGesture object is created for each * key tap gesture recognized. */ var KeyTapGesture = function(data) { /** * The position where the key tap is registered. * * @member position * @memberof Leap.KeyTapGesture.prototype * @type {number[]} */ this.position = data.position; /** * The direction of finger tip motion. * * @member direction * @memberof Leap.KeyTapGesture.prototype * @type {number[]} */ this.direction = data.direction; /** * The progess value is always 1.0 for a key tap gesture. * * @member progress * @memberof Leap.KeyTapGesture.prototype * @type {number} */ this.progress = data.progress; } KeyTapGesture.prototype.toString = function() { return "KeyTapGesture ["+JSON.stringify(this)+"]"; }