diff --git a/.project b/.project new file mode 100644 index 0000000..eae200c --- /dev/null +++ b/.project @@ -0,0 +1,11 @@ + + + molecular-control-toolkit-js + + + + + + + + diff --git a/README.md b/README.md index edc9840..ffefa7d 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,49 @@ molecular-control-toolkit-js ============================ A javascript adapter to the molecular control toolkit. + +see https://github.com/Traksewt/molecular-control-toolkit + +This toolkit can be used to transfer Leap Motion events in the browser direct to an applet. + +in the browser JS: + + var leapToolkit = new molecularControlToolkitJS.leap(gestures()); + + var gestures = function () { + var functions = ['triggerPan', 'triggerRotate', 'triggerZoom', 'point', 'reset', 'zoomToSelection', 'selectMouseCursor']; + var ret = {}; + functions.forEach(function (funcName) { + ret[funcName] = function () { + var newArgs = [funcName]; + newArgs.push(Array.prototype.slice.call(arguments)); + if (document.applets[0]) { + document.applets[0].molecularControlToolkit.apply(document.applets[0], newArgs); + } + } + }) + return ret; + } + + leapToolkit.start() + + +in the applet: + +TunnellingConnector connector = null; + + public void initialise() { + MolecularControlToolkit molecularControlToolkit = new MolecularControlToolkit(); + this.connector = (TunnellingConnector) molecularControlToolkit.addConnector(ConnectorType.LeapMotionJS); + MyDispatcher dispatcher = new MyDispatcher(); + molecularControlToolkit.setListeners(dispatcher); + } + + /** The entry point for the browser events */ + public void molecularControlToolkit(String methodName, float[] arguments) { + System.out.println("Method: " + methodName + ", args: " + Arrays.asList(arguments).toString()); + if (this.connector != null) { + this.connector.tunnel(methodName, arguments); + } + } + diff --git a/index.js b/index.js new file mode 100644 index 0000000..7957c0f --- /dev/null +++ b/index.js @@ -0,0 +1,4 @@ +var leapConnector = require('./src/leapConnector'); + + +module.exports.leap = leapConnector; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..c204b76 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "molecular-control-toolkit-js", + "version": "0.0.2", + "description": "A javascript adapter to the molecular control toolkit", + "main": "index.js", + "repository": { + "type": "git", + "url": "git://github.com/Traksewt/molecular-control-toolkit-js.git" + }, + "keywords": [ + "LeapMotion", + "Gesture", + "Device", + "MolecularControlToolkit" + ], + "author": "Kenny Sabir", + "license": "BSD", + "bugs": { + "url": "https://github.com/Traksewt/molecular-control-toolkit-js/issues" + }, + "dependencies": { + "leapjs": "*" + } +} diff --git a/src/leapConnector.js b/src/leapConnector.js new file mode 100644 index 0000000..683bdc7 --- /dev/null +++ b/src/leapConnector.js @@ -0,0 +1,144 @@ +var Leap = require('leapjs'); +var Pointer = require('./pointer') + +/** constant for scaling pointing in the x axis */ +var X_SCALE = 150; + +/** constant for scaling pointing in the y axis */ +var Y_SCALE = -400; + +/** constant for offsetting pointing in the y axis */ +var Y_OFFSET = 400; + +var ROTATION_SCALE = 500; + +var MAX_ROTATION = 30; + +var MIN_ROTATION = 3; + +var LeapConnector = function(gestureDispatcher) { + this.gestureDispatcher = gestureDispatcher; + + + /** Store the lastX for custom pointing algorithm */ + this.lastX = -1; + + /** Store the lastY for custom pointing algorithm */ + this.lastY = -1; + +}; + +LeapConnector.prototype.start = function() { + var that = this; + this.controller = Leap.loop({ +// frameEventName: 'deviceFrame', + enableGestures : false + }, function(frame) { + return that.onFrame(frame); + }); + return that; +} + +LeapConnector.prototype.limitRotation = function(r) { + r *= ROTATION_SCALE; + r = Math.max(-MAX_ROTATION, r); + r = Math.min(MAX_ROTATION, r); + if (Math.abs(r) < MIN_ROTATION) { + r = 0; + } + return r; +} + +LeapConnector.prototype.onFrame = function(frame) { + var that = this; + if (frame.hands.length == 1) { + if (frame.fingers.length > 3) { + var lastFrame = this.controller.frame(1); + if (lastFrame && lastFrame.hands.length == 1) { + + // rotate + that.lastY = -1; + that.lastX = -1; + + var hand = frame.hands[0]; + var translation = hand.translation(lastFrame); + // comment out for the moment until JS Leap Motion rotation bugs are fixed. +// that.gestureDispatcher.triggerPan(translation[0], -translation[1]); +// that.gestureDispatcher.triggerZoom(-translation[2]); + + // yaw pitch roll are slightly better, but there still seems to be an issue. +// var xRotation = this.limitRotation(hand.pitch() - lastFrame.hands[0].pitch()); +// var yRotation = this.limitRotation(hand.yaw() - lastFrame.hands[0].yaw()); +// var zRotation = this.limitRotation(hand.roll() - lastFrame.hands[0].roll()); + + // these rotations have an issue where changes aren't updated properly. + // see https://github.com/leapmotion/leapjs/issues/188 + var xRotation = this.limitRotation(hand.rotationAngle(lastFrame, [ 1, 0, 0 ])); + var yRotation = this.limitRotation(hand.rotationAngle(lastFrame, [ 0, 1, 0 ])); + var zRotation = this.limitRotation(hand.rotationAngle(lastFrame, [ 0, 0, 1 ])); + // console.log('lastframe1: ' + that.lastFrame); + if (Math.abs(xRotation) > MIN_ROTATION || + Math.abs(yRotation) > MIN_ROTATION || + Math.abs(zRotation) > MIN_ROTATION) { + that.gestureDispatcher.triggerRotate((xRotation), + -(yRotation), (zRotation)); + } + } + } else if (frame.fingers.length > 0) { + + try { + var finger = frame.fingers[0]; + var hit = Pointer(finger); + // Vector hit = + // controller.locatedScreens().closestScreenHit(finger).intersect(finger,true); + var zVel = finger.tipVelocity.getZ[2]; + var absZVel = Math.abs(zVel); + var y = hit.getY(); + var x = hit.getX(); + + if (absZVel > 50 && that.lastY != -1) { + var scale = 2500 / (absZVel * absZVel); + x = (hit.x - that.lastX) * scale + that.lastX; + y = (hit.y - that.lastY) * scale + that.lastY; + } + that.lastY = y; + that.lastX = x; + if (!isNaN(x) && !isNaN(y)) { + // that.gestureDispatcher.point(x, y); + } + } catch (e) { + console.log('error occurred when pointing: ' + e); + } + } else { + that.lastY = -1; + that.lastX = -1; + + } + var i; + if (frame.gestures) { + + frame.gestures.forEach(function(gesture) { + switch (gesture.type) { + case 'swipe': + that.gestureDispatcher.reset(); + break; + case 'screenTap': + + that.gestureDispatcher.selectMouseCursor(); + // add a delay so the app can process the mouse clicks. + setTimeout(that.gestureDispatcher.zoomToSelection, 100); + break; + case 'keyTap': + that.gestureDispatcher.zoomToSelection(); + break; + default: + console.log("Unknown gesture type: " + gesture.type); + break; + } + }); + } + } + // console.log('lastframe2: ' + that.lastFrame); +} + +module.exports = LeapConnector; diff --git a/src/pointer.js b/src/pointer.js new file mode 100644 index 0000000..870fd61 --- /dev/null +++ b/src/pointer.js @@ -0,0 +1,172 @@ +// adapted from a forum post by cabbibo: +// https://github.com/leapmotion/leapjs/issues/31 +// http://cabbibo.com/leap/lmlab/mouse/test.html + +/* + +Below are all of the starting parameters for the different +Pointer Acceleration Functions. + +Additionally the look and feel of the graph is created here. + +*/ +var PARAMS = function(){ + + + //Function Type that we are starting with + this.functionType = 'atan' + + + //power function + this.power = { + division:300, + divisionRange:[1,10000], + power:1, + powerRange:[0,3] + + } + + + this.atan = { + division1:660, + division1Range:[0,10000], + division2:0.5, + division2Range:[0,1] + } + + this.asymp1 = { + max:0.5, + maxRange:[0,1], + min:0.001, + minRange:[0,.1], + Vo:200, + VoRange:[1,10000] + } + + this.asymp2 = { + max:0.001, + maxRange:[0,.01], + min:0.001, + minRange:[0,.01], + Vo:1500, + VoRange:[0,3000], + } + + + + //Look and Feel Section + this.mouseSize = 10 + this.frameOpacity = 800 + + this.gainGraphFrames = 200 + + + + + + //Moves Cursor to the center of the screen + this.resetPosition = function(){ + curPos.x = window.innerWidth/2 + curPos.y = window.innerHeight/2 + } +} + +//Initialize params +var params = new PARAMS(); + + +//setting up the current position to be in the very center of the screen +var curPos = { + x:window.innerWidth/2, + y:window.innerHeight/2 +} + + +var point = function (pointer) { + + //get the amount to be added by calling the 'convert' function. + //The convert function will basically use whatever equation that we have selected + //In order to create Pointer Acceleration + var addAmount = convert(pointer.tipVelocity[0],pointer.tipVelocity[1], params.functionType) + + + //Add the altered velocity to the current Position + var newPos = { + x:(curPos.x + addAmount.x), + y:(curPos.y + addAmount.y) + } + + + /* + Setting up the bounding box + */ + if(newPos.x >= window.innerWidth){ + newPos.x = window.innerWidth + } + + if(newPos.x <= 0 ){ + newPos.x = 0 + } + + if(newPos.y >= window.innerHeight){ + newPos.y = window.innerHeight + } + + if(newPos.y <= 0 ){ + newPos.y = 0 + } + + //Assign the current position to the be the new position for use in the next frame + curPos = newPos; + return newPos; +} + + +//Function to convert the velocity to something useful +function convert(velocityX, velocityY, type){ + + //Gets the magnitude outside the individual functions + //Becasue it will be the same for every function type + var magnitude = Math.sqrt((velocityX*velocityX)+(velocityY*velocityY)) + + //CD or Control Display Gain, is the amount that + var cdGain = 1 + + + + + + if(type == 'power'){ + + cdGain = Math.pow(magnitude,params.power.power) / Math.pow(params.power.division,params.power.power) + + }else if (type == 'atan'){ + + cdGain = Math.atan(magnitude/params.atan.division1) / params.atan.division2 + + }else if(type == 'asymp1'){ + + cdGain = ((params.asymp1.max) *(1- Math.exp(-magnitude/params.asymp1.Vo))+params.asymp1.min) + + }else if(type == 'asymp2'){ + + cdGain = (((params.asymp2.max) * magnitude * Math.exp(-magnitude/params.asymp2.Vo))+params.asymp2.min) + + } + + //The amount that we are going to be adding to the current position + //is simply the Control Display Gain Multiplied by the Velocity + //the velocityY is negated becasue of the change of Axis direction in browser + toReturn = { + x:cdGain*velocityX, + y:-cdGain*velocityY + } + + //console.log(toReturn.x) + return toReturn + +} + + +module.exports = point; +module.exports.reset = params.resetPosition; \ No newline at end of file