UNPKG

14.4 kBJavaScriptView Raw
1var glMatrix = require("gl-matrix")
2 , vec3 = glMatrix.vec3
3 , EventEmitter = require('events').EventEmitter
4 , _ = require('underscore');
5
6/**
7 * Constructs a new Gesture object.
8 *
9 * An uninitialized Gesture object is considered invalid. Get valid instances
10 * of the Gesture class, which will be one of the Gesture subclasses, from a
11 * Frame object.
12 *
13 * @class Gesture
14 * @abstract
15 * @memberof Leap
16 * @classdesc
17 * The Gesture class represents a recognized movement by the user.
18 *
19 * The Leap watches the activity within its field of view for certain movement
20 * patterns typical of a user gesture or command. For example, a movement from side to
21 * side with the hand can indicate a swipe gesture, while a finger poking forward
22 * can indicate a screen tap gesture.
23 *
24 * When the Leap recognizes a gesture, it assigns an ID and adds a
25 * Gesture object to the frame gesture list. For continuous gestures, which
26 * occur over many frames, the Leap updates the gesture by adding
27 * a Gesture object having the same ID and updated properties in each
28 * subsequent frame.
29 *
30 * **Important:** Recognition for each type of gesture must be enabled;
31 * otherwise **no gestures are recognized or reported**.
32 *
33 * Subclasses of Gesture define the properties for the specific movement patterns
34 * recognized by the Leap.
35 *
36 * The Gesture subclasses for include:
37 *
38 * * CircleGesture -- A circular movement by a finger.
39 * * SwipeGesture -- A straight line movement by the hand with fingers extended.
40 * * ScreenTapGesture -- A forward tapping movement by a finger.
41 * * KeyTapGesture -- A downward tapping movement by a finger.
42 *
43 * Circle and swipe gestures are continuous and these objects can have a
44 * state of start, update, and stop.
45 *
46 * The screen tap gesture is a discrete gesture. The Leap only creates a single
47 * ScreenTapGesture object appears for each tap and it always has a stop state.
48 *
49 * Get valid Gesture instances from a Frame object. You can get a list of gestures
50 * from the Frame gestures array. You can also use the Frame gesture() method
51 * to find a gesture in the current frame using an ID value obtained in a
52 * previous frame.
53 *
54 * Gesture objects can be invalid. For example, when you get a gesture by ID
55 * using Frame.gesture(), and there is no gesture with that ID in the current
56 * frame, then gesture() returns an Invalid Gesture object (rather than a null
57 * value). Always check object validity in situations where a gesture might be
58 * invalid.
59 */
60var createGesture = exports.createGesture = function(data) {
61 var gesture;
62 switch (data.type) {
63 case 'circle':
64 gesture = new CircleGesture(data);
65 break;
66 case 'swipe':
67 gesture = new SwipeGesture(data);
68 break;
69 case 'screenTap':
70 gesture = new ScreenTapGesture(data);
71 break;
72 case 'keyTap':
73 gesture = new KeyTapGesture(data);
74 break;
75 default:
76 throw "unknown gesture type";
77 }
78
79 /**
80 * The gesture ID.
81 *
82 * All Gesture objects belonging to the same recognized movement share the
83 * same ID value. Use the ID value with the Frame::gesture() method to
84 * find updates related to this Gesture object in subsequent frames.
85 *
86 * @member id
87 * @memberof Leap.Gesture.prototype
88 * @type {number}
89 */
90 gesture.id = data.id;
91 /**
92 * The list of hands associated with this Gesture, if any.
93 *
94 * If no hands are related to this gesture, the list is empty.
95 *
96 * @member handIds
97 * @memberof Leap.Gesture.prototype
98 * @type {Array}
99 */
100 gesture.handIds = data.handIds.slice();
101 /**
102 * The list of fingers and tools associated with this Gesture, if any.
103 *
104 * If no Pointable objects are related to this gesture, the list is empty.
105 *
106 * @member pointableIds
107 * @memberof Leap.Gesture.prototype
108 * @type {Array}
109 */
110 gesture.pointableIds = data.pointableIds.slice();
111 /**
112 * The elapsed duration of the recognized movement up to the
113 * frame containing this Gesture object, in microseconds.
114 *
115 * The duration reported for the first Gesture in the sequence (with the
116 * start state) will typically be a small positive number since
117 * the movement must progress far enough for the Leap to recognize it as
118 * an intentional gesture.
119 *
120 * @member duration
121 * @memberof Leap.Gesture.prototype
122 * @type {number}
123 */
124 gesture.duration = data.duration;
125 /**
126 * The gesture ID.
127 *
128 * Recognized movements occur over time and have a beginning, a middle,
129 * and an end. The 'state()' attribute reports where in that sequence this
130 * Gesture object falls.
131 *
132 * Possible values for the state field are:
133 *
134 * * start
135 * * update
136 * * stop
137 *
138 * @member state
139 * @memberof Leap.Gesture.prototype
140 * @type {String}
141 */
142 gesture.state = data.state;
143 /**
144 * The gesture type.
145 *
146 * Possible values for the type field are:
147 *
148 * * circle
149 * * swipe
150 * * screenTap
151 * * keyTap
152 *
153 * @member type
154 * @memberof Leap.Gesture.prototype
155 * @type {String}
156 */
157 gesture.type = data.type;
158 return gesture;
159}
160
161/*
162 * Returns a builder object, which uses method chaining for gesture callback binding.
163 */
164var gestureListener = exports.gestureListener = function(controller, type) {
165 var handlers = {};
166 var gestureMap = {};
167
168 controller.on('gesture', function(gesture, frame) {
169 if (gesture.type == type) {
170 if (gesture.state == "start" || gesture.state == "stop") {
171 if (gestureMap[gesture.id] === undefined) {
172 var gestureTracker = new Gesture(gesture, frame);
173 gestureMap[gesture.id] = gestureTracker;
174 _.each(handlers, function(cb, name) {
175 gestureTracker.on(name, cb);
176 });
177 }
178 }
179 gestureMap[gesture.id].update(gesture, frame);
180 if (gesture.state == "stop") {
181 delete gestureMap[gesture.id];
182 }
183 }
184 });
185 var builder = {
186 start: function(cb) {
187 handlers['start'] = cb;
188 return builder;
189 },
190 stop: function(cb) {
191 handlers['stop'] = cb;
192 return builder;
193 },
194 complete: function(cb) {
195 handlers['stop'] = cb;
196 return builder;
197 },
198 update: function(cb) {
199 handlers['update'] = cb;
200 return builder;
201 }
202 }
203 return builder;
204}
205
206var Gesture = exports.Gesture = function(gesture, frame) {
207 this.gestures = [gesture];
208 this.frames = [frame];
209}
210
211Gesture.prototype.update = function(gesture, frame) {
212 this.lastGesture = gesture;
213 this.lastFrame = frame;
214 this.gestures.push(gesture);
215 this.frames.push(frame);
216 this.emit(gesture.state, this);
217}
218
219Gesture.prototype.translation = function() {
220 return vec3.subtract(vec3.create(), this.lastGesture.startPosition, this.lastGesture.position);
221}
222
223_.extend(Gesture.prototype, EventEmitter.prototype);
224
225/**
226 * Constructs a new CircleGesture object.
227 *
228 * An uninitialized CircleGesture object is considered invalid. Get valid instances
229 * of the CircleGesture class from a Frame object.
230 *
231 * @class CircleGesture
232 * @memberof Leap
233 * @augments Leap.Gesture
234 * @classdesc
235 * The CircleGesture classes represents a circular finger movement.
236 *
237 * A circle movement is recognized when the tip of a finger draws a circle
238 * within the Leap field of view.
239 *
240 * ![CircleGesture](images/Leap_Gesture_Circle.png)
241 *
242 * Circle gestures are continuous. The CircleGesture objects for the gesture have
243 * three possible states:
244 *
245 * * start -- The circle gesture has just started. The movement has
246 * progressed far enough for the recognizer to classify it as a circle.
247 * * update -- The circle gesture is continuing.
248 * * stop -- The circle gesture is finished.
249 */
250var CircleGesture = function(data) {
251 /**
252 * The center point of the circle within the Leap frame of reference.
253 *
254 * @member center
255 * @memberof Leap.CircleGesture.prototype
256 * @type {number[]}
257 */
258 this.center = data.center;
259 /**
260 * The normal vector for the circle being traced.
261 *
262 * If you draw the circle clockwise, the normal vector points in the same
263 * general direction as the pointable object drawing the circle. If you draw
264 * the circle counterclockwise, the normal points back toward the
265 * pointable. If the angle between the normal and the pointable object
266 * drawing the circle is less than 90 degrees, then the circle is clockwise.
267 *
268 * ```javascript
269 * var clockwiseness;
270 * if (circle.pointable.direction.angleTo(circle.normal) <= PI/4) {
271 * clockwiseness = "clockwise";
272 * }
273 * else
274 * {
275 * clockwiseness = "counterclockwise";
276 * }
277 * ```
278 *
279 * @member normal
280 * @memberof Leap.CircleGesture.prototype
281 * @type {number[]}
282 */
283 this.normal = data.normal;
284 /**
285 * The number of times the finger tip has traversed the circle.
286 *
287 * Progress is reported as a positive number of the number. For example,
288 * a progress value of .5 indicates that the finger has gone halfway
289 * around, while a value of 3 indicates that the finger has gone around
290 * the the circle three times.
291 *
292 * Progress starts where the circle gesture began. Since the circle
293 * must be partially formed before the Leap can recognize it, progress
294 * will be greater than zero when a circle gesture first appears in the
295 * frame.
296 *
297 * @member progress
298 * @memberof Leap.CircleGesture.prototype
299 * @type {number}
300 */
301 this.progress = data.progress;
302 /**
303 * The radius of the circle in mm.
304 *
305 * @member radius
306 * @memberof Leap.CircleGesture.prototype
307 * @type {number}
308 */
309 this.radius = data.radius;
310}
311
312CircleGesture.prototype.toString = function() {
313 return "CircleGesture ["+JSON.stringify(this)+"]";
314}
315
316/**
317 * Constructs a new SwipeGesture object.
318 *
319 * An uninitialized SwipeGesture object is considered invalid. Get valid instances
320 * of the SwipeGesture class from a Frame object.
321 *
322 * @class SwipeGesture
323 * @memberof Leap
324 * @augments Leap.Gesture
325 * @classdesc
326 * The SwipeGesture class represents a swiping motion of a finger or tool.
327 *
328 * ![SwipeGesture](images/Leap_Gesture_Swipe.png)
329 *
330 * Swipe gestures are continuous.
331 */
332var SwipeGesture = function(data) {
333 /**
334 * The starting position within the Leap frame of
335 * reference, in mm.
336 *
337 * @member startPosition
338 * @memberof Leap.SwipeGesture.prototype
339 * @type {number[]}
340 */
341 this.startPosition = data.startPosition;
342 /**
343 * The current swipe position within the Leap frame of
344 * reference, in mm.
345 *
346 * @member position
347 * @memberof Leap.SwipeGesture.prototype
348 * @type {number[]}
349 */
350 this.position = data.position;
351 /**
352 * The unit direction vector parallel to the swipe motion.
353 *
354 * You can compare the components of the vector to classify the swipe as
355 * appropriate for your application. For example, if you are using swipes
356 * for two dimensional scrolling, you can compare the x and y values to
357 * determine if the swipe is primarily horizontal or vertical.
358 *
359 * @member direction
360 * @memberof Leap.SwipeGesture.prototype
361 * @type {number[]}
362 */
363 this.direction = data.direction;
364 /**
365 * The speed of the finger performing the swipe gesture in
366 * millimeters per second.
367 *
368 * @member speed
369 * @memberof Leap.SwipeGesture.prototype
370 * @type {number}
371 */
372 this.speed = data.speed;
373}
374
375SwipeGesture.prototype.toString = function() {
376 return "SwipeGesture ["+JSON.stringify(this)+"]";
377}
378
379/**
380 * Constructs a new ScreenTapGesture object.
381 *
382 * An uninitialized ScreenTapGesture object is considered invalid. Get valid instances
383 * of the ScreenTapGesture class from a Frame object.
384 *
385 * @class ScreenTapGesture
386 * @memberof Leap
387 * @augments Leap.Gesture
388 * @classdesc
389 * The ScreenTapGesture class represents a tapping gesture by a finger or tool.
390 *
391 * A screen tap gesture is recognized when the tip of a finger pokes forward
392 * and then springs back to approximately the original postion, as if
393 * tapping a vertical screen. The tapping finger must pause briefly before beginning the tap.
394 *
395 * ![ScreenTap](images/Leap_Gesture_Tap2.png)
396 *
397 * ScreenTap gestures are discrete. The ScreenTapGesture object representing a tap always
398 * has the state, STATE_STOP. Only one ScreenTapGesture object is created for each
399 * screen tap gesture recognized.
400 */
401var ScreenTapGesture = function(data) {
402 /**
403 * The position where the screen tap is registered.
404 *
405 * @member position
406 * @memberof Leap.ScreenTapGesture.prototype
407 * @type {number[]}
408 */
409 this.position = data.position;
410 /**
411 * The direction of finger tip motion.
412 *
413 * @member direction
414 * @memberof Leap.ScreenTapGesture.prototype
415 * @type {number[]}
416 */
417 this.direction = data.direction;
418 /**
419 * The progess value is always 1.0 for a screen tap gesture.
420 *
421 * @member progress
422 * @memberof Leap.ScreenTapGesture.prototype
423 * @type {number}
424 */
425 this.progress = data.progress;
426}
427
428ScreenTapGesture.prototype.toString = function() {
429 return "ScreenTapGesture ["+JSON.stringify(this)+"]";
430}
431
432/**
433 * Constructs a new KeyTapGesture object.
434 *
435 * An uninitialized KeyTapGesture object is considered invalid. Get valid instances
436 * of the KeyTapGesture class from a Frame object.
437 *
438 * @class KeyTapGesture
439 * @memberof Leap
440 * @augments Leap.Gesture
441 * @classdesc
442 * The KeyTapGesture class represents a tapping gesture by a finger or tool.
443 *
444 * A key tap gesture is recognized when the tip of a finger rotates down toward the
445 * palm and then springs back to approximately the original postion, as if
446 * tapping. The tapping finger must pause briefly before beginning the tap.
447 *
448 * ![KeyTap](images/Leap_Gesture_Tap.png)
449 *
450 * Key tap gestures are discrete. The KeyTapGesture object representing a tap always
451 * has the state, STATE_STOP. Only one KeyTapGesture object is created for each
452 * key tap gesture recognized.
453 */
454var KeyTapGesture = function(data) {
455 /**
456 * The position where the key tap is registered.
457 *
458 * @member position
459 * @memberof Leap.KeyTapGesture.prototype
460 * @type {number[]}
461 */
462 this.position = data.position;
463 /**
464 * The direction of finger tip motion.
465 *
466 * @member direction
467 * @memberof Leap.KeyTapGesture.prototype
468 * @type {number[]}
469 */
470 this.direction = data.direction;
471 /**
472 * The progess value is always 1.0 for a key tap gesture.
473 *
474 * @member progress
475 * @memberof Leap.KeyTapGesture.prototype
476 * @type {number}
477 */
478 this.progress = data.progress;
479}
480
481KeyTapGesture.prototype.toString = function() {
482 return "KeyTapGesture ["+JSON.stringify(this)+"]";
483}