leapmotion-ts
Version:
TypeScript framework for Leap Motion.
1,351 lines (1,199 loc) • 133 kB
text/typescript
/**
* The EventDispatcher class provides strongly typed events.
*/
export class EventDispatcher
{
private listeners:any[];
constructor()
{
this.listeners = [];
}
public hasEventListener( type:string, listener:Function ):boolean
{
var exists:boolean = false;
for( var i:number = 0; i < this.listeners.length; i++ )
{
if( this.listeners[ i ].type === type && this.listeners[ i ].listener === listener )
{
exists = true;
break;
}
}
return exists;
}
public addEventListener ( typeStr:string, listenerFunction:Function ):void
{
if( this.hasEventListener( typeStr, listenerFunction ) )
return;
this.listeners.push( { type: typeStr, listener: listenerFunction } );
}
public removeEventListener ( typeStr:string, listenerFunction:Function ):void
{
for( var i:number = 0; i < this.listeners.length; i++ )
{
if( this.listeners[ i ].type === typeStr && this.listeners[ i ].listener === listenerFunction )
this.listeners.splice( i, 1 );
}
}
public dispatchEvent ( event:LeapEvent ):void
{
for( var i:number = 0; i < this.listeners.length; i++ )
{
if( this.listeners[ i ].type === event.getType() )
this.listeners[ i ].listener.call( this, event );
}
}
}
/**
* The Listener interface defines a set of callback functions that you can
* implement to respond to events dispatched by the Leap.
*
* <p>To handle Leap events, implement the Listener interface and assign
* it to the Controller instance. The Controller calls the relevant Listener
* callback when an event occurs, passing in a reference to itself.
* You have to implement callbacks for every method specified in the interface.</p>
*
* <p>Note: you have to create an instance of the LeapMotion class and set the Listener to your class:</p>
*
* <listing>
* leap = new LeapMotion();
* leap.controller.setListener( this );</listing>
*
* @author logotype
*
*/
export interface Listener
{
/**
* Called when the Controller object connects to the Leap software,
* or when this Listener object is added to a Controller that is already connected.
*
* @param controller The Controller object invoking this callback function.
*
*/
onConnect( controller:Controller ):void;
/**
* Called when the Controller object disconnects from the Leap software.
*
* <p>The controller can disconnect when the Leap device is unplugged,
* the user shuts the Leap software down, or the Leap software encounters
* an unrecoverable error.</p>
*
* <listing>
* public onDisconnect( controller:Controller ):void {
* trace( "Disconnected" );
* }</listing>
*
* <p>Note: When you launch a Leap-enabled application in a debugger,
* the Leap library does not disconnect from the application.
* This is to allow you to step through code without losing the connection
* because of time outs.</p>
*
* @param controller The Controller object invoking this callback function.
*
*/
onDisconnect( controller:Controller ):void;
/**
* Called when this Listener object is removed from the Controller or
* the Controller instance is destroyed.
*
* <listing>
* public onExit( controller:Controller ):void {
* trace( "Exited" );
* }</listing>
*
* @param controller The Controller object invoking this callback function.
*
*/
onExit( controller:Controller ):void;
/**
* Called when a new frame of hand and finger tracking data is available.
*
* <p>Access the new frame data using the <code>controller.frame()</code> function.</p>
*
* <listing>
* public onFrame( controller:Controller, frame:Frame ):void {
* trace( "New frame" );
* }</listing>
*
* <p>Note, the Controller skips any pending onFrame events while your
* onFrame handler executes. If your implementation takes too long to
* return, one or more frames can be skipped. The Controller still inserts
* the skipped frames into the frame history. You can access recent frames
* by setting the history parameter when calling the <code>controller.frame()</code>
* function. You can determine if any pending onFrame events were skipped
* by comparing the ID of the most recent frame with the ID of the last
* received frame.</p>
*
* @param controller The Controller object invoking this callback function.
* @param frame The most recent frame object.
*
*/
onFrame( controller:Controller, frame:Frame ):void;
/**
* Called once, when this Listener object is newly added to a Controller.
*
* <listing>
* public onInit( controller:Controller ):void {
* trace( "Init" );
* }</listing>
*
* @param controller The Controller object invoking this callback function.
*
*/
onInit( controller:Controller ):void;
}
export class DefaultListener extends EventDispatcher implements Listener
{
constructor()
{
super();
}
public onConnect( controller:Controller ):void
{
controller.dispatchEvent( new LeapEvent( LeapEvent.LEAPMOTION_CONNECTED, this ) );
}
public onDisconnect( controller:Controller ):void
{
controller.dispatchEvent( new LeapEvent( LeapEvent.LEAPMOTION_DISCONNECTED, this ) );
}
public onExit( controller:Controller ):void
{
controller.dispatchEvent( new LeapEvent( LeapEvent.LEAPMOTION_EXIT, this ) );
}
public onFrame( controller:Controller, frame:Frame ):void
{
controller.dispatchEvent( new LeapEvent( LeapEvent.LEAPMOTION_FRAME, this, frame ) );
}
public onInit( controller:Controller ):void
{
controller.dispatchEvent( new LeapEvent( LeapEvent.LEAPMOTION_INIT, this ) );
}
}
export class LeapEvent
{
public static LEAPMOTION_INIT:string = "leapMotionInit";
public static LEAPMOTION_CONNECTED:string = "leapMotionConnected";
public static LEAPMOTION_DISCONNECTED:string = "leapMotionDisconnected";
public static LEAPMOTION_EXIT:string = "leapMotionExit";
public static LEAPMOTION_FRAME:string = "leapMotionFrame";
private _type:string;
private _target:Listener;
public frame:Frame;
constructor( type:string, targetListener:Listener, frame:Frame = null )
{
this._type = type;
this._target = targetListener;
this.frame = frame;
}
public getTarget():any
{
return this._target;
}
public getType():string
{
return this._type;
}
}
/**
* LeapUtil is a collection of static utility functions.
*
*/
export class LeapUtil
{
/** The constant pi as a single precision floating point number. */
public static PI:number = 3.1415926536;
/**
* The constant ratio to convert an angle measure from degrees to radians.
* Multiply a value in degrees by this constant to convert to radians.
*/
public static DEG_TO_RAD:number = 0.0174532925;
/**
* The constant ratio to convert an angle measure from radians to degrees.
* Multiply a value in radians by this constant to convert to degrees.
*/
public static RAD_TO_DEG:number = 57.295779513;
/**
* Pi * 2.
*/
public static TWO_PI:number = Math.PI + Math.PI;
/**
* Pi * 0.5.
*/
public static HALF_PI:number = Math.PI * 0.5;
/**
* Represents the smallest positive single value greater than zero.
*/
public static EPSILON:number = 0.00001;
constructor()
{
}
/**
* Convert an angle measure from radians to degrees.
*
* @param radians
* @return The value, in degrees.
*
*/
public static toDegrees( radians:number ):number
{
return radians * 180 / Math.PI;
}
/**
* Determines if a value is equal to or less than 0.00001.
*
* @return True, if equal to or less than 0.00001; false otherwise.
*/
public static isNearZero( value:number ):boolean
{
return Math.abs( value ) <= LeapUtil.EPSILON;
}
/**
* Determines if all Vector3 components is equal to or less than 0.00001.
*
* @return True, if equal to or less than 0.00001; false otherwise.
*/
public static vectorIsNearZero( inVector:Vector3 ):boolean
{
return this.isNearZero( inVector.x ) && this.isNearZero( inVector.y ) && this.isNearZero( inVector.z );
}
/**
* Create a new matrix with just the rotation block from the argument matrix
*/
public static extractRotation( mtxTransform:Matrix ):Matrix
{
return new Matrix( mtxTransform.xBasis, mtxTransform.yBasis, mtxTransform.zBasis );
}
/**
* Returns a matrix representing the inverse rotation by simple transposition of the rotation block.
*/
public static rotationInverse( mtxRot:Matrix ):Matrix
{
return new Matrix( new Vector3( mtxRot.xBasis.x, mtxRot.yBasis.x, mtxRot.zBasis.x ), new Vector3( mtxRot.xBasis.y, mtxRot.yBasis.y, mtxRot.zBasis.y ), new Vector3( mtxRot.xBasis.z, mtxRot.yBasis.z, mtxRot.zBasis.z ) );
}
/**
* Returns a matrix that is the orthonormal inverse of the argument matrix.
* This is only valid if the input matrix is orthonormal
* (the basis vectors are mutually perpendicular and of length 1)
*/
public static rigidInverse( mtxTransform:Matrix ):Matrix
{
var rigidInverse:Matrix = this.rotationInverse( mtxTransform );
rigidInverse.origin = rigidInverse.transformDirection( mtxTransform.origin.opposite() );
return rigidInverse;
}
public static componentWiseMin( vLHS:Vector3, vRHS:Vector3 ):Vector3
{
return new Vector3( Math.min( vLHS.x, vRHS.x ), Math.min( vLHS.y, vRHS.y ), Math.min( vLHS.z, vRHS.z ) );
}
public static componentWiseMax( vLHS:Vector3, vRHS:Vector3 ):Vector3
{
return new Vector3( Math.max( vLHS.x, vRHS.x ), Math.max( vLHS.y, vRHS.y ), Math.max( vLHS.z, vRHS.z ) );
}
public static componentWiseScale( vLHS:Vector3, vRHS:Vector3 ):Vector3
{
return new Vector3( vLHS.x * vRHS.x, vLHS.y * vRHS.y, vLHS.z * vRHS.z );
}
public static componentWiseReciprocal( inVector:Vector3 ):Vector3
{
return new Vector3( 1.0 / inVector.x, 1.0 / inVector.y, 1.0 / inVector.z );
}
public static minComponent( inVector:Vector3 ):number
{
return Math.min( inVector.x, Math.min( inVector.y, inVector.z ) );
}
public static maxComponent( inVector:Vector3 ):number
{
return Math.max( inVector.x, Math.max( inVector.y, inVector.z ) );
}
/**
* Compute the polar/spherical heading of a vector direction in z/x plane
*/
public static heading( inVector:Vector3 ):number
{
return Math.atan2( inVector.z, inVector.x );
}
/**
* Compute the spherical elevation of a vector direction in y above the z/x plane
*/
public static elevation( inVector:Vector3 ):number
{
return Math.atan2( inVector.y, Math.sqrt( inVector.z * inVector.z + inVector.x * inVector.x ) );
}
/**
* Set magnitude to 1 and bring heading to [-Pi,Pi], elevation into [-Pi/2, Pi/2]
*
* @param vSpherical The Vector3 to convert.
* @return The normalized spherical Vector3.
*
*/
public static normalizeSpherical( vSpherical:Vector3 ):Vector3
{
var fHeading:number = vSpherical.y;
var fElevation:number = vSpherical.z;
while ( fElevation <= -Math.PI ) fElevation += LeapUtil.TWO_PI;
while ( fElevation > Math.PI ) fElevation -= LeapUtil.TWO_PI;
if ( Math.abs( fElevation ) > LeapUtil.HALF_PI )
{
fHeading += Math.PI;
fElevation = fElevation > 0 ? ( Math.PI - fElevation ) : -( Math.PI + fElevation );
}
while ( fHeading <= -Math.PI ) fHeading += LeapUtil.TWO_PI;
while ( fHeading > Math.PI ) fHeading -= LeapUtil.TWO_PI;
return new Vector3( 1, fHeading, fElevation );
}
/**
* Convert from Cartesian (rectangular) coordinates to spherical coordinates
* (magnitude, heading, elevation).
*
* @param vCartesian The Vector3 to convert.
* @return The cartesian Vector3 converted to spherical.
*
*/
public static cartesianToSpherical( vCartesian:Vector3 ):Vector3
{
return new Vector3( vCartesian.magnitude(), this.heading( vCartesian ), this.elevation( vCartesian ) );
}
/**
* Convert from spherical coordinates (magnitude, heading, elevation) to
* Cartesian (rectangular) coordinates.
*
* @param vSpherical The Vector3 to convert.
* @return The spherical Vector3 converted to cartesian.
*
*/
public static sphericalToCartesian( vSpherical:Vector3 ):Vector3
{
var fMagnitude:number = vSpherical.x;
var fCosHeading:number = Math.cos( vSpherical.y );
var fSinHeading:number = Math.sin( vSpherical.y );
var fCosElevation:number = Math.cos( vSpherical.z );
var fSinElevation:number = Math.sin( vSpherical.z );
return new Vector3( fCosHeading * fCosElevation * fMagnitude,
fSinElevation * fMagnitude,
fSinHeading * fCosElevation * fMagnitude);
}
/**
* Clamps a value between a minimum Number and maximum Number value.
*
* @param inVal The number to clamp.
* @param minVal The minimum value.
* @param maxVal The maximum value.
* @return The value clamped between minVal and maxVal.
*
*/
public static clamp( inVal:number, minVal:number, maxVal:number ):number
{
return ( inVal < minVal ) ? minVal : (( inVal > maxVal ) ? maxVal : inVal );
}
/**
* Linearly interpolates between two Numbers.
*
* @param a A number.
* @param b A number.
* @param coefficient The interpolation coefficient [0-1].
* @return The interpolated number.
*
*/
public static lerp( a:number, b:number, coefficient:number ):number
{
return a + ( ( b - a ) * coefficient );
}
/**
* Linearly interpolates between two Vector3 objects.
*
* @param vec1 A Vector3 object.
* @param vec2 A Vector3 object.
* @param coefficient The interpolation coefficient [0-1].
* @return A new interpolated Vector3 object.
*
*/
public static lerpVector( vec1:Vector3, vec2:Vector3, coefficient:number ):Vector3
{
return vec1.plus( vec2.minus( vec1 ).multiply( coefficient ) );
}
}
/**
* 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;
}
}
/**
* The InteractionBox class represents a box-shaped region completely within
* the field of view of the Leap Motion controller.
*
* <p>The interaction box is an axis-aligned rectangular prism and provides
* normalized coordinates for hands, fingers, and tools within this box.
* The InteractionBox class can make it easier to map positions in the
* Leap Motion coordinate system to 2D or 3D coordinate systems used
* for application drawing.</p>
*
* <p>The InteractionBox region is defined by a center and dimensions along the x, y, and z axes.</p>
*
* @author logotype
*
*/
export class InteractionBox
{
/**
* The center of the InteractionBox in device coordinates (millimeters).
* <p>This point is equidistant from all sides of the box.</p>
*/
public center:Vector3;
/**
* The depth of the InteractionBox in millimeters, measured along the z-axis.
*
*/
public depth:number;
/**
* The height of the InteractionBox in millimeters, measured along the y-axis.
*
*/
public height:number;
/**
* The width of the InteractionBox in millimeters, measured along the x-axis.
*
*/
public width:number;
constructor()
{
}
/**
* Converts a position defined by normalized InteractionBox coordinates
* into device coordinates in millimeters.
*
* This function performs the inverse of normalizePoint().
*
* @param normalizedPosition The input position in InteractionBox coordinates.
* @return The corresponding denormalized position in device coordinates.
*
*/
public denormalizePoint( normalizedPosition:Vector3 ):Vector3
{
var vec:Vector3 = Vector3.invalid();
vec.x = ( normalizedPosition.x - 0.5 ) * this.width + this.center.x;
vec.y = ( normalizedPosition.y - 0.5 ) * this.height + this.center.y;
vec.z = ( normalizedPosition.z - 0.5 ) * this.depth + this.center.z;
return vec;
}
/**
* Normalizes the coordinates of a point using the interaction box.
*
* <p>Coordinates from the Leap Motion frame of reference (millimeters) are
* converted to a range of [0..1] such that the minimum value of the
* InteractionBox maps to 0 and the maximum value of the InteractionBox maps to 1.</p>
*
* @param position The input position in device coordinates.
* @param clamp Whether or not to limit the output value to the range [0,1]
* when the input position is outside the InteractionBox. Defaults to true.
* @return The normalized position.
*
*/
public normalizePoint( position:Vector3, clamp:boolean = true ):Vector3
{
var vec:Vector3 = Vector3.invalid();
vec.x = ( ( position.x - this.center.x ) / this.width ) + 0.5;
vec.y = ( ( position.y - this.center.y ) / this.height ) + 0.5;
vec.z = ( ( position.z - this.center.z ) / this.depth ) + 0.5;
if( clamp )
{
vec.x = Math.min( Math.max( vec.x, 0 ), 1 );
vec.y = Math.min( Math.max( vec.y, 0 ), 1 );
vec.z = Math.min( Math.max( vec.z, 0 ), 1 );
}
return vec;
}
/**
* Reports whether this is a valid InteractionBox object.
* @return True, if this InteractionBox object contains valid data.
*
*/
public isValid():boolean
{
return this.center.isValid() && this.width > 0 && this.height > 0 && this.depth > 0;
}
/**
* Compare InteractionBox object equality/inequality.
*
* <p>Two InteractionBox objects are equal if and only if both InteractionBox
* objects represent the exact same InteractionBox and both InteractionBoxes are valid.</p>
*
* @param other
* @return
*
*/
public isEqualTo( other:InteractionBox ):boolean
{
if( !this.isValid() || !other.isValid() )
return false;
if( !this.center.isEqualTo( other.center ) )
return false;
if( this.depth != other.depth )
return false;
if( this.height != other.height )
return false;
if( this.width != other.width )
return false;
return true;
}
/**
* Returns an invalid InteractionBox object.
*
* <p>You can use the instance returned by this function in comparisons
* testing whether a given InteractionBox instance is valid or invalid.
* (You can also use the <code>InteractionBox.isValid()</code> function.)</p>
*
* @return The invalid InteractionBox instance.
*
*/
public static invalid():InteractionBox
{
return new InteractionBox();
}
/**
* Writes a brief, human readable description of the InteractionBox object.
* @return A description of the InteractionBox as a string.
*
*/
public toString():string
{
return "[InteractionBox depth:" + this.depth + " height:" + this.height + " width:" + this.width + "]";
}
}
/**
* The Pointable class reports the physical characteristics of a detected finger or tool.
* Both fingers and tools are classified as Pointable objects. Use the Pointable.isFinger
* property to determine whether a Pointable object represents a finger. Use the
* Pointable.isTool property to determine whether a Pointable object represents a tool.
* The Leap classifies a detected entity as a tool when it is thinner, straighter,
* and longer than a typical finger.
*
* <p>Note that Pointable objects can be invalid, which means that they do not contain valid
* tracking data and do not correspond to a physical entity. Invalid Pointable objects
* can be the result of asking for a Pointable object using an ID from an earlier frame
* when no Pointable objects with that ID exist in the current frame. A Pointable object
* created from the Pointable constructor is also invalid. Test for validity with
* the <code>Pointable.isValid()</code> function.</p>
*
* @author logotype
*
*/
/**
* Defines the values for reporting the state of a Pointable object in relation to an adaptive touch plane.
*/
enum Zone {
/**
* The Pointable object is too far from the plane to be considered hovering or touching.
*
* <p>Defines the values for reporting the state of a Pointable object in relation to an adaptive touch plane.</p>
*/
ZONE_NONE = 0,
/**
* The Pointable object is close to, but not touching the plane.
*
* <p>Defines the values for reporting the state of a Pointable object in relation to an adaptive touch plane.</p>
*/
ZONE_HOVERING = 1,
/**
* The Pointable has penetrated the plane.
*
* <p>Defines the values for reporting the state of a Pointable object in relation to an adaptive touch plane.</p>
*/
ZONE_TOUCHING = 2
}
export class Pointable
{
/**
* The current touch zone of this Pointable object.
*
* <p>The Leap Motion software computes the touch zone based on a
* floating touch plane that adapts to the user's finger movement
* and hand posture. The Leap Motion software interprets purposeful
* movements toward this plane as potential touch points.
* When a Pointable moves close to the adaptive touch plane,
* it enters the "hovering" zone. When a Pointable reaches or
* passes through the plane, it enters the "touching" zone.</p>
*
* <p>The possible states are present in the Zone enum of this class:</p>
*
* <code>Zone.NONE – The Pointable is outside the hovering zone.
* Zone.HOVERING – The Pointable is close to, but not touching the touch plane.
* Zone.TOUCHING – The Pointable has penetrated the touch plane.</code>
*
* <p>The touchDistance value provides a normalized indication of the
* distance to the touch plane when the Pointable is in the hovering
* or touching zones.</p>
*
*/
public touchZone:number = Zone.ZONE_NONE;
/**
* A value proportional to the distance between this Pointable
* object and the adaptive touch plane.
*
* <p>The touch distance is a value in the range [-1, 1].
* The value 1.0 indicates the Pointable is at the far edge of
* the hovering zone. The value 0 indicates the Pointable is
* just entering the touching zone. A value of -1.0 indicates
* the Pointable is firmly within the touching zone.
* Values in between are proportional to the distance from the plane.
* Thus, the touchDistance of 0.5 indicates that the Pointable
* is halfway into the hovering zone.</p>
*
* <p>You can use the touchDistance value to modulate visual
* feedback given to the user as their fingers close in on a
* touch target, such as a button.</p>
*
*/
public touchDistance:number = 0;
/**
* The direction in which this finger or tool is pointing.<br/>
* The direction is expressed as a unit vector pointing in the
* same direction as the tip.
*/
public direction:Vector3;
/**
* The Frame associated with this Pointable object.<br/>
* The associated Frame object, if available; otherwise, an invalid
* Frame object is returned.
* @see Frame
*/
public frame:Frame;
/**
* The Hand associated with this finger or tool.<br/>
* The associated Hand object, if available; otherwise, an invalid
* Hand object is returned.
* @see Hand
*/
public hand:Hand;
/**
* A unique ID assigned to this Pointable object, whose value remains
* the same across consecutive frames while the tracked finger or
* tool remains visible.
*
* <p>If tracking is lost (for example, when a finger is occluded by another
* finger or when it is withdrawn from the Leap field of view), the Leap
* may assign a new ID when it detects the entity in a future frame.</p>
*
* <p>Use the ID value with the <code>Frame.pointable()</code> to find this
* Pointable object in future frames.</p>
*/
public id:number;
/**
* The estimated length of the finger or tool in millimeters.
*
* <p>The reported length is the visible length of the finger or tool from
* the hand to tip.</p>
*
* <p>If the length isn't known, then a value of 0 is returned.</p>
*/
public length:number = 0;
/**
* The estimated width of the finger or tool in millimeters.
*
* <p>The reported width is the average width of the visible portion
* of the finger or tool from the hand to the tip.</p>
*
* <p>If the width isn't known, then a value of 0 is returned.</p>
*/
public width:number = 0;
/**
* The tip position in millimeters from the Leap origin.
*/
public tipPosition:Vector3;
/**
* The stabilized tip position of this Pointable.
* <p>Smoothing and stabilization is performed in order to make this value more suitable for interaction with 2D content.</p>
* <p>A modified tip position of this Pointable object with some additional smoothing and stabilization applied.</p>
*/
public stabilizedTipPosition:Vector3;
/**
* The duration of time this Pointable has been visible to the Leap Motion Controller.
* <p>The duration (in seconds) that this Pointable has been tracked.</p>
*/
public timeVisible:number;
/**
* The rate of change of the tip position in millimeters/second.
*/
public tipVelocity:Vector3;
/**
* Whether or not the Pointable is believed to be a finger.
*/
public isFinger:boolean;
/**
* Whether or not the Pointable is believed to be a tool.
*/
public isTool:boolean;
constructor()
{
this.direction = Vector3.invalid();
this.tipPosition = Vector3.invalid();
this.tipVelocity = Vector3.invalid();
}
/**
* Reports whether this is a valid Pointable object.
* @return True if <code>direction</code>, <code>tipPosition</code> and <code>tipVelocity</code> are true.
*/
public isValid():boolean
{
var returnValue:boolean = false;
if ( ( this.direction && this.direction.isValid()) && ( this.tipPosition && this.tipPosition.isValid()) && ( this.tipVelocity && this.tipVelocity.isValid()) )
returnValue = true;
return returnValue;