├── lib ├── GeosansLight.ttf ├── stats.min.js ├── OBJLoader.js └── dat.gui.min.js ├── examples ├── lib │ ├── GeosansLight.ttf │ ├── stats.min.js │ ├── LeapDataPlotter.js │ └── OBJLoader.js ├── PinchRotateControls.html ├── TrackballControls.html ├── PaddleControls.html ├── SpringControls.html ├── TwoHandControls.html ├── OBJViewer.html ├── EyeLookControls.html └── PointerControls.html ├── index.html ├── controls ├── LeapPointerControls.js ├── LeapPaddleControls.js ├── LeapEyeLookControls.js ├── LeapSpringControls.js ├── LeapWeightControls.js ├── LeapPinchRotateControls.js ├── LeapTrackballControls.js └── LeapTwoHandControls.js ├── CONTRIBUTORS ├── LICENSE └── README.md /lib/GeosansLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leapmotion/Leap-Three-Camera-Controls/HEAD/lib/GeosansLight.ttf -------------------------------------------------------------------------------- /examples/lib/GeosansLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leapmotion/Leap-Three-Camera-Controls/HEAD/examples/lib/GeosansLight.ttf -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 30 | 31 | 32 | 33 | 34 |

Leap.js / Three.js Camera Controls

35 | Paddle Controls 36 | Trackball Controls 37 | Two Hand Controls 38 | Spring Controls 39 | Pointer Controls 40 | Pinch Rotate Controls 41 | Eye Look Controls 42 | OBJ Viewer 43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/PinchRotateControls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /lib/stats.min.js: -------------------------------------------------------------------------------- 1 | // stats.js - http://github.com/mrdoob/stats.js 2 | var Stats=function(){var l=Date.now(),m=l,g=0,n=Infinity,o=0,h=0,p=Infinity,q=0,r=0,s=0,f=document.createElement("div");f.id="stats";f.addEventListener("mousedown",function(b){b.preventDefault();t(++s%2)},!1);f.style.cssText="width:80px;opacity:0.9;cursor:pointer";var a=document.createElement("div");a.id="fps";a.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#002";f.appendChild(a);var i=document.createElement("div");i.id="fpsText";i.style.cssText="color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px"; 3 | i.innerHTML="FPS";a.appendChild(i);var c=document.createElement("div");c.id="fpsGraph";c.style.cssText="position:relative;width:74px;height:30px;background-color:#0ff";for(a.appendChild(c);74>c.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div"); 4 | k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display= 5 | "block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{REVISION:11,domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height= 6 | a+"px",m=b,r=0);return b},update:function(){l=this.end()}}}; 7 | -------------------------------------------------------------------------------- /examples/lib/stats.min.js: -------------------------------------------------------------------------------- 1 | // stats.js - http://github.com/mrdoob/stats.js 2 | var Stats=function(){var l=Date.now(),m=l,g=0,n=Infinity,o=0,h=0,p=Infinity,q=0,r=0,s=0,f=document.createElement("div");f.id="stats";f.addEventListener("mousedown",function(b){b.preventDefault();t(++s%2)},!1);f.style.cssText="width:80px;opacity:0.9;cursor:pointer";var a=document.createElement("div");a.id="fps";a.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#002";f.appendChild(a);var i=document.createElement("div");i.id="fpsText";i.style.cssText="color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px"; 3 | i.innerHTML="FPS";a.appendChild(i);var c=document.createElement("div");c.id="fpsGraph";c.style.cssText="position:relative;width:74px;height:30px;background-color:#0ff";for(a.appendChild(c);74>c.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div"); 4 | k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display= 5 | "block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{REVISION:11,domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height= 6 | a+"px",m=b,r=0);return b},update:function(){l=this.end()}}}; 7 | -------------------------------------------------------------------------------- /examples/TrackballControls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /examples/PaddleControls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /controls/LeapPointerControls.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Leap Pointer Controls 3 | * 4 | * http://github.com/leapmotion/Leap-Three-Camera-Controls/ 5 | * 6 | * Copyright 2014 LeapMotion, Inc 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | */ 21 | 22 | THREE.LeapPointerControls = function ( object , controller , params , domElement ) { 23 | 24 | this.object = object; 25 | this.controller = controller; 26 | this.domElement = ( domElement !== undefined ) ? domElement : document; 27 | 28 | this.velocity = new THREE.Vector3(); 29 | 30 | // API 31 | 32 | this.enable = true; 33 | 34 | this.velocity = new THREE.Vector3(); 35 | 36 | this.size = 100; 37 | this.speed = .1; 38 | this.dampening = .9; 39 | 40 | this.target = new THREE.Vector3(); 41 | 42 | 43 | 44 | 45 | this.update = function(){ 46 | 47 | 48 | this.frame = this.controller.frame(); 49 | 50 | if( this.frame.valid == false ){ 51 | 52 | this.controller.connect(); 53 | this.frame = this.controller.frame(); 54 | 55 | } 56 | 57 | if( this.frame ){ 58 | 59 | if( this.frame.hands[0] ){ 60 | 61 | 62 | if( this.frame.hands[0].pinchStrength > .5 ){ 63 | 64 | var pos = this.leapToScene(this.frame.hands[0].palmPosition); 65 | var dir = new THREE.Vector3().fromArray( this.frame.hands[0].palmNormal ); 66 | var p = this.object.position; 67 | this.velocity = pos.clone().sub( p ).multiplyScalar( this.speed ); 68 | 69 | } 70 | 71 | 72 | } 73 | 74 | this.object.lookAt( new THREE.Vector3() ); 75 | 76 | this.object.position.add( this.velocity ); 77 | this.velocity.multiplyScalar( this.dampening ); 78 | 79 | } 80 | 81 | 82 | 83 | } 84 | 85 | this.leapToScene = function( position , clamp ){ 86 | 87 | var box = this.frame.interactionBox; 88 | var nPos = box.normalizePoint( position , false ); 89 | 90 | nPos[0] = (nPos[0]-.5) * this.size; 91 | nPos[1] = (nPos[1]-.5) * this.size; 92 | nPos[2] = (nPos[2]-.5) * this.size; 93 | 94 | return new THREE.Vector3().fromArray( nPos ); 95 | 96 | } 97 | 98 | 99 | } 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /examples/SpringControls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /examples/TwoHandControls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /examples/OBJViewer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /examples/EyeLookControls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /examples/lib/LeapDataPlotter.js: -------------------------------------------------------------------------------- 1 | var LeapDataPlotter; 2 | var TimeSeries; 3 | (function () { 4 | 5 | var colors = ['#900', '#090', '#009', '#990', '#909', '#099']; 6 | var colorIndex = 0; 7 | 8 | LeapDataPlotter = function () { 9 | this.seriesHash = {}; 10 | this.series = []; 11 | this.init(); 12 | } 13 | 14 | LeapDataPlotter.prototype.init = function() { 15 | 16 | var canvas = document.createElement('canvas'); 17 | document.body.appendChild(canvas); 18 | this.canvas = canvas; 19 | this.context = canvas.getContext('2d'); 20 | 21 | 22 | var styles = getComputedStyle(document.body); 23 | var windowWidth = parseInt(styles.width); 24 | var windowHeight = parseInt(styles.height); 25 | this.width = windowWidth; 26 | this.height = windowHeight; 27 | 28 | canvas.setAttribute('width', windowWidth); 29 | canvas.setAttribute('height', windowHeight); 30 | console.log(canvas.attributes.width, canvas.attributes.height); 31 | 32 | 33 | var devicePixelRatio = window.devicePixelRatio || 1; 34 | var backingStoreRatio = this.context.webkitBackingStorePixelRatio || 35 | this.context.mozBackingStorePixelRatio || 36 | this.context.msBackingStorePixelRatio || 37 | this.context.oBackingStorePixelRatio || 38 | this.context.backingStorePixelRatio || 1; 39 | 40 | var ratio = devicePixelRatio / backingStoreRatio; 41 | if (devicePixelRatio !== backingStoreRatio) { 42 | 43 | var oldWidth = canvas.width; 44 | var oldHeight = canvas.height; 45 | 46 | canvas.width = oldWidth * ratio; 47 | canvas.height = oldHeight * ratio; 48 | 49 | canvas.style.width = oldWidth + 'px'; 50 | canvas.style.height = oldHeight + 'px'; 51 | 52 | this.context.scale(ratio, ratio); 53 | } 54 | } 55 | 56 | LeapDataPlotter.prototype.plot = function (id, data) { 57 | if (data.length) { 58 | for (var i = 0, c = 120; i < data.length; i++, c=++c>122?97:c) { 59 | this.getTimeSeries(id+'.'+String.fromCharCode(c)).push(data[i]); 60 | } 61 | } else { 62 | this.getTimeSeries(id).push(data); 63 | } 64 | } 65 | 66 | LeapDataPlotter.prototype.getTimeSeries = function (id) { 67 | var ts = this.seriesHash[id]; 68 | if (!ts) { 69 | var opts = this.getOptions(id); 70 | ts = new TimeSeries(opts); 71 | this.series.push(ts); 72 | this.seriesHash[id] = ts; 73 | } 74 | return ts; 75 | } 76 | 77 | LeapDataPlotter.prototype.getOptions = function (name) { 78 | var c = colorIndex; 79 | colorIndex = (colorIndex + 1) % colors.length; 80 | return { 81 | y: this.series.length * 50, 82 | width: this.width, 83 | color: colors[c], 84 | name: name 85 | } 86 | } 87 | 88 | LeapDataPlotter.prototype.clear = function() { 89 | this.context.clearRect(0, 0, this.width, this.height); 90 | } 91 | 92 | LeapDataPlotter.prototype.draw = function(frame) { 93 | var context = this.context; 94 | this.series.forEach(function (s) { 95 | s.draw(context); 96 | }); 97 | } 98 | 99 | TimeSeries = function (opts) { 100 | opts = opts || {}; 101 | this.x = opts.x || 0; 102 | this.y = opts.y || 0; 103 | this.width = opts.width || 1000; 104 | this.height = opts.height || 50; 105 | this.length = opts.length || 1000; 106 | this.color = opts.color || '#000'; 107 | this.name = opts.name || ""; 108 | this.frameHandler = opts.frameHandler; 109 | 110 | this.max = -Infinity; 111 | this.min = Infinity; 112 | this.data = []; 113 | } 114 | 115 | TimeSeries.prototype.push = function (value) { 116 | this.data.push(value); 117 | 118 | if (this.data.length >= this.length) { 119 | this.data.shift(); 120 | } 121 | } 122 | 123 | TimeSeries.prototype.draw = function (context) { 124 | var self = this; 125 | var xScale = (this.width - 10) / (this.length - 1); 126 | var yScale = -(this.height - 10) / (this.max - this.min); 127 | 128 | context.save(); 129 | context.strokeRect(this.x, this.y, this.width, this.height); 130 | context.translate(this.x, this.y + this.height - 5); 131 | context.strokeStyle = this.color; 132 | context.fillText(this.name, 5, -(this.height - 10) * 0.5); 133 | context.fillText(this.min.toPrecision("5"), this.width - 50, 0); 134 | context.fillText(this.max.toPrecision("5"), this.width - 50, (this.max - this.min) * yScale + 10); 135 | context.beginPath(); 136 | 137 | var max = -Infinity; 138 | var min = Infinity; 139 | this.data.forEach(function (d, i) { 140 | if (d > max) max = d; 141 | if (d < min) min = d; 142 | 143 | if (isNaN(d)) { 144 | context.stroke(); 145 | context.beginPath(); 146 | } else { 147 | context.lineTo(i * xScale, (d - self.min) * yScale); 148 | } 149 | }); 150 | context.stroke(); 151 | context.restore(); 152 | this.min = min; 153 | this.max = max; 154 | } 155 | }()); 156 | -------------------------------------------------------------------------------- /controls/LeapPaddleControls.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Leap Paddle Controls 3 | * Author: @Cabbibo 4 | * 5 | * http://github.com/leapmotion/Leap-Three-Camera-Controls/ 6 | * 7 | * Copyright 2014 LeapMotion, Inc 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * 21 | */ 22 | 23 | THREE.LeapPaddleControls = function ( object , controller , params , domElement ) { 24 | 25 | this.object = object; 26 | this.controller = controller; 27 | this.domElement = ( domElement !== undefined ) ? domElement : document; 28 | 29 | this.velocity = new THREE.Vector3(); 30 | 31 | this.weakDampening = .99; 32 | this.strongDampening = .9; 33 | 34 | this.fingerMatchCutoff = .5; 35 | this.velocityMatchCutoff =.5; 36 | 37 | this.fingerMatchPower = 5; 38 | this.velocityMatchPower = 5; 39 | 40 | this.movementSpeed = 1; 41 | this.maxSpeed = 10; 42 | 43 | // API 44 | 45 | this.velocity = new THREE.Vector3(); 46 | this.handVel = new THREE.Vector3(); 47 | this.handNorm = new THREE.Vector3(); 48 | 49 | this.dampening = this.strongDampening; 50 | 51 | this.getForce = function( frame ){ 52 | 53 | var totalForce = new THREE.Vector3(); 54 | 55 | if( frame.hands[0] ){ 56 | 57 | var hand = frame.hands[0]; 58 | 59 | var hDirection = new THREE.Vector3().fromArray( hand.direction ); 60 | var hNormal = new THREE.Vector3().fromArray( hand.palmNormal ); 61 | 62 | for( var i = 0; i < hand.fingers.length; i++ ){ 63 | 64 | var finger = hand.fingers[i]; 65 | 66 | if( finger.extended ){ 67 | 68 | var fD = finger.direction; 69 | var fV = finger.tipVelocity; 70 | 71 | // First off see if the fingers pointed 72 | // the same direction as the hand 73 | var fDirection = new THREE.Vector3().fromArray( fD ); 74 | var fingerMatch = Math.abs(fDirection.dot( hDirection )); 75 | 76 | // See if the finger velocity is in the same direction 77 | // as the hand normal 78 | var fVelocity = new THREE.Vector3().fromArray( fV ); 79 | var tmp = fVelocity.clone(); 80 | var velocityMatch = Math.abs( tmp.normalize().dot( hNormal ) ); 81 | 82 | 83 | 84 | if( fingerMatch < this.fingerMatchCutoff ){ 85 | fingerMatch = 0; 86 | } 87 | 88 | if( velocityMatch < this.velocityMatchCutoff ){ 89 | velocityMatch = 0; 90 | } 91 | 92 | // Scaling by the neccesary Dampening factor 93 | var velocityMatchFactor = Math.pow( 94 | velocityMatch, 95 | this.velocityMatchPower 96 | ); 97 | 98 | var fingerMatchFactor = Math.pow( 99 | fingerMatch, 100 | this.fingerMatchPower 101 | ); 102 | 103 | var matchFactor = fingerMatchFactor * velocityMatchFactor; 104 | var speedFactor = this.movementSpeed / 10000; 105 | var multiplier = matchFactor * speedFactor; 106 | 107 | fVelocity.multiplyScalar( multiplier ); 108 | 109 | totalForce.add( fVelocity ); 110 | 111 | } 112 | 113 | } 114 | 115 | 116 | } 117 | 118 | return totalForce; 119 | 120 | } 121 | 122 | this.getDampening = function( frame ){ 123 | var dampening = this.strongDampening; 124 | 125 | if( frame.hands[0] ){ 126 | dampening = this.weakDampening; 127 | } 128 | 129 | return dampening; 130 | 131 | } 132 | this.update = function(){ 133 | 134 | 135 | // Just incase this is overwritten somewhere else in the code 136 | this.object.matrixAutoUpdate = true; 137 | 138 | var frame = this.controller.frame(); 139 | 140 | if( frame.valid == false ){ 141 | 142 | this.controller.connect(); 143 | frame = this.controller.frame(); 144 | 145 | } 146 | 147 | var force = this.getForce( frame ); 148 | var dampening = this.getDampening( frame ); 149 | 150 | this.velocity.add( force ); 151 | 152 | var speed = this.velocity.length(); 153 | 154 | if( speed > this.maxSpeed ){ 155 | 156 | this.velocity.normalize().multiplyScalar( this.maxSpeed ); 157 | 158 | } 159 | 160 | 161 | 162 | // Convert from straight X , Y , Z, 163 | // to the X , Y , and Z of the camera 164 | var vTemp = this.velocity.clone(); 165 | vTemp.applyQuaternion( this.object.quaternion ); 166 | this.object.position.add( vTemp ); 167 | 168 | this.velocity.multiplyScalar( dampening ); 169 | 170 | 171 | 172 | 173 | } 174 | 175 | } 176 | 177 | 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /controls/LeapEyeLookControls.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Leap Eye Look Controls 3 | * 4 | * http://github.com/leapmotion/Leap-Three-Camera-Controls/ 5 | * 6 | * Copyright 2014 LeapMotion, Inc 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | * 20 | */ 21 | 22 | THREE.LeapEyeLookControls = function ( object , controller , scene , params , domElement ) { 23 | 24 | this.object = object; 25 | this.controller = controller; 26 | this.scene = scene; 27 | this.domElement = ( domElement !== undefined ) ? domElement : document; 28 | 29 | // API 30 | 31 | this.lookSize = 1; 32 | this.lookMass = 1; 33 | this.lookSpeed = .001; 34 | this.lookDampening = .9; 35 | 36 | this.eyeSize = 1; 37 | this.eyeMass = 1; 38 | this.eyeSpeed = .001; 39 | this.eyeDampening = .9; 40 | 41 | 42 | 43 | this.look = new THREE.Object3D(); 44 | 45 | this.lookPosition = new THREE.Vector3( 0 , 0 , this.size); 46 | this.lookVelocity = new THREE.Vector3(); 47 | this.lookForce = new THREE.Vector3(); 48 | 49 | this.eyePosition = new THREE.Vector3(); 50 | this.eyeVelocity = new THREE.Vector3(); 51 | this.eyeForce = new THREE.Vector3(); 52 | 53 | this.object.position = this.eyePosition; 54 | this.look.position = this.lookPosition; 55 | 56 | this.scene.add( this.look ); 57 | 58 | 59 | 60 | this.update = function(){ 61 | 62 | 63 | this.frame = this.controller.frame(); 64 | 65 | if( this.frame.valid == false ){ 66 | 67 | this.controller.connect(); 68 | this.frame = this.controller.frame(); 69 | 70 | } 71 | 72 | this.lookForce.set( 0 , 0 , 0 ); 73 | this.eyeForce.set( 0 , 0 , 0 ); 74 | 75 | if( this.frame ){ 76 | 77 | if( this.frame.hands[0] ){ 78 | 79 | if( this.frame.hands[0].type == 'right' ){ 80 | this.updateEyeForce( this.frame.hands[0] ); 81 | }else if( this.frame.hands[0].type == 'left' ){ 82 | this.updateLookForce( this.frame.hands[0] ); 83 | } 84 | 85 | } 86 | 87 | if( this.frame.hands[1] ){ 88 | 89 | if( this.frame.hands[1].type == 'right' ){ 90 | this.updateEyeForce( this.frame.hands[1] ); 91 | }else if( this.frame.hands[1].type == 'left' ){ 92 | this.updateLookForce( this.frame.hands[1] ); 93 | } 94 | 95 | } 96 | 97 | this.lookForce.multiplyScalar( 1 / this.lookMass ); 98 | this.eyeForce.multiplyScalar( 1 / this.eyeMass ); 99 | 100 | this.lookForce.applyQuaternion( this.object.quaternion ); 101 | this.eyeForce.applyQuaternion( this.object.quaternion ); 102 | 103 | this.lookVelocity.add( this.lookForce ); 104 | this.eyeVelocity.add( this.eyeForce ); 105 | 106 | var lV = this.lookVelocity.clone().multiplyScalar( this.lookSpeed ); 107 | var eV = this.eyeVelocity.clone().multiplyScalar( this.eyeSpeed ); 108 | 109 | 110 | this.lookPosition.add( lV ); 111 | this.eyePosition.add( eV ); 112 | 113 | this.object.lookAt( this.lookPosition ); 114 | 115 | this.lookVelocity.multiplyScalar( this.lookDampening ); 116 | this.eyeVelocity.multiplyScalar( this.eyeDampening ); 117 | 118 | } 119 | 120 | } 121 | 122 | this.updateEyeForce = function( hand ){ 123 | 124 | var force = new THREE.Vector3(); 125 | 126 | if( hand.pinchStrength > .5 ){ 127 | force = new THREE.Vector3().fromArray( hand.palmVelocity ); 128 | force.multiplyScalar( this.eyeSize ); 129 | } 130 | 131 | this.eyeForce.add( force ); 132 | 133 | } 134 | 135 | this.updateLookForce = function( hand ){ 136 | 137 | var force = new THREE.Vector3(); 138 | 139 | if( hand.pinchStrength > .5 ){ 140 | force = new THREE.Vector3().fromArray( hand.palmVelocity ); 141 | force.multiplyScalar( this.lookSize ); 142 | } 143 | 144 | this.lookForce.add( force ); 145 | 146 | } 147 | 148 | 149 | this.addLookMarker = function( mesh ){ 150 | 151 | this.look.add( mesh ); 152 | 153 | } 154 | 155 | this.removeLookMarker = function( mesh ){ 156 | 157 | this.look.remove( mesh ); 158 | 159 | } 160 | 161 | this.leapToScene = function( position , clamp ){ 162 | 163 | var clamp = clamp || false; 164 | var box = this.frame.interactionBox; 165 | var nPos = box.normalizePoint( position , clamp ); 166 | 167 | nPos[0] = (nPos[0]-.5) * this.size; 168 | nPos[1] = (nPos[1]-.5) * this.size; 169 | nPos[2] = (nPos[2]-.5) * this.size; 170 | 171 | return new THREE.Vector3().fromArray( nPos ); 172 | 173 | } 174 | 175 | 176 | } 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /examples/PointerControls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /controls/LeapSpringControls.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Leap Spring Controls 3 | * Author: @Cabbibo 4 | * 5 | * http://github.com/leapmotion/Leap-Three-Camera-Controls/ 6 | * 7 | * Copyright 2014 LeapMotion, Inc 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * 21 | */ 22 | 23 | THREE.LeapSpringControls = function ( object , controller , scene , domElement ) { 24 | 25 | this.object = object; 26 | this.controller = controller; 27 | this.scene = scene; 28 | this.domElement = ( domElement !== undefined ) ? domElement : document; 29 | 30 | this.velocity = new THREE.Vector3(); 31 | this.acceleration = new THREE.Vector3(); 32 | 33 | // API 34 | 35 | this.enable = true; 36 | 37 | this.dampening = .9; 38 | this.size = 120; 39 | this.springConstant = 7; 40 | this.staticLength = this.size; 41 | this.mass = 100; 42 | 43 | this.anchorSpeed = .1; 44 | 45 | this.handBasis = new THREE.Matrix4(); 46 | 47 | this.interactionBox = { 48 | center: [ 0 , 0 , 0 ], 49 | size: [ 0 , 0 , 0 ] 50 | } 51 | 52 | // Creates the Target Object ( object that will tween to anchor ) 53 | this.target = new THREE.Object3D(); 54 | this.scene.add( this.target ); 55 | 56 | 57 | // Creates the Anchor Object ( object that will switch instantly ) 58 | this.anchor = new THREE.Object3D(); 59 | this.scene.add( this.anchor ); 60 | 61 | // Creates the Hand Object ( Object that defines the Anchor placement ) 62 | this.hand = new THREE.Object3D(); 63 | this.scene.add( this.hand ); 64 | 65 | 66 | /* 67 | 68 | Functions for adding and removing markers 69 | 70 | */ 71 | this.addTargetMarker = function( mesh ){ 72 | this.target.add( mesh ); 73 | } 74 | 75 | this.removeTargetMarker = function( mesh ){ 76 | this.target.remove( mesh ); 77 | } 78 | 79 | this.addAnchorMarker = function( mesh ){ 80 | this.anchor.add( mesh ); 81 | } 82 | 83 | this.removeAnchorMarker = function( mesh ){ 84 | this.anchor.remove( mesh ); 85 | } 86 | 87 | this.addHandMarker = function( mesh ){ 88 | this.hand.add( mesh ); 89 | } 90 | 91 | this.removeHandMarker = function( mesh ){ 92 | this.hand.remove( mesh ); 93 | } 94 | 95 | this.getForce = function(){ 96 | 97 | var difference = new THREE.Vector3(); 98 | difference.subVectors( this.object.position , this.anchor.position ); 99 | 100 | var l = difference.length(); 101 | var x = l - this.staticLength; 102 | 103 | // Hooke's Law 104 | var f = difference.normalize().multiplyScalar(x).multiplyScalar( this.springConstant ); 105 | 106 | return f; 107 | 108 | } 109 | 110 | this.applyForce = function( f ){ 111 | 112 | this.acceleration = f.multiplyScalar( 1 / this.mass ); 113 | 114 | this.velocity.add( this.acceleration ); 115 | 116 | this.velocity.multiplyScalar( this.dampening ); 117 | 118 | this.object.position.sub( this.velocity ); 119 | 120 | } 121 | 122 | this.leapToScene = function( position , clamp ){ 123 | 124 | var clamp = clamp || false; 125 | var box = this.frame.interactionBox; 126 | var nPos = box.normalizePoint( position , clamp ); 127 | 128 | nPos[0] = (nPos[0]-.5) * this.size; 129 | nPos[1] = (nPos[1]-.5) * this.size; 130 | nPos[2] = (nPos[2]-.5) * this.size; 131 | 132 | return new THREE.Vector3().fromArray( nPos ); 133 | 134 | } 135 | 136 | 137 | this.checkForNewAnchor = function(){ 138 | 139 | // getting our frame object 140 | this.frame = this.controller.frame(); 141 | 142 | // If this is the first frame, assign an old frame, 143 | // and also the interaction box 144 | if( !this.oFrame ){ 145 | this.oFrame = this.frame; 146 | } 147 | 148 | if( this.frame.valid ){ 149 | this.interactionBox = this.frame.data.interactionBox; 150 | } 151 | 152 | if( this.frame ){ 153 | 154 | if( this.frame.hands[0] && this.frame.pointables.length ){ 155 | 156 | /* 157 | 158 | First off move the finger indicator to the correct position 159 | 160 | */ 161 | var position = this.leapToScene( this.frame.hands[0].palmPosition ); 162 | position.z -= this.size; 163 | position.applyMatrix4( this.object.matrix ); 164 | 165 | this.hand.position = position; 166 | 167 | var pinchStrength = this.frame.hands[0].pinchStrength; 168 | 169 | if( pinchStrength > .5 ){ 170 | 171 | this.target.position = position; 172 | 173 | } 174 | 175 | } 176 | 177 | } 178 | 179 | this.oFrame = this.frame; 180 | 181 | } 182 | 183 | this.update = function(){ 184 | 185 | // Just incase this is overwritten somewhere else in the code 186 | this.object.matrixAutoUpdate = true; 187 | 188 | var f = this.controller.frame(); 189 | 190 | this.target.rotation.setFromRotationMatrix(camera.matrix); 191 | 192 | /* 193 | 194 | Since we always want to look at the anchor, 195 | This means that we want to make sure that it doesn't jump 196 | from position to position whenever we select a new target 197 | 198 | Because of this, always move the anchor towards the target 199 | 200 | */ 201 | 202 | var a = this.anchor.position; 203 | var t = this.target.position; 204 | 205 | // Moves the anchor towards the target 206 | var dif = a.clone().sub( t ); 207 | 208 | a = a.sub( dif.multiplyScalar( this.anchorSpeed ) ); 209 | 210 | // Get and apply the spring photos 211 | f = this.getForce(); 212 | this.applyForce( f ); 213 | 214 | // Makes sure that we are always looking at the 215 | // anchor position 216 | this.object.lookAt( this.anchor.position ); 217 | 218 | 219 | this.checkForNewAnchor(); 220 | 221 | } 222 | 223 | } 224 | -------------------------------------------------------------------------------- /controls/LeapWeightControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author cabbibo / http://cabbibo.com 3 | * 4 | * Circle to create a new anchor! 5 | * Will need to pass in a Scene, as well as a leap controller 6 | * In order to create the camera, so that you can place the 7 | * UI elements 8 | * 9 | * 10 | */ 11 | 12 | THREE.LeapWeightControls = function ( object , controller , scene , domElement ) { 13 | 14 | this.object = object; 15 | this.controller = controller; 16 | this.scene = scene; 17 | this.domElement = ( domElement !== undefined ) ? domElement : document; 18 | 19 | // API 20 | 21 | this.enable = true; 22 | 23 | this.velocity = new THREE.Vector3(); 24 | this.acceleration = new THREE.Vector3(); 25 | 26 | this.dampening = ( object.dampening !== undefined ) ? object.dampening : .95; 27 | 28 | this.weakDampening = .99; 29 | this.strongDampening = .8; 30 | 31 | this.dampening = this.strongDampening; 32 | 33 | this.size = 120; 34 | this.springConstant = 7; 35 | this.staticLength = this.size ; 36 | this.mass = 1000; 37 | 38 | this.anchorToTarget = 24; 39 | 40 | 41 | this.interactionBox = { 42 | center: [ 0 , 0 , 0 ], 43 | size: [ 0 , 0 , 0 ] 44 | } 45 | 46 | this.placesTraveled = []; 47 | 48 | //this.lDivisionFactor = 50; 49 | 50 | 51 | // Creates the Target Object ( object that will tween to anchor 52 | this.target = new THREE.Object3D(); 53 | this.targetIndicator = new THREE.Mesh( 54 | new THREE.IcosahedronGeometry( this.size / 250 , 1 ), 55 | new THREE.MeshBasicMaterial({ color: 0xffffff, opacity: .5 , transparent:true }) 56 | ); 57 | this.target.add( this.targetIndicator ); 58 | this.scene.add( this.target ); 59 | 60 | 61 | // Creates the Anchor Object ( object hat will switch instantly ) 62 | this.anchor = new THREE.Object3D(); 63 | this.anchorIndicator = new THREE.Mesh( 64 | new THREE.IcosahedronGeometry( this.size/200 , 1 ), 65 | new THREE.MeshBasicMaterial({ color:0x00ff00 }) 66 | ); 67 | //this.anchor.add( this.anchorIndicator ); // Uncomment , so show where target is tweening 68 | this.scene.add( this.anchor ); 69 | 70 | 71 | // Lets us know where our finger is 72 | this.fingerIndicator = new THREE.Mesh( 73 | new THREE.IcosahedronGeometry( this.size/200 , 1 ), 74 | new THREE.MeshBasicMaterial({ color:0xffffff , opacity: .5 , transparent:true }) 75 | ); 76 | scene.add( this.fingerIndicator ); 77 | 78 | this.getForce = function(){ 79 | 80 | var difference = new THREE.Vector3(); 81 | difference.subVectors( this.object.position , this.anchor.position ); 82 | 83 | var l = difference.length(); 84 | var x = l - this.staticLength; 85 | 86 | 87 | // Hooke's Law 88 | var f = difference.normalize().multiplyScalar(x).multiplyScalar( this.springConstant ); 89 | 90 | /*if( x < 0 ){ 91 | var addForce = difference.normalize().multiplyScalar( - x / l ); 92 | f.add( addForce ); 93 | }*/ 94 | 95 | return f; 96 | 97 | } 98 | 99 | this.applyForce = function( f ){ 100 | 101 | this.acceleration = f.multiplyScalar( 1 / this.mass ); 102 | 103 | this.velocity.add( this.acceleration ); 104 | 105 | this.velocity.multiplyScalar( this.dampening ); 106 | 107 | this.object.position.sub( this.velocity ); 108 | 109 | } 110 | 111 | this.leapToScene = function( position ){ 112 | 113 | var x = position[0] - this.interactionBox.center[0]; 114 | var y = position[1] - this.interactionBox.center[1]; 115 | var z = position[2] - this.interactionBox.center[2]; 116 | 117 | x /= this.interactionBox.size[0]; 118 | y /= this.interactionBox.size[1]; 119 | z /= this.interactionBox.size[2]; 120 | 121 | x *= this.size; 122 | y *= this.size; 123 | z *= this.size; 124 | 125 | return new THREE.Vector3( x , y , z ); 126 | 127 | } 128 | 129 | 130 | this.checkForNewAnchor = function(){ 131 | 132 | // getting our frame object 133 | this.frame = this.controller.frame(); 134 | 135 | // If this is the first frame, assign an old frame, 136 | // and also the interaction box 137 | if( !this.oFrame ){ 138 | this.oFrame = this.frame; 139 | this.interactionBox = this.frame.data.interactionBox; 140 | } 141 | 142 | if( this.frame ){ 143 | 144 | if( this.frame.hands[0] && this.frame.pointables.length ){ 145 | 146 | /* 147 | 148 | First off move the finger indicator to the correct position 149 | 150 | */ 151 | var position = this.leapToScene( this.frame.hands[0].palmPosition ); 152 | position.z -= this.size; 153 | position.applyMatrix4( this.object.matrix ); 154 | 155 | this.fingerIndicator.position = position; 156 | 157 | var pinchStrength = this.frame.hands[0].pinchStrength; 158 | if( pinchStrength > .5 ){ 159 | 160 | this.target.position = position; 161 | 162 | this.placesTraveled.push( position ); 163 | 164 | // Uses the gesture radius to define the size of attraction 165 | this.staticLength = /*( 1-pinchStrength ) */ this.size; 166 | } 167 | 168 | }else{ 169 | 170 | this.fingerIndicator.position.x = this.size * 10000; 171 | 172 | } 173 | 174 | } 175 | 176 | this.oFrame = this.frame; 177 | 178 | } 179 | 180 | // Non - rigid, don't update if past x = 0 , only look at if x > 0 181 | this.update = function(){ 182 | 183 | // Just incase this is overwritten somewhere else in the code 184 | this.object.matrixAutoUpdate = true; 185 | 186 | 187 | /* 188 | 189 | Since we always want to look at the anchor, 190 | This means that we want to make sure that it doesn't jump 191 | from position to position whenever we select a new target 192 | 193 | Because of this, always move the anchor towards the target 194 | 195 | */ 196 | 197 | var a = this.anchor.position; 198 | var t = this.target.position; 199 | 200 | // Moves the anchor towards the target 201 | a.x = a.x - ( a.x - t.x ) / this.anchorToTarget; 202 | a.y = a.y - ( a.y - t.y ) / this.anchorToTarget; 203 | a.z = a.z - ( a.z - t.z ) / this.anchorToTarget; 204 | 205 | 206 | // Get and apply the spring photos 207 | f = this.getForce(); 208 | this.applyForce( f ); 209 | 210 | // Makes sure that we are always looking at the 211 | // anchor position 212 | this.object.lookAt( this.anchor.position ); 213 | 214 | 215 | this.checkForNewAnchor(); 216 | 217 | } 218 | 219 | } 220 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | 2 | Leap Motion Individual Contributor License Agreement, v1.0 3 | 4 | In order to clarify the intellectual property license granted with 5 | Contributions from any person or entity, Leap Motion, Inc. ("Leap 6 | Motion") must have a Contributor License Agreement ("CLA") on file that 7 | has been signed by each Contributor, indicating agreement to the license 8 | terms below. This license is for your protection as a Contributor as 9 | well as the protection of Leap Motion; it does not change your rights to 10 | use your own Contributions for any other purpose. 11 | 12 | If you wish to submit a pull request, please read the terms of this agreement 13 | and indicate your acceptance of it by adding your name, email, and github account name 14 | to the bottom of this file. Pull requests submitted without accepting this agreement 15 | will not be reviewed. 16 | 17 | By adding your information to this file, you accept and agree to the following 18 | terms and conditions for Your present and future Contributions submitted to 19 | Leap Motion. Except for the license granted herein to Leap Motion and recipients of 20 | software distributed by Leap Motion, You reserve all right, title, and interest 21 | in and to Your Contributions. 22 | 23 | 1. Definitions. 24 | 25 | "You" (or "Your") shall mean the copyright owner or legal entity 26 | authorized by the copyright owner that is making this Agreement with 27 | Leap Motion. 28 | 29 | For legal entities, the entity making a Contribution and all other 30 | entities that control, are controlled by, or are under common control 31 | with that entity are considered to be a single Contributor. 32 | 33 | For the purposes of this definition, "control" means (i) the power, 34 | direct or indirect, to cause the direction or management of such entity, 35 | whether by contract or otherwise, or (ii) ownership of fifty percent 36 | (50%) or more of the outstanding shares, or (iii) beneficial ownership 37 | of such entity. 38 | 39 | “Contribution" shall mean any original work of authorship, including 40 | any modifications or additions to an existing work, that is 41 | intentionally submitted by You to Leap Motion for inclusion in, or 42 | documentation of, any of the products owned or managed by Leap Motion 43 | (the "Work"). For the purposes of this definition, "submitted" means any 44 | form of electronic, verbal, or written communication sent to Leap Motion 45 | or its representatives, including but not limited to communication on 46 | electronic mailing lists, source code control systems, and issue 47 | tracking systems that are managed by, or on behalf of, Leap Motion for 48 | the purpose of discussing and improving the Work, but excluding 49 | communication that is conspicuously marked or otherwise designated in 50 | writing by You as "Not a Contribution." 51 | 52 | 2. Grant of Copyright License. 53 | 54 | Subject to the terms and conditions of this Agreement, You hereby grant 55 | to Leap Motion and to recipients of software distributed by Leap Motion 56 | a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 57 | irrevocable copyright license to reproduce, prepare derivative works of, 58 | publicly display, publicly perform, sublicense, and distribute Your 59 | Contributions and such derivative works. 60 | 61 | 3. Grant of Patent License. 62 | 63 | Subject to the terms and conditions of this Agreement, You hereby grant 64 | to Leap Motion and to recipients of software distributed by Leap Motion 65 | a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 66 | irrevocable (except as stated in this section) patent license to make, 67 | have made, use, offer to sell, import, and otherwise transfer the Work, 68 | where such license applies only to those patent claims licensable by You 69 | that are necessarily infringed by Your Contribution(s) alone or by 70 | combination of Your Contribution(s) with the Work to which such 71 | Contribution(s) was submitted. If any entity institutes patent 72 | litigation against You or any other entity (including a cross-claim or 73 | counterclaim in a lawsuit) alleging that your Contribution, or the Work 74 | to which you have contributed, constitutes direct or contributory patent 75 | infringement, then any patent licenses granted to that entity under this 76 | Agreement for that Contribution or Work shall terminate as of the date 77 | such litigation is filed. 78 | 79 | 4. You represent that you are legally entitled to grant the above 80 | license. 81 | 82 | If your employer(s) has rights to intellectual property that you create 83 | that includes your Contributions, you represent that you have received 84 | permission to make Contributions on behalf of that employer, that your 85 | employer has waived such rights for your Contributions to Leap Motion, 86 | or that your employer has executed a separate Corporate CLA with Leap 87 | Motion. 88 | 89 | 5. You represent that each of Your Contributions is Your original 90 | creation (see section 7 for submissions on behalf of others). 91 | 92 | You represent that Your Contribution submissions include complete 93 | details of any third-party license or other restriction (including, but 94 | not limited to, related patents and trademarks) of which you are 95 | personally aware and which are associated with any part of Your 96 | Contributions. 97 | 98 | 6. You are not expected to provide support for Your Contributions, 99 | except to the extent You desire to provide support. You may provide 100 | support for free, for a fee, or not at all. Unless required by 101 | applicable law or agreed to in writing, You provide Your Contributions 102 | on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 103 | either express or implied, including, without limitation, any warranties 104 | or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS 105 | FOR A PARTICULAR PURPOSE. 106 | 107 | 7. Should You wish to submit work that is not Your original creation, 108 | You may submit it to Leap Motion separately from any Contribution, 109 | identifying the complete details of its source and of any license or 110 | other restriction (including, but not limited to, related patents, 111 | trademarks, and license agreements) of which you are personally aware, 112 | and conspicuously marking the work as "Submitted on behalf of a 113 | third-party: [[]named here]". 114 | 115 | 8. You agree to notify Leap Motion of any facts or circumstances of 116 | which you become aware that would make these representations inaccurate 117 | in any respect. 118 | 119 | ====================================================== 120 | 121 | 1. Isaac Cohen 122 | Github account: Cabbibo 123 | Email: icohen@leapmotion.com 124 | 125 | 2. Nash Lincoln 126 | Github account: nashira 127 | Email: nLincoln@leapmotion.com 128 | 129 | -------------------------------------------------------------------------------- /controls/LeapPinchRotateControls.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Leap Pinch Rotate Controls 4 | * Author: @Cabbibo 5 | * 6 | * http://github.com/leapmotion/Leap-Three-Camera-Controls/ 7 | * 8 | * Copyright 2014 LeapMotion, Inc 9 | * 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | * 22 | */ 23 | 24 | THREE.LeapPinchRotateControls = function ( object , controller , params, domElement ) { 25 | 26 | this.object = object; 27 | this.controller = controller; 28 | this.domElement = ( domElement !== undefined ) ? domElement : document; 29 | 30 | this.clock = new THREE.Clock(); // for smoother transitions 31 | 32 | // Place the camera wherever you want it 33 | // but use a rotating object to place 34 | this.rotatingObject = new THREE.Object3D(); 35 | 36 | this.rotatingCamera = new THREE.Object3D(); 37 | this.rotatingCamera.position = this.object.position.clone(); 38 | 39 | this.rotatingObject.add( this.rotatingCamera ); 40 | 41 | this.zoomSpeed = 0; 42 | this.rotation = new THREE.Quaternion(); 43 | this.angularVelocity = new THREE.Vector3(); 44 | 45 | //API 46 | 47 | this.rotationSpeed = 10; 48 | this.rotationLowDampening = .98; 49 | this.rotationHighDampening = .7; 50 | 51 | this.pinchCutoff = .5; 52 | this.zoomVsRotate = 1; 53 | 54 | this.zoomEnabled = true; 55 | this.zoom = 40; 56 | this.zoomDampening = .6; 57 | this.zoomSpeedRatio = 10; 58 | 59 | this.minZoom = 20; 60 | this.maxZoom = 80; 61 | 62 | this.getTorque = function( frame ){ 63 | 64 | var torqueTotal = new THREE.Vector3(); 65 | 66 | if( frame.hands[0] ){ 67 | 68 | if( frame.hands[0].pinchStrength > this.pinchCutoff ){ 69 | 70 | var palmVel = frame.hands[0].palmVelocity; 71 | var v = new THREE.Vector3( palmVel[0] , palmVel[1] , 0 ); 72 | var d = new THREE.Vector3( 0 , 0 , -1 ); 73 | 74 | // Dividing this.rotationSpeed by large number just to 75 | // keep parameters in a reasonable range 76 | var rotationSpeed = this.rotationSpeed; 77 | v.multiplyScalar( rotationSpeed ); 78 | 79 | 80 | var torque = new THREE.Vector3().crossVectors( v , d ); 81 | torqueTotal.add( torque ); 82 | 83 | } 84 | 85 | } 86 | 87 | return torqueTotal; 88 | 89 | } 90 | 91 | 92 | this.getZoomForce = function( frame ){ 93 | 94 | var zoomForce = 0; 95 | 96 | if( frame.hands[0] ){ 97 | 98 | if( frame.hands[0].pinchStrength > this.pinchCutoff ){ 99 | 100 | var handVel = frame.hands[0].palmVelocity; 101 | 102 | var zMovement = Math.abs(handVel[2]); 103 | var xyMovement = (Math.abs( handVel[0] ) + Math.abs( handVel[1] ))/2; 104 | 105 | if( zMovement > xyMovement * this.zoomVsRotate ){ 106 | 107 | var zoomSpeedRatio = this.zoomSpeedRatio * 10; 108 | zoomForce = -handVel[2]*this.zoomSpeedRatio*10; 109 | 110 | } 111 | } 112 | } 113 | 114 | return zoomForce; 115 | 116 | } 117 | 118 | // Will have high dampening, only if we are moving 119 | // more in the z direction than the x and y, 120 | // and are pinching 121 | this.getRotationDampening = function( frame ){ 122 | 123 | var dampening = this.rotationLowDampening; 124 | 125 | if( frame.hands[0] ){ 126 | 127 | if( frame.hands[0].pinchStrength > this.pinchCutoff ){ 128 | 129 | var handVel = frame.hands[0].palmVelocity; 130 | 131 | var zMovement = Math.abs(handVel[2]); 132 | var xyMovement = (Math.abs( handVel[0] ) + Math.abs( handVel[1] ))/2; 133 | 134 | if( zMovement > xyMovement * this.zoomVsRotate ){ 135 | 136 | dampening = this.rotationHighDampening; 137 | 138 | } 139 | 140 | } 141 | 142 | } 143 | 144 | return dampening; 145 | 146 | } 147 | 148 | this.update = function(){ 149 | 150 | // making sure our matrix transforms don't get overwritten 151 | this.object.matrixAutoUpdate = false; 152 | 153 | var frame = this.controller.frame(); 154 | 155 | var dTime = this.clock.getDelta(); 156 | 157 | var torque = this.getTorque( frame ); 158 | var dampening = this.getRotationDampening( frame ); 159 | var dTime = this.clock.getDelta(); 160 | 161 | if( this.zoomEnabled ){ 162 | 163 | var zoomForce = this.getZoomForce( frame ); 164 | 165 | this.zoomSpeed += zoomForce * dTime; 166 | this.zoom += this.zoomSpeed; 167 | this.zoomSpeed *= this.zoomDampening; 168 | 169 | // Maxes sure that we done go below or above the max zoom! 170 | if( this.zoom > this.maxZoom ){ 171 | 172 | this.zoom = this.maxZoom; 173 | this.zoomSpeed = 0; 174 | 175 | }else if( this.zoom < this.minZoom ){ 176 | 177 | this.zoom = this.minZoom; 178 | this.zoomSpeed = 0; 179 | 180 | } 181 | 182 | } 183 | 184 | this.angularVelocity.add( torque ); 185 | this.angularVelocity.multiplyScalar( dampening ); 186 | 187 | var angularDistance = this.angularVelocity.clone().multiplyScalar( dTime ); 188 | 189 | var axis = angularDistance.clone().normalize(); 190 | var angle = angularDistance.length(); 191 | 192 | var rotationChange = new THREE.Quaternion(); 193 | rotationChange.setFromAxisAngle( axis , angle ); 194 | 195 | var rotation = new THREE.Quaternion(); 196 | rotation.multiplyQuaternions( rotationChange , this.rotation ); 197 | 198 | this.rotation = rotation; 199 | 200 | this.rotatingObject.rotation.setFromQuaternion( rotation ); 201 | 202 | this.rotatingObject.updateMatrix(); 203 | 204 | this.updateCameraPosition(); 205 | 206 | } 207 | 208 | 209 | this.updateCameraPosition = function(){ 210 | 211 | var matrix = this.rotatingObject.matrix; 212 | 213 | var inverse = new THREE.Matrix4().getInverse( matrix ); 214 | 215 | var translationMatrix = new THREE.Matrix4(); 216 | 217 | var pos = new THREE.Vector3( 0 , 0 , this.zoom ); 218 | translationMatrix.setPosition( pos ); 219 | 220 | var rotatedMatrix = new THREE.Matrix4(); 221 | rotatedMatrix.multiplyMatrices( inverse , translationMatrix ); 222 | 223 | this.object.matrix.copy( rotatedMatrix ); 224 | this.object.matrixWorldNeedsUpdate = true; 225 | 226 | 227 | // Makes sure that if the camera is moving we are updating it 228 | this.rotatingCamera.position = this.object.position.clone(); 229 | 230 | // Need to convert to world position here. 231 | var worldPosition = this.object.position.clone(); 232 | worldPosition.applyMatrix4( this.rotatingObject.matrix ); 233 | 234 | this.object.position = worldPosition; 235 | 236 | // The camera is always looking at the center of the object 237 | // it is rotating around. 238 | this.object.lookAt( this.rotatingObject.position ); 239 | 240 | } 241 | 242 | } 243 | 244 | 245 | // This function moves from a position from leap space, 246 | // to a position in scene space 247 | this.leapToScene = function( position , clamp ){ 248 | 249 | var clamp = clamp || false; 250 | var box = this.frame.interactionBox; 251 | var nPos = box.normalizePoint( position , clamp ); 252 | 253 | nPos[0] = (nPos[0]-.5) * this.size; 254 | nPos[1] = (nPos[1]-.5) * this.size; 255 | nPos[2] = (nPos[2]-.5) * this.size; 256 | 257 | return new THREE.Vector3().fromArray( nPos ); 258 | 259 | } 260 | -------------------------------------------------------------------------------- /lib/OBJLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.OBJLoader = function ( manager ) { 6 | 7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 | 9 | }; 10 | 11 | THREE.OBJLoader.prototype = { 12 | 13 | constructor: THREE.OBJLoader, 14 | 15 | load: function ( url, onLoad, onProgress, onError ) { 16 | 17 | var scope = this; 18 | 19 | var loader = new THREE.XHRLoader( scope.manager ); 20 | loader.setCrossOrigin( this.crossOrigin ); 21 | loader.load( url, function ( text ) { 22 | 23 | onLoad( scope.parse( text ) ); 24 | 25 | } ); 26 | 27 | }, 28 | 29 | parse: function ( text ) { 30 | 31 | function vector( x, y, z ) { 32 | 33 | return new THREE.Vector3( parseFloat( x ), parseFloat( y ), parseFloat( z ) ); 34 | 35 | } 36 | 37 | function uv( u, v ) { 38 | 39 | return new THREE.Vector2( parseFloat( u ), parseFloat( v ) ); 40 | 41 | } 42 | 43 | function face3( a, b, c, normals ) { 44 | 45 | return new THREE.Face3( a, b, c, normals ); 46 | 47 | } 48 | 49 | var object = new THREE.Object3D(); 50 | var geometry, material, mesh; 51 | 52 | function parseVertexIndex( index ) { 53 | 54 | index = parseInt( index ); 55 | 56 | return index >= 0 ? index - 1 : index + vertices.length; 57 | 58 | } 59 | 60 | function parseNormalIndex( index ) { 61 | 62 | index = parseInt( index ); 63 | 64 | return index >= 0 ? index - 1 : index + normals.length; 65 | 66 | } 67 | 68 | function parseUVIndex( index ) { 69 | 70 | index = parseInt( index ); 71 | 72 | return index >= 0 ? index - 1 : index + uvs.length; 73 | 74 | } 75 | 76 | function add_face( a, b, c, normals_inds ) { 77 | 78 | if ( normals_inds === undefined ) { 79 | 80 | geometry.faces.push( face3( 81 | vertices[ parseVertexIndex( a ) ] - 1, 82 | vertices[ parseVertexIndex( b ) ] - 1, 83 | vertices[ parseVertexIndex( c ) ] - 1 84 | ) ); 85 | 86 | } else { 87 | 88 | geometry.faces.push( face3( 89 | vertices[ parseVertexIndex( a ) ] - 1, 90 | vertices[ parseVertexIndex( b ) ] - 1, 91 | vertices[ parseVertexIndex( c ) ] - 1, 92 | [ 93 | normals[ parseNormalIndex( normals_inds[ 0 ] ) ].clone(), 94 | normals[ parseNormalIndex( normals_inds[ 1 ] ) ].clone(), 95 | normals[ parseNormalIndex( normals_inds[ 2 ] ) ].clone() 96 | ] 97 | ) ); 98 | 99 | } 100 | 101 | } 102 | 103 | function add_uvs( a, b, c ) { 104 | 105 | geometry.faceVertexUvs[ 0 ].push( [ 106 | uvs[ parseUVIndex( a ) ].clone(), 107 | uvs[ parseUVIndex( b ) ].clone(), 108 | uvs[ parseUVIndex( c ) ].clone() 109 | ] ); 110 | 111 | } 112 | 113 | function handle_face_line(faces, uvs, normals_inds) { 114 | 115 | if ( faces[ 3 ] === undefined ) { 116 | 117 | add_face( faces[ 0 ], faces[ 1 ], faces[ 2 ], normals_inds ); 118 | 119 | if ( uvs !== undefined && uvs.length > 0 ) { 120 | 121 | add_uvs( uvs[ 0 ], uvs[ 1 ], uvs[ 2 ] ); 122 | 123 | } 124 | 125 | } else { 126 | 127 | if ( normals_inds !== undefined && normals_inds.length > 0 ) { 128 | 129 | add_face( faces[ 0 ], faces[ 1 ], faces[ 3 ], [ normals_inds[ 0 ], normals_inds[ 1 ], normals_inds[ 3 ] ] ); 130 | add_face( faces[ 1 ], faces[ 2 ], faces[ 3 ], [ normals_inds[ 1 ], normals_inds[ 2 ], normals_inds[ 3 ] ] ); 131 | 132 | } else { 133 | 134 | add_face( faces[ 0 ], faces[ 1 ], faces[ 3 ] ); 135 | add_face( faces[ 1 ], faces[ 2 ], faces[ 3 ] ); 136 | 137 | } 138 | 139 | if ( uvs !== undefined && uvs.length > 0 ) { 140 | 141 | add_uvs( uvs[ 0 ], uvs[ 1 ], uvs[ 3 ] ); 142 | add_uvs( uvs[ 1 ], uvs[ 2 ], uvs[ 3 ] ); 143 | 144 | } 145 | 146 | } 147 | 148 | } 149 | 150 | // create mesh if no objects in text 151 | 152 | if ( /^o /gm.test( text ) === false ) { 153 | 154 | geometry = new THREE.Geometry(); 155 | material = new THREE.MeshLambertMaterial(); 156 | mesh = new THREE.Mesh( geometry, material ); 157 | object.add( mesh ); 158 | 159 | } 160 | 161 | var vertices = []; 162 | var normals = []; 163 | var uvs = []; 164 | 165 | // v float float float 166 | 167 | var vertex_pattern = /v( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; 168 | 169 | // vn float float float 170 | 171 | var normal_pattern = /vn( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; 172 | 173 | // vt float float 174 | 175 | var uv_pattern = /vt( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; 176 | 177 | // f vertex vertex vertex ... 178 | 179 | var face_pattern1 = /f( +-?\d+)( +-?\d+)( +-?\d+)( +-?\d+)?/; 180 | 181 | // f vertex/uv vertex/uv vertex/uv ... 182 | 183 | var face_pattern2 = /f( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))?/; 184 | 185 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ... 186 | 187 | var face_pattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/; 188 | 189 | // f vertex//normal vertex//normal vertex//normal ... 190 | 191 | var face_pattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/ 192 | 193 | // 194 | 195 | var lines = text.split( '\n' ); 196 | 197 | for ( var i = 0; i < lines.length; i ++ ) { 198 | 199 | var line = lines[ i ]; 200 | line = line.trim(); 201 | 202 | var result; 203 | 204 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) { 205 | 206 | continue; 207 | 208 | } else if ( ( result = vertex_pattern.exec( line ) ) !== null ) { 209 | 210 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 211 | 212 | vertices.push( 213 | geometry.vertices.push( 214 | vector( 215 | result[ 1 ], result[ 2 ], result[ 3 ] 216 | ) 217 | ) 218 | ); 219 | 220 | } else if ( ( result = normal_pattern.exec( line ) ) !== null ) { 221 | 222 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 223 | 224 | normals.push( 225 | vector( 226 | result[ 1 ], result[ 2 ], result[ 3 ] 227 | ) 228 | ); 229 | 230 | } else if ( ( result = uv_pattern.exec( line ) ) !== null ) { 231 | 232 | // ["vt 0.1 0.2", "0.1", "0.2"] 233 | 234 | uvs.push( 235 | uv( 236 | result[ 1 ], result[ 2 ] 237 | ) 238 | ); 239 | 240 | } else if ( ( result = face_pattern1.exec( line ) ) !== null ) { 241 | 242 | // ["f 1 2 3", "1", "2", "3", undefined] 243 | 244 | handle_face_line( 245 | [ result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] ] 246 | ); 247 | 248 | } else if ( ( result = face_pattern2.exec( line ) ) !== null ) { 249 | 250 | // ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined] 251 | 252 | handle_face_line( 253 | [ result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ] ], //faces 254 | [ result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] ] //uv 255 | ); 256 | 257 | } else if ( ( result = face_pattern3.exec( line ) ) !== null ) { 258 | 259 | // ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3", undefined, undefined, undefined, undefined] 260 | 261 | handle_face_line( 262 | [ result[ 2 ], result[ 6 ], result[ 10 ], result[ 14 ] ], //faces 263 | [ result[ 3 ], result[ 7 ], result[ 11 ], result[ 15 ] ], //uv 264 | [ result[ 4 ], result[ 8 ], result[ 12 ], result[ 16 ] ] //normal 265 | ); 266 | 267 | } else if ( ( result = face_pattern4.exec( line ) ) !== null ) { 268 | 269 | // ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined] 270 | 271 | handle_face_line( 272 | [ result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ] ], //faces 273 | [ ], //uv 274 | [ result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] ] //normal 275 | ); 276 | 277 | } else if ( /^o /.test( line ) ) { 278 | 279 | geometry = new THREE.Geometry(); 280 | material = new THREE.MeshLambertMaterial(); 281 | 282 | mesh = new THREE.Mesh( geometry, material ); 283 | mesh.name = line.substring( 2 ).trim(); 284 | object.add( mesh ); 285 | 286 | } else if ( /^g /.test( line ) ) { 287 | 288 | // group 289 | 290 | } else if ( /^usemtl /.test( line ) ) { 291 | 292 | // material 293 | 294 | material.name = line.substring( 7 ).trim(); 295 | 296 | } else if ( /^mtllib /.test( line ) ) { 297 | 298 | // mtl file 299 | 300 | } else if ( /^s /.test( line ) ) { 301 | 302 | // smooth shading 303 | 304 | } else { 305 | 306 | // console.log( "THREE.OBJLoader: Unhandled line " + line ); 307 | 308 | } 309 | 310 | } 311 | 312 | var children = object.children; 313 | 314 | for ( var i = 0, l = children.length; i < l; i ++ ) { 315 | 316 | var geometry = children[ i ].geometry; 317 | 318 | geometry.computeCentroids(); 319 | geometry.computeFaceNormals(); 320 | geometry.computeBoundingSphere(); 321 | 322 | } 323 | 324 | return object; 325 | 326 | } 327 | 328 | }; -------------------------------------------------------------------------------- /examples/lib/OBJLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.OBJLoader = function ( manager ) { 6 | 7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 | 9 | }; 10 | 11 | THREE.OBJLoader.prototype = { 12 | 13 | constructor: THREE.OBJLoader, 14 | 15 | load: function ( url, onLoad, onProgress, onError ) { 16 | 17 | var scope = this; 18 | 19 | var loader = new THREE.XHRLoader( scope.manager ); 20 | loader.setCrossOrigin( this.crossOrigin ); 21 | loader.load( url, function ( text ) { 22 | 23 | onLoad( scope.parse( text ) ); 24 | 25 | } ); 26 | 27 | }, 28 | 29 | parse: function ( text ) { 30 | 31 | function vector( x, y, z ) { 32 | 33 | return new THREE.Vector3( parseFloat( x ), parseFloat( y ), parseFloat( z ) ); 34 | 35 | } 36 | 37 | function uv( u, v ) { 38 | 39 | return new THREE.Vector2( parseFloat( u ), parseFloat( v ) ); 40 | 41 | } 42 | 43 | function face3( a, b, c, normals ) { 44 | 45 | return new THREE.Face3( a, b, c, normals ); 46 | 47 | } 48 | 49 | var object = new THREE.Object3D(); 50 | var geometry, material, mesh; 51 | 52 | function parseVertexIndex( index ) { 53 | 54 | index = parseInt( index ); 55 | 56 | return index >= 0 ? index - 1 : index + vertices.length; 57 | 58 | } 59 | 60 | function parseNormalIndex( index ) { 61 | 62 | index = parseInt( index ); 63 | 64 | return index >= 0 ? index - 1 : index + normals.length; 65 | 66 | } 67 | 68 | function parseUVIndex( index ) { 69 | 70 | index = parseInt( index ); 71 | 72 | return index >= 0 ? index - 1 : index + uvs.length; 73 | 74 | } 75 | 76 | function add_face( a, b, c, normals_inds ) { 77 | 78 | if ( normals_inds === undefined ) { 79 | 80 | geometry.faces.push( face3( 81 | vertices[ parseVertexIndex( a ) ] - 1, 82 | vertices[ parseVertexIndex( b ) ] - 1, 83 | vertices[ parseVertexIndex( c ) ] - 1 84 | ) ); 85 | 86 | } else { 87 | 88 | geometry.faces.push( face3( 89 | vertices[ parseVertexIndex( a ) ] - 1, 90 | vertices[ parseVertexIndex( b ) ] - 1, 91 | vertices[ parseVertexIndex( c ) ] - 1, 92 | [ 93 | normals[ parseNormalIndex( normals_inds[ 0 ] ) ].clone(), 94 | normals[ parseNormalIndex( normals_inds[ 1 ] ) ].clone(), 95 | normals[ parseNormalIndex( normals_inds[ 2 ] ) ].clone() 96 | ] 97 | ) ); 98 | 99 | } 100 | 101 | } 102 | 103 | function add_uvs( a, b, c ) { 104 | 105 | geometry.faceVertexUvs[ 0 ].push( [ 106 | uvs[ parseUVIndex( a ) ].clone(), 107 | uvs[ parseUVIndex( b ) ].clone(), 108 | uvs[ parseUVIndex( c ) ].clone() 109 | ] ); 110 | 111 | } 112 | 113 | function handle_face_line(faces, uvs, normals_inds) { 114 | 115 | if ( faces[ 3 ] === undefined ) { 116 | 117 | add_face( faces[ 0 ], faces[ 1 ], faces[ 2 ], normals_inds ); 118 | 119 | if ( uvs !== undefined && uvs.length > 0 ) { 120 | 121 | add_uvs( uvs[ 0 ], uvs[ 1 ], uvs[ 2 ] ); 122 | 123 | } 124 | 125 | } else { 126 | 127 | if ( normals_inds !== undefined && normals_inds.length > 0 ) { 128 | 129 | add_face( faces[ 0 ], faces[ 1 ], faces[ 3 ], [ normals_inds[ 0 ], normals_inds[ 1 ], normals_inds[ 3 ] ] ); 130 | add_face( faces[ 1 ], faces[ 2 ], faces[ 3 ], [ normals_inds[ 1 ], normals_inds[ 2 ], normals_inds[ 3 ] ] ); 131 | 132 | } else { 133 | 134 | add_face( faces[ 0 ], faces[ 1 ], faces[ 3 ] ); 135 | add_face( faces[ 1 ], faces[ 2 ], faces[ 3 ] ); 136 | 137 | } 138 | 139 | if ( uvs !== undefined && uvs.length > 0 ) { 140 | 141 | add_uvs( uvs[ 0 ], uvs[ 1 ], uvs[ 3 ] ); 142 | add_uvs( uvs[ 1 ], uvs[ 2 ], uvs[ 3 ] ); 143 | 144 | } 145 | 146 | } 147 | 148 | } 149 | 150 | // create mesh if no objects in text 151 | 152 | if ( /^o /gm.test( text ) === false ) { 153 | 154 | geometry = new THREE.Geometry(); 155 | material = new THREE.MeshLambertMaterial(); 156 | mesh = new THREE.Mesh( geometry, material ); 157 | object.add( mesh ); 158 | 159 | } 160 | 161 | var vertices = []; 162 | var normals = []; 163 | var uvs = []; 164 | 165 | // v float float float 166 | 167 | var vertex_pattern = /v( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; 168 | 169 | // vn float float float 170 | 171 | var normal_pattern = /vn( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; 172 | 173 | // vt float float 174 | 175 | var uv_pattern = /vt( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/; 176 | 177 | // f vertex vertex vertex ... 178 | 179 | var face_pattern1 = /f( +-?\d+)( +-?\d+)( +-?\d+)( +-?\d+)?/; 180 | 181 | // f vertex/uv vertex/uv vertex/uv ... 182 | 183 | var face_pattern2 = /f( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+))?/; 184 | 185 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ... 186 | 187 | var face_pattern3 = /f( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))( +(-?\d+)\/(-?\d+)\/(-?\d+))?/; 188 | 189 | // f vertex//normal vertex//normal vertex//normal ... 190 | 191 | var face_pattern4 = /f( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))( +(-?\d+)\/\/(-?\d+))?/ 192 | 193 | // 194 | 195 | var lines = text.split( '\n' ); 196 | 197 | for ( var i = 0; i < lines.length; i ++ ) { 198 | 199 | var line = lines[ i ]; 200 | line = line.trim(); 201 | 202 | var result; 203 | 204 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) { 205 | 206 | continue; 207 | 208 | } else if ( ( result = vertex_pattern.exec( line ) ) !== null ) { 209 | 210 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 211 | 212 | vertices.push( 213 | geometry.vertices.push( 214 | vector( 215 | result[ 1 ], result[ 2 ], result[ 3 ] 216 | ) 217 | ) 218 | ); 219 | 220 | } else if ( ( result = normal_pattern.exec( line ) ) !== null ) { 221 | 222 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 223 | 224 | normals.push( 225 | vector( 226 | result[ 1 ], result[ 2 ], result[ 3 ] 227 | ) 228 | ); 229 | 230 | } else if ( ( result = uv_pattern.exec( line ) ) !== null ) { 231 | 232 | // ["vt 0.1 0.2", "0.1", "0.2"] 233 | 234 | uvs.push( 235 | uv( 236 | result[ 1 ], result[ 2 ] 237 | ) 238 | ); 239 | 240 | } else if ( ( result = face_pattern1.exec( line ) ) !== null ) { 241 | 242 | // ["f 1 2 3", "1", "2", "3", undefined] 243 | 244 | handle_face_line( 245 | [ result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] ] 246 | ); 247 | 248 | } else if ( ( result = face_pattern2.exec( line ) ) !== null ) { 249 | 250 | // ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined] 251 | 252 | handle_face_line( 253 | [ result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ] ], //faces 254 | [ result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] ] //uv 255 | ); 256 | 257 | } else if ( ( result = face_pattern3.exec( line ) ) !== null ) { 258 | 259 | // ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3", undefined, undefined, undefined, undefined] 260 | 261 | handle_face_line( 262 | [ result[ 2 ], result[ 6 ], result[ 10 ], result[ 14 ] ], //faces 263 | [ result[ 3 ], result[ 7 ], result[ 11 ], result[ 15 ] ], //uv 264 | [ result[ 4 ], result[ 8 ], result[ 12 ], result[ 16 ] ] //normal 265 | ); 266 | 267 | } else if ( ( result = face_pattern4.exec( line ) ) !== null ) { 268 | 269 | // ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined] 270 | 271 | handle_face_line( 272 | [ result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ] ], //faces 273 | [ ], //uv 274 | [ result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] ] //normal 275 | ); 276 | 277 | } else if ( /^o /.test( line ) ) { 278 | 279 | geometry = new THREE.Geometry(); 280 | material = new THREE.MeshLambertMaterial(); 281 | 282 | mesh = new THREE.Mesh( geometry, material ); 283 | mesh.name = line.substring( 2 ).trim(); 284 | object.add( mesh ); 285 | 286 | } else if ( /^g /.test( line ) ) { 287 | 288 | // group 289 | 290 | } else if ( /^usemtl /.test( line ) ) { 291 | 292 | // material 293 | 294 | material.name = line.substring( 7 ).trim(); 295 | 296 | } else if ( /^mtllib /.test( line ) ) { 297 | 298 | // mtl file 299 | 300 | } else if ( /^s /.test( line ) ) { 301 | 302 | // smooth shading 303 | 304 | } else { 305 | 306 | // console.log( "THREE.OBJLoader: Unhandled line " + line ); 307 | 308 | } 309 | 310 | } 311 | 312 | var children = object.children; 313 | 314 | for ( var i = 0, l = children.length; i < l; i ++ ) { 315 | 316 | var geometry = children[ i ].geometry; 317 | 318 | geometry.computeCentroids(); 319 | geometry.computeFaceNormals(); 320 | geometry.computeBoundingSphere(); 321 | 322 | } 323 | 324 | return object; 325 | 326 | } 327 | 328 | }; -------------------------------------------------------------------------------- /controls/LeapTrackballControls.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Leap Trackball Controls 4 | * Author: @Cabbibo 5 | * 6 | * http://github.com/leapmotion/Leap-Three-Camera-Controls/ 7 | * 8 | * Copyright 2014 LeapMotion, Inc 9 | * 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | * 22 | */ 23 | 24 | THREE.LeapTrackballControls = function ( object , controller , params, domElement ) { 25 | 26 | this.object = object; 27 | this.controller = controller; 28 | this.domElement = ( domElement !== undefined ) ? domElement : document; 29 | 30 | this.clock = new THREE.Clock(); // for smoother transitions 31 | 32 | // Place the camera wherever you want it 33 | // but use a rotating object to place 34 | this.rotatingObject = new THREE.Object3D(); 35 | 36 | this.rotatingCamera = new THREE.Object3D(); 37 | this.rotatingCamera.position = this.object.position.clone(); 38 | 39 | this.rotatingObject.add( this.rotatingCamera ); 40 | 41 | this.zoomSpeed = 0; 42 | 43 | 44 | //API 45 | 46 | this.rotationSpeed = 10; 47 | this.rotationLowDampening = .98; 48 | this.rotationHighDampening = .7; 49 | this.rotationDampeningCutoff = .9; 50 | 51 | this.zoomEnabled = true; 52 | this.zoom = 40; 53 | this.zoomDampening = .6; 54 | this.zoomCutoff = .9; 55 | this.zoomSpeedRatio = 10; 56 | 57 | this.minZoom = 20; 58 | this.maxZoom = 80; 59 | 60 | this.rotation = new THREE.Quaternion(); 61 | this.angularVelocity = new THREE.Vector3(); 62 | 63 | 64 | this.getTorque = function( frame ){ 65 | 66 | var torqueTotal = new THREE.Vector3(); 67 | 68 | 69 | if( frame.hands[0] ){ 70 | 71 | hand = frame.hands[0]; 72 | 73 | var hDirection = new THREE.Vector3().fromArray( hand.direction ); 74 | var hNormal = new THREE.Vector3().fromArray( hand.palmNormal ); 75 | 76 | 77 | for( var i = 0; i < hand.fingers.length; i++ ){ 78 | 79 | var finger = hand.fingers[i]; 80 | 81 | if( finger.extended ){ 82 | 83 | var fD = finger.direction; 84 | var fV = finger.tipVelocity; 85 | 86 | // First off see if the fingers pointed 87 | // the same direction as the hand 88 | var fDirection = new THREE.Vector3().fromArray( fD ); 89 | var fMatch = fDirection.dot( hDirection ); 90 | 91 | // See if the finger velocity is in the same direction 92 | // as the hand normal 93 | var fVelocity = new THREE.Vector3().fromArray( fV ); 94 | var tmp = fVelocity.clone(); 95 | var vMatch = Math.abs( tmp.normalize().dot( hNormal ) ); 96 | 97 | 98 | // Dividing this.rotationSpeed by large number just to 99 | // keep parameters in a reasonable range 100 | var rotationSpeed = this.rotationSpeed / 100000; 101 | fVelocity.multiplyScalar( rotationSpeed * fMatch * vMatch ); 102 | 103 | var torque = new THREE.Vector3().crossVectors( fVelocity , hDirection ); 104 | torqueTotal.add( torque ); 105 | 106 | } 107 | 108 | } 109 | 110 | } 111 | 112 | return torqueTotal; 113 | 114 | } 115 | 116 | this.getRotationDampening = function( frame ){ 117 | 118 | var dampening = this.rotationLowDampening; 119 | 120 | if( frame.hands[0] ){ 121 | 122 | var handNormal = frame.hands[0].palmNormal; 123 | 124 | // only dampening if I'm giving a 'STOP' symbol to the camera 125 | if( -handNormal[2] > this.rotationDampeningCutoff ){ 126 | dampening = this.rotationHighDampening; 127 | } 128 | } 129 | 130 | return dampening; 131 | 132 | } 133 | 134 | this.getZoomForce = function( frame ){ 135 | 136 | var zoomForce = 0; 137 | 138 | if( frame.hands[0] ){ 139 | 140 | var hand = frame.hands[0]; 141 | var handNormal = new THREE.Vector3().fromArray( hand.palmNormal ); 142 | 143 | if( Math.abs( handNormal.z ) > this.zoomCutoff ){ 144 | 145 | var palmVelocity = new THREE.Vector3().fromArray( hand.palmVelocity ); 146 | 147 | for( var i = 0; i < hand.fingers.length; i++ ){ 148 | 149 | var finger = hand.fingers[i]; 150 | 151 | if( finger.extended ){ 152 | 153 | var fD = finger.direction; 154 | var fV = finger.tipVelocity; 155 | 156 | // First off see if the fingers pointed 157 | // the same direction as the hand 158 | var fDirection = new THREE.Vector3().fromArray( fD ); 159 | 160 | var match = fDirection.dot( handNormal ); 161 | 162 | // because fingers should be perp to handNormal, make the answer 1 - match 163 | var force = 1 - match; 164 | 165 | var fVelocity = new THREE.Vector3().fromArray( fV ); 166 | 167 | var dir = fVelocity.dot( new THREE.Vector3( 0 , 0 , 1 ) ); 168 | 169 | var zoomSpeedRatio = this.zoomSpeedRatio / 1000; 170 | zoomForce -= dir* zoomSpeedRatio; 171 | 172 | } 173 | 174 | } 175 | } 176 | } 177 | return zoomForce; 178 | 179 | } 180 | 181 | this.update = function(){ 182 | 183 | // making sure our matrix transforms don't get overwritten 184 | this.object.matrixAutoUpdate = false; 185 | 186 | var frame = this.controller.frame(); 187 | 188 | var torque = this.getTorque( frame ); 189 | var dTime = this.clock.getDelta(); 190 | 191 | var rotationDampening = this.getRotationDampening( frame ); 192 | 193 | if( this.zoomEnabled ){ 194 | 195 | var zoomForce = this.getZoomForce( frame ); 196 | 197 | this.zoomSpeed += zoomForce * dTime; 198 | this.zoom += this.zoomSpeed; 199 | this.zoomSpeed *= this.zoomDampening; 200 | 201 | // Maxes sure that we done go below or above the max zoom! 202 | if( this.zoom > this.maxZoom ){ 203 | 204 | this.zoom = this.maxZoom; 205 | this.zoomSpeed = 0; 206 | 207 | }else if( this.zoom < this.minZoom ){ 208 | 209 | this.zoom = this.minZoom; 210 | this.zoomSpeed = 0; 211 | 212 | } 213 | 214 | } 215 | 216 | this.angularVelocity.add( torque ); 217 | this.angularVelocity.multiplyScalar( rotationDampening ); 218 | 219 | var angularDistance = this.angularVelocity.clone().multiplyScalar( dTime ); 220 | 221 | var axis = angularDistance.clone().normalize(); 222 | var angle = angularDistance.length(); 223 | 224 | var rotationChange = new THREE.Quaternion(); 225 | rotationChange.setFromAxisAngle( axis , angle ); 226 | 227 | var rotation = new THREE.Quaternion(); 228 | rotation.multiplyQuaternions( rotationChange , this.rotation ); 229 | 230 | this.rotation = rotation; 231 | 232 | this.rotatingObject.rotation.setFromQuaternion( rotation ); 233 | 234 | this.rotatingObject.updateMatrix(); 235 | 236 | this.updateCameraPosition(); 237 | 238 | } 239 | 240 | 241 | this.updateCameraPosition = function(){ 242 | 243 | var matrix = this.rotatingObject.matrix; 244 | 245 | var inverse = new THREE.Matrix4().getInverse( matrix ); 246 | 247 | var translationMatrix = new THREE.Matrix4(); 248 | 249 | var pos = new THREE.Vector3( 0 , 0 , this.zoom ); 250 | translationMatrix.setPosition( pos ); 251 | 252 | var rotatedMatrix = new THREE.Matrix4(); 253 | rotatedMatrix.multiplyMatrices( inverse , translationMatrix ); 254 | 255 | this.object.matrix.copy( rotatedMatrix ); 256 | this.object.matrixWorldNeedsUpdate = true; 257 | 258 | 259 | // Makes sure that if the camera is moving we are updating it 260 | this.rotatingCamera.position = this.object.position.clone(); 261 | 262 | // Need to convert to world position here. 263 | var worldPosition = this.object.position.clone(); 264 | worldPosition.applyMatrix4( this.rotatingObject.matrix ); 265 | 266 | this.object.position = worldPosition; 267 | 268 | // The camera is always looking at the center of the object 269 | // it is rotating around. 270 | this.object.lookAt( this.rotatingObject.position ); 271 | 272 | } 273 | 274 | } 275 | 276 | 277 | // This function moves from a position from leap space, 278 | // to a position in scene space 279 | this.leapToScene = function( position , clamp ){ 280 | 281 | var clamp = clamp || false; 282 | var box = this.frame.interactionBox; 283 | var nPos = box.normalizePoint( position , clamp ); 284 | 285 | nPos[0] = (nPos[0]-.5) * this.size; 286 | nPos[1] = (nPos[1]-.5) * this.size; 287 | nPos[2] = (nPos[2]-.5) * this.size; 288 | 289 | return new THREE.Vector3().fromArray( nPos ); 290 | 291 | } 292 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | -------------------------------------------------------------------------------- /controls/LeapTwoHandControls.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Leap Eye Look Controls 3 | * Author: @Nashira 4 | * 5 | * http://github.com/leapmotion/Leap-Three-Camera-Controls/ 6 | * 7 | * Copyright 2014 LeapMotion, Inc 8 | * 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * 21 | */ 22 | 23 | THREE.LeapTwoHandControls = (function () { 24 | 25 | var PI_2 = Math.PI * 2; 26 | var X_AXIS = new THREE.Vector3(1, 0, 0); 27 | var Y_AXIS = new THREE.Vector3(0, 1, 0); 28 | var Z_AXIS = new THREE.Vector3(0, 0, 1); 29 | 30 | var LeapTwoHandControls = function (object, controller, invert) { 31 | this.object = object; 32 | this.controller = controller; 33 | this.invert = (invert === undefined ? true : invert); 34 | this.anchorDelta = 1; 35 | 36 | this.translationSpeed = 20; 37 | this.translationDecay = 0.3; 38 | this.scaleDecay = 0.5; 39 | this.rotationSlerp = 0.8; 40 | this.rotationSpeed = 4; 41 | this.pinchThreshold = 0.5; 42 | this.transSmoothing = 0.5; 43 | this.rotationSmoothing = 0.2; 44 | 45 | this.vector = new THREE.Vector3(); 46 | this.vector2 = new THREE.Vector3(); 47 | this.matrix = new THREE.Matrix4(); 48 | this.quaternion = new THREE.Quaternion(); 49 | this.rotationMomentum = new THREE.Quaternion(); 50 | this.translationMomentum = new THREE.Vector3(); 51 | this.scaleMomentum = new THREE.Vector3(1, 1, 1); 52 | this.rotationMomentum = this.object.quaternion.clone(); 53 | 54 | this.transLP = [ 55 | new LowPassFilter(this.transSmoothing), 56 | new LowPassFilter(this.transSmoothing), 57 | new LowPassFilter(this.transSmoothing)]; 58 | 59 | this.rotLP = [ 60 | new LowPassFilter(this.rotationSmoothing), 61 | new LowPassFilter(this.rotationSmoothing), 62 | new LowPassFilter(this.rotationSmoothing)]; 63 | } 64 | 65 | LeapTwoHandControls.prototype.update = function() { 66 | 67 | // Just incase this is overwritten somewhere else in the code 68 | this.object.matrixAutoUpdate = true; 69 | 70 | var self = this; 71 | var frame = this.controller.frame(); 72 | var anchorFrame = this.controller.frame(this.anchorDelta); 73 | 74 | // do we have a frame 75 | if (!frame || !frame.valid || !anchorFrame || !anchorFrame.valid) { 76 | return; 77 | } 78 | 79 | // match hands to anchors 80 | // remove hands that have disappeared 81 | // add hands that have appeared 82 | var rawHands = frame.hands; 83 | var rawAnchorHands = anchorFrame.hands; 84 | 85 | var hands = []; 86 | var anchorHands = []; 87 | 88 | rawHands.forEach(function (hand, hIdx) { 89 | var anchorHand = anchorFrame.hand(hand.id); 90 | if (anchorHand.valid) { 91 | hands.push(hand); 92 | anchorHands.push(anchorHand); 93 | } 94 | }); 95 | 96 | if (hands.length) { 97 | // translation 98 | if (this.shouldTranslate(anchorHands, hands)) { 99 | this.applyTranslation(anchorHands, hands); 100 | } 101 | 102 | // rotation 103 | if (this.shouldRotate(anchorHands, hands)) { 104 | this.applyRotation(anchorHands, hands); 105 | } 106 | 107 | // scale 108 | if (this.shouldScale(anchorHands, hands)) { 109 | this.applyScale(anchorHands, hands); 110 | } 111 | } 112 | 113 | this.object.position.add(this.translationMomentum); 114 | this.translationMomentum.multiplyScalar(this.translationDecay); 115 | 116 | this.object.quaternion.slerp(this.rotationMomentum, this.rotationSlerp); 117 | this.object.quaternion.normalize(); 118 | 119 | this.object.scale.lerp(this.scaleMomentum, this.scaleDecay); 120 | } 121 | 122 | LeapTwoHandControls.prototype.shouldTranslate = function (anchorHands, hands) { 123 | var isEngaged = this.isEngaged.bind(this); 124 | return hands.some(isEngaged); 125 | } 126 | 127 | LeapTwoHandControls.prototype.shouldScale = function (anchorHands, hands) { 128 | var isEngaged = this.isEngaged.bind(this); 129 | return anchorHands.every(isEngaged) && hands.every(isEngaged); 130 | } 131 | 132 | LeapTwoHandControls.prototype.shouldRotate = function (anchorHands, hands) { 133 | var isEngaged = this.isEngaged.bind(this); 134 | return anchorHands.length > 1 135 | && hands.length > 1 136 | && anchorHands.every(isEngaged) 137 | && hands.every(isEngaged); 138 | } 139 | 140 | LeapTwoHandControls.prototype.applyTranslation = function (anchorHands, hands) { 141 | var isEngaged = this.isEngaged.bind(this); 142 | var translation = this.getTranslation( 143 | anchorHands.filter(isEngaged), 144 | hands.filter(isEngaged)); 145 | 146 | translation[0] = this.transLP[0].sample(translation[0]); 147 | translation[1] = this.transLP[1].sample(translation[1]); 148 | translation[2] = this.transLP[2].sample(translation[2]); 149 | 150 | this.vector.fromArray(translation); 151 | if (this.invert) { 152 | this.vector.negate(); 153 | } 154 | this.vector.multiplyScalar(this.translationSpeed); 155 | this.vector.applyQuaternion(this.object.quaternion); 156 | this.translationMomentum.add(this.vector); 157 | } 158 | 159 | LeapTwoHandControls.prototype.applyRotation = function (anchorHands, hands) { 160 | var rotation = this.getRotation(anchorHands, hands); 161 | rotation[0] = this.rotLP[0].sample(rotation[0]); 162 | rotation[1] = this.rotLP[1].sample(rotation[1]); 163 | rotation[2] = this.rotLP[2].sample(rotation[2]); 164 | this.vector.fromArray(rotation); 165 | this.vector.multiplyScalar(this.rotationSpeed); 166 | if (this.invert) { 167 | this.vector.negate(); 168 | } 169 | 170 | this.quaternion.setFromAxisAngle(X_AXIS, this.vector.x); 171 | this.rotationMomentum.multiply(this.quaternion); 172 | this.quaternion.setFromAxisAngle(Y_AXIS, this.vector.y); 173 | this.rotationMomentum.multiply(this.quaternion); 174 | this.quaternion.setFromAxisAngle(Z_AXIS, this.vector.z); 175 | this.rotationMomentum.multiply(this.quaternion); 176 | 177 | this.rotationMomentum.normalize(); 178 | } 179 | 180 | LeapTwoHandControls.prototype.applyScale = function (anchorHands, hands) { 181 | var scale = this.getScale(anchorHands, hands); 182 | this.scaleMomentum.multiplyScalar(scale[3]); 183 | } 184 | 185 | LeapTwoHandControls.prototype.getTranslation = function(anchorHands, hands) { 186 | if (anchorHands.length != hands.length) { 187 | return [0, 0, 0]; 188 | } 189 | var centerAnchor = getCenter(anchorHands); 190 | var centerCurrent = getCenter(hands); 191 | return [ 192 | centerCurrent[0] - centerAnchor[0], 193 | centerCurrent[1] - centerAnchor[1], 194 | centerCurrent[2] - centerAnchor[2] 195 | ]; 196 | } 197 | 198 | LeapTwoHandControls.prototype.getScale = function(anchorHands, hands) { 199 | if (hands.length < 2 || anchorHands.length < 2) { 200 | return [1, 1, 1, 1]; 201 | } 202 | 203 | var centerAnchor = getCenter(anchorHands); 204 | var centerCurrent = getCenter(hands); 205 | var aveRadiusAnchor = aveDistance(centerAnchor, anchorHands); 206 | var aveRadiusCurrent = aveDistance(centerCurrent, hands); 207 | 208 | // scale of current over previous 209 | return [ 210 | aveRadiusCurrent[0] / aveRadiusAnchor[0], 211 | aveRadiusCurrent[1] / aveRadiusAnchor[1], 212 | aveRadiusCurrent[2] / aveRadiusAnchor[2], 213 | length(aveRadiusCurrent) / length(aveRadiusAnchor) 214 | ]; 215 | } 216 | 217 | LeapTwoHandControls.prototype.getRotation = function(anchorHands, hands) { 218 | if (hands.length < 1 || anchorHands.length < 1 219 | || hands.length != anchorHands.length) { 220 | return [0, 0, 0]; 221 | } 222 | 223 | var am = getAxisMag(hands); 224 | if (am[3] < 6000) { 225 | return [0, 0, 0]; 226 | } 227 | var mi = 1 / am[3]; 228 | am[0]*=mi; 229 | am[1]*=mi; 230 | am[2]*=mi; 231 | 232 | var anchorAngles = getAngles(anchorHands); 233 | var angles = getAngles(hands); 234 | 235 | var dx = angles[0] - anchorAngles[0]; 236 | var dy = angles[1] - anchorAngles[1]; 237 | var dz = angles[2] - anchorAngles[2]; 238 | 239 | if (dx > Math.PI) dx = dx - PI_2; 240 | else if (dx < -Math.PI) dx = dx + PI_2; 241 | if (dy > Math.PI) dy = dy - PI_2; 242 | else if (dy < -Math.PI) dy = dy + PI_2; 243 | if (dz > Math.PI) dz = dz - PI_2; 244 | else if (dz < -Math.PI) dz = dz + PI_2; 245 | 246 | return [dx * am[0], dy * am[1], dz * am[2]]; 247 | } 248 | 249 | 250 | LeapTwoHandControls.prototype.isEngaged = function(h) { 251 | return h && (h.pinchStrength > this.pinchThreshold); 252 | } 253 | 254 | function getCenter(hands) { 255 | var l = hands.length; 256 | if (l == 0) { 257 | return [0, 0, 0]; 258 | } else if (l == 1) { 259 | return hands[0].palmPosition; 260 | } 261 | 262 | var x = y = z = 0; 263 | hands.forEach(function (hand, i) { 264 | x += hand.palmPosition[0]; 265 | y += hand.palmPosition[1]; 266 | z += hand.palmPosition[2]; 267 | }); 268 | return [x/l, y/l, z/l]; 269 | } 270 | 271 | function getAngles(hands) { 272 | if (hands.length == 0) { 273 | return [0, 0, 0]; 274 | } 275 | 276 | var pos1; 277 | var hand = hands[0]; 278 | if (hands.length > 1) { 279 | pos1 = hands[1].palmPosition; 280 | } else { 281 | pos1 = hand.frame.interactionBox.center; 282 | } 283 | 284 | var pos2 = hand.palmPosition; 285 | 286 | var dx = pos2[0] - pos1[0]; 287 | var dy = pos2[1] - pos1[1]; 288 | var dz = pos2[2] - pos1[2]; 289 | 290 | var ax = Math.atan2(dy, dz); 291 | var ay = Math.atan2(dx, dz); 292 | var az = Math.atan2(dy, dx); 293 | return [ax, ay, az]; 294 | } 295 | 296 | function getAxisMag(hands) { 297 | if (hands.length == 0) { 298 | return [0, 0, 0, 0]; 299 | } 300 | 301 | var pos1; 302 | var hand = hands[0]; 303 | if (hands.length > 1) { 304 | pos1 = hands[1].palmPosition; 305 | } else { 306 | pos1 = hand.frame.interactionBox.center; 307 | } 308 | 309 | var pos2 = hand.palmPosition; 310 | 311 | var dx = pos2[0] - pos1[0]; 312 | var dy = pos2[1] - pos1[1]; 313 | var dz = pos2[2] - pos1[2]; 314 | var mag = dx * dx + dy * dy + dz * dz; 315 | 316 | var ax = dy * dy + dz * dz; 317 | var ay = dx * dx + dz * dz; 318 | var az = dy * dy + dx * dx; 319 | 320 | return [ax, ay, az, mag]; 321 | } 322 | 323 | function aveDistance(center, hands) { 324 | var aveDistance = [0, 0, 0]; 325 | hands.forEach(function (hand) { 326 | var p = hand.palmPosition; 327 | aveDistance[0] += Math.abs(p[0] - center[0]); 328 | aveDistance[1] += Math.abs(p[1] - center[1]); 329 | aveDistance[2] += Math.abs(p[2] - center[2]); 330 | }); 331 | aveDistance[0] /= hands.length; 332 | aveDistance[1] /= hands.length; 333 | aveDistance[2] /= hands.length; 334 | return aveDistance; 335 | } 336 | 337 | function length(arr) { 338 | var sum = 0; 339 | arr.forEach(function (v) { 340 | sum += v * v; 341 | }); 342 | return Math.sqrt(sum); 343 | } 344 | 345 | function dist(arr1, arr2) { 346 | var sum = 0; 347 | arr1.forEach(function (v, i) { 348 | var d = v - arr2[i]; 349 | sum += d * d; 350 | }); 351 | return Math.sqrt(sum); 352 | } 353 | 354 | 355 | function LowPassFilter(cutoff) { 356 | var accumulator = 0; 357 | 358 | this.setCutoff = function (value) { 359 | cutoff = value; 360 | }; 361 | 362 | this.sample = function(sample) { 363 | accumulator += (sample - accumulator) * cutoff; 364 | return accumulator; 365 | } 366 | } 367 | 368 | return LeapTwoHandControls; 369 | }()); 370 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Three.js Camera Controls using Leap Motion 2 | ===== 3 | 4 | Hey Friends! 5 | 6 | I hope you are here to use the Leap.js / Three.js Camera controls. 7 | If you aren't, the following might not make since, but since you are here, 8 | you should try using them anyway! 9 | 10 | This Repo is most definitly still a work in progress, so if you see any 11 | errors, have any questions, think all the controls are horrendous, think the 12 | code is ugly, have your own controls that are way better than all of these, 13 | PLEASE, PLEASE, PLEASE let me know at icohen@leapmotion.com || @cabbibo . 14 | 15 | The Realm we have entered with gesture controlled devices is a wild frontier, 16 | an unexplored landscape. We will make mistakes, and the more we know about these 17 | mistakes, the better we can survive in this new magical space. So let me know 18 | your opinions, my fellow Motion Pioneers! 19 | 20 | Whats in this Readme? 21 | ----- 22 | 23 | - Links to Useful Resources 24 | - Basic Implementation 25 | - A full example from start to finish! 26 | - Further explanations of each individual camera controls 27 | - How you can help! 28 | 29 | Useful Resources 30 | ===== 31 | 32 | 33 | - [ The github page for this repo ]( http://leapmotion.github.io/Leap-Three-Camera-Controls/ ) 34 | - [ three.js library ]( http://threejs.org/ ) 35 | - [ Example of three.js Camera Control ]( http://threejs.org/examples/#misc_controls_trackball ) 36 | - [ TODO: Really Pretty Tech Demo of Nvidias Open Source Driver Call Graph ]() 37 | 38 | 39 | Basic Implementation 40 | ===== 41 | 42 | Like Any other javascript program, the first thing we need to 43 | do is include the proper files. In our case we use three.js 44 | leap.js and which ever camera control we decide to use 45 | ( For our demo use case, we will use 'LeapSpringControls.js", so we 46 | start of our program like so: 47 | 48 | Include Scripts 49 | ------ 50 | 51 | ``` 52 | 53 | 54 | 55 | ``` 56 | 57 | The next thing we will do is set up our controls. We are going to 58 | skip over all of the three.js initialization, but if you want, you can 59 | just grab that from the next section. 60 | 61 | Whichever camera controls we use, we initialize them the same way we would 62 | a three.js camera control. The only difference is that instead of just 63 | passing through which camera we want the controls to apply to, in this case 64 | we also need to tell the camera controls which leap controller we want to use. 65 | 66 | If this seems convoluted, I promise its only because I suck at the English 67 | language. Lets talk in javascript instead: 68 | 69 | 70 | Initializing Controls 71 | ----- 72 | 73 | ``` 74 | // our leap controller 75 | var controller; 76 | 77 | // our leap camera controls 78 | var controls; 79 | 80 | // our three.js variables 81 | var scene , camera; 82 | 83 | 84 | // Whatever function you use to initialize 85 | // your three.js scene, add your code here 86 | function init(){ 87 | 88 | 89 | // Three.js initialization 90 | var scene = new THREE.Scene(); 91 | var camera = new THREE.PerspectiveCamera( 92 | 50 , 93 | window.innerWidth / window.innerHeight, 94 | 1, 95 | 1000 96 | ); 97 | 98 | // Our Leap Controller 99 | var controller = new Leap.controller(); 100 | controller.connect(); 101 | 102 | // The long awaited camera controls! 103 | var controls = new THREE.LeapSpringControls( camera , controller , scene ); 104 | 105 | } 106 | ``` 107 | 108 | It is important to note that the LeapSpringControls take in a camera , a controller , 109 | AND a scene as input. This is because this specific control places a marker on the page 110 | as a helper. 111 | 112 | The last thing we need to do is to make sure the controls are constantly being updated, 113 | so we include a few more lines in whatever function we are using to render the three.js 114 | scene. 115 | 116 | Updating Controls 117 | ----- 118 | 119 | ``` 120 | function animate(){ 121 | 122 | //THREE.js rendering goes here 123 | 124 | 125 | // This is the only thing we need! 126 | controls.update(); 127 | 128 | 129 | // Make sure animate gets called again 130 | requestAnimationFrame( animate ); 131 | 132 | 133 | } 134 | ``` 135 | 136 | If any of this doesn't make sense, check out the full example below. Also email 137 | icohen@leapmotion.com || @cabbibo with any questions / comments! 138 | 139 | 140 | Full Code Example 141 | ===== 142 | 143 | ``` 144 | 145 | 146 | 158 | 159 | 160 | 161 |
162 | 163 | 164 | 165 | 166 | 167 | 168 | 231 | 232 | 233 | ``` 234 | 235 | More Information About Controls 236 | ===== 237 | 238 | This section is about what each of the controls are the most useful 239 | for. It will also try to include descriptions of what works, what 240 | doesn't work, and what will hopefully one day work. 241 | 242 | Pointer Controls 243 | ----- 244 | 245 | The pointer controls basically has the camera always pointing at a 246 | 'target', when you pinch, you begin moving the camera around the object, 247 | and when you release, the camera will stop moving. 248 | 249 | Pros: 250 | 251 | - Always looking at the same place, so its hard to get out of control 252 | - movements feel smoothish 253 | - Absolute positioning means that when comparing to the leap, 254 | the position will always make sense 255 | 256 | Cons: 257 | 258 | - Moving camera near poles results in some weirdness 259 | - Because there is only a single target, hard to move around scene 260 | unless the target is dynamically updated 261 | - Uses pinch, which removes the ability to use it for other gestures 262 | 263 | 264 | Pairings: 265 | 266 | - Pointer controls work well with a single examined object 267 | - 3D Modeling camera controls 268 | - A Game with a single scene that we are always looking at 269 | - A quick addition to visual experiments 270 | 271 | Called using: 272 | 273 | ``` 274 | 275 | 276 | 277 | // Inside Init Function 278 | var controls = THREE.LeapPointerControls( camera , controller ); 279 | 280 | controls.size = 100; 281 | controls.speed = .01; 282 | controls.dampening = .99; 283 | controls.target = new THREE.Vector3( 0 , 100 , 0 ); 284 | 285 | // Inside Animate Function 286 | controls.update(); 287 | ``` 288 | 289 | Using the following parameters: 290 | 291 | - size: Tells us how big the motions will be, basically the spherical 292 | distance from the target 293 | 294 | - dampening: Tells us how fast the camera will slow down once we release 295 | it. also how 'smoothed' the movement will be 296 | 297 | - speed: Tells us how fast the camera will follow our hand movements. 298 | This number should be between 0 and 1 299 | 300 | - target: Tells us where the camera is looking. A THREE.Vector3(), 301 | target basically defines the center of the scene 302 | 303 | 304 | Eye Look Controls 305 | ----- 306 | 307 | Eye Look Controls are very similar to the Pointer controls. Infact when 308 | you use your right hand, they are exactly the same. The biggest difference 309 | is that when you use your left hand, you dynamically move the target. 310 | This leads to the ability to easily move around a scene, but always have a 311 | specific point you are focused on. Also, all movements are relative, 312 | rather than absolute. 313 | 314 | 315 | Pros: 316 | 317 | - Always looking at the same place, so its hard to get out of control 318 | - movements feel smoothish 319 | - Relative movements allow for the exploration of the entire scene 320 | 321 | Cons: 322 | 323 | - Moving camera near poles results in some weirdness 324 | - Uses pinch, which removes the ability to use it for other gestures 325 | - Relative movement means that you can get very far away from yourr 326 | target, leading to depth being difficult to judge 327 | - Difficult to move through an entire scene quickly 328 | 329 | 330 | Pairings: 331 | 332 | - Slowly examining a full scene 333 | - 3D Modeling camera controls 334 | - A quick addition to visual experiments 335 | 336 | 337 | Called using: 338 | 339 | ``` 340 | 341 | 342 | 343 | // Inside Init Function 344 | var controls = THREE.LeapEyeLookControls( camera , controller , scene ); 345 | 346 | controls.lookSize = 10; 347 | controls.lookMass = 10; 348 | controls.lookSpeed = 10; 349 | controls.lookDampening = .9; 350 | 351 | controls.eyeSize = 10; 352 | controls.eyeMass = 10; 353 | controls.eyeSpeed = 10; 354 | controls.eyeDampening = .9; 355 | 356 | // If you want to have a marker for your eye 357 | // Which you probably do... 358 | 359 | var geo = new THREE.CubeGeometry( 1 , 1 , 1 ); 360 | var mat = new THREE.MeshNormalMaterial(); 361 | var mesh = new THREE.Mesh( geo , mat ); 362 | 363 | controls.addLookMarker( mesh ); 364 | 365 | // Inside Animate Function 366 | controls.update(); 367 | ``` 368 | 369 | Using the following parameters: 370 | 371 | - lookSize: Tells us how big the movements will be for the look object 372 | by adding bigger or smaller numbers to the force 373 | 374 | - lookMass: Tells us more about how the look object will move by giving 375 | it different mass. A smaller mass with fling around the field 376 | while a larger mass will be slower / harder to move 377 | 378 | - lookSpeed: Tells us how much the speed will be multiplied by when we 379 | determine the final speed to be added to the position 380 | 381 | - lookDampening: Tells us how quickly the look object will slow down 382 | 383 | - eyeSize: Tells us how big the movements will be for the eye object 384 | by adding bigger or smaller numbers to the force 385 | 386 | - eyeMass: Tells us more about how the eye object will move by giving 387 | it different mass. A smaller mass with fling around the field 388 | while a larger mass will be slower / harder to move 389 | 390 | - eyeSpeed: Tells us how much the speed will be multiplied by when we 391 | determine the final speed to be added to the position 392 | 393 | - eyeDampening: Tells us how quickly the eye object will slow down 394 | 395 | 396 | Spring Controls 397 | ----- 398 | 399 | Spring controls Attatch a spring from your camera to a target, which it 400 | is always looking at. When you pinch, it places a new anchor that the 401 | target will tween to, always giving you a smooth movement. To see exactly 402 | what this means, try adding markers to the anchor , hand , and target 403 | as described in the below code snippet 404 | 405 | Pros: 406 | 407 | - Smooth like butter 408 | - Lets you fly to anywhere you want in the scene with relative ease 409 | - Once you let go, gives slowly brings you to a final resting point 410 | 411 | 412 | Cons: 413 | 414 | - Moving camera near poles results in some weirdness... 415 | - Uses pinch, which removes the ability to use it for other gestures 416 | - Easy to get lost in space if you have no reference points 417 | 418 | Pairings: 419 | 420 | - Space Flying Games 421 | - Plane Flying Games 422 | - A quick addition to visual experiments 423 | 424 | 425 | Called using: 426 | 427 | ``` 428 | 429 | 430 | 431 | // Inside Init Function 432 | controls = new THREE.LeapSpringControls( camera , controller , scene ); 433 | 434 | controls.dampening = .75; 435 | controls.size = 120; 436 | controls.springConstant = 1; 437 | controls.mass = 100; 438 | controls.anchorSpeed = .1; 439 | controls.staticLength = 100; 440 | 441 | 442 | // Adding meshes to the Anchor , Target and Hand 443 | var geo = new THREE.IcosahedronGeometry( 5, 2 ); 444 | var mat = new THREE.MeshNormalMaterial(); 445 | 446 | var targetMesh = new THREE.Mesh( geo , mat ); 447 | var anchorMesh = new THREE.Mesh( geo , mat ); 448 | var handMesh = new THREE.Mesh( geo , mat ); 449 | 450 | controls.addTargetMarker( targetMesh ); 451 | controls.addAnchorMarker( anchorMesh ); 452 | controls.addHandMarker( handMesh ); 453 | 454 | // Inside Animate Function 455 | controls.update(); 456 | ``` 457 | 458 | Using the following parameters: 459 | 460 | - dampening: Tells us how quickly movement slows down 461 | - size: Tells us size of hand movements 462 | - springConstant: Tells us value for Hooke's Law constant k 463 | - mass: Tells us mass of camera 464 | - anchorSpeed: Tells us how fast Anchor tweens to target 465 | ( .5 and higher gets weird. but it shouldn't, 466 | I just forgot how to do physics. Pull request maybe ?!!???!? ) 467 | - staticLength: Tells us how far away camera comes to rest from target 468 | 469 | 470 | Trackball Controls 471 | ----- 472 | 473 | Trackball Controls let you swipe the camera around a target, as if you 474 | were pushing a giant bowling ball around ( your hand is always behind the ball ) 475 | Also , if you turn your hand straight up, and zoom is enabled, you will 476 | stop spinning and start zooming, based on moving your hand forward and backwards 477 | 478 | 479 | Pros: 480 | 481 | - Supersmooth. 482 | - No Gimbal Lock! 483 | - No use of Pinch! 484 | 485 | Cons: 486 | 487 | - Only moves around single point 488 | - Controls take some getting used to for some people 489 | - No clear up vector, which leads to possible deorientation 490 | 491 | Pairings: 492 | 493 | - 3D Modeling camera controls 494 | - A quick addition to visual experiments 495 | 496 | 497 | Called using: 498 | 499 | ``` 500 | 501 | 502 | 503 | // Inside Init Function 504 | var controls = THREE.LeapTrackballControls( camera , controller ); 505 | 506 | controls.rotationSpeed = 10; 507 | controls.rotationLowDampening = .98; 508 | controls.rotationHighDampening = .7; 509 | controls.zoom = 40; 510 | controls.zoomDampening = .6; 511 | controls.zoomSpeedRatio = 10; 512 | controls.zoomCutoff = .9; 513 | controls.zoomEnabled = true; 514 | controls.minZoom = 20; 515 | controls.maxZoom = 80; 516 | 517 | // Inside Animate Function 518 | controls.update(); 519 | ``` 520 | 521 | Using the following parameters: 522 | 523 | - rotationSpeed: Tells us the speed of the rotation 524 | - rotationLowDampening: Tells us how quickly the rotation will slow down when in moving state 525 | - rotationHighDampening: Tells us how quickly the rotation will slow down when in stopping state 526 | - zoomEnabled: Tells us if zooming is enabled 527 | - zoom: Tells us how close we are to the center 528 | - zoomDampening: Tells us how quickly the zoom will slow down 529 | - zoomSpeedRatio: Tells us how quickly the zoom moves compared to palm 530 | - zoomCutoff: Tells us how forward facing our palm needs to be to zoom 531 | - minZoom: Tells us the closest we can be 532 | - maxZoom: Tells us the farthest we can be 533 | 534 | 535 | Pinch Rotate Controls 536 | ----- 537 | 538 | Pinch Rotate Controls are nearly Identical to the Trackball controls, except that they use pinch in order to move the camera. As well, they have the ability to zoom in and out, by simply pinching and moving inwards or outwards. In order to define when this happens, it looks at the movement in Z vs the movement in X and Y, and compares the too to see if there is more movement in Z than XY or vis versa 539 | 540 | Pros: 541 | 542 | - Supersmooth. 543 | - No Gimbal Lock! 544 | 545 | Cons: 546 | 547 | - Only moves around single point 548 | - Controls take some getting used to for some people 549 | - No clear up vector, which leads to possible deorientation 550 | - Uses Pinch :-( 551 | 552 | Pairings: 553 | 554 | - 3D Modeling camera controls 555 | - A quick addition to visual experiments 556 | 557 | 558 | Called using: 559 | 560 | ``` 561 | 562 | 563 | 564 | // Inside Init Function 565 | var controls = THREE.LeapPinchRotateControls( camera , controller ); 566 | 567 | controls.rotationSpeed = 10; 568 | controls.rotationLowDampening = .98; 569 | controls.rotationHighDampening = .7; 570 | controls.zoom = 40; 571 | controls.zoomDampening = .6; 572 | controls.zoomSpeedRatio = 10; 573 | controls.zoomCutoff = .9; 574 | controls.zoomEnabled = true; 575 | controls.zoomVsRotate = 1; 576 | controls.minZoom = 20; 577 | controls.maxZoom = 80; 578 | 579 | // Inside Animate Function 580 | controls.update(); 581 | ``` 582 | 583 | Using the following parameters: 584 | 585 | - rotationSpeed: Tells us the speed of the rotation 586 | - rotationLowDampening: Tells us how quickly the rotation will slow down when in moving state 587 | - rotationHighDampening: Tells us how quickly the rotation will slow down when in stopping state 588 | - zoomEnabled: Tells us if zooming is enabled 589 | - zoom: Tells us how close we are to the center 590 | - zoomDampening: Tells us how quickly the zoom will slow down 591 | - zoomSpeedRatio: Tells us how quickly the zoom moves compared to palm 592 | - zoomCutoff: Tells us how forward facing our palm needs to be to zoom 593 | - zoomVsRotate: Tells us how much more we need to be moving in Z than XY to start zooming, vs rotating 594 | - minZoom: Tells us the closest we can be 595 | - maxZoom: Tells us the farthest we can be 596 | 597 | 598 | 599 | Paddle Controls 600 | ----- 601 | 602 | Paddle Controls Let you 'Paddle' Around a scene, the way that you would paddle 603 | through water. Pretty cool huh? 604 | 605 | Pros: 606 | 607 | - Supersmooth. 608 | - Makes you feel a bit like a god 609 | - No Gimbal Lock! 610 | - No Pinch! 611 | 612 | Cons: 613 | 614 | - No Rotate... 615 | - Controls take some getting used to for some people 616 | 617 | 618 | Pairings: 619 | 620 | - Great for moving a scene where you want don't want to rotate 621 | - Great for 'infinite' terrains 622 | - Great to combine with other methods of control! 623 | 624 | Called using: 625 | 626 | ``` 627 | 628 | 629 | 630 | // Inside Init Function 631 | var controls = THREE.LeapPaddleControls( camera , controller ); 632 | 633 | controls.weakDampening = .99; 634 | controls.strongDampening = .9; 635 | controls.fingerMatchCutoff = .5; 636 | controls.velocityMatchCutoff =.5; 637 | controls.fingerMatchPower = 5; 638 | controls.velocityMatchPower = 5; 639 | controls.movementSpeed = 1; 640 | controls.maxSpeed = 10; 641 | 642 | 643 | // Inside Animate Function 644 | controls.update(); 645 | ``` 646 | 647 | Using the following parameters: 648 | 649 | - weakDampening: Tells us dampening when there is a hand in field 650 | - strongDampening: Tells us dampening when there is no hand in field 651 | - fingerMatchCutoff: Tells us the number at which we will stop moving if the finger direction does not match the hand direction 652 | - velocityMatchCutoff: Tells us the number at which we will stop moving if the finger velocity does not match the hand normal 653 | - fingerMatchPower: Tells us the amount that the fingerMatch will be raised to to give a higher or lower turn on for movement 654 | - velocityMatchPower: Tells us the amount that the velocityMatch will be raised to to give a higher or lower turn on for movement 655 | - movementSpeed: Tells us how fast we are moving, by multiplying the force 656 | - maxSpeed: Tells us what we will limit the cameras speed to 657 | 658 | 659 | Two Hand Controls 660 | ----- 661 | 662 | Two Hand controls let you translate around a scene by pinching with a single 663 | hand, and rotate scene when you pinch with two hands 664 | 665 | Pros: 666 | 667 | - You feel a bit like iron man 668 | - You don't accidentally rotate the scene when you don't want to 669 | - Once you let go, gives slowly brings you to a final resting point 670 | 671 | 672 | Cons: 673 | 674 | - Sometimes difficult for tracking to pick up with two hands in the field 675 | - Uses pinch, which removes the ability to use it for other gestures 676 | - Using two hands might be more tiring 677 | 678 | 679 | Pairings: 680 | 681 | - Quickly exploring large swatches of land 682 | - Manipulating large scenes 683 | - A quick addition to visual experiments 684 | 685 | 686 | Called using: 687 | 688 | ``` 689 | 690 | 691 | 692 | // Inside Init Function 693 | controls = new THREE.LeapTwoHandControls( camera , controller , scene ); 694 | 695 | controls.translationSpeed = 20; 696 | controls.translationDecay = 0.3; 697 | controls.scaleDecay = 0.5; 698 | controls.rotationSlerp = 0.8; 699 | controls.rotationSpeed = 4; 700 | controls.pinchThreshold = 0.5; 701 | controls.transSmoothing = 0.5; 702 | controls.rotationSmoothing = 0.2; 703 | 704 | // Inside Animate Function 705 | controls.update(); 706 | ``` 707 | 708 | TODO: Description of Parameters 709 | 710 | 711 | 712 | First Person Controls 713 | ----- 714 | 715 | TODO 716 | 717 | 718 | What You can Do To Help 719 | ======= 720 | 721 | - Let me know about comments / suggestions / complaints etc via issues 722 | - Contribute your own controls via a pull request 723 | - Tell your friends, and get more people user testing / involved! 724 | -------------------------------------------------------------------------------- /lib/dat.gui.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * dat-gui JavaScript Controller Library 3 | * http://code.google.com/p/dat-gui 4 | * 5 | * Copyright 2011 Data Arts Team, Google Creative Lab 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | */ 13 | var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(e,a){var a=a||document,c=a.createElement("link");c.type="text/css";c.rel="stylesheet";c.href=e;a.getElementsByTagName("head")[0].appendChild(c)},inject:function(e,a){var a=a||document,c=document.createElement("style");c.type="text/css";c.innerHTML=e;a.getElementsByTagName("head")[0].appendChild(c)}}}(); 14 | dat.utils.common=function(){var e=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(c){this.each(a.call(arguments,1),function(a){for(var f in a)this.isUndefined(a[f])||(c[f]=a[f])},this);return c},defaults:function(c){this.each(a.call(arguments,1),function(a){for(var f in a)this.isUndefined(c[f])&&(c[f]=a[f])},this);return c},compose:function(){var c=a.call(arguments);return function(){for(var d=a.call(arguments),f=c.length-1;f>=0;f--)d=[c[f].apply(this,d)];return d[0]}}, 15 | each:function(a,d,f){if(e&&a.forEach===e)a.forEach(d,f);else if(a.length===a.length+0)for(var b=0,n=a.length;b-1?d.length-d.indexOf(".")-1:0};c.superclass=e;a.extend(c.prototype,e.prototype,{setValue:function(a){if(this.__min!==void 0&&athis.__max)a=this.__max;this.__step!==void 0&&a%this.__step!=0&&(a=Math.round(a/this.__step)*this.__step);return c.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__step=a;return this}});return c}(dat.controllers.Controller,dat.utils.common); 29 | dat.controllers.NumberControllerBox=function(e,a,c){var d=function(f,b,e){function h(){var a=parseFloat(l.__input.value);c.isNaN(a)||l.setValue(a)}function j(a){var b=o-a.clientY;l.setValue(l.getValue()+b*l.__impliedStep);o=a.clientY}function m(){a.unbind(window,"mousemove",j);a.unbind(window,"mouseup",m)}this.__truncationSuspended=false;d.superclass.call(this,f,b,e);var l=this,o;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",h); 30 | a.bind(this.__input,"blur",function(){h();l.__onFinishChange&&l.__onFinishChange.call(l,l.getValue())});a.bind(this.__input,"mousedown",function(b){a.bind(window,"mousemove",j);a.bind(window,"mouseup",m);o=b.clientY});a.bind(this.__input,"keydown",function(a){if(a.keyCode===13)l.__truncationSuspended=true,this.blur(),l.__truncationSuspended=false});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;c.extend(d.prototype,e.prototype,{updateDisplay:function(){var a=this.__input, 31 | b;if(this.__truncationSuspended)b=this.getValue();else{b=this.getValue();var c=Math.pow(10,this.__precision);b=Math.round(b*c)/c}a.value=b;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common); 32 | dat.controllers.NumberControllerSlider=function(e,a,c,d,f){var b=function(d,c,f,e,l){function o(b){b.preventDefault();var d=a.getOffset(g.__background),c=a.getWidth(g.__background);g.setValue(g.__min+(g.__max-g.__min)*((b.clientX-d.left)/(d.left+c-d.left)));return false}function y(){a.unbind(window,"mousemove",o);a.unbind(window,"mouseup",y);g.__onFinishChange&&g.__onFinishChange.call(g,g.getValue())}b.superclass.call(this,d,c,{min:f,max:e,step:l});var g=this;this.__background=document.createElement("div"); 33 | this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(b){a.bind(window,"mousemove",o);a.bind(window,"mouseup",y);o(b)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};b.superclass=e;b.useDefaultStyles=function(){c.inject(f)};d.extend(b.prototype,e.prototype,{updateDisplay:function(){this.__foreground.style.width= 34 | (this.getValue()-this.__min)/(this.__max-this.__min)*100+"%";return b.superclass.prototype.updateDisplay.call(this)}});return b}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,".slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}"); 35 | dat.controllers.FunctionController=function(e,a,c){var d=function(c,b,e){d.superclass.call(this,c,b);var h=this;this.__button=document.createElement("div");this.__button.innerHTML=e===void 0?"Fire":e;a.bind(this.__button,"click",function(a){a.preventDefault();h.fire();return false});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};d.superclass=e;c.extend(d.prototype,e.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.__onFinishChange&&this.__onFinishChange.call(this, 36 | this.getValue());this.getValue().call(this.object)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 37 | dat.controllers.BooleanController=function(e,a,c){var d=function(c,b){d.superclass.call(this,c,b);var e=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){e.setValue(!e.__prev)},false);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};d.superclass=e;c.extend(d.prototype,e.prototype,{setValue:function(a){a=d.superclass.prototype.setValue.call(this,a);this.__onFinishChange&& 38 | this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){this.getValue()===true?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=true):this.__checkbox.checked=false;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); 39 | dat.color.toString=function(e){return function(a){if(a.a==1||e.isUndefined(a.a)){for(a=a.hex.toString(16);a.length<6;)a="0"+a;return"#"+a}else return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common); 40 | dat.color.interpret=function(e,a){var c,d,f=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return a===null?false:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:e},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return a===null?false:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:e},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); 41 | return a===null?false:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:e},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return a===null?false:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:e}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return a.length!= 42 | 3?false:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return a.length!=4?false:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(b){return a.isNumber(b.r)&&a.isNumber(b.g)&&a.isNumber(b.b)&&a.isNumber(b.a)?{space:"RGB",r:b.r,g:b.g,b:b.b,a:b.a}:false},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(b){return a.isNumber(b.r)&& 43 | a.isNumber(b.g)&&a.isNumber(b.b)?{space:"RGB",r:b.r,g:b.g,b:b.b}:false},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)&&a.isNumber(b.a)?{space:"HSV",h:b.h,s:b.s,v:b.v,a:b.a}:false},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)?{space:"HSV",h:b.h,s:b.s,v:b.v}:false},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){d= 44 | false;var b=arguments.length>1?a.toArray(arguments):arguments[0];a.each(f,function(e){if(e.litmus(b))return a.each(e.conversions,function(e,f){c=e.read(b);if(d===false&&c!==false)return d=c,c.conversionName=f,c.conversion=e,a.BREAK}),a.BREAK});return d}}(dat.color.toString,dat.utils.common); 45 | dat.GUI=dat.gui.GUI=function(e,a,c,d,f,b,n,h,j,m,l,o,y,g,i){function q(a,b,r,c){if(b[r]===void 0)throw Error("Object "+b+' has no property "'+r+'"');c.color?b=new l(b,r):(b=[b,r].concat(c.factoryArgs),b=d.apply(a,b));if(c.before instanceof f)c.before=c.before.__li;t(a,b);g.addClass(b.domElement,"c");r=document.createElement("span");g.addClass(r,"property-name");r.innerHTML=b.property;var e=document.createElement("div");e.appendChild(r);e.appendChild(b.domElement);c=s(a,e,c.before);g.addClass(c,k.CLASS_CONTROLLER_ROW); 46 | g.addClass(c,typeof b.getValue());p(a,c,b);a.__controllers.push(b);return b}function s(a,b,d){var c=document.createElement("li");b&&c.appendChild(b);d?a.__ul.insertBefore(c,params.before):a.__ul.appendChild(c);a.onResize();return c}function p(a,d,c){c.__li=d;c.__gui=a;i.extend(c,{options:function(b){if(arguments.length>1)return c.remove(),q(a,c.object,c.property,{before:c.__li.nextElementSibling,factoryArgs:[i.toArray(arguments)]});if(i.isArray(b)||i.isObject(b))return c.remove(),q(a,c.object,c.property, 47 | {before:c.__li.nextElementSibling,factoryArgs:[b]})},name:function(a){c.__li.firstElementChild.firstElementChild.innerHTML=a;return c},listen:function(){c.__gui.listen(c);return c},remove:function(){c.__gui.remove(c);return c}});if(c instanceof j){var e=new h(c.object,c.property,{min:c.__min,max:c.__max,step:c.__step});i.each(["updateDisplay","onChange","onFinishChange"],function(a){var b=c[a],H=e[a];c[a]=e[a]=function(){var a=Array.prototype.slice.call(arguments);b.apply(c,a);return H.apply(e,a)}}); 48 | g.addClass(d,"has-slider");c.domElement.insertBefore(e.domElement,c.domElement.firstElementChild)}else if(c instanceof h){var f=function(b){return i.isNumber(c.__min)&&i.isNumber(c.__max)?(c.remove(),q(a,c.object,c.property,{before:c.__li.nextElementSibling,factoryArgs:[c.__min,c.__max,c.__step]})):b};c.min=i.compose(f,c.min);c.max=i.compose(f,c.max)}else if(c instanceof b)g.bind(d,"click",function(){g.fakeEvent(c.__checkbox,"click")}),g.bind(c.__checkbox,"click",function(a){a.stopPropagation()}); 49 | else if(c instanceof n)g.bind(d,"click",function(){g.fakeEvent(c.__button,"click")}),g.bind(d,"mouseover",function(){g.addClass(c.__button,"hover")}),g.bind(d,"mouseout",function(){g.removeClass(c.__button,"hover")});else if(c instanceof l)g.addClass(d,"color"),c.updateDisplay=i.compose(function(a){d.style.borderLeftColor=c.__color.toString();return a},c.updateDisplay),c.updateDisplay();c.setValue=i.compose(function(b){a.getRoot().__preset_select&&c.isModified()&&B(a.getRoot(),true);return b},c.setValue)} 50 | function t(a,b){var c=a.getRoot(),d=c.__rememberedObjects.indexOf(b.object);if(d!=-1){var e=c.__rememberedObjectIndecesToControllers[d];e===void 0&&(e={},c.__rememberedObjectIndecesToControllers[d]=e);e[b.property]=b;if(c.load&&c.load.remembered){c=c.load.remembered;if(c[a.preset])c=c[a.preset];else if(c[w])c=c[w];else return;if(c[d]&&c[d][b.property]!==void 0)d=c[d][b.property],b.initialValue=d,b.setValue(d)}}}function I(a){var b=a.__save_row=document.createElement("li");g.addClass(a.domElement, 51 | "has-save");a.__ul.insertBefore(b,a.__ul.firstChild);g.addClass(b,"save-row");var c=document.createElement("span");c.innerHTML=" ";g.addClass(c,"button gears");var d=document.createElement("span");d.innerHTML="Save";g.addClass(d,"button");g.addClass(d,"save");var e=document.createElement("span");e.innerHTML="New";g.addClass(e,"button");g.addClass(e,"save-as");var f=document.createElement("span");f.innerHTML="Revert";g.addClass(f,"button");g.addClass(f,"revert");var m=a.__preset_select=document.createElement("select"); 52 | a.load&&a.load.remembered?i.each(a.load.remembered,function(b,c){C(a,c,c==a.preset)}):C(a,w,false);g.bind(m,"change",function(){for(var b=0;b0){a.preset=this.preset;if(!a.remembered)a.remembered={};a.remembered[this.preset]=z(this)}a.folders={};i.each(this.__folders,function(b, 69 | c){a.folders[c]=b.getSaveObject()});return a},save:function(){if(!this.load.remembered)this.load.remembered={};this.load.remembered[this.preset]=z(this);B(this,false)},saveAs:function(a){if(!this.load.remembered)this.load.remembered={},this.load.remembered[w]=z(this,true);this.load.remembered[a]=z(this);this.preset=a;C(this,a,true)},revert:function(a){i.each(this.__controllers,function(b){this.getRoot().load.remembered?t(a||this.getRoot(),b):b.setValue(b.initialValue)},this);i.each(this.__folders, 70 | function(a){a.revert(a)});a||B(this.getRoot(),false)},listen:function(a){var b=this.__listening.length==0;this.__listening.push(a);b&&E(this.__listening)}});return k}(dat.utils.css,'
\n\n Here\'s the new load parameter for your GUI\'s constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI\'s constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n \n
\n \n
\n\n
', 71 | ".dg ul{list-style:none;margin:0;padding:0;width:100%;clear:both}.dg.ac{position:fixed;top:0;left:0;right:0;height:0;z-index:0}.dg:not(.ac) .main{overflow:hidden}.dg.main{-webkit-transition:opacity 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.1s linear}.dg.main.taller-than-window{overflow-y:auto}.dg.main.taller-than-window .close-button{opacity:1;margin-top:-1px;border-top:1px solid #2c2c2c}.dg.main ul.closed .close-button{opacity:1 !important}.dg.main:hover .close-button,.dg.main .close-button.drag{opacity:1}.dg.main .close-button{-webkit-transition:opacity 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.1s linear;border:0;position:absolute;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-x:hidden}.dg.a.has-save ul{margin-top:27px}.dg.a.has-save ul.closed{margin-top:0}.dg.a .save-row{position:fixed;top:0;z-index:1002}.dg li{-webkit-transition:height 0.1s ease-out;-o-transition:height 0.1s ease-out;-moz-transition:height 0.1s ease-out;transition:height 0.1s ease-out}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;overflow:hidden;padding:0 4px 0 5px}.dg li.folder{padding:0;border-left:4px solid rgba(0,0,0,0)}.dg li.title{cursor:pointer;margin-left:-4px}.dg .closed li:not(.title),.dg .closed ul li,.dg .closed ul li > *{height:0;overflow:hidden;border:0}.dg .cr{clear:both;padding-left:3px;height:27px}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .c{float:left;width:60%}.dg .c input[type=text]{border:0;margin-top:4px;padding:3px;width:100%;float:right}.dg .has-slider input[type=text]{width:30%;margin-left:0}.dg .slider{float:left;width:66%;margin-left:-5px;margin-right:0;height:19px;margin-top:4px}.dg .slider-fg{height:100%}.dg .c input[type=checkbox]{margin-top:9px}.dg .c select{margin-top:5px}.dg .cr.function,.dg .cr.function .property-name,.dg .cr.function *,.dg .cr.boolean,.dg .cr.boolean *{cursor:pointer}.dg .selector{display:none;position:absolute;margin-left:-9px;margin-top:23px;z-index:10}.dg .c:hover .selector,.dg .selector.drag{display:block}.dg li.save-row{padding:0}.dg li.save-row .button{display:inline-block;padding:0px 6px}.dg.dialogue{background-color:#222;width:460px;padding:15px;font-size:13px;line-height:15px}#dg-new-constructor{padding:10px;color:#222;font-family:Monaco, monospace;font-size:10px;border:0;resize:none;box-shadow:inset 1px 1px 1px #888;word-wrap:break-word;margin:12px 0;display:block;width:440px;overflow-y:scroll;height:100px;position:relative}#dg-local-explain{display:none;font-size:11px;line-height:17px;border-radius:3px;background-color:#333;padding:8px;margin-top:10px}#dg-local-explain code{font-size:10px}#dat-gui-save-locally{display:none}.dg{color:#eee;font:11px 'Lucida Grande', sans-serif;text-shadow:0 -1px 0 #111}.dg.main::-webkit-scrollbar{width:5px;background:#1a1a1a}.dg.main::-webkit-scrollbar-corner{height:0;display:none}.dg.main::-webkit-scrollbar-thumb{border-radius:5px;background:#676767}.dg li:not(.folder){background:#1a1a1a;border-bottom:1px solid #2c2c2c}.dg li.save-row{line-height:25px;background:#dad5cb;border:0}.dg li.save-row select{margin-left:5px;width:108px}.dg li.save-row .button{margin-left:5px;margin-top:1px;border-radius:2px;font-size:9px;line-height:7px;padding:4px 4px 5px 4px;background:#c5bdad;color:#fff;text-shadow:0 1px 0 #b0a58f;box-shadow:0 -1px 0 #b0a58f;cursor:pointer}.dg li.save-row .button.gears{background:#c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;height:7px;width:8px}.dg li.save-row .button:hover{background-color:#bab19e;box-shadow:0 -1px 0 #b0a58f}.dg li.folder{border-bottom:0}.dg li.title{padding-left:16px;background:#000 url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.2)}.dg .closed li.title{background-image:url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)}.dg .cr.boolean{border-left:3px solid #806787}.dg .cr.function{border-left:3px solid #e61d5f}.dg .cr.number{border-left:3px solid #2fa1d6}.dg .cr.number input[type=text]{color:#2fa1d6}.dg .cr.string{border-left:3px solid #1ed36f}.dg .cr.string input[type=text]{color:#1ed36f}.dg .cr.function:hover,.dg .cr.boolean:hover{background:#111}.dg .c input[type=text]{background:#303030;outline:none}.dg .c input[type=text]:hover{background:#3c3c3c}.dg .c input[type=text]:focus{background:#494949;color:#fff}.dg .c .slider{background:#303030;cursor:ew-resize}.dg .c .slider-fg{background:#2fa1d6}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}\n", 72 | dat.controllers.factory=function(e,a,c,d,f,b,n){return function(h,j,m,l){var o=h[j];if(n.isArray(m)||n.isObject(m))return new e(h,j,m);if(n.isNumber(o))return n.isNumber(m)&&n.isNumber(l)?new c(h,j,m,l):new a(h,j,{min:m,max:l});if(n.isString(o))return new d(h,j);if(n.isFunction(o))return new f(h,j,"");if(n.isBoolean(o))return new b(h,j)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(e,a,c){var d= 73 | function(c,b){function e(){h.setValue(h.__input.value)}d.superclass.call(this,c,b);var h=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",e);a.bind(this.__input,"change",e);a.bind(this.__input,"blur",function(){h.__onFinishChange&&h.__onFinishChange.call(h,h.getValue())});a.bind(this.__input,"keydown",function(a){a.keyCode===13&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;c.extend(d.prototype, 74 | e.prototype,{updateDisplay:function(){if(!a.isActive(this.__input))this.__input.value=this.getValue();return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController, 75 | dat.controllers.ColorController=function(e,a,c,d,f){function b(a,b,c,d){a.style.background="";f.each(j,function(e){a.style.cssText+="background: "+e+"linear-gradient("+b+", "+c+" 0%, "+d+" 100%); "})}function n(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"; 76 | a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var h=function(e,l){function o(b){q(b);a.bind(window,"mousemove",q);a.bind(window, 77 | "mouseup",j)}function j(){a.unbind(window,"mousemove",q);a.unbind(window,"mouseup",j)}function g(){var a=d(this.value);a!==false?(p.__color.__state=a,p.setValue(p.__color.toOriginal())):this.value=p.__color.toString()}function i(){a.unbind(window,"mousemove",s);a.unbind(window,"mouseup",i)}function q(b){b.preventDefault();var c=a.getWidth(p.__saturation_field),d=a.getOffset(p.__saturation_field),e=(b.clientX-d.left+document.body.scrollLeft)/c,b=1-(b.clientY-d.top+document.body.scrollTop)/c;b>1?b= 78 | 1:b<0&&(b=0);e>1?e=1:e<0&&(e=0);p.__color.v=b;p.__color.s=e;p.setValue(p.__color.toOriginal());return false}function s(b){b.preventDefault();var c=a.getHeight(p.__hue_field),d=a.getOffset(p.__hue_field),b=1-(b.clientY-d.top+document.body.scrollTop)/c;b>1?b=1:b<0&&(b=0);p.__color.h=b*360;p.setValue(p.__color.toOriginal());return false}h.superclass.call(this,e,l);this.__color=new c(this.getValue());this.__temp=new c(0);var p=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement, 79 | false);this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input= 80 | document.createElement("input");this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){a.keyCode===13&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(){a.addClass(this,"drag").bind(window,"mouseup",function(){a.removeClass(p.__selector,"drag")})});var t=document.createElement("div");f.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"}); 81 | f.extend(this.__field_knob.style,{position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(this.__color.v<0.5?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});f.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});f.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});f.extend(t.style, 82 | {width:"100%",height:"100%",background:"none"});b(t,"top","rgba(0,0,0,0)","#000");f.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});n(this.__hue_field);f.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",o);a.bind(this.__field_knob,"mousedown",o);a.bind(this.__hue_field,"mousedown", 83 | function(b){s(b);a.bind(window,"mousemove",s);a.bind(window,"mouseup",i)});this.__saturation_field.appendChild(t);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};h.superclass=e;f.extend(h.prototype,e.prototype,{updateDisplay:function(){var a=d(this.getValue()); 84 | if(a!==false){var e=false;f.each(c.COMPONENTS,function(b){if(!f.isUndefined(a[b])&&!f.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return e=true,{}},this);e&&f.extend(this.__color.__state,a)}f.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var h=this.__color.v<0.5||this.__color.s>0.5?255:0,j=255-h;f.extend(this.__field_knob.style,{marginLeft:100*this.__color.s-7+"px",marginTop:100*(1-this.__color.v)-7+"px",backgroundColor:this.__temp.toString(),border:this.__field_knob_border+ 85 | "rgb("+h+","+h+","+h+")"});this.__hue_knob.style.marginTop=(1-this.__color.h/360)*100+"px";this.__temp.s=1;this.__temp.v=1;b(this.__saturation_field,"left","#fff",this.__temp.toString());f.extend(this.__input.style,{backgroundColor:this.__input.value=this.__color.toString(),color:"rgb("+h+","+h+","+h+")",textShadow:this.__input_textShadow+"rgba("+j+","+j+","+j+",.7)"})}});var j=["-moz-","-o-","-webkit-","-ms-",""];return h}(dat.controllers.Controller,dat.dom.dom,dat.color.Color=function(e,a,c,d){function f(a, 86 | b,c){Object.defineProperty(a,b,{get:function(){if(this.__state.space==="RGB")return this.__state[b];n(this,b,c);return this.__state[b]},set:function(a){if(this.__state.space!=="RGB")n(this,b,c),this.__state.space="RGB";this.__state[b]=a}})}function b(a,b){Object.defineProperty(a,b,{get:function(){if(this.__state.space==="HSV")return this.__state[b];h(this);return this.__state[b]},set:function(a){if(this.__state.space!=="HSV")h(this),this.__state.space="HSV";this.__state[b]=a}})}function n(b,c,e){if(b.__state.space=== 87 | "HEX")b.__state[c]=a.component_from_hex(b.__state.hex,e);else if(b.__state.space==="HSV")d.extend(b.__state,a.hsv_to_rgb(b.__state.h,b.__state.s,b.__state.v));else throw"Corrupted color state";}function h(b){var c=a.rgb_to_hsv(b.r,b.g,b.b);d.extend(b.__state,{s:c.s,v:c.v});if(d.isNaN(c.h)){if(d.isUndefined(b.__state.h))b.__state.h=0}else b.__state.h=c.h}var j=function(){this.__state=e.apply(this,arguments);if(this.__state===false)throw"Failed to interpret color arguments";this.__state.a=this.__state.a|| 88 | 1};j.COMPONENTS="r,g,b,h,s,v,hex,a".split(",");d.extend(j.prototype,{toString:function(){return c(this)},toOriginal:function(){return this.__state.conversion.write(this)}});f(j.prototype,"r",2);f(j.prototype,"g",1);f(j.prototype,"b",0);b(j.prototype,"h");b(j.prototype,"s");b(j.prototype,"v");Object.defineProperty(j.prototype,"a",{get:function(){return this.__state.a},set:function(a){this.__state.a=a}});Object.defineProperty(j.prototype,"hex",{get:function(){if(!this.__state.space!=="HEX")this.__state.hex= 89 | a.rgb_to_hex(this.r,this.g,this.b);return this.__state.hex},set:function(a){this.__state.space="HEX";this.__state.hex=a}});return j}(dat.color.interpret,dat.color.math=function(){var e;return{hsv_to_rgb:function(a,c,d){var e=a/60-Math.floor(a/60),b=d*(1-c),n=d*(1-e*c),c=d*(1-(1-e)*c),a=[[d,c,b],[n,d,b],[b,d,c],[b,n,d],[c,b,d],[d,b,n]][Math.floor(a/60)%6];return{r:a[0]*255,g:a[1]*255,b:a[2]*255}},rgb_to_hsv:function(a,c,d){var e=Math.min(a,c,d),b=Math.max(a,c,d),e=b-e;if(b==0)return{h:NaN,s:0,v:0}; 90 | a=a==b?(c-d)/e:c==b?2+(d-a)/e:4+(a-c)/e;a/=6;a<0&&(a+=1);return{h:a*360,s:e/b,v:b/255}},rgb_to_hex:function(a,c,d){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,c);return a=this.hex_with_component(a,0,d)},component_from_hex:function(a,c){return a>>c*8&255},hex_with_component:function(a,c,d){return d<<(e=c*8)|a&~(255<