UNPKG

leapmotion-ts

Version:

TypeScript framework for Leap Motion.

528 lines (471 loc) 22.9 kB
/// <reference path="./util/EventDispatcher.ts"/> /// <reference path="./interfaces/DefaultListener.ts"/> /// <reference path="./util/LeapEvent.ts"/> /// <reference path="./Frame.ts"/> /// <reference path="./CircleGesture.ts"/> /// <reference path="./KeyTapGesture.ts"/> /// <reference path="./ScreenTapGesture.ts"/> /// <reference path="./SwipeGesture.ts"/> /** * The Controller class is your main interface to the Leap Motion Controller. * * <p>Create an instance of this Controller class to access frames of tracking * data and configuration information. Frame data can be polled at any time using * the <code>Controller::frame()</code> . Call <code>frame()</code> or <code>frame(0)</code> * to get the most recent frame. Set the history parameter to a positive integer * to access previous frames. A controller stores up to 60 frames in its frame history.</p> * * <p>Polling is an appropriate strategy for applications which already have an * intrinsic update loop, such as a game. You can also implement the Leap::Listener * interface to handle events as they occur. The Leap dispatches events to the listener * upon initialization and exiting, on connection changes, and when a new frame * of tracking data is available. When these events occur, the controller object * invokes the appropriate callback defined in the Listener interface.</p> * * <p>To access frames of tracking data as they become available:</p> * * <ul> * <li>Implement the Listener interface and override the <code>Listener::onFrame()</code> .</li> * <li>In your <code>Listener::onFrame()</code> , call the <code>Controller::frame()</code> to access the newest frame of tracking data.</li> * <li>To start receiving frames, create a Controller object and add event listeners to the <code>Controller::addEventListener()</code> .</li> * </ul> * * <p>When an instance of a Controller object has been initialized, * it calls the <code>Listener::onInit()</code> when the listener is ready for use. * When a connection is established between the controller and the Leap, * the controller calls the <code>Listener::onConnect()</code> . At this point, * your application will start receiving frames of data. The controller calls * the <code>Listener::onFrame()</code> each time a new frame is available. * If the controller loses its connection with the Leap software or * device for any reason, it calls the <code>Listener::onDisconnect()</code> . * If the listener is removed from the controller or the controller is destroyed, * it calls the <code>Listener::onExit()</code> . At that point, unless the listener * is added to another controller again, it will no longer receive frames of tracking data.</p> * * @author logotype * */ export class Controller extends EventDispatcher { /** * @private * The Listener subclass instance. */ private listener:Listener; /** * @private * History of frame of tracking data from the Leap. */ public frameHistory:Frame[] = []; /** * Most recent received Frame. */ private latestFrame:Frame; /** * Socket connection. */ public connection:WebSocket; /** * @private * Reports whether this Controller is connected to the Leap Motion Controller. */ public _isConnected:boolean = false; /** * @private * Reports whether gestures is enabled. */ public _isGesturesEnabled:boolean = false; /** * Constructs a Controller object. * @param host IP or hostname of the computer running the Leap software. * (currently only supported for socket connections). * */ constructor( host:string = null ) { super(); this.listener = new DefaultListener(); if( !host ) { this.connection = new WebSocket("ws://localhost:6437/v6.json"); } else { this.connection = new WebSocket("ws://" + host + ":6437/v6.json"); } this.listener.onInit( this ); this.connection.onopen = ( event:Event ) => { this._isConnected = true; this.listener.onConnect( this ); var focusedState:any = {}; focusedState.focused = true; this.connection.send( JSON.stringify( focusedState ) ); }; this.connection.onclose = ( data:Object ) => { this._isConnected = false; this.listener.onDisconnect( this ); }; this.connection.onmessage = ( data:any ) => { var i:number; var json:any; var currentFrame:Frame; var hand:Hand; var pointable:Pointable; var gesture:Gesture; var isTool:boolean; var length:number; var type:number; json = JSON.parse( data.data ); currentFrame = new Frame(); currentFrame.controller = this; // Hands if ( typeof json.hands !== "undefined" ) { i = 0; length = json.hands.length; for ( i = 0; i < length; i++ ) { hand = new Hand(); hand.frame = currentFrame; hand.direction = new Vector3( json.hands[ i ].direction[ 0 ], json.hands[ i ].direction[ 1 ], json.hands[ i ].direction[ 2 ] ); hand.id = json.hands[ i ].id; hand.palmNormal = new Vector3( json.hands[ i ].palmNormal[ 0 ], json.hands[ i ].palmNormal[ 1 ], json.hands[ i ].palmNormal[ 2 ] ); hand.palmPosition = new Vector3( json.hands[ i ].palmPosition[ 0 ], json.hands[ i ].palmPosition[ 1 ], json.hands[ i ].palmPosition[ 2 ] ); hand.stabilizedPalmPosition = new Vector3( json.hands[ i ].stabilizedPalmPosition[ 0 ], json.hands[ i ].stabilizedPalmPosition[ 1 ], json.hands[ i ].stabilizedPalmPosition[ 2 ] ); hand.palmVelocity = new Vector3( json.hands[ i ].palmPosition[ 0 ], json.hands[ i ].palmPosition[ 1 ], json.hands[ i ].palmPosition[ 2 ] ); hand.rotation = new Matrix( new Vector3( json.hands[ i ].r[ 0 ][ 0 ], json.hands[ i ].r[ 0 ][ 1 ], json.hands[ i ].r[ 0 ][ 2 ] ), new Vector3( json.hands[ i ].r[ 1 ][ 0 ], json.hands[ i ].r[ 1 ][ 1 ], json.hands[ i ].r[ 1 ][ 2 ] ), new Vector3( json.hands[ i ].r[ 2 ][ 0 ], json.hands[ i ].r[ 2 ][ 1 ], json.hands[ i ].r[ 2 ][ 2 ] ) ); hand.scaleFactorNumber = json.hands[ i ].s; hand.sphereCenter = new Vector3( json.hands[ i ].sphereCenter[ 0 ], json.hands[ i ].sphereCenter[ 1 ], json.hands[ i ].sphereCenter[ 2 ] ); hand.sphereRadius = json.hands[ i ].sphereRadius; hand.timeVisible = json.hands[ i ].timeVisible; hand.translationVector = new Vector3( json.hands[ i ].t[ 0 ], json.hands[ i ].t[ 1 ], json.hands[ i ].t[ 2 ] ); currentFrame.hands.push( hand ); } } currentFrame.id = json.id; currentFrame.currentFramesPerSecond = json.currentFramesPerSecond; // InteractionBox if ( typeof json.interactionBox !== "undefined" ) { currentFrame.interactionBox = new InteractionBox(); currentFrame.interactionBox.center = new Vector3( json.interactionBox.center[ 0 ], json.interactionBox.center[ 1 ], json.interactionBox.center[ 2 ] ); currentFrame.interactionBox.width = json.interactionBox.size[ 0 ]; currentFrame.interactionBox.height = json.interactionBox.size[ 1 ]; currentFrame.interactionBox.depth = json.interactionBox.size[ 2 ]; } // Pointables if ( typeof json.pointables !== "undefined" ) { i = 0; length = json.pointables.length; for ( i = 0; i < length; i++ ) { isTool = json.pointables[ i ].tool; if ( isTool ) pointable = new Tool(); else pointable = new Finger(); pointable.frame = currentFrame; pointable.id = json.pointables[ i ].id; pointable.hand = Controller.getHandByID( currentFrame, json.pointables[ i ].handId ); pointable.length = json.pointables[ i ].length; pointable.direction = new Vector3( json.pointables[ i ].direction[ 0 ], json.pointables[ i ].direction[ 1 ], json.pointables[ i ].direction[ 2 ] ); pointable.tipPosition = new Vector3( json.pointables[ i ].tipPosition[ 0 ], json.pointables[ i ].tipPosition[ 1 ], json.pointables[ i ].tipPosition[ 2 ] ); pointable.stabilizedTipPosition = new Vector3( json.pointables[ i ].stabilizedTipPosition[ 0 ], json.pointables[ i ].stabilizedTipPosition[ 1 ], json.pointables[ i ].stabilizedTipPosition[ 2 ] ); pointable.tipVelocity = new Vector3( json.pointables[ i ].tipVelocity[ 0 ], json.pointables[ i ].tipVelocity[ 1 ], json.pointables[ i ].tipVelocity[ 2 ] ); pointable.touchDistance = json.pointables[ i ].touchDistance; pointable.timeVisible = json.pointables[ i ].timeVisible; currentFrame.pointables.push( pointable ); switch( json.pointables[ i ].touchZone ) { case "hovering": pointable.touchZone = Zone.ZONE_HOVERING; break; case "touching": pointable.touchZone = Zone.ZONE_TOUCHING; break; default: pointable.touchZone = Zone.ZONE_NONE; break; } if ( pointable.hand ) pointable.hand.pointables.push( pointable ); if ( isTool ) { pointable.isTool = true; pointable.isFinger = false; pointable.width = json.pointables[ i ].width; currentFrame.tools.push( <Tool>pointable ); if ( pointable.hand ) pointable.hand.tools.push( <Tool>pointable ); } else { pointable.isTool = false; pointable.isFinger = true; currentFrame.fingers.push( <Finger>pointable ); if ( pointable.hand ) pointable.hand.fingers.push( <Finger>pointable ); } } } // Gestures if ( typeof json.gestures !== "undefined" ) { i = 0; length = json.gestures.length; for ( i = 0; i < length; i++ ) { switch( json.gestures[ i ].type ) { case "circle": gesture = new CircleGesture(); type = Type.TYPE_CIRCLE; var circle:CircleGesture = <CircleGesture>gesture; circle.center = new Vector3( json.gestures[ i ].center[ 0 ], json.gestures[ i ].center[ 1 ], json.gestures[ i ].center[ 2 ] ); circle.normal = new Vector3( json.gestures[ i ].normal[ 0 ], json.gestures[ i ].normal[ 1 ], json.gestures[ i ].normal[ 2 ] ); circle.progress = json.gestures[ i ].progress; circle.radius = json.gestures[ i ].radius; break; case "swipe": gesture = new SwipeGesture(); type = Type.TYPE_SWIPE; var swipe:SwipeGesture = <SwipeGesture>gesture; swipe.startPosition = new Vector3( json.gestures[ i ].startPosition[ 0 ], json.gestures[ i ].startPosition[ 1 ], json.gestures[ i ].startPosition[ 2 ] ); swipe.position = new Vector3( json.gestures[ i ].position[ 0 ], json.gestures[ i ].position[ 1 ], json.gestures[ i ].position[ 2 ] ); swipe.direction = new Vector3( json.gestures[ i ].direction[ 0 ], json.gestures[ i ].direction[ 1 ], json.gestures[ i ].direction[ 2 ] ); swipe.speed = json.gestures[ i ].speed; break; case "screenTap": gesture = new ScreenTapGesture(); type = Type.TYPE_SCREEN_TAP; var screenTap:ScreenTapGesture = <ScreenTapGesture>gesture; screenTap.position = new Vector3( json.gestures[ i ].position[ 0 ], json.gestures[ i ].position[ 1 ], json.gestures[ i ].position[ 2 ] ); screenTap.direction = new Vector3( json.gestures[ i ].direction[ 0 ], json.gestures[ i ].direction[ 1 ], json.gestures[ i ].direction[ 2 ] ); screenTap.progress = json.gestures[ i ].progress; break; case "keyTap": gesture = new KeyTapGesture(); type = Type.TYPE_KEY_TAP; var keyTap:KeyTapGesture = <KeyTapGesture>gesture; keyTap.position = new Vector3( json.gestures[ i ].position[ 0 ], json.gestures[ i ].position[ 1 ], json.gestures[ i ].position[ 2 ] ); keyTap.direction = new Vector3( json.gestures[ i ].direction[ 0 ], json.gestures[ i ].direction[ 1 ], json.gestures[ i ].direction[ 2 ] ); keyTap.progress = json.gestures[ i ].progress; break; default: throw new Error( "unkown gesture type" ); } var j:number = 0; var lengthInner:number = 0; if( typeof json.gestures[ i ].handIds !== "undefined" ) { j = 0; lengthInner = json.gestures[ i ].handIds.length; for( j = 0; j < lengthInner; ++j ) { var gestureHand:Hand = Controller.getHandByID( currentFrame, json.gestures[ i ].handIds[ j ] ); gesture.hands.push( gestureHand ); } } if( typeof json.gestures[ i ].pointableIds !== "undefined" ) { j = 0; lengthInner = json.gestures[ i ].pointableIds.length; for( j = 0; j < lengthInner; ++j ) { var gesturePointable:Pointable = Controller.getPointableByID( currentFrame, json.gestures[ i ].pointableIds[ j ] ); if( gesturePointable ) { gesture.pointables.push( gesturePointable ); } } if( gesture instanceof CircleGesture && gesture.pointables.length > 0 ) { (<CircleGesture>gesture).pointable = gesture.pointables[ 0 ]; } } gesture.frame = currentFrame; gesture.id = json.gestures[ i ].id; gesture.duration = json.gestures[ i ].duration; gesture.durationSeconds = gesture.duration / 1000000; switch( json.gestures[ i ].state ) { case "start": gesture.state = State.STATE_START; break; case "update": gesture.state = State.STATE_UPDATE; break; case "stop": gesture.state = State.STATE_STOP; break; default: gesture.state = State.STATE_INVALID; } gesture.type = type; currentFrame._gestures.push( gesture ); } } // Rotation (since last frame), interpolate for smoother motion if ( json.r ) currentFrame.rotation = new Matrix( new Vector3( json.r[ 0 ][ 0 ], json.r[ 0 ][ 1 ], json.r[ 0 ][ 2 ] ), new Vector3( json.r[ 1 ][ 0 ], json.r[ 1 ][ 1 ], json.r[ 1 ][ 2 ] ), new Vector3( json.r[ 2 ][ 0 ], json.r[ 2 ][ 1 ], json.r[ 2 ][ 2 ] ) ); // Scale factor (since last frame), interpolate for smoother motion currentFrame.scaleFactorNumber = json.s; // Translation (since last frame), interpolate for smoother motion if ( json.t ) currentFrame.translationVector = new Vector3( json.t[ 0 ], json.t[ 1 ], json.t[ 2 ] ); // Timestamp currentFrame.timestamp = json.timestamp; // Add frame to history if ( this.frameHistory.length > 59 ) this.frameHistory.splice( 59, 1 ); this.frameHistory.unshift( this.latestFrame ); this.latestFrame = currentFrame; this.listener.onFrame( this, this.latestFrame ); }; } /** * Finds a Hand object by ID. * * @param frame The Frame object in which the Hand contains * @param id The ID of the Hand object * @return The Hand object if found, otherwise null * */ private static getHandByID( frame:Frame, id:number ):Hand { var returnValue:Hand = null; var i:number = 0; for( i = 0; i < frame.hands.length; i++ ) { if ( (<Hand>frame.hands[ i ]).id === id ) { returnValue = (<Hand>frame.hands[ i ]); break; } } return returnValue; } /** * Finds a Pointable object by ID. * * @param frame The Frame object in which the Pointable contains * @param id The ID of the Pointable object * @return The Pointable object if found, otherwise null * */ private static getPointableByID( frame:Frame, id:number ):Pointable { var returnValue:Pointable = null; var i:number = 0; for( i = 0; i < frame.pointables.length; i++ ) { if ( (<Pointable>frame.pointables[ i ]).id === id ) { returnValue = (<Pointable>frame.pointables[ i ]); break; } } return returnValue; } /** * Returns a frame of tracking data from the Leap. * * <p>Use the optional history parameter to specify which frame to retrieve. * Call <code>frame()</code> or <code>frame(0)</code> to access the most recent frame; * call <code>frame(1)</code> to access the previous frame, and so on. If you use a history value * greater than the number of stored frames, then the controller returns * an invalid frame.</p> * * @param history The age of the frame to return, counting backwards from * the most recent frame (0) into the past and up to the maximum age (59). * * @return The specified frame; or, if no history parameter is specified, * the newest frame. If a frame is not available at the specified * history position, an invalid Frame is returned. * */ public frame( history:number = 0 ):Frame { if( history >= this.frameHistory.length ) return Frame.invalid(); else return this.frameHistory[ history ]; } /** * Update the object that receives direct updates from the Leap Motion Controller. * * <p>The default listener will make the controller dispatch flash events. * You can override this behaviour, by implementing the IListener interface * in your own classes, and use this method to set the listener to your * own implementation.</p> * * @param listener */ public setListener( listener:Listener ):void { this.listener = listener; } /** * Enables or disables reporting of a specified gesture type. * * <p>By default, all gesture types are disabled. When disabled, gestures of * the disabled type are never reported and will not appear in the frame * gesture list.</p> * * <p>As a performance optimization, only enable recognition for the types * of movements that you use in your application.</p> * * @param type The type of gesture to enable or disable. Must be a member of the Gesture::Type enumeration. * @param enable True, to enable the specified gesture type; False, to disable. * */ public enableGesture( type:Type, enable:boolean = true ):void { var enableObject:any = {}; if( enable ) { this._isGesturesEnabled = true; enableObject.enableGestures = true; } else { this._isGesturesEnabled = false; enableObject.enableGestures = false; } this.connection.send( JSON.stringify( enableObject ) ); } /** * Reports whether the specified gesture type is enabled. * * @param type The Gesture.TYPE parameter. * @return True, if the specified type is enabled; false, otherwise. * */ public isGestureEnabled( type:Type ):boolean { return this._isGesturesEnabled; } /** * Reports whether this Controller is connected to the Leap Motion Controller. * * <p>When you first create a Controller object, <code>isConnected()</code> returns false. * After the controller finishes initializing and connects to * the Leap, <code>isConnected()</code> will return true.</p> * * <p>You can either handle the onConnect event using a event listener * or poll the <code>isConnected()</code> if you need to wait for your * application to be connected to the Leap before performing * some other operation.</p> * * @return True, if connected; false otherwise. * */ public isConnected():boolean { return this._isConnected; } }