1 | var 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 | */
|
28 | var 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 | */
|
193 | Hand.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 | */
|
218 | Hand.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 | */
|
248 | Hand.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 | */
|
274 | Hand.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 | */
|
301 | Hand.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 | */
|
324 | Hand.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 | */
|
341 | Hand.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 | */
|
359 | Hand.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 | */
|
377 | Hand.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 | */
|
395 | Hand.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 | */
|
411 | Hand.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 | };
|