1 | var 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 | */
|
29 | var 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 |
|
143 | Frame.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 | */
|
172 | Frame.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 | */
|
220 | Frame.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 | */
|
245 | Frame.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 | */
|
269 | Frame.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 | */
|
293 | Frame.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 | */
|
318 | Frame.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 | */
|
352 | Frame.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 | */
|
377 | Frame.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 | */
|
400 | Frame.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 | */
|
424 | Frame.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 | */
|
436 | Frame.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 | */
|
451 | Frame.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 | */
|
486 | Frame.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 | };
|