leapmotion-ts
Version:
TypeScript framework for Leap Motion.
528 lines (471 loc) • 22.9 kB
text/typescript
/// <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;
}
}