node-red-contrib-leap-motion
Version:
Node-Red nodes for leap motion
484 lines (467 loc) • 14.4 kB
JavaScript
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.
*
* 
*
* 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.
*
* 
*
* 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 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.
*
* 
*
* 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)+"]";
}