UNPKG

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