1 | var 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 | */
|
29 | var 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 | */
|
219 | Hand.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 | */
|
244 | Hand.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 | */
|
274 | Hand.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 | */
|
300 | Hand.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 | */
|
327 | Hand.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 | */
|
350 | Hand.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 | */
|
367 | Hand.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 | */
|
385 | Hand.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 | */
|
403 | Hand.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 | */
|
421 | Hand.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 | */
|
437 | Hand.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 | };
|