node-red-contrib-leap-motion
Version:
Node-Red nodes for leap motion
453 lines (434 loc) • 16.5 kB
JavaScript
var Pointable = require("./pointable")
, Bone = require('./bone')
, glMatrix = require("gl-matrix")
, mat3 = glMatrix.mat3
, vec3 = glMatrix.vec3
, _ = require("underscore");
/**
* Constructs a Hand object.
*
* An uninitialized hand is considered invalid.
* Get valid Hand objects from a Frame object.
* @class Hand
* @memberof Leap
* @classdesc
* The Hand class reports the physical characteristics of a detected hand.
*
* Hand tracking data includes a palm position and velocity; vectors for
* the palm normal and direction to the fingers; properties of a sphere fit
* to the hand; and lists of the attached fingers and tools.
*
* Note that Hand objects can be invalid, which means that they do not contain
* valid tracking data and do not correspond to a physical entity. Invalid Hand
* objects can be the result of asking for a Hand object using an ID from an
* earlier frame when no Hand objects with that ID exist in the current frame.
* A Hand object created from the Hand constructor is also invalid.
* Test for validity with the [Hand.valid]{@link Leap.Hand#valid} property.
*/
var Hand = module.exports = function(data) {
/**
* A unique ID assigned to this Hand object, whose value remains the same
* across consecutive frames while the tracked hand remains visible. If
* tracking is lost (for example, when a hand is occluded by another hand
* or when it is withdrawn from or reaches the edge of the Leap field of view),
* the Leap may assign a new ID when it detects the hand in a future frame.
*
* Use the ID value with the {@link Frame.hand}() function to find this
* Hand object in future frames.
*
* @member id
* @memberof Leap.Hand.prototype
* @type {String}
*/
this.id = data.id;
/**
* The center position of the palm in millimeters from the Leap origin.
* @member palmPosition
* @memberof Leap.Hand.prototype
* @type {number[]}
*/
this.palmPosition = data.palmPosition;
/**
* The direction from the palm position toward the fingers.
*
* The direction is expressed as a unit vector pointing in the same
* direction as the directed line from the palm position to the fingers.
*
* @member direction
* @memberof Leap.Hand.prototype
* @type {number[]}
*/
this.direction = data.direction;
/**
* The rate of change of the palm position in millimeters/second.
*
* @member palmVeclocity
* @memberof Leap.Hand.prototype
* @type {number[]}
*/
this.palmVelocity = data.palmVelocity;
/**
* The normal vector to the palm. If your hand is flat, this vector will
* point downward, or "out" of the front surface of your palm.
*
* 
*
* The direction is expressed as a unit vector pointing in the same
* direction as the palm normal (that is, a vector orthogonal to the palm).
* @member palmNormal
* @memberof Leap.Hand.prototype
* @type {number[]}
*/
this.palmNormal = data.palmNormal;
/**
* The center of a sphere fit to the curvature of this hand.
*
* This sphere is placed roughly as if the hand were holding a ball.
*
* 
* @member sphereCenter
* @memberof Leap.Hand.prototype
* @type {number[]}
*/
this.sphereCenter = data.sphereCenter;
/**
* The radius of a sphere fit to the curvature of this hand, in millimeters.
*
* This sphere is placed roughly as if the hand were holding a ball. Thus the
* size of the sphere decreases as the fingers are curled into a fist.
*
* @member sphereRadius
* @memberof Leap.Hand.prototype
* @type {number}
*/
this.sphereRadius = data.sphereRadius;
/**
* Reports whether this is a valid Hand object.
*
* @member valid
* @memberof Leap.Hand.prototype
* @type {boolean}
*/
this.valid = true;
/**
* The list of Pointable objects (fingers and tools) detected in this frame
* that are associated with this hand, given in arbitrary order. The list
* can be empty if no fingers or tools associated with this hand are detected.
*
* Use the {@link Pointable} tool property to determine
* whether or not an item in the list represents a tool or finger.
* You can also get only the tools using the Hand.tools[] list or
* only the fingers using the Hand.fingers[] list.
*
* @member pointables[]
* @memberof Leap.Hand.prototype
* @type {Leap.Pointable[]}
*/
this.pointables = [];
/**
* The list of fingers detected in this frame that are attached to
* this hand, given in arbitrary order.
*
* The list can be empty if no fingers attached to this hand are detected.
*
* @member fingers[]
* @memberof Leap.Hand.prototype
* @type {Leap.Pointable[]}
*/
this.fingers = [];
if (data.armBasis){
this.arm = new Bone(this, {
type: 4,
width: data.armWidth,
prevJoint: data.elbow,
nextJoint: data.wrist,
basis: data.armBasis
});
}else{
this.arm = null;
}
/**
* The list of tools detected in this frame that are held by this
* hand, given in arbitrary order.
*
* The list can be empty if no tools held by this hand are detected.
*
* @member tools[]
* @memberof Leap.Hand.prototype
* @type {Leap.Pointable[]}
*/
this.tools = [];
this._translation = data.t;
this._rotation = _.flatten(data.r);
this._scaleFactor = data.s;
/**
* Time the hand has been visible in seconds.
*
* @member timeVisible
* @memberof Leap.Hand.prototype
* @type {number}
*/
this.timeVisible = data.timeVisible;
/**
* The palm position with stabalization
* @member stabilizedPalmPosition
* @memberof Leap.Hand.prototype
* @type {number[]}
*/
this.stabilizedPalmPosition = data.stabilizedPalmPosition;
/**
* Reports whether this is a left or a right hand.
*
* @member type
* @type {String}
* @memberof Leap.Hand.prototype
*/
this.type = data.type;
this.grabStrength = data.grabStrength;
this.pinchStrength = data.pinchStrength;
this.confidence = data.confidence;
}
/**
* The finger with the specified ID attached to this hand.
*
* Use this function to retrieve a Pointable object representing a finger
* attached to this hand using an ID value obtained from a previous frame.
* This function always returns a Pointable object, but if no finger
* with the specified ID is present, an invalid Pointable object is returned.
*
* Note that the ID values assigned to fingers persist across frames, but only
* until tracking of a particular finger is lost. If tracking of a finger is
* lost and subsequently regained, the new Finger object representing that
* finger may have a different ID than that representing the finger in an
* earlier frame.
*
* @method finger
* @memberof Leap.Hand.prototype
* @param {String} id The ID value of a finger from a previous frame.
* @returns {Leap.Pointable} The Finger object with
* the matching ID if one exists for this hand in this frame; otherwise, an
* invalid Finger object is returned.
*/
Hand.prototype.finger = function(id) {
var finger = this.frame.finger(id);
return (finger && (finger.handId == this.id)) ? finger : Pointable.Invalid;
}
/**
* The angle of rotation around the rotation axis derived from the change in
* orientation of this hand, and any associated fingers and tools, 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).
*
* If a corresponding Hand object is not found in sinceFrame, or if either
* this frame or sinceFrame are invalid Frame objects, then the angle of rotation is zero.
*
* @method rotationAngle
* @memberof Leap.Hand.prototype
* @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation.
* @param {numnber[]} [axis] The axis to measure rotation around.
* @returns {number} A positive value representing the heuristically determined
* rotational change of the hand between the current frame and that specified in
* the sinceFrame parameter.
*/
Hand.prototype.rotationAngle = function(sinceFrame, axis) {
if (!this.valid || !sinceFrame.valid) return 0.0;
var sinceHand = sinceFrame.hand(this.id);
if(!sinceHand.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 change in orientation of this hand, and
* any associated fingers and tools, between the current frame and the specified frame.
*
* The returned direction vector is normalized.
*
* If a corresponding Hand object is not found in sinceFrame, or if either
* this frame or sinceFrame are invalid Frame objects, then this method returns a zero vector.
*
* @method rotationAxis
* @memberof Leap.Hand.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 of the hand between the current frame and that specified in the sinceFrame parameter.
*/
Hand.prototype.rotationAxis = function(sinceFrame) {
if (!this.valid || !sinceFrame.valid) return vec3.create();
var sinceHand = sinceFrame.hand(this.id);
if (!sinceHand.valid) return vec3.create();
return vec3.normalize(vec3.create(), [
this._rotation[7] - sinceHand._rotation[5],
this._rotation[2] - sinceHand._rotation[6],
this._rotation[3] - sinceHand._rotation[1]
]);
}
/**
* The transform matrix expressing the rotation derived from the change in
* orientation of this hand, and any associated fingers and tools, between
* the current frame and the specified frame.
*
* If a corresponding Hand object is not found in sinceFrame, or if either
* this frame or sinceFrame are invalid Frame objects, then this method returns
* an identity matrix.
*
* @method rotationMatrix
* @memberof Leap.Hand.prototype
* @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation.
* @returns {number[]} A transformation Matrix containing the heuristically determined
* rotational change of the hand between the current frame and that specified in the sinceFrame parameter.
*/
Hand.prototype.rotationMatrix = function(sinceFrame) {
if (!this.valid || !sinceFrame.valid) return mat3.create();
var sinceHand = sinceFrame.hand(this.id);
if(!sinceHand.valid) return mat3.create();
var transpose = mat3.transpose(mat3.create(), this._rotation);
var m = mat3.multiply(mat3.create(), sinceHand._rotation, transpose);
return m;
}
/**
* The scale factor derived from the hand's 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 a hand
* and its associated fingers and tools (independent of translation and rotation).
*
* If a corresponding Hand object is not found in sinceFrame, or if either this frame or sinceFrame
* are invalid Frame objects, then this method returns 1.0.
*
* @method scaleFactor
* @memberof Leap.Hand.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 of the hand between the current frame and that specified in the sinceFrame parameter.
*/
Hand.prototype.scaleFactor = function(sinceFrame) {
if (!this.valid || !sinceFrame.valid) return 1.0;
var sinceHand = sinceFrame.hand(this.id);
if(!sinceHand.valid) return 1.0;
return Math.exp(this._scaleFactor - sinceHand._scaleFactor);
}
/**
* The change of position of this hand between the current frame and the specified frame
*
* The returned translation vector provides the magnitude and direction of the
* movement in millimeters.
*
* If a corresponding Hand object is not found in sinceFrame, or if either this frame or
* sinceFrame are invalid Frame objects, then this method returns a zero vector.
*
* @method translation
* @memberof Leap.Hand.prototype
* @param {Leap.Frame} sinceFrame The starting frame for computing the relative translation.
* @returns {number[]} A Vector representing the heuristically determined change in hand
* position between the current frame and that specified in the sinceFrame parameter.
*/
Hand.prototype.translation = function(sinceFrame) {
if (!this.valid || !sinceFrame.valid) return vec3.create();
var sinceHand = sinceFrame.hand(this.id);
if(!sinceHand.valid) return vec3.create();
return [
this._translation[0] - sinceHand._translation[0],
this._translation[1] - sinceHand._translation[1],
this._translation[2] - sinceHand._translation[2]
];
}
/**
* A string containing a brief, human readable description of the Hand object.
* @method toString
* @memberof Leap.Hand.prototype
* @returns {String} A description of the Hand as a string.
*/
Hand.prototype.toString = function() {
return "Hand (" + this.type + ") [ id: "+ this.id + " | palm velocity:"+this.palmVelocity+" | sphere center:"+this.sphereCenter+" ] ";
}
/**
* The pitch angle in radians.
*
* Pitch is the angle between the negative z-axis and the projection of
* the vector onto the y-z plane. In other words, pitch represents rotation
* around the x-axis.
* If the vector points upward, the returned angle is between 0 and pi radians
* (180 degrees); if it points downward, the angle is between 0 and -pi radians.
*
* @method pitch
* @memberof Leap.Hand.prototype
* @returns {number} The angle of this vector above or below the horizon (x-z plane).
*
*/
Hand.prototype.pitch = function() {
return Math.atan2(this.direction[1], -this.direction[2]);
}
/**
* The yaw angle in radians.
*
* Yaw is the angle between the negative z-axis and the projection of
* the vector onto the x-z plane. In other words, yaw represents rotation
* around the y-axis. If the vector points to the right of the negative z-axis,
* then the returned angle is between 0 and pi radians (180 degrees);
* if it points to the left, the angle is between 0 and -pi radians.
*
* @method yaw
* @memberof Leap.Hand.prototype
* @returns {number} The angle of this vector to the right or left of the y-axis.
*
*/
Hand.prototype.yaw = function() {
return Math.atan2(this.direction[0], -this.direction[2]);
}
/**
* The roll angle in radians.
*
* Roll is the angle between the y-axis and the projection of
* the vector onto the x-y plane. In other words, roll represents rotation
* around the z-axis. If the vector points to the left of the y-axis,
* then the returned angle is between 0 and pi radians (180 degrees);
* if it points to the right, the angle is between 0 and -pi radians.
*
* @method roll
* @memberof Leap.Hand.prototype
* @returns {number} The angle of this vector to the right or left of the y-axis.
*
*/
Hand.prototype.roll = function() {
return Math.atan2(this.palmNormal[0], -this.palmNormal[1]);
}
/**
* An invalid Hand object.
*
* You can use an invalid Hand object in comparisons testing
* whether a given Hand instance is valid or invalid. (You can also use the
* Hand valid property.)
*
* @static
* @type {Leap.Hand}
* @name Invalid
* @memberof Leap.Hand
*/
Hand.Invalid = {
valid: false,
fingers: [],
tools: [],
pointables: [],
left: false,
pointable: function() { return Pointable.Invalid },
finger: function() { return Pointable.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(); }
};