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