UNPKG

leapmotion-ts

Version:

TypeScript framework for Leap Motion.

1,351 lines (1,199 loc) 133 kB
/** * 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 &#42; 2. */ public static TWO_PI:number = Math.PI + Math.PI; /** * Pi &#42; 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;