UNPKG

16.5 kBJavaScriptView Raw
1var Pointable = require("./pointable")
2 , Bone = require('./bone')
3 , glMatrix = require("gl-matrix")
4 , mat3 = glMatrix.mat3
5 , vec3 = glMatrix.vec3
6 , _ = require("underscore");
7
8/**
9 * Constructs a Hand object.
10 *
11 * An uninitialized hand is considered invalid.
12 * Get valid Hand objects from a Frame object.
13 * @class Hand
14 * @memberof Leap
15 * @classdesc
16 * The Hand class reports the physical characteristics of a detected hand.
17 *
18 * Hand tracking data includes a palm position and velocity; vectors for
19 * the palm normal and direction to the fingers; properties of a sphere fit
20 * to the hand; and lists of the attached fingers and tools.
21 *
22 * Note that Hand objects can be invalid, which means that they do not contain
23 * valid tracking data and do not correspond to a physical entity. Invalid Hand
24 * objects can be the result of asking for a Hand object using an ID from an
25 * earlier frame when no Hand objects with that ID exist in the current frame.
26 * A Hand object created from the Hand constructor is also invalid.
27 * Test for validity with the [Hand.valid]{@link Leap.Hand#valid} property.
28 */
29var Hand = module.exports = function(data) {
30 /**
31 * A unique ID assigned to this Hand object, whose value remains the same
32 * across consecutive frames while the tracked hand remains visible. If
33 * tracking is lost (for example, when a hand is occluded by another hand
34 * or when it is withdrawn from or reaches the edge of the Leap field of view),
35 * the Leap may assign a new ID when it detects the hand in a future frame.
36 *
37 * Use the ID value with the {@link Frame.hand}() function to find this
38 * Hand object in future frames.
39 *
40 * @member id
41 * @memberof Leap.Hand.prototype
42 * @type {String}
43 */
44 this.id = data.id;
45 /**
46 * The center position of the palm in millimeters from the Leap origin.
47 * @member palmPosition
48 * @memberof Leap.Hand.prototype
49 * @type {number[]}
50 */
51 this.palmPosition = data.palmPosition;
52 /**
53 * The direction from the palm position toward the fingers.
54 *
55 * The direction is expressed as a unit vector pointing in the same
56 * direction as the directed line from the palm position to the fingers.
57 *
58 * @member direction
59 * @memberof Leap.Hand.prototype
60 * @type {number[]}
61 */
62 this.direction = data.direction;
63 /**
64 * The rate of change of the palm position in millimeters/second.
65 *
66 * @member palmVeclocity
67 * @memberof Leap.Hand.prototype
68 * @type {number[]}
69 */
70 this.palmVelocity = data.palmVelocity;
71 /**
72 * The normal vector to the palm. If your hand is flat, this vector will
73 * point downward, or "out" of the front surface of your palm.
74 *
75 * ![Palm Vectors](images/Leap_Palm_Vectors.png)
76 *
77 * The direction is expressed as a unit vector pointing in the same
78 * direction as the palm normal (that is, a vector orthogonal to the palm).
79 * @member palmNormal
80 * @memberof Leap.Hand.prototype
81 * @type {number[]}
82 */
83 this.palmNormal = data.palmNormal;
84 /**
85 * The center of a sphere fit to the curvature of this hand.
86 *
87 * This sphere is placed roughly as if the hand were holding a ball.
88 *
89 * ![Hand Ball](images/Leap_Hand_Ball.png)
90 * @member sphereCenter
91 * @memberof Leap.Hand.prototype
92 * @type {number[]}
93 */
94 this.sphereCenter = data.sphereCenter;
95 /**
96 * The radius of a sphere fit to the curvature of this hand, in millimeters.
97 *
98 * This sphere is placed roughly as if the hand were holding a ball. Thus the
99 * size of the sphere decreases as the fingers are curled into a fist.
100 *
101 * @member sphereRadius
102 * @memberof Leap.Hand.prototype
103 * @type {number}
104 */
105 this.sphereRadius = data.sphereRadius;
106 /**
107 * Reports whether this is a valid Hand object.
108 *
109 * @member valid
110 * @memberof Leap.Hand.prototype
111 * @type {boolean}
112 */
113 this.valid = true;
114 /**
115 * The list of Pointable objects (fingers and tools) detected in this frame
116 * that are associated with this hand, given in arbitrary order. The list
117 * can be empty if no fingers or tools associated with this hand are detected.
118 *
119 * Use the {@link Pointable} tool property to determine
120 * whether or not an item in the list represents a tool or finger.
121 * You can also get only the tools using the Hand.tools[] list or
122 * only the fingers using the Hand.fingers[] list.
123 *
124 * @member pointables[]
125 * @memberof Leap.Hand.prototype
126 * @type {Leap.Pointable[]}
127 */
128 this.pointables = [];
129 /**
130 * The list of fingers detected in this frame that are attached to
131 * this hand, given in arbitrary order.
132 *
133 * The list can be empty if no fingers attached to this hand are detected.
134 *
135 * @member fingers[]
136 * @memberof Leap.Hand.prototype
137 * @type {Leap.Pointable[]}
138 */
139 this.fingers = [];
140
141 if (data.armBasis){
142 this.arm = new Bone(this, {
143 type: 4,
144 width: data.armWidth,
145 prevJoint: data.elbow,
146 nextJoint: data.wrist,
147 basis: data.armBasis
148 });
149 }else{
150 this.arm = null;
151 }
152
153 /**
154 * The list of tools detected in this frame that are held by this
155 * hand, given in arbitrary order.
156 *
157 * The list can be empty if no tools held by this hand are detected.
158 *
159 * @member tools[]
160 * @memberof Leap.Hand.prototype
161 * @type {Leap.Pointable[]}
162 */
163 this.tools = [];
164 this._translation = data.t;
165 this._rotation = _.flatten(data.r);
166 this._scaleFactor = data.s;
167
168 /**
169 * Time the hand has been visible in seconds.
170 *
171 * @member timeVisible
172 * @memberof Leap.Hand.prototype
173 * @type {number}
174 */
175 this.timeVisible = data.timeVisible;
176
177 /**
178 * The palm position with stabalization
179 * @member stabilizedPalmPosition
180 * @memberof Leap.Hand.prototype
181 * @type {number[]}
182 */
183 this.stabilizedPalmPosition = data.stabilizedPalmPosition;
184
185 /**
186 * Reports whether this is a left or a right hand.
187 *
188 * @member type
189 * @type {String}
190 * @memberof Leap.Hand.prototype
191 */
192 this.type = data.type;
193 this.grabStrength = data.grabStrength;
194 this.pinchStrength = data.pinchStrength;
195 this.confidence = data.confidence;
196}
197
198/**
199 * The finger with the specified ID attached to this hand.
200 *
201 * Use this function to retrieve a Pointable object representing a finger
202 * attached to this hand using an ID value obtained from a previous frame.
203 * This function always returns a Pointable object, but if no finger
204 * with the specified ID is present, an invalid Pointable object is returned.
205 *
206 * Note that the ID values assigned to fingers persist across frames, but only
207 * until tracking of a particular finger is lost. If tracking of a finger is
208 * lost and subsequently regained, the new Finger object representing that
209 * finger may have a different ID than that representing the finger in an
210 * earlier frame.
211 *
212 * @method finger
213 * @memberof Leap.Hand.prototype
214 * @param {String} id The ID value of a finger from a previous frame.
215 * @returns {Leap.Pointable} The Finger object with
216 * the matching ID if one exists for this hand in this frame; otherwise, an
217 * invalid Finger object is returned.
218 */
219Hand.prototype.finger = function(id) {
220 var finger = this.frame.finger(id);
221 return (finger && (finger.handId == this.id)) ? finger : Pointable.Invalid;
222}
223
224/**
225 * The angle of rotation around the rotation axis derived from the change in
226 * orientation of this hand, and any associated fingers and tools, between the
227 * current frame and the specified frame.
228 *
229 * The returned angle is expressed in radians measured clockwise around the
230 * rotation axis (using the right-hand rule) between the start and end frames.
231 * The value is always between 0 and pi radians (0 and 180 degrees).
232 *
233 * If a corresponding Hand object is not found in sinceFrame, or if either
234 * this frame or sinceFrame are invalid Frame objects, then the angle of rotation is zero.
235 *
236 * @method rotationAngle
237 * @memberof Leap.Hand.prototype
238 * @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation.
239 * @param {numnber[]} [axis] The axis to measure rotation around.
240 * @returns {number} A positive value representing the heuristically determined
241 * rotational change of the hand between the current frame and that specified in
242 * the sinceFrame parameter.
243 */
244Hand.prototype.rotationAngle = function(sinceFrame, axis) {
245 if (!this.valid || !sinceFrame.valid) return 0.0;
246 var sinceHand = sinceFrame.hand(this.id);
247 if(!sinceHand.valid) return 0.0;
248 var rot = this.rotationMatrix(sinceFrame);
249 var cs = (rot[0] + rot[4] + rot[8] - 1.0)*0.5
250 var angle = Math.acos(cs);
251 angle = isNaN(angle) ? 0.0 : angle;
252 if (axis !== undefined) {
253 var rotAxis = this.rotationAxis(sinceFrame);
254 angle *= vec3.dot(rotAxis, vec3.normalize(vec3.create(), axis));
255 }
256 return angle;
257}
258
259/**
260 * The axis of rotation derived from the change in orientation of this hand, and
261 * any associated fingers and tools, between the current frame and the specified frame.
262 *
263 * The returned direction vector is normalized.
264 *
265 * If a corresponding Hand object is not found in sinceFrame, or if either
266 * this frame or sinceFrame are invalid Frame objects, then this method returns a zero vector.
267 *
268 * @method rotationAxis
269 * @memberof Leap.Hand.prototype
270 * @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation.
271 * @returns {number[]} A normalized direction Vector representing the axis of the heuristically determined
272 * rotational change of the hand between the current frame and that specified in the sinceFrame parameter.
273 */
274Hand.prototype.rotationAxis = function(sinceFrame) {
275 if (!this.valid || !sinceFrame.valid) return vec3.create();
276 var sinceHand = sinceFrame.hand(this.id);
277 if (!sinceHand.valid) return vec3.create();
278 return vec3.normalize(vec3.create(), [
279 this._rotation[7] - sinceHand._rotation[5],
280 this._rotation[2] - sinceHand._rotation[6],
281 this._rotation[3] - sinceHand._rotation[1]
282 ]);
283}
284
285/**
286 * The transform matrix expressing the rotation derived from the change in
287 * orientation of this hand, and any associated fingers and tools, between
288 * the current frame and the specified frame.
289 *
290 * If a corresponding Hand object is not found in sinceFrame, or if either
291 * this frame or sinceFrame are invalid Frame objects, then this method returns
292 * an identity matrix.
293 *
294 * @method rotationMatrix
295 * @memberof Leap.Hand.prototype
296 * @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation.
297 * @returns {number[]} A transformation Matrix containing the heuristically determined
298 * rotational change of the hand between the current frame and that specified in the sinceFrame parameter.
299 */
300Hand.prototype.rotationMatrix = function(sinceFrame) {
301 if (!this.valid || !sinceFrame.valid) return mat3.create();
302 var sinceHand = sinceFrame.hand(this.id);
303 if(!sinceHand.valid) return mat3.create();
304 var transpose = mat3.transpose(mat3.create(), this._rotation);
305 var m = mat3.multiply(mat3.create(), sinceHand._rotation, transpose);
306 return m;
307}
308
309/**
310 * The scale factor derived from the hand's motion between the current frame and the specified frame.
311 *
312 * The scale factor is always positive. A value of 1.0 indicates no scaling took place.
313 * Values between 0.0 and 1.0 indicate contraction and values greater than 1.0 indicate expansion.
314 *
315 * The Leap derives scaling from the relative inward or outward motion of a hand
316 * and its associated fingers and tools (independent of translation and rotation).
317 *
318 * If a corresponding Hand object is not found in sinceFrame, or if either this frame or sinceFrame
319 * are invalid Frame objects, then this method returns 1.0.
320 *
321 * @method scaleFactor
322 * @memberof Leap.Hand.prototype
323 * @param {Leap.Frame} sinceFrame The starting frame for computing the relative scaling.
324 * @returns {number} A positive value representing the heuristically determined
325 * scaling change ratio of the hand between the current frame and that specified in the sinceFrame parameter.
326 */
327Hand.prototype.scaleFactor = function(sinceFrame) {
328 if (!this.valid || !sinceFrame.valid) return 1.0;
329 var sinceHand = sinceFrame.hand(this.id);
330 if(!sinceHand.valid) return 1.0;
331
332 return Math.exp(this._scaleFactor - sinceHand._scaleFactor);
333}
334
335/**
336 * The change of position of this hand between the current frame and the specified frame
337 *
338 * The returned translation vector provides the magnitude and direction of the
339 * movement in millimeters.
340 *
341 * If a corresponding Hand object is not found in sinceFrame, or if either this frame or
342 * sinceFrame are invalid Frame objects, then this method returns a zero vector.
343 *
344 * @method translation
345 * @memberof Leap.Hand.prototype
346 * @param {Leap.Frame} sinceFrame The starting frame for computing the relative translation.
347 * @returns {number[]} A Vector representing the heuristically determined change in hand
348 * position between the current frame and that specified in the sinceFrame parameter.
349 */
350Hand.prototype.translation = function(sinceFrame) {
351 if (!this.valid || !sinceFrame.valid) return vec3.create();
352 var sinceHand = sinceFrame.hand(this.id);
353 if(!sinceHand.valid) return vec3.create();
354 return [
355 this._translation[0] - sinceHand._translation[0],
356 this._translation[1] - sinceHand._translation[1],
357 this._translation[2] - sinceHand._translation[2]
358 ];
359}
360
361/**
362 * A string containing a brief, human readable description of the Hand object.
363 * @method toString
364 * @memberof Leap.Hand.prototype
365 * @returns {String} A description of the Hand as a string.
366 */
367Hand.prototype.toString = function() {
368 return "Hand (" + this.type + ") [ id: "+ this.id + " | palm velocity:"+this.palmVelocity+" | sphere center:"+this.sphereCenter+" ] ";
369}
370
371/**
372 * The pitch angle in radians.
373 *
374 * Pitch is the angle between the negative z-axis and the projection of
375 * the vector onto the y-z plane. In other words, pitch represents rotation
376 * around the x-axis.
377 * If the vector points upward, the returned angle is between 0 and pi radians
378 * (180 degrees); if it points downward, the angle is between 0 and -pi radians.
379 *
380 * @method pitch
381 * @memberof Leap.Hand.prototype
382 * @returns {number} The angle of this vector above or below the horizon (x-z plane).
383 *
384 */
385Hand.prototype.pitch = function() {
386 return Math.atan2(this.direction[1], -this.direction[2]);
387}
388
389/**
390 * The yaw angle in radians.
391 *
392 * Yaw is the angle between the negative z-axis and the projection of
393 * the vector onto the x-z plane. In other words, yaw represents rotation
394 * around the y-axis. If the vector points to the right of the negative z-axis,
395 * then the returned angle is between 0 and pi radians (180 degrees);
396 * if it points to the left, the angle is between 0 and -pi radians.
397 *
398 * @method yaw
399 * @memberof Leap.Hand.prototype
400 * @returns {number} The angle of this vector to the right or left of the y-axis.
401 *
402 */
403Hand.prototype.yaw = function() {
404 return Math.atan2(this.direction[0], -this.direction[2]);
405}
406
407/**
408 * The roll angle in radians.
409 *
410 * Roll is the angle between the y-axis and the projection of
411 * the vector onto the x-y plane. In other words, roll represents rotation
412 * around the z-axis. If the vector points to the left of the y-axis,
413 * then the returned angle is between 0 and pi radians (180 degrees);
414 * if it points to the right, the angle is between 0 and -pi radians.
415 *
416 * @method roll
417 * @memberof Leap.Hand.prototype
418 * @returns {number} The angle of this vector to the right or left of the y-axis.
419 *
420 */
421Hand.prototype.roll = function() {
422 return Math.atan2(this.palmNormal[0], -this.palmNormal[1]);
423}
424
425/**
426 * An invalid Hand object.
427 *
428 * You can use an invalid Hand object in comparisons testing
429 * whether a given Hand instance is valid or invalid. (You can also use the
430 * Hand valid property.)
431 *
432 * @static
433 * @type {Leap.Hand}
434 * @name Invalid
435 * @memberof Leap.Hand
436 */
437Hand.Invalid = {
438 valid: false,
439 fingers: [],
440 tools: [],
441 pointables: [],
442 left: false,
443 pointable: function() { return Pointable.Invalid },
444 finger: function() { return Pointable.Invalid },
445 toString: function() { return "invalid frame" },
446 dump: function() { return this.toString(); },
447 rotationAngle: function() { return 0.0; },
448 rotationMatrix: function() { return mat3.create(); },
449 rotationAxis: function() { return vec3.create(); },
450 scaleFactor: function() { return 1.0; },
451 translation: function() { return vec3.create(); }
452};