UNPKG

node-red-contrib-leap-motion

Version:

Node-Red nodes for leap motion

504 lines (480 loc) 18.7 kB
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") , Finger = require('./finger') , _ = 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; 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])); } } this.postprocessData(data); }; Frame.prototype.postprocessData = function(data){ if (!data) { data = this.data; } 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; } data.pointables = _.sortBy(data.pointables, function(pointable) { return pointable.id }); for (var pointableIdx = 0, pointableCount = data.pointables.length; pointableIdx != pointableCount; pointableIdx++) { var pointableData = data.pointables[pointableIdx]; var pointable = pointableData.dipPosition ? new Finger(pointableData) : new Pointable(pointableData); pointable.frame = this; this.addPointable(pointable); } }; /** * Adds data from a pointable element into the pointablesMap; * also adds the pointable to the frame.handsMap hand to which it belongs, * and to the hand's tools or hand's fingers map. * * @param pointable {Object} a Pointable */ Frame.prototype.addPointable = function (pointable) { this.pointables.push(pointable); this.pointablesMap[pointable.id] = pointable; (pointable.tool ? this.tools : this.fingers).push(pointable); if (pointable.handId !== undefined && this.handsMap.hasOwnProperty(pointable.handId)) { var hand = this.handsMap[pointable.handId]; hand.pointables.push(pointable); (pointable.tool ? hand.tools : hand.fingers).push(pointable); switch (pointable.type){ case 0: hand.thumb = pointable; break; case 1: hand.indexFinger = pointable; break; case 2: hand.middleFinger = pointable; break; case 3: hand.ringFinger = pointable; break; case 4: hand.pinky = pointable; break; } } }; /** * 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(); } };