UNPKG

18.7 kBJavaScriptView Raw
1var Hand = require("./hand")
2 , Pointable = require("./pointable")
3 , createGesture = require("./gesture").createGesture
4 , glMatrix = require("gl-matrix")
5 , mat3 = glMatrix.mat3
6 , vec3 = glMatrix.vec3
7 , InteractionBox = require("./interaction_box")
8 , Finger = require('./finger')
9 , _ = require("underscore");
10
11/**
12 * Constructs a Frame object.
13 *
14 * Frame instances created with this constructor are invalid.
15 * Get valid Frame objects by calling the
16 * [Controller.frame]{@link Leap.Controller#frame}() function.
17 *<C-D-Space>
18 * @class Frame
19 * @memberof Leap
20 * @classdesc
21 * The Frame class represents a set of hand and finger tracking data detected
22 * in a single frame.
23 *
24 * The Leap detects hands, fingers and tools within the tracking area, reporting
25 * their positions, orientations and motions in frames at the Leap frame rate.
26 *
27 * Access Frame objects using the [Controller.frame]{@link Leap.Controller#frame}() function.
28 */
29var Frame = module.exports = function(data) {
30 /**
31 * Reports whether this Frame instance is valid.
32 *
33 * A valid Frame is one generated by the Controller object that contains
34 * tracking data for all detected entities. An invalid Frame contains no
35 * actual tracking data, but you can call its functions without risk of a
36 * undefined object exception. The invalid Frame mechanism makes it more
37 * convenient to track individual data across the frame history. For example,
38 * you can invoke:
39 *
40 * ```javascript
41 * var finger = controller.frame(n).finger(fingerID);
42 * ```
43 *
44 * for an arbitrary Frame history value, "n", without first checking whether
45 * frame(n) returned a null object. (You should still check that the
46 * returned Finger instance is valid.)
47 *
48 * @member valid
49 * @memberof Leap.Frame.prototype
50 * @type {Boolean}
51 */
52 this.valid = true;
53 /**
54 * A unique ID for this Frame. Consecutive frames processed by the Leap
55 * have consecutive increasing values.
56 * @member id
57 * @memberof Leap.Frame.prototype
58 * @type {String}
59 */
60 this.id = data.id;
61 /**
62 * The frame capture time in microseconds elapsed since the Leap started.
63 * @member timestamp
64 * @memberof Leap.Frame.prototype
65 * @type {number}
66 */
67 this.timestamp = data.timestamp;
68 /**
69 * The list of Hand objects detected in this frame, given in arbitrary order.
70 * The list can be empty if no hands are detected.
71 *
72 * @member hands[]
73 * @memberof Leap.Frame.prototype
74 * @type {Leap.Hand}
75 */
76 this.hands = [];
77 this.handsMap = {};
78 /**
79 * The list of Pointable objects (fingers and tools) detected in this frame,
80 * given in arbitrary order. The list can be empty if no fingers or tools are
81 * detected.
82 *
83 * @member pointables[]
84 * @memberof Leap.Frame.prototype
85 * @type {Leap.Pointable}
86 */
87 this.pointables = [];
88 /**
89 * The list of Tool objects detected in this frame, given in arbitrary order.
90 * The list can be empty if no tools are detected.
91 *
92 * @member tools[]
93 * @memberof Leap.Frame.prototype
94 * @type {Leap.Pointable}
95 */
96 this.tools = [];
97 /**
98 * The list of Finger objects detected in this frame, given in arbitrary order.
99 * The list can be empty if no fingers are detected.
100 * @member fingers[]
101 * @memberof Leap.Frame.prototype
102 * @type {Leap.Pointable}
103 */
104 this.fingers = [];
105
106 /**
107 * The InteractionBox associated with the current frame.
108 *
109 * @member interactionBox
110 * @memberof Leap.Frame.prototype
111 * @type {Leap.InteractionBox}
112 */
113 if (data.interactionBox) {
114 this.interactionBox = new InteractionBox(data.interactionBox);
115 }
116 this.gestures = [];
117 this.pointablesMap = {};
118 this._translation = data.t;
119 this._rotation = _.flatten(data.r);
120 this._scaleFactor = data.s;
121 this.data = data;
122 this.type = 'frame'; // used by event emitting
123 this.currentFrameRate = data.currentFrameRate;
124
125 if (data.gestures) {
126 /**
127 * The list of Gesture objects detected in this frame, given in arbitrary order.
128 * The list can be empty if no gestures are detected.
129 *
130 * Circle and swipe gestures are updated every frame. Tap gestures
131 * only appear in the list for a single frame.
132 * @member gestures[]
133 * @memberof Leap.Frame.prototype
134 * @type {Leap.Gesture}
135 */
136 for (var gestureIdx = 0, gestureCount = data.gestures.length; gestureIdx != gestureCount; gestureIdx++) {
137 this.gestures.push(createGesture(data.gestures[gestureIdx]));
138 }
139 }
140 this.postprocessData(data);
141};
142
143Frame.prototype.postprocessData = function(data){
144 if (!data) {
145 data = this.data;
146 }
147
148 for (var handIdx = 0, handCount = data.hands.length; handIdx != handCount; handIdx++) {
149 var hand = new Hand(data.hands[handIdx]);
150 hand.frame = this;
151 this.hands.push(hand);
152 this.handsMap[hand.id] = hand;
153 }
154
155 data.pointables = _.sortBy(data.pointables, function(pointable) { return pointable.id });
156
157 for (var pointableIdx = 0, pointableCount = data.pointables.length; pointableIdx != pointableCount; pointableIdx++) {
158 var pointableData = data.pointables[pointableIdx];
159 var pointable = pointableData.dipPosition ? new Finger(pointableData) : new Pointable(pointableData);
160 pointable.frame = this;
161 this.addPointable(pointable);
162 }
163};
164
165/**
166 * Adds data from a pointable element into the pointablesMap;
167 * also adds the pointable to the frame.handsMap hand to which it belongs,
168 * and to the hand's tools or hand's fingers map.
169 *
170 * @param pointable {Object} a Pointable
171 */
172Frame.prototype.addPointable = function (pointable) {
173 this.pointables.push(pointable);
174 this.pointablesMap[pointable.id] = pointable;
175 (pointable.tool ? this.tools : this.fingers).push(pointable);
176 if (pointable.handId !== undefined && this.handsMap.hasOwnProperty(pointable.handId)) {
177 var hand = this.handsMap[pointable.handId];
178 hand.pointables.push(pointable);
179 (pointable.tool ? hand.tools : hand.fingers).push(pointable);
180 switch (pointable.type){
181 case 0:
182 hand.thumb = pointable;
183 break;
184 case 1:
185 hand.indexFinger = pointable;
186 break;
187 case 2:
188 hand.middleFinger = pointable;
189 break;
190 case 3:
191 hand.ringFinger = pointable;
192 break;
193 case 4:
194 hand.pinky = pointable;
195 break;
196 }
197 }
198};
199
200/**
201 * The tool with the specified ID in this frame.
202 *
203 * Use the Frame tool() function to retrieve a tool from
204 * this frame using an ID value obtained from a previous frame.
205 * This function always returns a Pointable object, but if no tool
206 * with the specified ID is present, an invalid Pointable object is returned.
207 *
208 * Note that ID values persist across frames, but only until tracking of a
209 * particular object is lost. If tracking of a tool is lost and subsequently
210 * regained, the new Pointable object representing that tool may have a
211 * different ID than that representing the tool in an earlier frame.
212 *
213 * @method tool
214 * @memberof Leap.Frame.prototype
215 * @param {String} id The ID value of a Tool object from a previous frame.
216 * @returns {Leap.Pointable} The tool with the
217 * matching ID if one exists in this frame; otherwise, an invalid Pointable object
218 * is returned.
219 */
220Frame.prototype.tool = function(id) {
221 var pointable = this.pointable(id);
222 return pointable.tool ? pointable : Pointable.Invalid;
223};
224
225/**
226 * The Pointable object with the specified ID in this frame.
227 *
228 * Use the Frame pointable() function to retrieve the Pointable object from
229 * this frame using an ID value obtained from a previous frame.
230 * This function always returns a Pointable object, but if no finger or tool
231 * with the specified ID is present, an invalid Pointable object is returned.
232 *
233 * Note that ID values persist across frames, but only until tracking of a
234 * particular object is lost. If tracking of a finger or tool is lost and subsequently
235 * regained, the new Pointable object representing that finger or tool may have
236 * a different ID than that representing the finger or tool in an earlier frame.
237 *
238 * @method pointable
239 * @memberof Leap.Frame.prototype
240 * @param {String} id The ID value of a Pointable object from a previous frame.
241 * @returns {Leap.Pointable} The Pointable object with
242 * the matching ID if one exists in this frame;
243 * otherwise, an invalid Pointable object is returned.
244 */
245Frame.prototype.pointable = function(id) {
246 return this.pointablesMap[id] || Pointable.Invalid;
247};
248
249/**
250 * The finger with the specified ID in this frame.
251 *
252 * Use the Frame finger() function to retrieve the finger from
253 * this frame using an ID value obtained from a previous frame.
254 * This function always returns a Finger object, but if no finger
255 * with the specified ID is present, an invalid Pointable object is returned.
256 *
257 * Note that ID values persist across frames, but only until tracking of a
258 * particular object is lost. If tracking of a finger is lost and subsequently
259 * regained, the new Pointable object representing that physical finger may have
260 * a different ID than that representing the finger in an earlier frame.
261 *
262 * @method finger
263 * @memberof Leap.Frame.prototype
264 * @param {String} id The ID value of a finger from a previous frame.
265 * @returns {Leap.Pointable} The finger with the
266 * matching ID if one exists in this frame; otherwise, an invalid Pointable
267 * object is returned.
268 */
269Frame.prototype.finger = function(id) {
270 var pointable = this.pointable(id);
271 return !pointable.tool ? pointable : Pointable.Invalid;
272};
273
274/**
275 * The Hand object with the specified ID in this frame.
276 *
277 * Use the Frame hand() function to retrieve the Hand object from
278 * this frame using an ID value obtained from a previous frame.
279 * This function always returns a Hand object, but if no hand
280 * with the specified ID is present, an invalid Hand object is returned.
281 *
282 * Note that ID values persist across frames, but only until tracking of a
283 * particular object is lost. If tracking of a hand is lost and subsequently
284 * regained, the new Hand object representing that physical hand may have
285 * a different ID than that representing the physical hand in an earlier frame.
286 *
287 * @method hand
288 * @memberof Leap.Frame.prototype
289 * @param {String} id The ID value of a Hand object from a previous frame.
290 * @returns {Leap.Hand} The Hand object with the matching
291 * ID if one exists in this frame; otherwise, an invalid Hand object is returned.
292 */
293Frame.prototype.hand = function(id) {
294 return this.handsMap[id] || Hand.Invalid;
295};
296
297/**
298 * The angle of rotation around the rotation axis derived from the overall
299 * rotational motion between the current frame and the specified frame.
300 *
301 * The returned angle is expressed in radians measured clockwise around
302 * the rotation axis (using the right-hand rule) between the start and end frames.
303 * The value is always between 0 and pi radians (0 and 180 degrees).
304 *
305 * The Leap derives frame rotation from the relative change in position and
306 * orientation of all objects detected in the field of view.
307 *
308 * If either this frame or sinceFrame is an invalid Frame object, then the
309 * angle of rotation is zero.
310 *
311 * @method rotationAngle
312 * @memberof Leap.Frame.prototype
313 * @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation.
314 * @param {number[]} [axis] The axis to measure rotation around.
315 * @returns {number} A positive value containing the heuristically determined
316 * rotational change between the current frame and that specified in the sinceFrame parameter.
317 */
318Frame.prototype.rotationAngle = function(sinceFrame, axis) {
319 if (!this.valid || !sinceFrame.valid) return 0.0;
320
321 var rot = this.rotationMatrix(sinceFrame);
322 var cs = (rot[0] + rot[4] + rot[8] - 1.0)*0.5;
323 var angle = Math.acos(cs);
324 angle = isNaN(angle) ? 0.0 : angle;
325
326 if (axis !== undefined) {
327 var rotAxis = this.rotationAxis(sinceFrame);
328 angle *= vec3.dot(rotAxis, vec3.normalize(vec3.create(), axis));
329 }
330
331 return angle;
332};
333
334/**
335 * The axis of rotation derived from the overall rotational motion between
336 * the current frame and the specified frame.
337 *
338 * The returned direction vector is normalized.
339 *
340 * The Leap derives frame rotation from the relative change in position and
341 * orientation of all objects detected in the field of view.
342 *
343 * If either this frame or sinceFrame is an invalid Frame object, or if no
344 * rotation is detected between the two frames, a zero vector is returned.
345 *
346 * @method rotationAxis
347 * @memberof Leap.Frame.prototype
348 * @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation.
349 * @returns {number[]} A normalized direction vector representing the axis of the heuristically determined
350 * rotational change between the current frame and that specified in the sinceFrame parameter.
351 */
352Frame.prototype.rotationAxis = function(sinceFrame) {
353 if (!this.valid || !sinceFrame.valid) return vec3.create();
354 return vec3.normalize(vec3.create(), [
355 this._rotation[7] - sinceFrame._rotation[5],
356 this._rotation[2] - sinceFrame._rotation[6],
357 this._rotation[3] - sinceFrame._rotation[1]
358 ]);
359}
360
361/**
362 * The transform matrix expressing the rotation derived from the overall
363 * rotational motion between the current frame and the specified frame.
364 *
365 * The Leap derives frame rotation from the relative change in position and
366 * orientation of all objects detected in the field of view.
367 *
368 * If either this frame or sinceFrame is an invalid Frame object, then
369 * this method returns an identity matrix.
370 *
371 * @method rotationMatrix
372 * @memberof Leap.Frame.prototype
373 * @param {Leap.Frame} sinceFrame The starting frame for computing the relative rotation.
374 * @returns {number[]} A transformation matrix containing the heuristically determined
375 * rotational change between the current frame and that specified in the sinceFrame parameter.
376 */
377Frame.prototype.rotationMatrix = function(sinceFrame) {
378 if (!this.valid || !sinceFrame.valid) return mat3.create();
379 var transpose = mat3.transpose(mat3.create(), this._rotation)
380 return mat3.multiply(mat3.create(), sinceFrame._rotation, transpose);
381}
382
383/**
384 * The scale factor derived from the overall motion between the current frame and the specified frame.
385 *
386 * The scale factor is always positive. A value of 1.0 indicates no scaling took place.
387 * Values between 0.0 and 1.0 indicate contraction and values greater than 1.0 indicate expansion.
388 *
389 * The Leap derives scaling from the relative inward or outward motion of all
390 * objects detected in the field of view (independent of translation and rotation).
391 *
392 * If either this frame or sinceFrame is an invalid Frame object, then this method returns 1.0.
393 *
394 * @method scaleFactor
395 * @memberof Leap.Frame.prototype
396 * @param {Leap.Frame} sinceFrame The starting frame for computing the relative scaling.
397 * @returns {number} A positive value representing the heuristically determined
398 * scaling change ratio between the current frame and that specified in the sinceFrame parameter.
399 */
400Frame.prototype.scaleFactor = function(sinceFrame) {
401 if (!this.valid || !sinceFrame.valid) return 1.0;
402 return Math.exp(this._scaleFactor - sinceFrame._scaleFactor);
403}
404
405/**
406 * The change of position derived from the overall linear motion between the
407 * current frame and the specified frame.
408 *
409 * The returned translation vector provides the magnitude and direction of the
410 * movement in millimeters.
411 *
412 * The Leap derives frame translation from the linear motion of all objects
413 * detected in the field of view.
414 *
415 * If either this frame or sinceFrame is an invalid Frame object, then this
416 * method returns a zero vector.
417 *
418 * @method translation
419 * @memberof Leap.Frame.prototype
420 * @param {Leap.Frame} sinceFrame The starting frame for computing the relative translation.
421 * @returns {number[]} A vector representing the heuristically determined change in
422 * position of all objects between the current frame and that specified in the sinceFrame parameter.
423 */
424Frame.prototype.translation = function(sinceFrame) {
425 if (!this.valid || !sinceFrame.valid) return vec3.create();
426 return vec3.subtract(vec3.create(), this._translation, sinceFrame._translation);
427}
428
429/**
430 * A string containing a brief, human readable description of the Frame object.
431 *
432 * @method toString
433 * @memberof Leap.Frame.prototype
434 * @returns {String} A brief description of this frame.
435 */
436Frame.prototype.toString = function() {
437 var str = "Frame [ id:"+this.id+" | timestamp:"+this.timestamp+" | Hand count:("+this.hands.length+") | Pointable count:("+this.pointables.length+")";
438 if (this.gestures) str += " | Gesture count:("+this.gestures.length+")";
439 str += " ]";
440 return str;
441}
442
443/**
444 * Returns a JSON-formatted string containing the hands, pointables and gestures
445 * in this frame.
446 *
447 * @method dump
448 * @memberof Leap.Frame.prototype
449 * @returns {String} A JSON-formatted string.
450 */
451Frame.prototype.dump = function() {
452 var out = '';
453 out += "Frame Info:<br/>";
454 out += this.toString();
455 out += "<br/><br/>Hands:<br/>"
456 for (var handIdx = 0, handCount = this.hands.length; handIdx != handCount; handIdx++) {
457 out += " "+ this.hands[handIdx].toString() + "<br/>";
458 }
459 out += "<br/><br/>Pointables:<br/>";
460 for (var pointableIdx = 0, pointableCount = this.pointables.length; pointableIdx != pointableCount; pointableIdx++) {
461 out += " "+ this.pointables[pointableIdx].toString() + "<br/>";
462 }
463 if (this.gestures) {
464 out += "<br/><br/>Gestures:<br/>";
465 for (var gestureIdx = 0, gestureCount = this.gestures.length; gestureIdx != gestureCount; gestureIdx++) {
466 out += " "+ this.gestures[gestureIdx].toString() + "<br/>";
467 }
468 }
469 out += "<br/><br/>Raw JSON:<br/>";
470 out += JSON.stringify(this.data);
471 return out;
472}
473
474/**
475 * An invalid Frame object.
476 *
477 * You can use this invalid Frame in comparisons testing
478 * whether a given Frame instance is valid or invalid. (You can also check the
479 * [Frame.valid]{@link Leap.Frame#valid} property.)
480 *
481 * @static
482 * @type {Leap.Frame}
483 * @name Invalid
484 * @memberof Leap.Frame
485 */
486Frame.Invalid = {
487 valid: false,
488 hands: [],
489 fingers: [],
490 tools: [],
491 gestures: [],
492 pointables: [],
493 pointable: function() { return Pointable.Invalid },
494 finger: function() { return Pointable.Invalid },
495 hand: function() { return Hand.Invalid },
496 toString: function() { return "invalid frame" },
497 dump: function() { return this.toString() },
498 rotationAngle: function() { return 0.0; },
499 rotationMatrix: function() { return mat3.create(); },
500 rotationAxis: function() { return vec3.create(); },
501 scaleFactor: function() { return 1.0; },
502 translation: function() { return vec3.create(); }
503};