├── README.md ├── all_boxes.JPG ├── build ├── three.js ├── three.min.js └── three.module.js ├── index.html ├── js ├── Stats.js ├── Tween.js ├── WebGL.js ├── controls │ ├── DeviceOrientationControls.js │ ├── DragControls.js │ ├── EditorControls.js │ ├── FirstPersonControls.js │ ├── FlyControls.js │ ├── MapControls.js │ ├── OrbitControls.js │ ├── OrthographicTrackballControls.js │ ├── PointerLockControls.js │ ├── TrackballControls.js │ └── TransformControls.js ├── loaders │ ├── BinaryLoader.js │ └── VTKLoader.js └── tween.min.js ├── source ├── 0.png ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── 7.png ├── 8.png ├── 9.png ├── bag.png ├── bottom.png ├── clock.png ├── cylinder_shadow.png ├── desk_shadow.png ├── dict.png ├── disk.png ├── emotion.png ├── express.png ├── game.png ├── gift.png ├── green_face.png ├── head.png ├── i.png ├── money.png ├── player_shadow.png ├── record.png ├── shadow.png ├── sing.png ├── stool.png ├── stool_shadow.png ├── stripe.png ├── tit.png ├── top.png ├── westore.png ├── westore_desk.png └── white_face.png ├── youjumpijump.gif ├── zjFirstStep.html ├── zjgame.html └── zjreplay.html /README.md: -------------------------------------------------------------------------------- 1 | # You_Jump_I_Jump 2 | 相关博客: 3 | 一步步配置腾讯云服务器Ubuntu 外网通过域名访问自己的网页tomcat https://blog.csdn.net/ffcjjhv/article/details/85140212 4 | Three.js+tween.js 基础(一) https://blog.csdn.net/ffcjjhv/article/details/86632320 5 | ## 运行 6 | 需要安装Tomcat后以类似 `http://localhost:8080/zjgame/zjgame.html `的方式访问,否则本地图片无法加载。 7 | 注:本项目基于Three.js,是对微信小游戏跳一跳的html版改写,只供研究学习使用。 8 | ## 效果图 9 | 10 | ![image](https://github.com/zj19941113/You_Jump_I_Jump/blob/master/youjumpijump.gif) 11 | -------------------------------------------------------------------------------- /all_boxes.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zj19941113/You_Jump_I_Jump/a4290bed7e2fb72bba41955369b85f49b6902bb7/all_boxes.JPG -------------------------------------------------------------------------------- /js/Stats.js: -------------------------------------------------------------------------------- 1 | // stats.js r10 - http://github.com/mrdoob/stats.js 2 | var Stats=function(){var l=Date.now(),m=l,g=0,n=1E3,o=0,h=0,p=1E3,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{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 | -------------------------------------------------------------------------------- /js/Tween.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author sole / http://soledadpenades.com 3 | * @author mrdoob / http://mrdoob.com 4 | * @author Robert Eisele / http://www.xarg.org 5 | * @author Philippe / http://philippe.elsass.me 6 | * @author Robert Penner / http://www.robertpenner.com/easing_terms_of_use.html 7 | * @author Paul Lewis / http://www.aerotwist.com/ 8 | * @author lechecacharro 9 | * @author Josh Faul / http://jocafa.com/ 10 | * @author egraether / http://egraether.com/ 11 | * @author endel / http://endel.me 12 | */ 13 | 14 | var TWEEN = TWEEN || ( function () { 15 | 16 | var _tweens = []; 17 | 18 | return { 19 | 20 | REVISION: '10', 21 | 22 | getAll: function () { 23 | 24 | return _tweens; 25 | 26 | }, 27 | 28 | removeAll: function () { 29 | 30 | _tweens = []; 31 | 32 | }, 33 | 34 | add: function ( tween ) { 35 | 36 | _tweens.push( tween ); 37 | 38 | }, 39 | 40 | remove: function ( tween ) { 41 | 42 | var i = _tweens.indexOf( tween ); 43 | 44 | if ( i !== -1 ) { 45 | 46 | _tweens.splice( i, 1 ); 47 | 48 | } 49 | 50 | }, 51 | 52 | update: function ( time ) { 53 | 54 | if ( _tweens.length === 0 ) return false; 55 | 56 | var i = 0, numTweens = _tweens.length; 57 | 58 | time = time !== undefined ? time : ( window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now() ); 59 | 60 | while ( i < numTweens ) { 61 | 62 | if ( _tweens[ i ].update( time ) ) { 63 | 64 | i ++; 65 | 66 | } else { 67 | 68 | _tweens.splice( i, 1 ); 69 | 70 | numTweens --; 71 | 72 | } 73 | 74 | } 75 | 76 | return true; 77 | 78 | } 79 | }; 80 | 81 | } )(); 82 | 83 | TWEEN.Tween = function ( object ) { 84 | 85 | var _object = object; 86 | var _valuesStart = {}; 87 | var _valuesEnd = {}; 88 | var _valuesStartRepeat = {}; 89 | var _duration = 1000; 90 | var _repeat = 0; 91 | var _delayTime = 0; 92 | var _startTime = null; 93 | var _easingFunction = TWEEN.Easing.Linear.None; 94 | var _interpolationFunction = TWEEN.Interpolation.Linear; 95 | var _chainedTweens = []; 96 | var _onStartCallback = null; 97 | var _onStartCallbackFired = false; 98 | var _onUpdateCallback = null; 99 | var _onCompleteCallback = null; 100 | 101 | // Set all starting values present on the target object 102 | for ( var field in object ) { 103 | 104 | _valuesStart[ field ] = parseFloat(object[field], 10); 105 | 106 | } 107 | 108 | this.to = function ( properties, duration ) { 109 | 110 | if ( duration !== undefined ) { 111 | 112 | _duration = duration; 113 | 114 | } 115 | 116 | _valuesEnd = properties; 117 | 118 | return this; 119 | 120 | }; 121 | 122 | this.start = function ( time ) { 123 | 124 | TWEEN.add( this ); 125 | 126 | _onStartCallbackFired = false; 127 | 128 | _startTime = time !== undefined ? time : (window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now() ); 129 | _startTime += _delayTime; 130 | 131 | for ( var property in _valuesEnd ) { 132 | 133 | // check if an Array was provided as property value 134 | if ( _valuesEnd[ property ] instanceof Array ) { 135 | 136 | if ( _valuesEnd[ property ].length === 0 ) { 137 | 138 | continue; 139 | 140 | } 141 | 142 | // create a local copy of the Array with the start value at the front 143 | _valuesEnd[ property ] = [ _object[ property ] ].concat( _valuesEnd[ property ] ); 144 | 145 | } 146 | 147 | _valuesStart[ property ] = _object[ property ]; 148 | 149 | if( ( _valuesStart[ property ] instanceof Array ) === false ) { 150 | _valuesStart[ property ] *= 1.0; // Ensures we're using numbers, not strings 151 | } 152 | 153 | _valuesStartRepeat[ property ] = _valuesStart[ property ] || 0; 154 | 155 | } 156 | 157 | return this; 158 | 159 | }; 160 | 161 | this.stop = function () { 162 | 163 | TWEEN.remove( this ); 164 | return this; 165 | 166 | }; 167 | 168 | this.delay = function ( amount ) { 169 | 170 | _delayTime = amount; 171 | return this; 172 | 173 | }; 174 | 175 | this.repeat = function ( times ) { 176 | 177 | _repeat = times; 178 | return this; 179 | 180 | }; 181 | 182 | this.easing = function ( easing ) { 183 | 184 | _easingFunction = easing; 185 | return this; 186 | 187 | }; 188 | 189 | this.interpolation = function ( interpolation ) { 190 | 191 | _interpolationFunction = interpolation; 192 | return this; 193 | 194 | }; 195 | 196 | this.chain = function () { 197 | 198 | _chainedTweens = arguments; 199 | return this; 200 | 201 | }; 202 | 203 | this.onStart = function ( callback ) { 204 | 205 | _onStartCallback = callback; 206 | return this; 207 | 208 | }; 209 | 210 | this.onUpdate = function ( callback ) { 211 | 212 | _onUpdateCallback = callback; 213 | return this; 214 | 215 | }; 216 | 217 | this.onComplete = function ( callback ) { 218 | 219 | _onCompleteCallback = callback; 220 | return this; 221 | 222 | }; 223 | 224 | this.update = function ( time ) { 225 | 226 | if ( time < _startTime ) { 227 | 228 | return true; 229 | 230 | } 231 | 232 | if ( _onStartCallbackFired === false ) { 233 | 234 | if ( _onStartCallback !== null ) { 235 | 236 | _onStartCallback.call( _object ); 237 | 238 | } 239 | 240 | _onStartCallbackFired = true; 241 | 242 | } 243 | 244 | var elapsed = ( time - _startTime ) / _duration; 245 | elapsed = elapsed > 1 ? 1 : elapsed; 246 | 247 | var value = _easingFunction( elapsed ); 248 | 249 | for ( var property in _valuesEnd ) { 250 | 251 | var start = _valuesStart[ property ] || 0; 252 | var end = _valuesEnd[ property ]; 253 | 254 | if ( end instanceof Array ) { 255 | 256 | _object[ property ] = _interpolationFunction( end, value ); 257 | 258 | } else { 259 | 260 | if ( typeof(end) === "string" ) { 261 | end = start + parseFloat(end, 10); 262 | } 263 | 264 | _object[ property ] = start + ( end - start ) * value; 265 | 266 | } 267 | 268 | } 269 | 270 | if ( _onUpdateCallback !== null ) { 271 | 272 | _onUpdateCallback.call( _object, value ); 273 | 274 | } 275 | 276 | if ( elapsed == 1 ) { 277 | 278 | if ( _repeat > 0 ) { 279 | 280 | if( isFinite( _repeat ) ) { 281 | _repeat--; 282 | } 283 | 284 | // reassign starting values, restart by making startTime = now 285 | for( var property in _valuesStartRepeat ) { 286 | 287 | if ( typeof( _valuesEnd[ property ] ) === "string" ) { 288 | _valuesStartRepeat[ property ] = _valuesStartRepeat[ property ] + parseFloat(_valuesEnd[ property ], 10); 289 | } 290 | 291 | _valuesStart[ property ] = _valuesStartRepeat[ property ]; 292 | 293 | } 294 | 295 | _startTime = time + _delayTime; 296 | 297 | return true; 298 | 299 | } else { 300 | 301 | if ( _onCompleteCallback !== null ) { 302 | 303 | _onCompleteCallback.call( _object ); 304 | 305 | } 306 | 307 | for ( var i = 0, numChainedTweens = _chainedTweens.length; i < numChainedTweens; i ++ ) { 308 | 309 | _chainedTweens[ i ].start( time ); 310 | 311 | } 312 | 313 | return false; 314 | 315 | } 316 | 317 | } 318 | 319 | return true; 320 | 321 | }; 322 | 323 | }; 324 | 325 | TWEEN.Easing = { 326 | 327 | Linear: { 328 | 329 | None: function ( k ) { 330 | 331 | return k; 332 | 333 | } 334 | 335 | }, 336 | 337 | Quadratic: { 338 | 339 | In: function ( k ) { 340 | 341 | return k * k; 342 | 343 | }, 344 | 345 | Out: function ( k ) { 346 | 347 | return k * ( 2 - k ); 348 | 349 | }, 350 | 351 | InOut: function ( k ) { 352 | 353 | if ( ( k *= 2 ) < 1 ) return 0.5 * k * k; 354 | return - 0.5 * ( --k * ( k - 2 ) - 1 ); 355 | 356 | } 357 | 358 | }, 359 | 360 | Cubic: { 361 | 362 | In: function ( k ) { 363 | 364 | return k * k * k; 365 | 366 | }, 367 | 368 | Out: function ( k ) { 369 | 370 | return --k * k * k + 1; 371 | 372 | }, 373 | 374 | InOut: function ( k ) { 375 | 376 | if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k; 377 | return 0.5 * ( ( k -= 2 ) * k * k + 2 ); 378 | 379 | } 380 | 381 | }, 382 | 383 | Quartic: { 384 | 385 | In: function ( k ) { 386 | 387 | return k * k * k * k; 388 | 389 | }, 390 | 391 | Out: function ( k ) { 392 | 393 | return 1 - ( --k * k * k * k ); 394 | 395 | }, 396 | 397 | InOut: function ( k ) { 398 | 399 | if ( ( k *= 2 ) < 1) return 0.5 * k * k * k * k; 400 | return - 0.5 * ( ( k -= 2 ) * k * k * k - 2 ); 401 | 402 | } 403 | 404 | }, 405 | 406 | Quintic: { 407 | 408 | In: function ( k ) { 409 | 410 | return k * k * k * k * k; 411 | 412 | }, 413 | 414 | Out: function ( k ) { 415 | 416 | return --k * k * k * k * k + 1; 417 | 418 | }, 419 | 420 | InOut: function ( k ) { 421 | 422 | if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k * k * k; 423 | return 0.5 * ( ( k -= 2 ) * k * k * k * k + 2 ); 424 | 425 | } 426 | 427 | }, 428 | 429 | Sinusoidal: { 430 | 431 | In: function ( k ) { 432 | 433 | return 1 - Math.cos( k * Math.PI / 2 ); 434 | 435 | }, 436 | 437 | Out: function ( k ) { 438 | 439 | return Math.sin( k * Math.PI / 2 ); 440 | 441 | }, 442 | 443 | InOut: function ( k ) { 444 | 445 | return 0.5 * ( 1 - Math.cos( Math.PI * k ) ); 446 | 447 | } 448 | 449 | }, 450 | 451 | Exponential: { 452 | 453 | In: function ( k ) { 454 | 455 | return k === 0 ? 0 : Math.pow( 1024, k - 1 ); 456 | 457 | }, 458 | 459 | Out: function ( k ) { 460 | 461 | return k === 1 ? 1 : 1 - Math.pow( 2, - 10 * k ); 462 | 463 | }, 464 | 465 | InOut: function ( k ) { 466 | 467 | if ( k === 0 ) return 0; 468 | if ( k === 1 ) return 1; 469 | if ( ( k *= 2 ) < 1 ) return 0.5 * Math.pow( 1024, k - 1 ); 470 | return 0.5 * ( - Math.pow( 2, - 10 * ( k - 1 ) ) + 2 ); 471 | 472 | } 473 | 474 | }, 475 | 476 | Circular: { 477 | 478 | In: function ( k ) { 479 | 480 | return 1 - Math.sqrt( 1 - k * k ); 481 | 482 | }, 483 | 484 | Out: function ( k ) { 485 | 486 | return Math.sqrt( 1 - ( --k * k ) ); 487 | 488 | }, 489 | 490 | InOut: function ( k ) { 491 | 492 | if ( ( k *= 2 ) < 1) return - 0.5 * ( Math.sqrt( 1 - k * k) - 1); 493 | return 0.5 * ( Math.sqrt( 1 - ( k -= 2) * k) + 1); 494 | 495 | } 496 | 497 | }, 498 | 499 | Elastic: { 500 | 501 | In: function ( k ) { 502 | 503 | var s, a = 0.1, p = 0.4; 504 | if ( k === 0 ) return 0; 505 | if ( k === 1 ) return 1; 506 | if ( !a || a < 1 ) { a = 1; s = p / 4; } 507 | else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI ); 508 | return - ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) ); 509 | 510 | }, 511 | 512 | Out: function ( k ) { 513 | 514 | var s, a = 0.1, p = 0.4; 515 | if ( k === 0 ) return 0; 516 | if ( k === 1 ) return 1; 517 | if ( !a || a < 1 ) { a = 1; s = p / 4; } 518 | else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI ); 519 | return ( a * Math.pow( 2, - 10 * k) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) + 1 ); 520 | 521 | }, 522 | 523 | InOut: function ( k ) { 524 | 525 | var s, a = 0.1, p = 0.4; 526 | if ( k === 0 ) return 0; 527 | if ( k === 1 ) return 1; 528 | if ( !a || a < 1 ) { a = 1; s = p / 4; } 529 | else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI ); 530 | if ( ( k *= 2 ) < 1 ) return - 0.5 * ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) ); 531 | return a * Math.pow( 2, -10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) * 0.5 + 1; 532 | 533 | } 534 | 535 | }, 536 | 537 | Back: { 538 | 539 | In: function ( k ) { 540 | 541 | var s = 1.70158; 542 | return k * k * ( ( s + 1 ) * k - s ); 543 | 544 | }, 545 | 546 | Out: function ( k ) { 547 | 548 | var s = 1.70158; 549 | return --k * k * ( ( s + 1 ) * k + s ) + 1; 550 | 551 | }, 552 | 553 | InOut: function ( k ) { 554 | 555 | var s = 1.70158 * 1.525; 556 | if ( ( k *= 2 ) < 1 ) return 0.5 * ( k * k * ( ( s + 1 ) * k - s ) ); 557 | return 0.5 * ( ( k -= 2 ) * k * ( ( s + 1 ) * k + s ) + 2 ); 558 | 559 | } 560 | 561 | }, 562 | 563 | Bounce: { 564 | 565 | In: function ( k ) { 566 | 567 | return 1 - TWEEN.Easing.Bounce.Out( 1 - k ); 568 | 569 | }, 570 | 571 | Out: function ( k ) { 572 | 573 | if ( k < ( 1 / 2.75 ) ) { 574 | 575 | return 7.5625 * k * k; 576 | 577 | } else if ( k < ( 2 / 2.75 ) ) { 578 | 579 | return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75; 580 | 581 | } else if ( k < ( 2.5 / 2.75 ) ) { 582 | 583 | return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375; 584 | 585 | } else { 586 | 587 | return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375; 588 | 589 | } 590 | 591 | }, 592 | 593 | InOut: function ( k ) { 594 | 595 | if ( k < 0.5 ) return TWEEN.Easing.Bounce.In( k * 2 ) * 0.5; 596 | return TWEEN.Easing.Bounce.Out( k * 2 - 1 ) * 0.5 + 0.5; 597 | 598 | } 599 | 600 | } 601 | 602 | }; 603 | 604 | TWEEN.Interpolation = { 605 | 606 | Linear: function ( v, k ) { 607 | 608 | var m = v.length - 1, f = m * k, i = Math.floor( f ), fn = TWEEN.Interpolation.Utils.Linear; 609 | 610 | if ( k < 0 ) return fn( v[ 0 ], v[ 1 ], f ); 611 | if ( k > 1 ) return fn( v[ m ], v[ m - 1 ], m - f ); 612 | 613 | return fn( v[ i ], v[ i + 1 > m ? m : i + 1 ], f - i ); 614 | 615 | }, 616 | 617 | Bezier: function ( v, k ) { 618 | 619 | var b = 0, n = v.length - 1, pw = Math.pow, bn = TWEEN.Interpolation.Utils.Bernstein, i; 620 | 621 | for ( i = 0; i <= n; i++ ) { 622 | b += pw( 1 - k, n - i ) * pw( k, i ) * v[ i ] * bn( n, i ); 623 | } 624 | 625 | return b; 626 | 627 | }, 628 | 629 | CatmullRom: function ( v, k ) { 630 | 631 | var m = v.length - 1, f = m * k, i = Math.floor( f ), fn = TWEEN.Interpolation.Utils.CatmullRom; 632 | 633 | if ( v[ 0 ] === v[ m ] ) { 634 | 635 | if ( k < 0 ) i = Math.floor( f = m * ( 1 + k ) ); 636 | 637 | return fn( v[ ( i - 1 + m ) % m ], v[ i ], v[ ( i + 1 ) % m ], v[ ( i + 2 ) % m ], f - i ); 638 | 639 | } else { 640 | 641 | if ( k < 0 ) return v[ 0 ] - ( fn( v[ 0 ], v[ 0 ], v[ 1 ], v[ 1 ], -f ) - v[ 0 ] ); 642 | if ( k > 1 ) return v[ m ] - ( fn( v[ m ], v[ m ], v[ m - 1 ], v[ m - 1 ], f - m ) - v[ m ] ); 643 | 644 | return fn( v[ i ? i - 1 : 0 ], v[ i ], v[ m < i + 1 ? m : i + 1 ], v[ m < i + 2 ? m : i + 2 ], f - i ); 645 | 646 | } 647 | 648 | }, 649 | 650 | Utils: { 651 | 652 | Linear: function ( p0, p1, t ) { 653 | 654 | return ( p1 - p0 ) * t + p0; 655 | 656 | }, 657 | 658 | Bernstein: function ( n , i ) { 659 | 660 | var fc = TWEEN.Interpolation.Utils.Factorial; 661 | return fc( n ) / fc( i ) / fc( n - i ); 662 | 663 | }, 664 | 665 | Factorial: ( function () { 666 | 667 | var a = [ 1 ]; 668 | 669 | return function ( n ) { 670 | 671 | var s = 1, i; 672 | if ( a[ n ] ) return a[ n ]; 673 | for ( i = n; i > 1; i-- ) s *= i; 674 | return a[ n ] = s; 675 | 676 | }; 677 | 678 | } )(), 679 | 680 | CatmullRom: function ( p0, p1, p2, p3, t ) { 681 | 682 | var v0 = ( p2 - p0 ) * 0.5, v1 = ( p3 - p1 ) * 0.5, t2 = t * t, t3 = t * t2; 683 | return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; 684 | 685 | } 686 | 687 | } 688 | 689 | }; 690 | 691 | -------------------------------------------------------------------------------- /js/WebGL.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * @author mr.doob / http://mrdoob.com/ 4 | */ 5 | 6 | var WEBGL = { 7 | 8 | isWebGLAvailable: function () { 9 | 10 | try { 11 | 12 | var canvas = document.createElement( 'canvas' ); 13 | return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) ); 14 | 15 | } catch ( e ) { 16 | 17 | return false; 18 | 19 | } 20 | 21 | }, 22 | 23 | isWebGL2Available: function () { 24 | 25 | try { 26 | 27 | var canvas = document.createElement( 'canvas' ); 28 | return !! ( window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ) ); 29 | 30 | } catch ( e ) { 31 | 32 | return false; 33 | 34 | } 35 | 36 | }, 37 | 38 | getWebGLErrorMessage: function () { 39 | 40 | return this.getErrorMessage( 1 ); 41 | 42 | }, 43 | 44 | getWebGL2ErrorMessage: function () { 45 | 46 | return this.getErrorMessage( 2 ); 47 | 48 | }, 49 | 50 | getErrorMessage: function ( version ) { 51 | 52 | var names = { 53 | 1: 'WebGL', 54 | 2: 'WebGL 2' 55 | }; 56 | 57 | var contexts = { 58 | 1: window.WebGLRenderingContext, 59 | 2: window.WebGL2RenderingContext 60 | }; 61 | 62 | var message = 'Your $0 does not seem to support $1'; 63 | 64 | var element = document.createElement( 'div' ); 65 | element.id = 'webglmessage'; 66 | element.style.fontFamily = 'monospace'; 67 | element.style.fontSize = '13px'; 68 | element.style.fontWeight = 'normal'; 69 | element.style.textAlign = 'center'; 70 | element.style.background = '#fff'; 71 | element.style.color = '#000'; 72 | element.style.padding = '1.5em'; 73 | element.style.width = '400px'; 74 | element.style.margin = '5em auto 0'; 75 | 76 | if ( contexts[ version ] ) { 77 | 78 | message = message.replace( '$0', 'graphics card' ); 79 | 80 | } else { 81 | 82 | message = message.replace( '$0', 'browser' ); 83 | 84 | } 85 | 86 | message = message.replace( '$1', names[ version ] ); 87 | 88 | element.innerHTML = message; 89 | 90 | return element; 91 | 92 | } 93 | 94 | }; 95 | -------------------------------------------------------------------------------- /js/controls/DeviceOrientationControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author richt / http://richt.me 3 | * @author WestLangley / http://github.com/WestLangley 4 | * 5 | * W3C Device Orientation control (http://w3c.github.io/deviceorientation/spec-source-orientation.html) 6 | */ 7 | 8 | THREE.DeviceOrientationControls = function ( object ) { 9 | 10 | var scope = this; 11 | 12 | this.object = object; 13 | this.object.rotation.reorder( 'YXZ' ); 14 | 15 | this.enabled = true; 16 | 17 | this.deviceOrientation = {}; 18 | this.screenOrientation = 0; 19 | 20 | this.alphaOffset = 0; // radians 21 | 22 | var onDeviceOrientationChangeEvent = function ( event ) { 23 | 24 | scope.deviceOrientation = event; 25 | 26 | }; 27 | 28 | var onScreenOrientationChangeEvent = function () { 29 | 30 | scope.screenOrientation = window.orientation || 0; 31 | 32 | }; 33 | 34 | // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y'' 35 | 36 | var setObjectQuaternion = function () { 37 | 38 | var zee = new THREE.Vector3( 0, 0, 1 ); 39 | 40 | var euler = new THREE.Euler(); 41 | 42 | var q0 = new THREE.Quaternion(); 43 | 44 | var q1 = new THREE.Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis 45 | 46 | return function ( quaternion, alpha, beta, gamma, orient ) { 47 | 48 | euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us 49 | 50 | quaternion.setFromEuler( euler ); // orient the device 51 | 52 | quaternion.multiply( q1 ); // camera looks out the back of the device, not the top 53 | 54 | quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation 55 | 56 | }; 57 | 58 | }(); 59 | 60 | this.connect = function () { 61 | 62 | onScreenOrientationChangeEvent(); // run once on load 63 | 64 | window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent, false ); 65 | window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false ); 66 | 67 | scope.enabled = true; 68 | 69 | }; 70 | 71 | this.disconnect = function () { 72 | 73 | window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent, false ); 74 | window.removeEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false ); 75 | 76 | scope.enabled = false; 77 | 78 | }; 79 | 80 | this.update = function () { 81 | 82 | if ( scope.enabled === false ) return; 83 | 84 | var device = scope.deviceOrientation; 85 | 86 | if ( device ) { 87 | 88 | var alpha = device.alpha ? THREE.Math.degToRad( device.alpha ) + scope.alphaOffset : 0; // Z 89 | 90 | var beta = device.beta ? THREE.Math.degToRad( device.beta ) : 0; // X' 91 | 92 | var gamma = device.gamma ? THREE.Math.degToRad( device.gamma ) : 0; // Y'' 93 | 94 | var orient = scope.screenOrientation ? THREE.Math.degToRad( scope.screenOrientation ) : 0; // O 95 | 96 | setObjectQuaternion( scope.object.quaternion, alpha, beta, gamma, orient ); 97 | 98 | } 99 | 100 | 101 | }; 102 | 103 | this.dispose = function () { 104 | 105 | scope.disconnect(); 106 | 107 | }; 108 | 109 | this.connect(); 110 | 111 | }; 112 | -------------------------------------------------------------------------------- /js/controls/DragControls.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @author zz85 / https://github.com/zz85 3 | * @author mrdoob / http://mrdoob.com 4 | * Running this will allow you to drag three.js objects around the screen. 5 | */ 6 | 7 | THREE.DragControls = function ( _objects, _camera, _domElement ) { 8 | 9 | if ( _objects instanceof THREE.Camera ) { 10 | 11 | console.warn( 'THREE.DragControls: Constructor now expects ( objects, camera, domElement )' ); 12 | var temp = _objects; _objects = _camera; _camera = temp; 13 | 14 | } 15 | 16 | var _plane = new THREE.Plane(); 17 | var _raycaster = new THREE.Raycaster(); 18 | 19 | var _mouse = new THREE.Vector2(); 20 | var _offset = new THREE.Vector3(); 21 | var _intersection = new THREE.Vector3(); 22 | 23 | var _selected = null, _hovered = null; 24 | 25 | // 26 | 27 | var scope = this; 28 | 29 | function activate() { 30 | 31 | _domElement.addEventListener( 'mousemove', onDocumentMouseMove, false ); 32 | _domElement.addEventListener( 'mousedown', onDocumentMouseDown, false ); 33 | _domElement.addEventListener( 'mouseup', onDocumentMouseCancel, false ); 34 | _domElement.addEventListener( 'mouseleave', onDocumentMouseCancel, false ); 35 | _domElement.addEventListener( 'touchmove', onDocumentTouchMove, false ); 36 | _domElement.addEventListener( 'touchstart', onDocumentTouchStart, false ); 37 | _domElement.addEventListener( 'touchend', onDocumentTouchEnd, false ); 38 | 39 | } 40 | 41 | function deactivate() { 42 | 43 | _domElement.removeEventListener( 'mousemove', onDocumentMouseMove, false ); 44 | _domElement.removeEventListener( 'mousedown', onDocumentMouseDown, false ); 45 | _domElement.removeEventListener( 'mouseup', onDocumentMouseCancel, false ); 46 | _domElement.removeEventListener( 'mouseleave', onDocumentMouseCancel, false ); 47 | _domElement.removeEventListener( 'touchmove', onDocumentTouchMove, false ); 48 | _domElement.removeEventListener( 'touchstart', onDocumentTouchStart, false ); 49 | _domElement.removeEventListener( 'touchend', onDocumentTouchEnd, false ); 50 | 51 | } 52 | 53 | function dispose() { 54 | 55 | deactivate(); 56 | 57 | } 58 | 59 | function onDocumentMouseMove( event ) { 60 | 61 | event.preventDefault(); 62 | 63 | var rect = _domElement.getBoundingClientRect(); 64 | 65 | _mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1; 66 | _mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1; 67 | 68 | _raycaster.setFromCamera( _mouse, _camera ); 69 | 70 | if ( _selected && scope.enabled ) { 71 | 72 | if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) { 73 | 74 | _selected.position.copy( _intersection.sub( _offset ) ); 75 | 76 | } 77 | 78 | scope.dispatchEvent( { type: 'drag', object: _selected } ); 79 | 80 | return; 81 | 82 | } 83 | 84 | _raycaster.setFromCamera( _mouse, _camera ); 85 | 86 | var intersects = _raycaster.intersectObjects( _objects ); 87 | 88 | if ( intersects.length > 0 ) { 89 | 90 | var object = intersects[ 0 ].object; 91 | 92 | _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), object.position ); 93 | 94 | if ( _hovered !== object ) { 95 | 96 | scope.dispatchEvent( { type: 'hoveron', object: object } ); 97 | 98 | _domElement.style.cursor = 'pointer'; 99 | _hovered = object; 100 | 101 | } 102 | 103 | } else { 104 | 105 | if ( _hovered !== null ) { 106 | 107 | scope.dispatchEvent( { type: 'hoveroff', object: _hovered } ); 108 | 109 | _domElement.style.cursor = 'auto'; 110 | _hovered = null; 111 | 112 | } 113 | 114 | } 115 | 116 | } 117 | 118 | function onDocumentMouseDown( event ) { 119 | 120 | event.preventDefault(); 121 | 122 | _raycaster.setFromCamera( _mouse, _camera ); 123 | 124 | var intersects = _raycaster.intersectObjects( _objects ); 125 | 126 | if ( intersects.length > 0 ) { 127 | 128 | _selected = intersects[ 0 ].object; 129 | 130 | if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) { 131 | 132 | _offset.copy( _intersection ).sub( _selected.position ); 133 | 134 | } 135 | 136 | _domElement.style.cursor = 'move'; 137 | 138 | scope.dispatchEvent( { type: 'dragstart', object: _selected } ); 139 | 140 | } 141 | 142 | 143 | } 144 | 145 | function onDocumentMouseCancel( event ) { 146 | 147 | event.preventDefault(); 148 | 149 | if ( _selected ) { 150 | 151 | scope.dispatchEvent( { type: 'dragend', object: _selected } ); 152 | 153 | _selected = null; 154 | 155 | } 156 | 157 | _domElement.style.cursor = _hovered ? 'pointer' : 'auto'; 158 | 159 | } 160 | 161 | function onDocumentTouchMove( event ) { 162 | 163 | event.preventDefault(); 164 | event = event.changedTouches[ 0 ]; 165 | 166 | var rect = _domElement.getBoundingClientRect(); 167 | 168 | _mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1; 169 | _mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1; 170 | 171 | _raycaster.setFromCamera( _mouse, _camera ); 172 | 173 | if ( _selected && scope.enabled ) { 174 | 175 | if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) { 176 | 177 | _selected.position.copy( _intersection.sub( _offset ) ); 178 | 179 | } 180 | 181 | scope.dispatchEvent( { type: 'drag', object: _selected } ); 182 | 183 | return; 184 | 185 | } 186 | 187 | } 188 | 189 | function onDocumentTouchStart( event ) { 190 | 191 | event.preventDefault(); 192 | event = event.changedTouches[ 0 ]; 193 | 194 | var rect = _domElement.getBoundingClientRect(); 195 | 196 | _mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1; 197 | _mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1; 198 | 199 | _raycaster.setFromCamera( _mouse, _camera ); 200 | 201 | var intersects = _raycaster.intersectObjects( _objects ); 202 | 203 | if ( intersects.length > 0 ) { 204 | 205 | _selected = intersects[ 0 ].object; 206 | 207 | _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _selected.position ); 208 | 209 | if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) { 210 | 211 | _offset.copy( _intersection ).sub( _selected.position ); 212 | 213 | } 214 | 215 | _domElement.style.cursor = 'move'; 216 | 217 | scope.dispatchEvent( { type: 'dragstart', object: _selected } ); 218 | 219 | } 220 | 221 | 222 | } 223 | 224 | function onDocumentTouchEnd( event ) { 225 | 226 | event.preventDefault(); 227 | 228 | if ( _selected ) { 229 | 230 | scope.dispatchEvent( { type: 'dragend', object: _selected } ); 231 | 232 | _selected = null; 233 | 234 | } 235 | 236 | _domElement.style.cursor = 'auto'; 237 | 238 | } 239 | 240 | activate(); 241 | 242 | // API 243 | 244 | this.enabled = true; 245 | 246 | this.activate = activate; 247 | this.deactivate = deactivate; 248 | this.dispose = dispose; 249 | 250 | // Backward compatibility 251 | 252 | this.setObjects = function () { 253 | 254 | console.error( 'THREE.DragControls: setObjects() has been removed.' ); 255 | 256 | }; 257 | 258 | this.on = function ( type, listener ) { 259 | 260 | console.warn( 'THREE.DragControls: on() has been deprecated. Use addEventListener() instead.' ); 261 | scope.addEventListener( type, listener ); 262 | 263 | }; 264 | 265 | this.off = function ( type, listener ) { 266 | 267 | console.warn( 'THREE.DragControls: off() has been deprecated. Use removeEventListener() instead.' ); 268 | scope.removeEventListener( type, listener ); 269 | 270 | }; 271 | 272 | this.notify = function ( type ) { 273 | 274 | console.error( 'THREE.DragControls: notify() has been deprecated. Use dispatchEvent() instead.' ); 275 | scope.dispatchEvent( { type: type } ); 276 | 277 | }; 278 | 279 | }; 280 | 281 | THREE.DragControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 282 | THREE.DragControls.prototype.constructor = THREE.DragControls; 283 | -------------------------------------------------------------------------------- /js/controls/EditorControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | */ 7 | 8 | THREE.EditorControls = function ( object, domElement ) { 9 | 10 | domElement = ( domElement !== undefined ) ? domElement : document; 11 | 12 | // API 13 | 14 | this.enabled = true; 15 | this.center = new THREE.Vector3(); 16 | this.panSpeed = 0.001; 17 | this.zoomSpeed = 0.1; 18 | this.rotationSpeed = 0.005; 19 | 20 | // internals 21 | 22 | var scope = this; 23 | var vector = new THREE.Vector3(); 24 | var delta = new THREE.Vector3(); 25 | var box = new THREE.Box3(); 26 | 27 | var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2 }; 28 | var state = STATE.NONE; 29 | 30 | var center = this.center; 31 | var normalMatrix = new THREE.Matrix3(); 32 | var pointer = new THREE.Vector2(); 33 | var pointerOld = new THREE.Vector2(); 34 | var spherical = new THREE.Spherical(); 35 | 36 | // events 37 | 38 | var changeEvent = { type: 'change' }; 39 | 40 | this.focus = function ( target ) { 41 | 42 | var distance; 43 | 44 | box.setFromObject( target ); 45 | 46 | if ( box.isEmpty() === false ) { 47 | 48 | center.copy( box.getCenter() ); 49 | distance = box.getBoundingSphere().radius; 50 | 51 | } else { 52 | 53 | // Focusing on an Group, AmbientLight, etc 54 | 55 | center.setFromMatrixPosition( target.matrixWorld ); 56 | distance = 0.1; 57 | 58 | } 59 | 60 | delta.set( 0, 0, 1 ); 61 | delta.applyQuaternion( object.quaternion ); 62 | delta.multiplyScalar( distance * 4 ); 63 | 64 | object.position.copy( center ).add( delta ); 65 | 66 | scope.dispatchEvent( changeEvent ); 67 | 68 | }; 69 | 70 | this.pan = function ( delta ) { 71 | 72 | var distance = object.position.distanceTo( center ); 73 | 74 | delta.multiplyScalar( distance * scope.panSpeed ); 75 | delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) ); 76 | 77 | object.position.add( delta ); 78 | center.add( delta ); 79 | 80 | scope.dispatchEvent( changeEvent ); 81 | 82 | }; 83 | 84 | this.zoom = function ( delta ) { 85 | 86 | var distance = object.position.distanceTo( center ); 87 | 88 | delta.multiplyScalar( distance * scope.zoomSpeed ); 89 | 90 | if ( delta.length() > distance ) return; 91 | 92 | delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) ); 93 | 94 | object.position.add( delta ); 95 | 96 | scope.dispatchEvent( changeEvent ); 97 | 98 | }; 99 | 100 | this.rotate = function ( delta ) { 101 | 102 | vector.copy( object.position ).sub( center ); 103 | 104 | spherical.setFromVector3( vector ); 105 | 106 | spherical.theta += delta.x; 107 | spherical.phi += delta.y; 108 | 109 | spherical.makeSafe(); 110 | 111 | vector.setFromSpherical( spherical ); 112 | 113 | object.position.copy( center ).add( vector ); 114 | 115 | object.lookAt( center ); 116 | 117 | scope.dispatchEvent( changeEvent ); 118 | 119 | }; 120 | 121 | // mouse 122 | 123 | function onMouseDown( event ) { 124 | 125 | if ( scope.enabled === false ) return; 126 | 127 | if ( event.button === 0 ) { 128 | 129 | state = STATE.ROTATE; 130 | 131 | } else if ( event.button === 1 ) { 132 | 133 | state = STATE.ZOOM; 134 | 135 | } else if ( event.button === 2 ) { 136 | 137 | state = STATE.PAN; 138 | 139 | } 140 | 141 | pointerOld.set( event.clientX, event.clientY ); 142 | 143 | domElement.addEventListener( 'mousemove', onMouseMove, false ); 144 | domElement.addEventListener( 'mouseup', onMouseUp, false ); 145 | domElement.addEventListener( 'mouseout', onMouseUp, false ); 146 | domElement.addEventListener( 'dblclick', onMouseUp, false ); 147 | 148 | } 149 | 150 | function onMouseMove( event ) { 151 | 152 | if ( scope.enabled === false ) return; 153 | 154 | pointer.set( event.clientX, event.clientY ); 155 | 156 | var movementX = pointer.x - pointerOld.x; 157 | var movementY = pointer.y - pointerOld.y; 158 | 159 | if ( state === STATE.ROTATE ) { 160 | 161 | scope.rotate( delta.set( - movementX * scope.rotationSpeed, - movementY * scope.rotationSpeed, 0 ) ); 162 | 163 | } else if ( state === STATE.ZOOM ) { 164 | 165 | scope.zoom( delta.set( 0, 0, movementY ) ); 166 | 167 | } else if ( state === STATE.PAN ) { 168 | 169 | scope.pan( delta.set( - movementX, movementY, 0 ) ); 170 | 171 | } 172 | 173 | pointerOld.set( event.clientX, event.clientY ); 174 | 175 | } 176 | 177 | function onMouseUp( event ) { 178 | 179 | domElement.removeEventListener( 'mousemove', onMouseMove, false ); 180 | domElement.removeEventListener( 'mouseup', onMouseUp, false ); 181 | domElement.removeEventListener( 'mouseout', onMouseUp, false ); 182 | domElement.removeEventListener( 'dblclick', onMouseUp, false ); 183 | 184 | state = STATE.NONE; 185 | 186 | } 187 | 188 | function onMouseWheel( event ) { 189 | 190 | event.preventDefault(); 191 | 192 | // Normalize deltaY due to https://bugzilla.mozilla.org/show_bug.cgi?id=1392460 193 | scope.zoom( delta.set( 0, 0, event.deltaY > 0 ? 1 : - 1 ) ); 194 | 195 | } 196 | 197 | function contextmenu( event ) { 198 | 199 | event.preventDefault(); 200 | 201 | } 202 | 203 | this.dispose = function () { 204 | 205 | domElement.removeEventListener( 'contextmenu', contextmenu, false ); 206 | domElement.removeEventListener( 'mousedown', onMouseDown, false ); 207 | domElement.removeEventListener( 'wheel', onMouseWheel, false ); 208 | 209 | domElement.removeEventListener( 'mousemove', onMouseMove, false ); 210 | domElement.removeEventListener( 'mouseup', onMouseUp, false ); 211 | domElement.removeEventListener( 'mouseout', onMouseUp, false ); 212 | domElement.removeEventListener( 'dblclick', onMouseUp, false ); 213 | 214 | domElement.removeEventListener( 'touchstart', touchStart, false ); 215 | domElement.removeEventListener( 'touchmove', touchMove, false ); 216 | 217 | }; 218 | 219 | domElement.addEventListener( 'contextmenu', contextmenu, false ); 220 | domElement.addEventListener( 'mousedown', onMouseDown, false ); 221 | domElement.addEventListener( 'wheel', onMouseWheel, false ); 222 | 223 | // touch 224 | 225 | var touches = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ]; 226 | var prevTouches = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ]; 227 | 228 | var prevDistance = null; 229 | 230 | function touchStart( event ) { 231 | 232 | if ( scope.enabled === false ) return; 233 | 234 | switch ( event.touches.length ) { 235 | 236 | case 1: 237 | touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ); 238 | touches[ 1 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ); 239 | break; 240 | 241 | case 2: 242 | touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ); 243 | touches[ 1 ].set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY, 0 ); 244 | prevDistance = touches[ 0 ].distanceTo( touches[ 1 ] ); 245 | break; 246 | 247 | } 248 | 249 | prevTouches[ 0 ].copy( touches[ 0 ] ); 250 | prevTouches[ 1 ].copy( touches[ 1 ] ); 251 | 252 | } 253 | 254 | 255 | function touchMove( event ) { 256 | 257 | if ( scope.enabled === false ) return; 258 | 259 | event.preventDefault(); 260 | event.stopPropagation(); 261 | 262 | function getClosest( touch, touches ) { 263 | 264 | var closest = touches[ 0 ]; 265 | 266 | for ( var i in touches ) { 267 | 268 | if ( closest.distanceTo( touch ) > touches[ i ].distanceTo( touch ) ) closest = touches[ i ]; 269 | 270 | } 271 | 272 | return closest; 273 | 274 | } 275 | 276 | switch ( event.touches.length ) { 277 | 278 | case 1: 279 | touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ); 280 | touches[ 1 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ); 281 | scope.rotate( touches[ 0 ].sub( getClosest( touches[ 0 ], prevTouches ) ).multiplyScalar( - scope.rotationSpeed ) ); 282 | break; 283 | 284 | case 2: 285 | touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ); 286 | touches[ 1 ].set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY, 0 ); 287 | var distance = touches[ 0 ].distanceTo( touches[ 1 ] ); 288 | scope.zoom( delta.set( 0, 0, prevDistance - distance ) ); 289 | prevDistance = distance; 290 | 291 | 292 | var offset0 = touches[ 0 ].clone().sub( getClosest( touches[ 0 ], prevTouches ) ); 293 | var offset1 = touches[ 1 ].clone().sub( getClosest( touches[ 1 ], prevTouches ) ); 294 | offset0.x = - offset0.x; 295 | offset1.x = - offset1.x; 296 | 297 | scope.pan( offset0.add( offset1 ).multiplyScalar( 0.5 ) ); 298 | 299 | break; 300 | 301 | } 302 | 303 | prevTouches[ 0 ].copy( touches[ 0 ] ); 304 | prevTouches[ 1 ].copy( touches[ 1 ] ); 305 | 306 | } 307 | 308 | domElement.addEventListener( 'touchstart', touchStart, false ); 309 | domElement.addEventListener( 'touchmove', touchMove, false ); 310 | 311 | }; 312 | 313 | THREE.EditorControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 314 | THREE.EditorControls.prototype.constructor = THREE.EditorControls; 315 | -------------------------------------------------------------------------------- /js/controls/FirstPersonControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | * @author alteredq / http://alteredqualia.com/ 4 | * @author paulirish / http://paulirish.com/ 5 | */ 6 | 7 | THREE.FirstPersonControls = function ( object, domElement ) { 8 | 9 | this.object = object; 10 | this.target = new THREE.Vector3( 0, 0, 0 ); 11 | 12 | this.domElement = ( domElement !== undefined ) ? domElement : document; 13 | 14 | this.enabled = true; 15 | 16 | this.movementSpeed = 1.0; 17 | this.lookSpeed = 0.005; 18 | 19 | this.lookVertical = true; 20 | this.autoForward = false; 21 | 22 | this.activeLook = true; 23 | 24 | this.heightSpeed = false; 25 | this.heightCoef = 1.0; 26 | this.heightMin = 0.0; 27 | this.heightMax = 1.0; 28 | 29 | this.constrainVertical = false; 30 | this.verticalMin = 0; 31 | this.verticalMax = Math.PI; 32 | 33 | this.autoSpeedFactor = 0.0; 34 | 35 | this.mouseX = 0; 36 | this.mouseY = 0; 37 | 38 | this.lat = 0; 39 | this.lon = 0; 40 | this.phi = 0; 41 | this.theta = 0; 42 | 43 | this.moveForward = false; 44 | this.moveBackward = false; 45 | this.moveLeft = false; 46 | this.moveRight = false; 47 | 48 | this.mouseDragOn = false; 49 | 50 | this.viewHalfX = 0; 51 | this.viewHalfY = 0; 52 | 53 | if ( this.domElement !== document ) { 54 | 55 | this.domElement.setAttribute( 'tabindex', - 1 ); 56 | 57 | } 58 | 59 | // 60 | 61 | this.handleResize = function () { 62 | 63 | if ( this.domElement === document ) { 64 | 65 | this.viewHalfX = window.innerWidth / 2; 66 | this.viewHalfY = window.innerHeight / 2; 67 | 68 | } else { 69 | 70 | this.viewHalfX = this.domElement.offsetWidth / 2; 71 | this.viewHalfY = this.domElement.offsetHeight / 2; 72 | 73 | } 74 | 75 | }; 76 | 77 | this.onMouseDown = function ( event ) { 78 | 79 | if ( this.domElement !== document ) { 80 | 81 | this.domElement.focus(); 82 | 83 | } 84 | 85 | event.preventDefault(); 86 | event.stopPropagation(); 87 | 88 | if ( this.activeLook ) { 89 | 90 | switch ( event.button ) { 91 | 92 | case 0: this.moveForward = true; break; 93 | case 2: this.moveBackward = true; break; 94 | 95 | } 96 | 97 | } 98 | 99 | this.mouseDragOn = true; 100 | 101 | }; 102 | 103 | this.onMouseUp = function ( event ) { 104 | 105 | event.preventDefault(); 106 | event.stopPropagation(); 107 | 108 | if ( this.activeLook ) { 109 | 110 | switch ( event.button ) { 111 | 112 | case 0: this.moveForward = false; break; 113 | case 2: this.moveBackward = false; break; 114 | 115 | } 116 | 117 | } 118 | 119 | this.mouseDragOn = false; 120 | 121 | }; 122 | 123 | this.onMouseMove = function ( event ) { 124 | 125 | if ( this.domElement === document ) { 126 | 127 | this.mouseX = event.pageX - this.viewHalfX; 128 | this.mouseY = event.pageY - this.viewHalfY; 129 | 130 | } else { 131 | 132 | this.mouseX = event.pageX - this.domElement.offsetLeft - this.viewHalfX; 133 | this.mouseY = event.pageY - this.domElement.offsetTop - this.viewHalfY; 134 | 135 | } 136 | 137 | }; 138 | 139 | this.onKeyDown = function ( event ) { 140 | 141 | //event.preventDefault(); 142 | 143 | switch ( event.keyCode ) { 144 | 145 | case 38: /*up*/ 146 | case 87: /*W*/ this.moveForward = true; break; 147 | 148 | case 37: /*left*/ 149 | case 65: /*A*/ this.moveLeft = true; break; 150 | 151 | case 40: /*down*/ 152 | case 83: /*S*/ this.moveBackward = true; break; 153 | 154 | case 39: /*right*/ 155 | case 68: /*D*/ this.moveRight = true; break; 156 | 157 | case 82: /*R*/ this.moveUp = true; break; 158 | case 70: /*F*/ this.moveDown = true; break; 159 | 160 | } 161 | 162 | }; 163 | 164 | this.onKeyUp = function ( event ) { 165 | 166 | switch ( event.keyCode ) { 167 | 168 | case 38: /*up*/ 169 | case 87: /*W*/ this.moveForward = false; break; 170 | 171 | case 37: /*left*/ 172 | case 65: /*A*/ this.moveLeft = false; break; 173 | 174 | case 40: /*down*/ 175 | case 83: /*S*/ this.moveBackward = false; break; 176 | 177 | case 39: /*right*/ 178 | case 68: /*D*/ this.moveRight = false; break; 179 | 180 | case 82: /*R*/ this.moveUp = false; break; 181 | case 70: /*F*/ this.moveDown = false; break; 182 | 183 | } 184 | 185 | }; 186 | 187 | this.update = function ( delta ) { 188 | 189 | if ( this.enabled === false ) return; 190 | 191 | if ( this.heightSpeed ) { 192 | 193 | var y = THREE.Math.clamp( this.object.position.y, this.heightMin, this.heightMax ); 194 | var heightDelta = y - this.heightMin; 195 | 196 | this.autoSpeedFactor = delta * ( heightDelta * this.heightCoef ); 197 | 198 | } else { 199 | 200 | this.autoSpeedFactor = 0.0; 201 | 202 | } 203 | 204 | var actualMoveSpeed = delta * this.movementSpeed; 205 | 206 | if ( this.moveForward || ( this.autoForward && ! this.moveBackward ) ) this.object.translateZ( - ( actualMoveSpeed + this.autoSpeedFactor ) ); 207 | if ( this.moveBackward ) this.object.translateZ( actualMoveSpeed ); 208 | 209 | if ( this.moveLeft ) this.object.translateX( - actualMoveSpeed ); 210 | if ( this.moveRight ) this.object.translateX( actualMoveSpeed ); 211 | 212 | if ( this.moveUp ) this.object.translateY( actualMoveSpeed ); 213 | if ( this.moveDown ) this.object.translateY( - actualMoveSpeed ); 214 | 215 | var actualLookSpeed = delta * this.lookSpeed; 216 | 217 | if ( ! this.activeLook ) { 218 | 219 | actualLookSpeed = 0; 220 | 221 | } 222 | 223 | var verticalLookRatio = 1; 224 | 225 | if ( this.constrainVertical ) { 226 | 227 | verticalLookRatio = Math.PI / ( this.verticalMax - this.verticalMin ); 228 | 229 | } 230 | 231 | this.lon += this.mouseX * actualLookSpeed; 232 | if ( this.lookVertical ) this.lat -= this.mouseY * actualLookSpeed * verticalLookRatio; 233 | 234 | this.lat = Math.max( - 85, Math.min( 85, this.lat ) ); 235 | this.phi = THREE.Math.degToRad( 90 - this.lat ); 236 | 237 | this.theta = THREE.Math.degToRad( this.lon ); 238 | 239 | if ( this.constrainVertical ) { 240 | 241 | this.phi = THREE.Math.mapLinear( this.phi, 0, Math.PI, this.verticalMin, this.verticalMax ); 242 | 243 | } 244 | 245 | var targetPosition = this.target, 246 | position = this.object.position; 247 | 248 | targetPosition.x = position.x + 100 * Math.sin( this.phi ) * Math.cos( this.theta ); 249 | targetPosition.y = position.y + 100 * Math.cos( this.phi ); 250 | targetPosition.z = position.z + 100 * Math.sin( this.phi ) * Math.sin( this.theta ); 251 | 252 | this.object.lookAt( targetPosition ); 253 | 254 | }; 255 | 256 | function contextmenu( event ) { 257 | 258 | event.preventDefault(); 259 | 260 | } 261 | 262 | this.dispose = function () { 263 | 264 | this.domElement.removeEventListener( 'contextmenu', contextmenu, false ); 265 | this.domElement.removeEventListener( 'mousedown', _onMouseDown, false ); 266 | this.domElement.removeEventListener( 'mousemove', _onMouseMove, false ); 267 | this.domElement.removeEventListener( 'mouseup', _onMouseUp, false ); 268 | 269 | window.removeEventListener( 'keydown', _onKeyDown, false ); 270 | window.removeEventListener( 'keyup', _onKeyUp, false ); 271 | 272 | }; 273 | 274 | var _onMouseMove = bind( this, this.onMouseMove ); 275 | var _onMouseDown = bind( this, this.onMouseDown ); 276 | var _onMouseUp = bind( this, this.onMouseUp ); 277 | var _onKeyDown = bind( this, this.onKeyDown ); 278 | var _onKeyUp = bind( this, this.onKeyUp ); 279 | 280 | this.domElement.addEventListener( 'contextmenu', contextmenu, false ); 281 | this.domElement.addEventListener( 'mousemove', _onMouseMove, false ); 282 | this.domElement.addEventListener( 'mousedown', _onMouseDown, false ); 283 | this.domElement.addEventListener( 'mouseup', _onMouseUp, false ); 284 | 285 | window.addEventListener( 'keydown', _onKeyDown, false ); 286 | window.addEventListener( 'keyup', _onKeyUp, false ); 287 | 288 | function bind( scope, fn ) { 289 | 290 | return function () { 291 | 292 | fn.apply( scope, arguments ); 293 | 294 | }; 295 | 296 | } 297 | 298 | this.handleResize(); 299 | 300 | }; 301 | -------------------------------------------------------------------------------- /js/controls/FlyControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author James Baicoianu / http://www.baicoianu.com/ 3 | */ 4 | 5 | THREE.FlyControls = function ( object, domElement ) { 6 | 7 | this.object = object; 8 | 9 | this.domElement = ( domElement !== undefined ) ? domElement : document; 10 | if ( domElement ) this.domElement.setAttribute( 'tabindex', - 1 ); 11 | 12 | // API 13 | 14 | this.movementSpeed = 1.0; 15 | this.rollSpeed = 0.005; 16 | 17 | this.dragToLook = false; 18 | this.autoForward = false; 19 | 20 | // disable default target object behavior 21 | 22 | // internals 23 | 24 | this.tmpQuaternion = new THREE.Quaternion(); 25 | 26 | this.mouseStatus = 0; 27 | 28 | this.moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 }; 29 | this.moveVector = new THREE.Vector3( 0, 0, 0 ); 30 | this.rotationVector = new THREE.Vector3( 0, 0, 0 ); 31 | 32 | this.keydown = function ( event ) { 33 | 34 | if ( event.altKey ) { 35 | 36 | return; 37 | 38 | } 39 | 40 | //event.preventDefault(); 41 | 42 | switch ( event.keyCode ) { 43 | 44 | case 16: /* shift */ this.movementSpeedMultiplier = .1; break; 45 | 46 | case 87: /*W*/ this.moveState.forward = 1; break; 47 | case 83: /*S*/ this.moveState.back = 1; break; 48 | 49 | case 65: /*A*/ this.moveState.left = 1; break; 50 | case 68: /*D*/ this.moveState.right = 1; break; 51 | 52 | case 82: /*R*/ this.moveState.up = 1; break; 53 | case 70: /*F*/ this.moveState.down = 1; break; 54 | 55 | case 38: /*up*/ this.moveState.pitchUp = 1; break; 56 | case 40: /*down*/ this.moveState.pitchDown = 1; break; 57 | 58 | case 37: /*left*/ this.moveState.yawLeft = 1; break; 59 | case 39: /*right*/ this.moveState.yawRight = 1; break; 60 | 61 | case 81: /*Q*/ this.moveState.rollLeft = 1; break; 62 | case 69: /*E*/ this.moveState.rollRight = 1; break; 63 | 64 | } 65 | 66 | this.updateMovementVector(); 67 | this.updateRotationVector(); 68 | 69 | }; 70 | 71 | this.keyup = function ( event ) { 72 | 73 | switch ( event.keyCode ) { 74 | 75 | case 16: /* shift */ this.movementSpeedMultiplier = 1; break; 76 | 77 | case 87: /*W*/ this.moveState.forward = 0; break; 78 | case 83: /*S*/ this.moveState.back = 0; break; 79 | 80 | case 65: /*A*/ this.moveState.left = 0; break; 81 | case 68: /*D*/ this.moveState.right = 0; break; 82 | 83 | case 82: /*R*/ this.moveState.up = 0; break; 84 | case 70: /*F*/ this.moveState.down = 0; break; 85 | 86 | case 38: /*up*/ this.moveState.pitchUp = 0; break; 87 | case 40: /*down*/ this.moveState.pitchDown = 0; break; 88 | 89 | case 37: /*left*/ this.moveState.yawLeft = 0; break; 90 | case 39: /*right*/ this.moveState.yawRight = 0; break; 91 | 92 | case 81: /*Q*/ this.moveState.rollLeft = 0; break; 93 | case 69: /*E*/ this.moveState.rollRight = 0; break; 94 | 95 | } 96 | 97 | this.updateMovementVector(); 98 | this.updateRotationVector(); 99 | 100 | }; 101 | 102 | this.mousedown = function ( event ) { 103 | 104 | if ( this.domElement !== document ) { 105 | 106 | this.domElement.focus(); 107 | 108 | } 109 | 110 | event.preventDefault(); 111 | event.stopPropagation(); 112 | 113 | if ( this.dragToLook ) { 114 | 115 | this.mouseStatus ++; 116 | 117 | } else { 118 | 119 | switch ( event.button ) { 120 | 121 | case 0: this.moveState.forward = 1; break; 122 | case 2: this.moveState.back = 1; break; 123 | 124 | } 125 | 126 | this.updateMovementVector(); 127 | 128 | } 129 | 130 | }; 131 | 132 | this.mousemove = function ( event ) { 133 | 134 | if ( ! this.dragToLook || this.mouseStatus > 0 ) { 135 | 136 | var container = this.getContainerDimensions(); 137 | var halfWidth = container.size[ 0 ] / 2; 138 | var halfHeight = container.size[ 1 ] / 2; 139 | 140 | this.moveState.yawLeft = - ( ( event.pageX - container.offset[ 0 ] ) - halfWidth ) / halfWidth; 141 | this.moveState.pitchDown = ( ( event.pageY - container.offset[ 1 ] ) - halfHeight ) / halfHeight; 142 | 143 | this.updateRotationVector(); 144 | 145 | } 146 | 147 | }; 148 | 149 | this.mouseup = function ( event ) { 150 | 151 | event.preventDefault(); 152 | event.stopPropagation(); 153 | 154 | if ( this.dragToLook ) { 155 | 156 | this.mouseStatus --; 157 | 158 | this.moveState.yawLeft = this.moveState.pitchDown = 0; 159 | 160 | } else { 161 | 162 | switch ( event.button ) { 163 | 164 | case 0: this.moveState.forward = 0; break; 165 | case 2: this.moveState.back = 0; break; 166 | 167 | } 168 | 169 | this.updateMovementVector(); 170 | 171 | } 172 | 173 | this.updateRotationVector(); 174 | 175 | }; 176 | 177 | this.update = function ( delta ) { 178 | 179 | var moveMult = delta * this.movementSpeed; 180 | var rotMult = delta * this.rollSpeed; 181 | 182 | this.object.translateX( this.moveVector.x * moveMult ); 183 | this.object.translateY( this.moveVector.y * moveMult ); 184 | this.object.translateZ( this.moveVector.z * moveMult ); 185 | 186 | this.tmpQuaternion.set( this.rotationVector.x * rotMult, this.rotationVector.y * rotMult, this.rotationVector.z * rotMult, 1 ).normalize(); 187 | this.object.quaternion.multiply( this.tmpQuaternion ); 188 | 189 | // expose the rotation vector for convenience 190 | this.object.rotation.setFromQuaternion( this.object.quaternion, this.object.rotation.order ); 191 | 192 | 193 | }; 194 | 195 | this.updateMovementVector = function () { 196 | 197 | var forward = ( this.moveState.forward || ( this.autoForward && ! this.moveState.back ) ) ? 1 : 0; 198 | 199 | this.moveVector.x = ( - this.moveState.left + this.moveState.right ); 200 | this.moveVector.y = ( - this.moveState.down + this.moveState.up ); 201 | this.moveVector.z = ( - forward + this.moveState.back ); 202 | 203 | //console.log( 'move:', [ this.moveVector.x, this.moveVector.y, this.moveVector.z ] ); 204 | 205 | }; 206 | 207 | this.updateRotationVector = function () { 208 | 209 | this.rotationVector.x = ( - this.moveState.pitchDown + this.moveState.pitchUp ); 210 | this.rotationVector.y = ( - this.moveState.yawRight + this.moveState.yawLeft ); 211 | this.rotationVector.z = ( - this.moveState.rollRight + this.moveState.rollLeft ); 212 | 213 | //console.log( 'rotate:', [ this.rotationVector.x, this.rotationVector.y, this.rotationVector.z ] ); 214 | 215 | }; 216 | 217 | this.getContainerDimensions = function () { 218 | 219 | if ( this.domElement != document ) { 220 | 221 | return { 222 | size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ], 223 | offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ] 224 | }; 225 | 226 | } else { 227 | 228 | return { 229 | size: [ window.innerWidth, window.innerHeight ], 230 | offset: [ 0, 0 ] 231 | }; 232 | 233 | } 234 | 235 | }; 236 | 237 | function bind( scope, fn ) { 238 | 239 | return function () { 240 | 241 | fn.apply( scope, arguments ); 242 | 243 | }; 244 | 245 | } 246 | 247 | function contextmenu( event ) { 248 | 249 | event.preventDefault(); 250 | 251 | } 252 | 253 | this.dispose = function () { 254 | 255 | this.domElement.removeEventListener( 'contextmenu', contextmenu, false ); 256 | this.domElement.removeEventListener( 'mousedown', _mousedown, false ); 257 | this.domElement.removeEventListener( 'mousemove', _mousemove, false ); 258 | this.domElement.removeEventListener( 'mouseup', _mouseup, false ); 259 | 260 | window.removeEventListener( 'keydown', _keydown, false ); 261 | window.removeEventListener( 'keyup', _keyup, false ); 262 | 263 | }; 264 | 265 | var _mousemove = bind( this, this.mousemove ); 266 | var _mousedown = bind( this, this.mousedown ); 267 | var _mouseup = bind( this, this.mouseup ); 268 | var _keydown = bind( this, this.keydown ); 269 | var _keyup = bind( this, this.keyup ); 270 | 271 | this.domElement.addEventListener( 'contextmenu', contextmenu, false ); 272 | 273 | this.domElement.addEventListener( 'mousemove', _mousemove, false ); 274 | this.domElement.addEventListener( 'mousedown', _mousedown, false ); 275 | this.domElement.addEventListener( 'mouseup', _mouseup, false ); 276 | 277 | window.addEventListener( 'keydown', _keydown, false ); 278 | window.addEventListener( 'keyup', _keyup, false ); 279 | 280 | this.updateMovementVector(); 281 | this.updateRotationVector(); 282 | 283 | }; 284 | -------------------------------------------------------------------------------- /js/controls/MapControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | * @author moroine / https://github.com/moroine 8 | */ 9 | 10 | // This set of controls performs orbiting, dollying (zooming), and panning. 11 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 12 | // This is very similar to OrbitControls, another set of touch behavior 13 | // 14 | // Orbit - right mouse, or left mouse + ctrl/metaKey / touch: two-finger rotate 15 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 16 | // Pan - left mouse, or arrow keys / touch: one-finger move 17 | 18 | THREE.MapControls = function ( object, domElement ) { 19 | 20 | this.object = object; 21 | 22 | this.domElement = ( domElement !== undefined ) ? domElement : document; 23 | 24 | // Set to false to disable this control 25 | this.enabled = true; 26 | 27 | // "target" sets the location of focus, where the object orbits around 28 | this.target = new THREE.Vector3(); 29 | 30 | // How far you can dolly in and out ( PerspectiveCamera only ) 31 | this.minDistance = 0; 32 | this.maxDistance = Infinity; 33 | 34 | // How far you can zoom in and out ( OrthographicCamera only ) 35 | this.minZoom = 0; 36 | this.maxZoom = Infinity; 37 | 38 | // How far you can orbit vertically, upper and lower limits. 39 | // Range is 0 to Math.PI radians. 40 | this.minPolarAngle = 0; // radians 41 | this.maxPolarAngle = Math.PI; // radians 42 | 43 | // How far you can orbit horizontally, upper and lower limits. 44 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 45 | this.minAzimuthAngle = - Infinity; // radians 46 | this.maxAzimuthAngle = Infinity; // radians 47 | 48 | // Set to true to enable damping (inertia) 49 | // If damping is enabled, you must call controls.update() in your animation loop 50 | this.enableDamping = false; 51 | this.dampingFactor = 0.25; 52 | 53 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 54 | // Set to false to disable zooming 55 | this.enableZoom = true; 56 | this.zoomSpeed = 1.0; 57 | 58 | // Set to false to disable rotating 59 | this.enableRotate = true; 60 | this.rotateSpeed = 1.0; 61 | 62 | // Set to false to disable panning 63 | this.enablePan = true; 64 | this.panSpeed = 1.0; 65 | this.screenSpacePanning = false; // if true, pan in screen-space 66 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 67 | 68 | // Set to true to automatically rotate around the target 69 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 70 | this.autoRotate = false; 71 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 72 | 73 | // Set to false to disable use of the keys 74 | this.enableKeys = true; 75 | 76 | // The four arrow keys 77 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 78 | 79 | // Mouse buttons 80 | this.mouseButtons = { LEFT: THREE.MOUSE.LEFT, MIDDLE: THREE.MOUSE.MIDDLE, RIGHT: THREE.MOUSE.RIGHT }; 81 | 82 | // for reset 83 | this.target0 = this.target.clone(); 84 | this.position0 = this.object.position.clone(); 85 | this.zoom0 = this.object.zoom; 86 | 87 | // 88 | // public methods 89 | // 90 | 91 | this.getPolarAngle = function () { 92 | 93 | return spherical.phi; 94 | 95 | }; 96 | 97 | this.getAzimuthalAngle = function () { 98 | 99 | return spherical.theta; 100 | 101 | }; 102 | 103 | this.saveState = function () { 104 | 105 | scope.target0.copy( scope.target ); 106 | scope.position0.copy( scope.object.position ); 107 | scope.zoom0 = scope.object.zoom; 108 | 109 | }; 110 | 111 | this.reset = function () { 112 | 113 | scope.target.copy( scope.target0 ); 114 | scope.object.position.copy( scope.position0 ); 115 | scope.object.zoom = scope.zoom0; 116 | 117 | scope.object.updateProjectionMatrix(); 118 | scope.dispatchEvent( changeEvent ); 119 | 120 | scope.update(); 121 | 122 | state = STATE.NONE; 123 | 124 | }; 125 | 126 | // this method is exposed, but perhaps it would be better if we can make it private... 127 | this.update = function () { 128 | 129 | var offset = new THREE.Vector3(); 130 | 131 | // so camera.up is the orbit axis 132 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 133 | var quatInverse = quat.clone().inverse(); 134 | 135 | var lastPosition = new THREE.Vector3(); 136 | var lastQuaternion = new THREE.Quaternion(); 137 | 138 | return function update() { 139 | 140 | var position = scope.object.position; 141 | 142 | offset.copy( position ).sub( scope.target ); 143 | 144 | // rotate offset to "y-axis-is-up" space 145 | offset.applyQuaternion( quat ); 146 | 147 | // angle from z-axis around y-axis 148 | spherical.setFromVector3( offset ); 149 | 150 | if ( scope.autoRotate && state === STATE.NONE ) { 151 | 152 | rotateLeft( getAutoRotationAngle() ); 153 | 154 | } 155 | 156 | spherical.theta += sphericalDelta.theta; 157 | spherical.phi += sphericalDelta.phi; 158 | 159 | // restrict theta to be between desired limits 160 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 161 | 162 | // restrict phi to be between desired limits 163 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 164 | 165 | spherical.makeSafe(); 166 | 167 | 168 | spherical.radius *= scale; 169 | 170 | // restrict radius to be between desired limits 171 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 172 | 173 | // move target to panned location 174 | scope.target.add( panOffset ); 175 | 176 | offset.setFromSpherical( spherical ); 177 | 178 | // rotate offset back to "camera-up-vector-is-up" space 179 | offset.applyQuaternion( quatInverse ); 180 | 181 | position.copy( scope.target ).add( offset ); 182 | 183 | scope.object.lookAt( scope.target ); 184 | 185 | if ( scope.enableDamping === true ) { 186 | 187 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 188 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 189 | 190 | panOffset.multiplyScalar( 1 - scope.dampingFactor ); 191 | 192 | } else { 193 | 194 | sphericalDelta.set( 0, 0, 0 ); 195 | 196 | panOffset.set( 0, 0, 0 ); 197 | 198 | } 199 | 200 | scale = 1; 201 | 202 | // update condition is: 203 | // min(camera displacement, camera rotation in radians)^2 > EPS 204 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 205 | 206 | if ( zoomChanged || 207 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 208 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 209 | 210 | scope.dispatchEvent( changeEvent ); 211 | 212 | lastPosition.copy( scope.object.position ); 213 | lastQuaternion.copy( scope.object.quaternion ); 214 | zoomChanged = false; 215 | 216 | return true; 217 | 218 | } 219 | 220 | return false; 221 | 222 | }; 223 | 224 | }(); 225 | 226 | this.dispose = function () { 227 | 228 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 229 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 230 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 231 | 232 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 233 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 234 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 235 | 236 | document.removeEventListener( 'mousemove', onMouseMove, false ); 237 | document.removeEventListener( 'mouseup', onMouseUp, false ); 238 | 239 | window.removeEventListener( 'keydown', onKeyDown, false ); 240 | 241 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 242 | 243 | }; 244 | 245 | // 246 | // internals 247 | // 248 | 249 | var scope = this; 250 | 251 | var changeEvent = { type: 'change' }; 252 | var startEvent = { type: 'start' }; 253 | var endEvent = { type: 'end' }; 254 | 255 | var STATE = { 256 | NONE: 0, 257 | ROTATE_UP: 1, 258 | ROTATE_LEFT: 2, 259 | ROTATE: 3, // ROTATE_UP | ROTATE_LEFT 260 | DOLLY: 4, 261 | DOLLY_ROTATE: 7, // ROTATE | DOLLY 262 | PAN: 8, 263 | DOLLY_PAN: 12, // DOLLY | PAN 264 | }; 265 | 266 | var state = STATE.NONE; 267 | 268 | var EPS = 0.000001; 269 | 270 | // current position in spherical coordinates 271 | var spherical = new THREE.Spherical(); 272 | var sphericalDelta = new THREE.Spherical(); 273 | 274 | var scale = 1; 275 | var panOffset = new THREE.Vector3(); 276 | var zoomChanged = false; 277 | 278 | var rotateStart = new THREE.Vector2(); 279 | var rotateStart2 = new THREE.Vector2(); 280 | var rotateEnd = new THREE.Vector2(); 281 | var rotateEnd2 = new THREE.Vector2(); 282 | var rotateDelta = new THREE.Vector2(); 283 | var rotateDelta2 = new THREE.Vector2(); 284 | var rotateDeltaStartFingers = new THREE.Vector2(); 285 | var rotateDeltaEndFingers = new THREE.Vector2(); 286 | 287 | var panStart = new THREE.Vector2(); 288 | var panEnd = new THREE.Vector2(); 289 | var panDelta = new THREE.Vector2(); 290 | 291 | var dollyStart = new THREE.Vector2(); 292 | var dollyEnd = new THREE.Vector2(); 293 | var dollyDelta = new THREE.Vector2(); 294 | 295 | function getAutoRotationAngle() { 296 | 297 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 298 | 299 | } 300 | 301 | function getZoomScale() { 302 | 303 | return Math.pow( 0.95, scope.zoomSpeed ); 304 | 305 | } 306 | 307 | function rotateLeft( angle ) { 308 | 309 | sphericalDelta.theta -= angle; 310 | 311 | } 312 | 313 | function rotateUp( angle ) { 314 | 315 | sphericalDelta.phi -= angle; 316 | 317 | } 318 | 319 | var panLeft = function () { 320 | 321 | var v = new THREE.Vector3(); 322 | 323 | return function panLeft( distance, objectMatrix ) { 324 | 325 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 326 | v.multiplyScalar( - distance ); 327 | 328 | panOffset.add( v ); 329 | 330 | }; 331 | 332 | }(); 333 | 334 | var panUp = function () { 335 | 336 | var v = new THREE.Vector3(); 337 | 338 | return function panUp( distance, objectMatrix ) { 339 | 340 | if ( scope.screenSpacePanning === true ) { 341 | 342 | v.setFromMatrixColumn( objectMatrix, 1 ); 343 | 344 | } else { 345 | 346 | v.setFromMatrixColumn( objectMatrix, 0 ); 347 | v.crossVectors( scope.object.up, v ); 348 | 349 | } 350 | 351 | v.multiplyScalar( distance ); 352 | 353 | panOffset.add( v ); 354 | 355 | }; 356 | 357 | }(); 358 | 359 | // deltaX and deltaY are in pixels; right and down are positive 360 | var pan = function () { 361 | 362 | var offset = new THREE.Vector3(); 363 | 364 | return function pan( deltaX, deltaY ) { 365 | 366 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 367 | 368 | if ( scope.object.isPerspectiveCamera ) { 369 | 370 | // perspective 371 | var position = scope.object.position; 372 | offset.copy( position ).sub( scope.target ); 373 | var targetDistance = offset.length(); 374 | 375 | // half of the fov is center to top of screen 376 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 377 | 378 | // we use only clientHeight here so aspect ratio does not distort speed 379 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 380 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 381 | 382 | } else if ( scope.object.isOrthographicCamera ) { 383 | 384 | // orthographic 385 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 386 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 387 | 388 | } else { 389 | 390 | // camera neither orthographic nor perspective 391 | console.warn( 'WARNING: MapControls.js encountered an unknown camera type - pan disabled.' ); 392 | scope.enablePan = false; 393 | 394 | } 395 | 396 | }; 397 | 398 | }(); 399 | 400 | function dollyIn( dollyScale ) { 401 | 402 | if ( scope.object.isPerspectiveCamera ) { 403 | 404 | scale /= dollyScale; 405 | 406 | } else if ( scope.object.isOrthographicCamera ) { 407 | 408 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 409 | scope.object.updateProjectionMatrix(); 410 | zoomChanged = true; 411 | 412 | } else { 413 | 414 | console.warn( 'WARNING: MapControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 415 | scope.enableZoom = false; 416 | 417 | } 418 | 419 | } 420 | 421 | function dollyOut( dollyScale ) { 422 | 423 | if ( scope.object.isPerspectiveCamera ) { 424 | 425 | scale *= dollyScale; 426 | 427 | } else if ( scope.object.isOrthographicCamera ) { 428 | 429 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 430 | scope.object.updateProjectionMatrix(); 431 | zoomChanged = true; 432 | 433 | } else { 434 | 435 | console.warn( 'WARNING: MapControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 436 | scope.enableZoom = false; 437 | 438 | } 439 | 440 | } 441 | 442 | // 443 | // event callbacks - update the object state 444 | // 445 | 446 | function handleMouseDownRotate( event ) { 447 | 448 | //console.log( 'handleMouseDownRotate' ); 449 | 450 | rotateStart.set( event.clientX, event.clientY ); 451 | 452 | } 453 | 454 | function handleMouseDownDolly( event ) { 455 | 456 | //console.log( 'handleMouseDownDolly' ); 457 | 458 | dollyStart.set( event.clientX, event.clientY ); 459 | 460 | } 461 | 462 | function handleMouseDownPan( event ) { 463 | 464 | //console.log( 'handleMouseDownPan' ); 465 | 466 | panStart.set( event.clientX, event.clientY ); 467 | 468 | } 469 | 470 | function handleMouseMoveRotate( event ) { 471 | 472 | //console.log( 'handleMouseMoveRotate' ); 473 | 474 | rotateEnd.set( event.clientX, event.clientY ); 475 | 476 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 477 | 478 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 479 | 480 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 481 | 482 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 483 | 484 | rotateStart.copy( rotateEnd ); 485 | 486 | scope.update(); 487 | 488 | } 489 | 490 | function handleMouseMoveDolly( event ) { 491 | 492 | //console.log( 'handleMouseMoveDolly' ); 493 | 494 | dollyEnd.set( event.clientX, event.clientY ); 495 | 496 | dollyDelta.subVectors( dollyEnd, dollyStart ); 497 | 498 | if ( dollyDelta.y > 0 ) { 499 | 500 | dollyIn( getZoomScale() ); 501 | 502 | } else if ( dollyDelta.y < 0 ) { 503 | 504 | dollyOut( getZoomScale() ); 505 | 506 | } 507 | 508 | dollyStart.copy( dollyEnd ); 509 | 510 | scope.update(); 511 | 512 | } 513 | 514 | function handleMouseMovePan( event ) { 515 | 516 | //console.log( 'handleMouseMovePan' ); 517 | 518 | panEnd.set( event.clientX, event.clientY ); 519 | 520 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 521 | 522 | pan( panDelta.x, panDelta.y ); 523 | 524 | panStart.copy( panEnd ); 525 | 526 | scope.update(); 527 | 528 | } 529 | 530 | function handleMouseUp( event ) { 531 | 532 | // console.log( 'handleMouseUp' ); 533 | 534 | } 535 | 536 | function handleMouseWheel( event ) { 537 | 538 | // console.log( 'handleMouseWheel' ); 539 | 540 | if ( event.deltaY < 0 ) { 541 | 542 | dollyOut( getZoomScale() ); 543 | 544 | } else if ( event.deltaY > 0 ) { 545 | 546 | dollyIn( getZoomScale() ); 547 | 548 | } 549 | 550 | scope.update(); 551 | 552 | } 553 | 554 | function handleKeyDown( event ) { 555 | 556 | //console.log( 'handleKeyDown' ); 557 | 558 | switch ( event.keyCode ) { 559 | 560 | case scope.keys.UP: 561 | pan( 0, scope.keyPanSpeed ); 562 | scope.update(); 563 | break; 564 | 565 | case scope.keys.BOTTOM: 566 | pan( 0, - scope.keyPanSpeed ); 567 | scope.update(); 568 | break; 569 | 570 | case scope.keys.LEFT: 571 | pan( scope.keyPanSpeed, 0 ); 572 | scope.update(); 573 | break; 574 | 575 | case scope.keys.RIGHT: 576 | pan( - scope.keyPanSpeed, 0 ); 577 | scope.update(); 578 | break; 579 | 580 | } 581 | 582 | } 583 | 584 | function handleTouchStartRotate( event ) { 585 | 586 | // console.log( 'handleTouchStartRotate' ); 587 | 588 | // First finger 589 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 590 | 591 | // Second finger 592 | rotateStart2.set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY ); 593 | 594 | } 595 | 596 | function handleTouchStartDolly( event ) { 597 | 598 | if ( scope.enableZoom ) { 599 | 600 | // console.log( 'handleTouchStartDolly' ); 601 | 602 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 603 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 604 | 605 | var distance = Math.sqrt( dx * dx + dy * dy ); 606 | 607 | dollyStart.set( 0, distance ); 608 | 609 | } 610 | 611 | } 612 | 613 | function handleTouchStartPan( event ) { 614 | 615 | if ( scope.enablePan ) { 616 | 617 | // console.log( 'handleTouchStartPan' ); 618 | 619 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 620 | 621 | } 622 | 623 | } 624 | 625 | function handleTouchMoveRotate( event ) { 626 | 627 | if ( scope.enableRotate === false ) return; 628 | if ( ( state & STATE.ROTATE ) === 0 ) return; 629 | 630 | // First finger 631 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 632 | 633 | // Second finger 634 | rotateEnd2.set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY ); 635 | 636 | rotateDelta.subVectors( rotateEnd, rotateStart ); 637 | rotateDelta2.subVectors( rotateEnd2, rotateStart2 ); 638 | rotateDeltaStartFingers.subVectors( rotateStart2, rotateStart ); 639 | rotateDeltaEndFingers.subVectors( rotateEnd2, rotateEnd ); 640 | 641 | if ( isRotateUp() ) { 642 | 643 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 644 | 645 | // rotating up and down along whole screen attempts to go 360, but limited to 180 646 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 647 | 648 | // Start rotateUp ==> disable all movement to prevent flickering 649 | state = STATE.ROTATE_UP; 650 | 651 | } else if ( ( state & STATE.ROTATE_LEFT ) !== 0 ) { 652 | 653 | rotateLeft( ( rotateDeltaStartFingers.angle() - rotateDeltaEndFingers.angle() ) * scope.rotateSpeed ); 654 | 655 | } 656 | 657 | rotateStart.copy( rotateEnd ); 658 | rotateStart2.copy( rotateEnd2 ); 659 | 660 | } 661 | 662 | function isRotateUp() { 663 | 664 | // At start, does the two fingers are aligned horizontally 665 | if ( ! isHorizontal( rotateDeltaStartFingers ) ) { 666 | 667 | return false; 668 | 669 | } 670 | 671 | // At end, does the two fingers are aligned horizontally 672 | if ( ! isHorizontal( rotateDeltaEndFingers ) ) { 673 | 674 | return false; 675 | 676 | } 677 | 678 | // does the first finger moved vertically between start and end 679 | if ( ! isVertical( rotateDelta ) ) { 680 | 681 | return false; 682 | 683 | } 684 | 685 | // does the second finger moved vertically between start and end 686 | if ( ! isVertical( rotateDelta2 ) ) { 687 | 688 | return false; 689 | 690 | } 691 | 692 | // Does the two finger moved in the same direction (prevent moving one finger vertically up while the other goes down) 693 | return rotateDelta.dot( rotateDelta2 ) > 0; 694 | 695 | } 696 | 697 | var isHorizontal = function () { 698 | 699 | var precision = Math.sin( Math.PI / 6 ); 700 | 701 | return function isHorizontal( vector ) { 702 | 703 | return Math.abs( Math.sin( vector.angle() ) ) < precision; 704 | 705 | }; 706 | 707 | }(); 708 | 709 | var isVertical = function () { 710 | 711 | var precision = Math.cos( Math.PI / 2 - Math.PI / 6 ); 712 | 713 | return function isVertical( vector ) { 714 | 715 | return Math.abs( Math.cos( vector.angle() ) ) < precision; 716 | 717 | }; 718 | 719 | }(); 720 | 721 | function handleTouchMoveDolly( event ) { 722 | 723 | if ( scope.enableZoom === false ) return; 724 | if ( ( state & STATE.DOLLY ) === 0 ) return; 725 | 726 | // console.log( 'handleTouchMoveDolly' ); 727 | 728 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 729 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 730 | 731 | var distance = Math.sqrt( dx * dx + dy * dy ); 732 | 733 | dollyEnd.set( 0, distance ); 734 | 735 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); 736 | 737 | dollyIn( dollyDelta.y ); 738 | 739 | dollyStart.copy( dollyEnd ); 740 | 741 | } 742 | 743 | function handleTouchMovePan( event ) { 744 | 745 | if ( scope.enablePan === false ) return; 746 | if ( ( state & STATE.PAN ) === 0 ) return; 747 | 748 | // console.log( 'handleTouchMovePan' ); 749 | 750 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 751 | 752 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 753 | 754 | pan( panDelta.x, panDelta.y ); 755 | 756 | panStart.copy( panEnd ); 757 | 758 | } 759 | 760 | function handleTouchEnd( event ) { 761 | 762 | //console.log( 'handleTouchEnd' ); 763 | 764 | } 765 | 766 | // 767 | // event handlers - FSM: listen for events and reset state 768 | // 769 | 770 | function onMouseDown( event ) { 771 | 772 | if ( scope.enabled === false ) return; 773 | 774 | event.preventDefault(); 775 | 776 | switch ( event.button ) { 777 | 778 | case scope.mouseButtons.LEFT: 779 | 780 | if ( event.ctrlKey || event.metaKey ) { 781 | 782 | if ( scope.enableRotate === false ) return; 783 | 784 | handleMouseDownRotate( event ); 785 | 786 | state = STATE.ROTATE; 787 | 788 | } else { 789 | 790 | if ( scope.enablePan === false ) return; 791 | 792 | handleMouseDownPan( event ); 793 | 794 | state = STATE.PAN; 795 | 796 | } 797 | 798 | break; 799 | 800 | case scope.mouseButtons.MIDDLE: 801 | 802 | if ( scope.enableZoom === false ) return; 803 | 804 | handleMouseDownDolly( event ); 805 | 806 | state = STATE.DOLLY; 807 | 808 | break; 809 | 810 | case scope.mouseButtons.RIGHT: 811 | 812 | if ( scope.enableRotate === false ) return; 813 | 814 | handleMouseDownRotate( event ); 815 | 816 | state = STATE.ROTATE; 817 | 818 | break; 819 | 820 | } 821 | 822 | if ( state !== STATE.NONE ) { 823 | 824 | document.addEventListener( 'mousemove', onMouseMove, false ); 825 | document.addEventListener( 'mouseup', onMouseUp, false ); 826 | 827 | scope.dispatchEvent( startEvent ); 828 | 829 | } 830 | 831 | } 832 | 833 | function onMouseMove( event ) { 834 | 835 | if ( scope.enabled === false ) return; 836 | 837 | event.preventDefault(); 838 | 839 | switch ( state ) { 840 | 841 | case STATE.ROTATE: 842 | 843 | if ( scope.enableRotate === false ) return; 844 | 845 | handleMouseMoveRotate( event ); 846 | 847 | break; 848 | 849 | case STATE.DOLLY: 850 | 851 | if ( scope.enableZoom === false ) return; 852 | 853 | handleMouseMoveDolly( event ); 854 | 855 | break; 856 | 857 | case STATE.PAN: 858 | 859 | if ( scope.enablePan === false ) return; 860 | 861 | handleMouseMovePan( event ); 862 | 863 | break; 864 | 865 | } 866 | 867 | } 868 | 869 | function onMouseUp( event ) { 870 | 871 | if ( scope.enabled === false ) return; 872 | 873 | handleMouseUp( event ); 874 | 875 | document.removeEventListener( 'mousemove', onMouseMove, false ); 876 | document.removeEventListener( 'mouseup', onMouseUp, false ); 877 | 878 | scope.dispatchEvent( endEvent ); 879 | 880 | state = STATE.NONE; 881 | 882 | } 883 | 884 | function onMouseWheel( event ) { 885 | 886 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 887 | 888 | event.preventDefault(); 889 | event.stopPropagation(); 890 | 891 | scope.dispatchEvent( startEvent ); 892 | 893 | handleMouseWheel( event ); 894 | 895 | scope.dispatchEvent( endEvent ); 896 | 897 | } 898 | 899 | function onKeyDown( event ) { 900 | 901 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 902 | 903 | handleKeyDown( event ); 904 | 905 | } 906 | 907 | function onTouchStart( event ) { 908 | 909 | if ( scope.enabled === false ) return; 910 | 911 | event.preventDefault(); 912 | 913 | switch ( event.touches.length ) { 914 | 915 | case 1: // one-fingered touch: pan 916 | 917 | if ( scope.enablePan === false ) return; 918 | 919 | handleTouchStartPan( event ); 920 | 921 | state = STATE.PAN; 922 | 923 | break; 924 | 925 | case 2: // two-fingered touch: rotate-dolly 926 | 927 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 928 | 929 | handleTouchStartRotate( event ); 930 | handleTouchStartDolly( event ); 931 | 932 | state = STATE.DOLLY_ROTATE; 933 | 934 | break; 935 | 936 | default: 937 | 938 | state = STATE.NONE; 939 | 940 | } 941 | 942 | if ( state !== STATE.NONE ) { 943 | 944 | scope.dispatchEvent( startEvent ); 945 | 946 | } 947 | 948 | } 949 | 950 | function onTouchMove( event ) { 951 | 952 | if ( scope.enabled === false ) return; 953 | 954 | event.preventDefault(); 955 | event.stopPropagation(); 956 | 957 | switch ( event.touches.length ) { 958 | 959 | case 1: // one-fingered touch: pan 960 | 961 | if ( scope.enablePan === false ) return; 962 | if ( state !== STATE.PAN ) return; // is this needed? 963 | 964 | handleTouchMovePan( event ); 965 | 966 | scope.update(); 967 | 968 | break; 969 | 970 | case 2: // two-fingered touch: rotate-dolly 971 | 972 | if ( scope.enableZoom === false && scope.enableRotate === false ) return; 973 | if ( ( state & STATE.DOLLY_ROTATE ) === 0 ) return; // is this needed? 974 | 975 | handleTouchMoveRotate( event ); 976 | handleTouchMoveDolly( event ); 977 | 978 | scope.update(); 979 | 980 | break; 981 | 982 | default: 983 | 984 | state = STATE.NONE; 985 | 986 | } 987 | 988 | } 989 | 990 | function onTouchEnd( event ) { 991 | 992 | if ( scope.enabled === false ) return; 993 | 994 | handleTouchEnd( event ); 995 | 996 | scope.dispatchEvent( endEvent ); 997 | 998 | state = STATE.NONE; 999 | 1000 | } 1001 | 1002 | function onContextMenu( event ) { 1003 | 1004 | if ( scope.enabled === false ) return; 1005 | 1006 | event.preventDefault(); 1007 | 1008 | } 1009 | 1010 | // 1011 | 1012 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 1013 | 1014 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 1015 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 1016 | 1017 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 1018 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 1019 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 1020 | 1021 | window.addEventListener( 'keydown', onKeyDown, false ); 1022 | 1023 | // force an update at start 1024 | 1025 | this.update(); 1026 | 1027 | }; 1028 | 1029 | THREE.MapControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 1030 | THREE.MapControls.prototype.constructor = THREE.MapControls; 1031 | 1032 | Object.defineProperties( THREE.MapControls.prototype, { 1033 | 1034 | center: { 1035 | 1036 | get: function () { 1037 | 1038 | console.warn( 'THREE.MapControls: .center has been renamed to .target' ); 1039 | return this.target; 1040 | 1041 | } 1042 | 1043 | }, 1044 | 1045 | // backward compatibility 1046 | 1047 | noZoom: { 1048 | 1049 | get: function () { 1050 | 1051 | console.warn( 'THREE.MapControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 1052 | return ! this.enableZoom; 1053 | 1054 | }, 1055 | 1056 | set: function ( value ) { 1057 | 1058 | console.warn( 'THREE.MapControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 1059 | this.enableZoom = ! value; 1060 | 1061 | } 1062 | 1063 | }, 1064 | 1065 | noRotate: { 1066 | 1067 | get: function () { 1068 | 1069 | console.warn( 'THREE.MapControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 1070 | return ! this.enableRotate; 1071 | 1072 | }, 1073 | 1074 | set: function ( value ) { 1075 | 1076 | console.warn( 'THREE.MapControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 1077 | this.enableRotate = ! value; 1078 | 1079 | } 1080 | 1081 | }, 1082 | 1083 | noPan: { 1084 | 1085 | get: function () { 1086 | 1087 | console.warn( 'THREE.MapControls: .noPan has been deprecated. Use .enablePan instead.' ); 1088 | return ! this.enablePan; 1089 | 1090 | }, 1091 | 1092 | set: function ( value ) { 1093 | 1094 | console.warn( 'THREE.MapControls: .noPan has been deprecated. Use .enablePan instead.' ); 1095 | this.enablePan = ! value; 1096 | 1097 | } 1098 | 1099 | }, 1100 | 1101 | noKeys: { 1102 | 1103 | get: function () { 1104 | 1105 | console.warn( 'THREE.MapControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1106 | return ! this.enableKeys; 1107 | 1108 | }, 1109 | 1110 | set: function ( value ) { 1111 | 1112 | console.warn( 'THREE.MapControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1113 | this.enableKeys = ! value; 1114 | 1115 | } 1116 | 1117 | }, 1118 | 1119 | staticMoving: { 1120 | 1121 | get: function () { 1122 | 1123 | console.warn( 'THREE.MapControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1124 | return ! this.enableDamping; 1125 | 1126 | }, 1127 | 1128 | set: function ( value ) { 1129 | 1130 | console.warn( 'THREE.MapControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1131 | this.enableDamping = ! value; 1132 | 1133 | } 1134 | 1135 | }, 1136 | 1137 | dynamicDampingFactor: { 1138 | 1139 | get: function () { 1140 | 1141 | console.warn( 'THREE.MapControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1142 | return this.dampingFactor; 1143 | 1144 | }, 1145 | 1146 | set: function ( value ) { 1147 | 1148 | console.warn( 'THREE.MapControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1149 | this.dampingFactor = value; 1150 | 1151 | } 1152 | 1153 | } 1154 | 1155 | } ); 1156 | -------------------------------------------------------------------------------- /js/controls/OrbitControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qiao / https://github.com/qiao 3 | * @author mrdoob / http://mrdoob.com 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author WestLangley / http://github.com/WestLangley 6 | * @author erich666 / http://erichaines.com 7 | */ 8 | 9 | // This set of controls performs orbiting, dollying (zooming), and panning. 10 | // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). 11 | // 12 | // Orbit - left mouse / touch: one-finger move 13 | // Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish 14 | // Pan - right mouse, or left mouse + ctrl/metaKey, or arrow keys / touch: two-finger move 15 | 16 | THREE.OrbitControls = function ( object, domElement ) { 17 | 18 | this.object = object; 19 | 20 | this.domElement = ( domElement !== undefined ) ? domElement : document; 21 | 22 | // Set to false to disable this control 23 | this.enabled = true; 24 | 25 | // "target" sets the location of focus, where the object orbits around 26 | this.target = new THREE.Vector3(); 27 | 28 | // How far you can dolly in and out ( PerspectiveCamera only ) 29 | this.minDistance = 0; 30 | this.maxDistance = Infinity; 31 | 32 | // How far you can zoom in and out ( OrthographicCamera only ) 33 | this.minZoom = 0; 34 | this.maxZoom = Infinity; 35 | 36 | // How far you can orbit vertically, upper and lower limits. 37 | // Range is 0 to Math.PI radians. 38 | this.minPolarAngle = 0; // radians 39 | this.maxPolarAngle = Math.PI; // radians 40 | 41 | // How far you can orbit horizontally, upper and lower limits. 42 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 43 | this.minAzimuthAngle = - Infinity; // radians 44 | this.maxAzimuthAngle = Infinity; // radians 45 | 46 | // Set to true to enable damping (inertia) 47 | // If damping is enabled, you must call controls.update() in your animation loop 48 | this.enableDamping = false; 49 | this.dampingFactor = 0.25; 50 | 51 | // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. 52 | // Set to false to disable zooming 53 | this.enableZoom = true; 54 | this.zoomSpeed = 1.0; 55 | 56 | // Set to false to disable rotating 57 | this.enableRotate = true; 58 | this.rotateSpeed = 1.0; 59 | 60 | // Set to false to disable panning 61 | this.enablePan = true; 62 | this.panSpeed = 1.0; 63 | this.screenSpacePanning = false; // if true, pan in screen-space 64 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 65 | 66 | // Set to true to automatically rotate around the target 67 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 68 | this.autoRotate = false; 69 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 70 | 71 | // Set to false to disable use of the keys 72 | this.enableKeys = true; 73 | 74 | // The four arrow keys 75 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 76 | 77 | // Mouse buttons 78 | this.mouseButtons = { LEFT: THREE.MOUSE.LEFT, MIDDLE: THREE.MOUSE.MIDDLE, RIGHT: THREE.MOUSE.RIGHT }; 79 | 80 | // for reset 81 | this.target0 = this.target.clone(); 82 | this.position0 = this.object.position.clone(); 83 | this.zoom0 = this.object.zoom; 84 | 85 | // 86 | // public methods 87 | // 88 | 89 | this.getPolarAngle = function () { 90 | 91 | return spherical.phi; 92 | 93 | }; 94 | 95 | this.getAzimuthalAngle = function () { 96 | 97 | return spherical.theta; 98 | 99 | }; 100 | 101 | this.saveState = function () { 102 | 103 | scope.target0.copy( scope.target ); 104 | scope.position0.copy( scope.object.position ); 105 | scope.zoom0 = scope.object.zoom; 106 | 107 | }; 108 | 109 | this.reset = function () { 110 | 111 | scope.target.copy( scope.target0 ); 112 | scope.object.position.copy( scope.position0 ); 113 | scope.object.zoom = scope.zoom0; 114 | 115 | scope.object.updateProjectionMatrix(); 116 | scope.dispatchEvent( changeEvent ); 117 | 118 | scope.update(); 119 | 120 | state = STATE.NONE; 121 | 122 | }; 123 | 124 | // this method is exposed, but perhaps it would be better if we can make it private... 125 | this.update = function () { 126 | 127 | var offset = new THREE.Vector3(); 128 | 129 | // so camera.up is the orbit axis 130 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 131 | var quatInverse = quat.clone().inverse(); 132 | 133 | var lastPosition = new THREE.Vector3(); 134 | var lastQuaternion = new THREE.Quaternion(); 135 | 136 | return function update() { 137 | 138 | var position = scope.object.position; 139 | 140 | offset.copy( position ).sub( scope.target ); 141 | 142 | // rotate offset to "y-axis-is-up" space 143 | offset.applyQuaternion( quat ); 144 | 145 | // angle from z-axis around y-axis 146 | spherical.setFromVector3( offset ); 147 | 148 | if ( scope.autoRotate && state === STATE.NONE ) { 149 | 150 | rotateLeft( getAutoRotationAngle() ); 151 | 152 | } 153 | 154 | spherical.theta += sphericalDelta.theta; 155 | spherical.phi += sphericalDelta.phi; 156 | 157 | // restrict theta to be between desired limits 158 | spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); 159 | 160 | // restrict phi to be between desired limits 161 | spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 162 | 163 | spherical.makeSafe(); 164 | 165 | 166 | spherical.radius *= scale; 167 | 168 | // restrict radius to be between desired limits 169 | spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 170 | 171 | // move target to panned location 172 | scope.target.add( panOffset ); 173 | 174 | offset.setFromSpherical( spherical ); 175 | 176 | // rotate offset back to "camera-up-vector-is-up" space 177 | offset.applyQuaternion( quatInverse ); 178 | 179 | position.copy( scope.target ).add( offset ); 180 | 181 | scope.object.lookAt( scope.target ); 182 | 183 | if ( scope.enableDamping === true ) { 184 | 185 | sphericalDelta.theta *= ( 1 - scope.dampingFactor ); 186 | sphericalDelta.phi *= ( 1 - scope.dampingFactor ); 187 | 188 | panOffset.multiplyScalar( 1 - scope.dampingFactor ); 189 | 190 | } else { 191 | 192 | sphericalDelta.set( 0, 0, 0 ); 193 | 194 | panOffset.set( 0, 0, 0 ); 195 | 196 | } 197 | 198 | scale = 1; 199 | 200 | // update condition is: 201 | // min(camera displacement, camera rotation in radians)^2 > EPS 202 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 203 | 204 | if ( zoomChanged || 205 | lastPosition.distanceToSquared( scope.object.position ) > EPS || 206 | 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { 207 | 208 | scope.dispatchEvent( changeEvent ); 209 | 210 | lastPosition.copy( scope.object.position ); 211 | lastQuaternion.copy( scope.object.quaternion ); 212 | zoomChanged = false; 213 | 214 | return true; 215 | 216 | } 217 | 218 | return false; 219 | 220 | }; 221 | 222 | }(); 223 | 224 | this.dispose = function () { 225 | 226 | scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); 227 | scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 228 | scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); 229 | 230 | scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); 231 | scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); 232 | scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); 233 | 234 | document.removeEventListener( 'mousemove', onMouseMove, false ); 235 | document.removeEventListener( 'mouseup', onMouseUp, false ); 236 | 237 | window.removeEventListener( 'keydown', onKeyDown, false ); 238 | 239 | //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? 240 | 241 | }; 242 | 243 | // 244 | // internals 245 | // 246 | 247 | var scope = this; 248 | 249 | var changeEvent = { type: 'change' }; 250 | var startEvent = { type: 'start' }; 251 | var endEvent = { type: 'end' }; 252 | 253 | var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY_PAN: 4 }; 254 | 255 | var state = STATE.NONE; 256 | 257 | var EPS = 0.000001; 258 | 259 | // current position in spherical coordinates 260 | var spherical = new THREE.Spherical(); 261 | var sphericalDelta = new THREE.Spherical(); 262 | 263 | var scale = 1; 264 | var panOffset = new THREE.Vector3(); 265 | var zoomChanged = false; 266 | 267 | var rotateStart = new THREE.Vector2(); 268 | var rotateEnd = new THREE.Vector2(); 269 | var rotateDelta = new THREE.Vector2(); 270 | 271 | var panStart = new THREE.Vector2(); 272 | var panEnd = new THREE.Vector2(); 273 | var panDelta = new THREE.Vector2(); 274 | 275 | var dollyStart = new THREE.Vector2(); 276 | var dollyEnd = new THREE.Vector2(); 277 | var dollyDelta = new THREE.Vector2(); 278 | 279 | function getAutoRotationAngle() { 280 | 281 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 282 | 283 | } 284 | 285 | function getZoomScale() { 286 | 287 | return Math.pow( 0.95, scope.zoomSpeed ); 288 | 289 | } 290 | 291 | function rotateLeft( angle ) { 292 | 293 | sphericalDelta.theta -= angle; 294 | 295 | } 296 | 297 | function rotateUp( angle ) { 298 | 299 | sphericalDelta.phi -= angle; 300 | 301 | } 302 | 303 | var panLeft = function () { 304 | 305 | var v = new THREE.Vector3(); 306 | 307 | return function panLeft( distance, objectMatrix ) { 308 | 309 | v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix 310 | v.multiplyScalar( - distance ); 311 | 312 | panOffset.add( v ); 313 | 314 | }; 315 | 316 | }(); 317 | 318 | var panUp = function () { 319 | 320 | var v = new THREE.Vector3(); 321 | 322 | return function panUp( distance, objectMatrix ) { 323 | 324 | if ( scope.screenSpacePanning === true ) { 325 | 326 | v.setFromMatrixColumn( objectMatrix, 1 ); 327 | 328 | } else { 329 | 330 | v.setFromMatrixColumn( objectMatrix, 0 ); 331 | v.crossVectors( scope.object.up, v ); 332 | 333 | } 334 | 335 | v.multiplyScalar( distance ); 336 | 337 | panOffset.add( v ); 338 | 339 | }; 340 | 341 | }(); 342 | 343 | // deltaX and deltaY are in pixels; right and down are positive 344 | var pan = function () { 345 | 346 | var offset = new THREE.Vector3(); 347 | 348 | return function pan( deltaX, deltaY ) { 349 | 350 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 351 | 352 | if ( scope.object.isPerspectiveCamera ) { 353 | 354 | // perspective 355 | var position = scope.object.position; 356 | offset.copy( position ).sub( scope.target ); 357 | var targetDistance = offset.length(); 358 | 359 | // half of the fov is center to top of screen 360 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 361 | 362 | // we use only clientHeight here so aspect ratio does not distort speed 363 | panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); 364 | panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); 365 | 366 | } else if ( scope.object.isOrthographicCamera ) { 367 | 368 | // orthographic 369 | panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); 370 | panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); 371 | 372 | } else { 373 | 374 | // camera neither orthographic nor perspective 375 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 376 | scope.enablePan = false; 377 | 378 | } 379 | 380 | }; 381 | 382 | }(); 383 | 384 | function dollyIn( dollyScale ) { 385 | 386 | if ( scope.object.isPerspectiveCamera ) { 387 | 388 | scale /= dollyScale; 389 | 390 | } else if ( scope.object.isOrthographicCamera ) { 391 | 392 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); 393 | scope.object.updateProjectionMatrix(); 394 | zoomChanged = true; 395 | 396 | } else { 397 | 398 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 399 | scope.enableZoom = false; 400 | 401 | } 402 | 403 | } 404 | 405 | function dollyOut( dollyScale ) { 406 | 407 | if ( scope.object.isPerspectiveCamera ) { 408 | 409 | scale *= dollyScale; 410 | 411 | } else if ( scope.object.isOrthographicCamera ) { 412 | 413 | scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); 414 | scope.object.updateProjectionMatrix(); 415 | zoomChanged = true; 416 | 417 | } else { 418 | 419 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 420 | scope.enableZoom = false; 421 | 422 | } 423 | 424 | } 425 | 426 | // 427 | // event callbacks - update the object state 428 | // 429 | 430 | function handleMouseDownRotate( event ) { 431 | 432 | //console.log( 'handleMouseDownRotate' ); 433 | 434 | rotateStart.set( event.clientX, event.clientY ); 435 | 436 | } 437 | 438 | function handleMouseDownDolly( event ) { 439 | 440 | //console.log( 'handleMouseDownDolly' ); 441 | 442 | dollyStart.set( event.clientX, event.clientY ); 443 | 444 | } 445 | 446 | function handleMouseDownPan( event ) { 447 | 448 | //console.log( 'handleMouseDownPan' ); 449 | 450 | panStart.set( event.clientX, event.clientY ); 451 | 452 | } 453 | 454 | function handleMouseMoveRotate( event ) { 455 | 456 | //console.log( 'handleMouseMoveRotate' ); 457 | 458 | rotateEnd.set( event.clientX, event.clientY ); 459 | 460 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 461 | 462 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 463 | 464 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 465 | 466 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 467 | 468 | rotateStart.copy( rotateEnd ); 469 | 470 | scope.update(); 471 | 472 | } 473 | 474 | function handleMouseMoveDolly( event ) { 475 | 476 | //console.log( 'handleMouseMoveDolly' ); 477 | 478 | dollyEnd.set( event.clientX, event.clientY ); 479 | 480 | dollyDelta.subVectors( dollyEnd, dollyStart ); 481 | 482 | if ( dollyDelta.y > 0 ) { 483 | 484 | dollyIn( getZoomScale() ); 485 | 486 | } else if ( dollyDelta.y < 0 ) { 487 | 488 | dollyOut( getZoomScale() ); 489 | 490 | } 491 | 492 | dollyStart.copy( dollyEnd ); 493 | 494 | scope.update(); 495 | 496 | } 497 | 498 | function handleMouseMovePan( event ) { 499 | 500 | //console.log( 'handleMouseMovePan' ); 501 | 502 | panEnd.set( event.clientX, event.clientY ); 503 | 504 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 505 | 506 | pan( panDelta.x, panDelta.y ); 507 | 508 | panStart.copy( panEnd ); 509 | 510 | scope.update(); 511 | 512 | } 513 | 514 | function handleMouseUp( event ) { 515 | 516 | // console.log( 'handleMouseUp' ); 517 | 518 | } 519 | 520 | function handleMouseWheel( event ) { 521 | 522 | // console.log( 'handleMouseWheel' ); 523 | 524 | if ( event.deltaY < 0 ) { 525 | 526 | dollyOut( getZoomScale() ); 527 | 528 | } else if ( event.deltaY > 0 ) { 529 | 530 | dollyIn( getZoomScale() ); 531 | 532 | } 533 | 534 | scope.update(); 535 | 536 | } 537 | 538 | function handleKeyDown( event ) { 539 | 540 | //console.log( 'handleKeyDown' ); 541 | 542 | switch ( event.keyCode ) { 543 | 544 | case scope.keys.UP: 545 | pan( 0, scope.keyPanSpeed ); 546 | scope.update(); 547 | break; 548 | 549 | case scope.keys.BOTTOM: 550 | pan( 0, - scope.keyPanSpeed ); 551 | scope.update(); 552 | break; 553 | 554 | case scope.keys.LEFT: 555 | pan( scope.keyPanSpeed, 0 ); 556 | scope.update(); 557 | break; 558 | 559 | case scope.keys.RIGHT: 560 | pan( - scope.keyPanSpeed, 0 ); 561 | scope.update(); 562 | break; 563 | 564 | } 565 | 566 | } 567 | 568 | function handleTouchStartRotate( event ) { 569 | 570 | //console.log( 'handleTouchStartRotate' ); 571 | 572 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 573 | 574 | } 575 | 576 | function handleTouchStartDollyPan( event ) { 577 | 578 | //console.log( 'handleTouchStartDollyPan' ); 579 | 580 | if ( scope.enableZoom ) { 581 | 582 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 583 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 584 | 585 | var distance = Math.sqrt( dx * dx + dy * dy ); 586 | 587 | dollyStart.set( 0, distance ); 588 | 589 | } 590 | 591 | if ( scope.enablePan ) { 592 | 593 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 594 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 595 | 596 | panStart.set( x, y ); 597 | 598 | } 599 | 600 | } 601 | 602 | function handleTouchMoveRotate( event ) { 603 | 604 | //console.log( 'handleTouchMoveRotate' ); 605 | 606 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 607 | 608 | rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); 609 | 610 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 611 | 612 | rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height 613 | 614 | rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); 615 | 616 | rotateStart.copy( rotateEnd ); 617 | 618 | scope.update(); 619 | 620 | } 621 | 622 | function handleTouchMoveDollyPan( event ) { 623 | 624 | //console.log( 'handleTouchMoveDollyPan' ); 625 | 626 | if ( scope.enableZoom ) { 627 | 628 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 629 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 630 | 631 | var distance = Math.sqrt( dx * dx + dy * dy ); 632 | 633 | dollyEnd.set( 0, distance ); 634 | 635 | dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); 636 | 637 | dollyIn( dollyDelta.y ); 638 | 639 | dollyStart.copy( dollyEnd ); 640 | 641 | } 642 | 643 | if ( scope.enablePan ) { 644 | 645 | var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); 646 | var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); 647 | 648 | panEnd.set( x, y ); 649 | 650 | panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); 651 | 652 | pan( panDelta.x, panDelta.y ); 653 | 654 | panStart.copy( panEnd ); 655 | 656 | } 657 | 658 | scope.update(); 659 | 660 | } 661 | 662 | function handleTouchEnd( event ) { 663 | 664 | //console.log( 'handleTouchEnd' ); 665 | 666 | } 667 | 668 | // 669 | // event handlers - FSM: listen for events and reset state 670 | // 671 | 672 | function onMouseDown( event ) { 673 | 674 | if ( scope.enabled === false ) return; 675 | 676 | event.preventDefault(); 677 | 678 | switch ( event.button ) { 679 | 680 | case scope.mouseButtons.LEFT: 681 | 682 | if ( event.ctrlKey || event.metaKey ) { 683 | 684 | if ( scope.enablePan === false ) return; 685 | 686 | handleMouseDownPan( event ); 687 | 688 | state = STATE.PAN; 689 | 690 | } else { 691 | 692 | if ( scope.enableRotate === false ) return; 693 | 694 | handleMouseDownRotate( event ); 695 | 696 | state = STATE.ROTATE; 697 | 698 | } 699 | 700 | break; 701 | 702 | case scope.mouseButtons.MIDDLE: 703 | 704 | if ( scope.enableZoom === false ) return; 705 | 706 | handleMouseDownDolly( event ); 707 | 708 | state = STATE.DOLLY; 709 | 710 | break; 711 | 712 | case scope.mouseButtons.RIGHT: 713 | 714 | if ( scope.enablePan === false ) return; 715 | 716 | handleMouseDownPan( event ); 717 | 718 | state = STATE.PAN; 719 | 720 | break; 721 | 722 | } 723 | 724 | if ( state !== STATE.NONE ) { 725 | 726 | document.addEventListener( 'mousemove', onMouseMove, false ); 727 | document.addEventListener( 'mouseup', onMouseUp, false ); 728 | 729 | scope.dispatchEvent( startEvent ); 730 | 731 | } 732 | 733 | } 734 | 735 | function onMouseMove( event ) { 736 | 737 | if ( scope.enabled === false ) return; 738 | 739 | event.preventDefault(); 740 | 741 | switch ( state ) { 742 | 743 | case STATE.ROTATE: 744 | 745 | if ( scope.enableRotate === false ) return; 746 | 747 | handleMouseMoveRotate( event ); 748 | 749 | break; 750 | 751 | case STATE.DOLLY: 752 | 753 | if ( scope.enableZoom === false ) return; 754 | 755 | handleMouseMoveDolly( event ); 756 | 757 | break; 758 | 759 | case STATE.PAN: 760 | 761 | if ( scope.enablePan === false ) return; 762 | 763 | handleMouseMovePan( event ); 764 | 765 | break; 766 | 767 | } 768 | 769 | } 770 | 771 | function onMouseUp( event ) { 772 | 773 | if ( scope.enabled === false ) return; 774 | 775 | handleMouseUp( event ); 776 | 777 | document.removeEventListener( 'mousemove', onMouseMove, false ); 778 | document.removeEventListener( 'mouseup', onMouseUp, false ); 779 | 780 | scope.dispatchEvent( endEvent ); 781 | 782 | state = STATE.NONE; 783 | 784 | } 785 | 786 | function onMouseWheel( event ) { 787 | 788 | if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; 789 | 790 | event.preventDefault(); 791 | event.stopPropagation(); 792 | 793 | scope.dispatchEvent( startEvent ); 794 | 795 | handleMouseWheel( event ); 796 | 797 | scope.dispatchEvent( endEvent ); 798 | 799 | } 800 | 801 | function onKeyDown( event ) { 802 | 803 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 804 | 805 | handleKeyDown( event ); 806 | 807 | } 808 | 809 | function onTouchStart( event ) { 810 | 811 | if ( scope.enabled === false ) return; 812 | 813 | event.preventDefault(); 814 | 815 | switch ( event.touches.length ) { 816 | 817 | case 1: // one-fingered touch: rotate 818 | 819 | if ( scope.enableRotate === false ) return; 820 | 821 | handleTouchStartRotate( event ); 822 | 823 | state = STATE.TOUCH_ROTATE; 824 | 825 | break; 826 | 827 | case 2: // two-fingered touch: dolly-pan 828 | 829 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 830 | 831 | handleTouchStartDollyPan( event ); 832 | 833 | state = STATE.TOUCH_DOLLY_PAN; 834 | 835 | break; 836 | 837 | default: 838 | 839 | state = STATE.NONE; 840 | 841 | } 842 | 843 | if ( state !== STATE.NONE ) { 844 | 845 | scope.dispatchEvent( startEvent ); 846 | 847 | } 848 | 849 | } 850 | 851 | function onTouchMove( event ) { 852 | 853 | if ( scope.enabled === false ) return; 854 | 855 | event.preventDefault(); 856 | event.stopPropagation(); 857 | 858 | switch ( event.touches.length ) { 859 | 860 | case 1: // one-fingered touch: rotate 861 | 862 | if ( scope.enableRotate === false ) return; 863 | if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed? 864 | 865 | handleTouchMoveRotate( event ); 866 | 867 | break; 868 | 869 | case 2: // two-fingered touch: dolly-pan 870 | 871 | if ( scope.enableZoom === false && scope.enablePan === false ) return; 872 | if ( state !== STATE.TOUCH_DOLLY_PAN ) return; // is this needed? 873 | 874 | handleTouchMoveDollyPan( event ); 875 | 876 | break; 877 | 878 | default: 879 | 880 | state = STATE.NONE; 881 | 882 | } 883 | 884 | } 885 | 886 | function onTouchEnd( event ) { 887 | 888 | if ( scope.enabled === false ) return; 889 | 890 | handleTouchEnd( event ); 891 | 892 | scope.dispatchEvent( endEvent ); 893 | 894 | state = STATE.NONE; 895 | 896 | } 897 | 898 | function onContextMenu( event ) { 899 | 900 | if ( scope.enabled === false ) return; 901 | 902 | event.preventDefault(); 903 | 904 | } 905 | 906 | // 907 | 908 | scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); 909 | 910 | scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); 911 | scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); 912 | 913 | scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); 914 | scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); 915 | scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); 916 | 917 | window.addEventListener( 'keydown', onKeyDown, false ); 918 | 919 | // force an update at start 920 | 921 | this.update(); 922 | 923 | }; 924 | 925 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 926 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 927 | 928 | Object.defineProperties( THREE.OrbitControls.prototype, { 929 | 930 | center: { 931 | 932 | get: function () { 933 | 934 | console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); 935 | return this.target; 936 | 937 | } 938 | 939 | }, 940 | 941 | // backward compatibility 942 | 943 | noZoom: { 944 | 945 | get: function () { 946 | 947 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 948 | return ! this.enableZoom; 949 | 950 | }, 951 | 952 | set: function ( value ) { 953 | 954 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 955 | this.enableZoom = ! value; 956 | 957 | } 958 | 959 | }, 960 | 961 | noRotate: { 962 | 963 | get: function () { 964 | 965 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 966 | return ! this.enableRotate; 967 | 968 | }, 969 | 970 | set: function ( value ) { 971 | 972 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 973 | this.enableRotate = ! value; 974 | 975 | } 976 | 977 | }, 978 | 979 | noPan: { 980 | 981 | get: function () { 982 | 983 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 984 | return ! this.enablePan; 985 | 986 | }, 987 | 988 | set: function ( value ) { 989 | 990 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 991 | this.enablePan = ! value; 992 | 993 | } 994 | 995 | }, 996 | 997 | noKeys: { 998 | 999 | get: function () { 1000 | 1001 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1002 | return ! this.enableKeys; 1003 | 1004 | }, 1005 | 1006 | set: function ( value ) { 1007 | 1008 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1009 | this.enableKeys = ! value; 1010 | 1011 | } 1012 | 1013 | }, 1014 | 1015 | staticMoving: { 1016 | 1017 | get: function () { 1018 | 1019 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1020 | return ! this.enableDamping; 1021 | 1022 | }, 1023 | 1024 | set: function ( value ) { 1025 | 1026 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1027 | this.enableDamping = ! value; 1028 | 1029 | } 1030 | 1031 | }, 1032 | 1033 | dynamicDampingFactor: { 1034 | 1035 | get: function () { 1036 | 1037 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1038 | return this.dampingFactor; 1039 | 1040 | }, 1041 | 1042 | set: function ( value ) { 1043 | 1044 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1045 | this.dampingFactor = value; 1046 | 1047 | } 1048 | 1049 | } 1050 | 1051 | } ); 1052 | -------------------------------------------------------------------------------- /js/controls/OrthographicTrackballControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Eberhard Graether / http://egraether.com/ 3 | * @author Mark Lundin / http://mark-lundin.com 4 | * @author Patrick Fuller / http://patrick-fuller.com 5 | * @author Max Smolens / https://github.com/msmolens 6 | */ 7 | 8 | THREE.OrthographicTrackballControls = function ( object, domElement ) { 9 | 10 | var _this = this; 11 | var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 }; 12 | 13 | this.object = object; 14 | this.domElement = ( domElement !== undefined ) ? domElement : document; 15 | 16 | // API 17 | 18 | this.enabled = true; 19 | 20 | this.screen = { left: 0, top: 0, width: 0, height: 0 }; 21 | 22 | this.radius = 0; 23 | 24 | this.rotateSpeed = 1.0; 25 | this.zoomSpeed = 1.2; 26 | 27 | this.noRotate = false; 28 | this.noZoom = false; 29 | this.noPan = false; 30 | this.noRoll = false; 31 | 32 | this.staticMoving = false; 33 | this.dynamicDampingFactor = 0.2; 34 | 35 | this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; 36 | 37 | // internals 38 | 39 | this.target = new THREE.Vector3(); 40 | 41 | var EPS = 0.000001; 42 | 43 | var _changed = true; 44 | 45 | var _state = STATE.NONE, 46 | _prevState = STATE.NONE, 47 | 48 | _eye = new THREE.Vector3(), 49 | 50 | _rotateStart = new THREE.Vector3(), 51 | _rotateEnd = new THREE.Vector3(), 52 | 53 | _zoomStart = new THREE.Vector2(), 54 | _zoomEnd = new THREE.Vector2(), 55 | 56 | _touchZoomDistanceStart = 0, 57 | _touchZoomDistanceEnd = 0, 58 | 59 | _panStart = new THREE.Vector2(), 60 | _panEnd = new THREE.Vector2(); 61 | 62 | // for reset 63 | 64 | this.target0 = this.target.clone(); 65 | this.position0 = this.object.position.clone(); 66 | this.up0 = this.object.up.clone(); 67 | 68 | this.left0 = this.object.left; 69 | this.right0 = this.object.right; 70 | this.top0 = this.object.top; 71 | this.bottom0 = this.object.bottom; 72 | 73 | // events 74 | 75 | var changeEvent = { type: 'change' }; 76 | var startEvent = { type: 'start' }; 77 | var endEvent = { type: 'end' }; 78 | 79 | 80 | // methods 81 | 82 | this.handleResize = function () { 83 | 84 | if ( this.domElement === document ) { 85 | 86 | this.screen.left = 0; 87 | this.screen.top = 0; 88 | this.screen.width = window.innerWidth; 89 | this.screen.height = window.innerHeight; 90 | 91 | } else { 92 | 93 | var box = this.domElement.getBoundingClientRect(); 94 | // adjustments come from similar code in the jquery offset() function 95 | var d = this.domElement.ownerDocument.documentElement; 96 | this.screen.left = box.left + window.pageXOffset - d.clientLeft; 97 | this.screen.top = box.top + window.pageYOffset - d.clientTop; 98 | this.screen.width = box.width; 99 | this.screen.height = box.height; 100 | 101 | } 102 | 103 | this.radius = 0.5 * Math.min( this.screen.width, this.screen.height ); 104 | 105 | this.left0 = this.object.left; 106 | this.right0 = this.object.right; 107 | this.top0 = this.object.top; 108 | this.bottom0 = this.object.bottom; 109 | 110 | }; 111 | 112 | var getMouseOnScreen = ( function () { 113 | 114 | var vector = new THREE.Vector2(); 115 | 116 | return function getMouseOnScreen( pageX, pageY ) { 117 | 118 | vector.set( 119 | ( pageX - _this.screen.left ) / _this.screen.width, 120 | ( pageY - _this.screen.top ) / _this.screen.height 121 | ); 122 | 123 | return vector; 124 | 125 | }; 126 | 127 | }() ); 128 | 129 | var getMouseProjectionOnBall = ( function () { 130 | 131 | var vector = new THREE.Vector3(); 132 | var objectUp = new THREE.Vector3(); 133 | var mouseOnBall = new THREE.Vector3(); 134 | 135 | return function getMouseProjectionOnBall( pageX, pageY ) { 136 | 137 | mouseOnBall.set( 138 | ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / _this.radius, 139 | ( _this.screen.height * 0.5 + _this.screen.top - pageY ) / _this.radius, 140 | 0.0 141 | ); 142 | 143 | var length = mouseOnBall.length(); 144 | 145 | if ( _this.noRoll ) { 146 | 147 | if ( length < Math.SQRT1_2 ) { 148 | 149 | mouseOnBall.z = Math.sqrt( 1.0 - length * length ); 150 | 151 | } else { 152 | 153 | mouseOnBall.z = .5 / length; 154 | 155 | } 156 | 157 | } else if ( length > 1.0 ) { 158 | 159 | mouseOnBall.normalize(); 160 | 161 | } else { 162 | 163 | mouseOnBall.z = Math.sqrt( 1.0 - length * length ); 164 | 165 | } 166 | 167 | _eye.copy( _this.object.position ).sub( _this.target ); 168 | 169 | vector.copy( _this.object.up ).setLength( mouseOnBall.y ); 170 | vector.add( objectUp.copy( _this.object.up ).cross( _eye ).setLength( mouseOnBall.x ) ); 171 | vector.add( _eye.setLength( mouseOnBall.z ) ); 172 | 173 | return vector; 174 | 175 | }; 176 | 177 | }() ); 178 | 179 | this.rotateCamera = ( function () { 180 | 181 | var axis = new THREE.Vector3(), 182 | quaternion = new THREE.Quaternion(); 183 | 184 | 185 | return function rotateCamera() { 186 | 187 | var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() ); 188 | 189 | if ( angle ) { 190 | 191 | axis.crossVectors( _rotateStart, _rotateEnd ).normalize(); 192 | 193 | angle *= _this.rotateSpeed; 194 | 195 | quaternion.setFromAxisAngle( axis, - angle ); 196 | 197 | _eye.applyQuaternion( quaternion ); 198 | _this.object.up.applyQuaternion( quaternion ); 199 | 200 | _rotateEnd.applyQuaternion( quaternion ); 201 | 202 | if ( _this.staticMoving ) { 203 | 204 | _rotateStart.copy( _rotateEnd ); 205 | 206 | } else { 207 | 208 | quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) ); 209 | _rotateStart.applyQuaternion( quaternion ); 210 | 211 | } 212 | 213 | _changed = true; 214 | 215 | } 216 | 217 | }; 218 | 219 | }() ); 220 | 221 | this.zoomCamera = function () { 222 | 223 | if ( _state === STATE.TOUCH_ZOOM_PAN ) { 224 | 225 | var factor = _touchZoomDistanceEnd / _touchZoomDistanceStart; 226 | _touchZoomDistanceStart = _touchZoomDistanceEnd; 227 | 228 | _this.object.zoom *= factor; 229 | 230 | _changed = true; 231 | 232 | } else { 233 | 234 | var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed; 235 | 236 | if ( Math.abs( factor - 1.0 ) > EPS && factor > 0.0 ) { 237 | 238 | _this.object.zoom /= factor; 239 | 240 | if ( _this.staticMoving ) { 241 | 242 | _zoomStart.copy( _zoomEnd ); 243 | 244 | } else { 245 | 246 | _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; 247 | 248 | } 249 | 250 | _changed = true; 251 | 252 | } 253 | 254 | } 255 | 256 | }; 257 | 258 | this.panCamera = ( function () { 259 | 260 | var mouseChange = new THREE.Vector2(), 261 | objectUp = new THREE.Vector3(), 262 | pan = new THREE.Vector3(); 263 | 264 | return function panCamera() { 265 | 266 | mouseChange.copy( _panEnd ).sub( _panStart ); 267 | 268 | if ( mouseChange.lengthSq() ) { 269 | 270 | // Scale movement to keep clicked/dragged position under cursor 271 | var scale_x = ( _this.object.right - _this.object.left ) / _this.object.zoom; 272 | var scale_y = ( _this.object.top - _this.object.bottom ) / _this.object.zoom; 273 | mouseChange.x *= scale_x; 274 | mouseChange.y *= scale_y; 275 | 276 | pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x ); 277 | pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) ); 278 | 279 | _this.object.position.add( pan ); 280 | _this.target.add( pan ); 281 | 282 | if ( _this.staticMoving ) { 283 | 284 | _panStart.copy( _panEnd ); 285 | 286 | } else { 287 | 288 | _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) ); 289 | 290 | } 291 | 292 | _changed = true; 293 | 294 | } 295 | 296 | }; 297 | 298 | }() ); 299 | 300 | this.update = function () { 301 | 302 | _eye.subVectors( _this.object.position, _this.target ); 303 | 304 | if ( ! _this.noRotate ) { 305 | 306 | _this.rotateCamera(); 307 | 308 | } 309 | 310 | if ( ! _this.noZoom ) { 311 | 312 | _this.zoomCamera(); 313 | 314 | if ( _changed ) { 315 | 316 | _this.object.updateProjectionMatrix(); 317 | 318 | } 319 | 320 | } 321 | 322 | if ( ! _this.noPan ) { 323 | 324 | _this.panCamera(); 325 | 326 | } 327 | 328 | _this.object.position.addVectors( _this.target, _eye ); 329 | 330 | _this.object.lookAt( _this.target ); 331 | 332 | if ( _changed ) { 333 | 334 | _this.dispatchEvent( changeEvent ); 335 | 336 | _changed = false; 337 | 338 | } 339 | 340 | }; 341 | 342 | this.reset = function () { 343 | 344 | _state = STATE.NONE; 345 | _prevState = STATE.NONE; 346 | 347 | _this.target.copy( _this.target0 ); 348 | _this.object.position.copy( _this.position0 ); 349 | _this.object.up.copy( _this.up0 ); 350 | 351 | _eye.subVectors( _this.object.position, _this.target ); 352 | 353 | _this.object.left = _this.left0; 354 | _this.object.right = _this.right0; 355 | _this.object.top = _this.top0; 356 | _this.object.bottom = _this.bottom0; 357 | 358 | _this.object.lookAt( _this.target ); 359 | 360 | _this.dispatchEvent( changeEvent ); 361 | 362 | _changed = false; 363 | 364 | }; 365 | 366 | // listeners 367 | 368 | function keydown( event ) { 369 | 370 | if ( _this.enabled === false ) return; 371 | 372 | window.removeEventListener( 'keydown', keydown ); 373 | 374 | _prevState = _state; 375 | 376 | if ( _state !== STATE.NONE ) { 377 | 378 | return; 379 | 380 | } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate ) { 381 | 382 | _state = STATE.ROTATE; 383 | 384 | } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom ) { 385 | 386 | _state = STATE.ZOOM; 387 | 388 | } else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan ) { 389 | 390 | _state = STATE.PAN; 391 | 392 | } 393 | 394 | } 395 | 396 | function keyup( event ) { 397 | 398 | if ( _this.enabled === false ) return; 399 | 400 | _state = _prevState; 401 | 402 | window.addEventListener( 'keydown', keydown, false ); 403 | 404 | } 405 | 406 | function mousedown( event ) { 407 | 408 | if ( _this.enabled === false ) return; 409 | 410 | event.preventDefault(); 411 | event.stopPropagation(); 412 | 413 | if ( _state === STATE.NONE ) { 414 | 415 | _state = event.button; 416 | 417 | } 418 | 419 | if ( _state === STATE.ROTATE && ! _this.noRotate ) { 420 | 421 | _rotateStart.copy( getMouseProjectionOnBall( event.pageX, event.pageY ) ); 422 | _rotateEnd.copy( _rotateStart ); 423 | 424 | } else if ( _state === STATE.ZOOM && ! _this.noZoom ) { 425 | 426 | _zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 427 | _zoomEnd.copy( _zoomStart ); 428 | 429 | } else if ( _state === STATE.PAN && ! _this.noPan ) { 430 | 431 | _panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 432 | _panEnd.copy( _panStart ); 433 | 434 | } 435 | 436 | document.addEventListener( 'mousemove', mousemove, false ); 437 | document.addEventListener( 'mouseup', mouseup, false ); 438 | 439 | _this.dispatchEvent( startEvent ); 440 | 441 | } 442 | 443 | function mousemove( event ) { 444 | 445 | if ( _this.enabled === false ) return; 446 | 447 | event.preventDefault(); 448 | event.stopPropagation(); 449 | 450 | if ( _state === STATE.ROTATE && ! _this.noRotate ) { 451 | 452 | _rotateEnd.copy( getMouseProjectionOnBall( event.pageX, event.pageY ) ); 453 | 454 | } else if ( _state === STATE.ZOOM && ! _this.noZoom ) { 455 | 456 | _zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 457 | 458 | } else if ( _state === STATE.PAN && ! _this.noPan ) { 459 | 460 | _panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 461 | 462 | } 463 | 464 | } 465 | 466 | function mouseup( event ) { 467 | 468 | if ( _this.enabled === false ) return; 469 | 470 | event.preventDefault(); 471 | event.stopPropagation(); 472 | 473 | _state = STATE.NONE; 474 | 475 | document.removeEventListener( 'mousemove', mousemove ); 476 | document.removeEventListener( 'mouseup', mouseup ); 477 | _this.dispatchEvent( endEvent ); 478 | 479 | } 480 | 481 | function mousewheel( event ) { 482 | 483 | if ( _this.enabled === false ) return; 484 | 485 | event.preventDefault(); 486 | event.stopPropagation(); 487 | 488 | _zoomStart.y += event.deltaY * 0.01; 489 | _this.dispatchEvent( startEvent ); 490 | _this.dispatchEvent( endEvent ); 491 | 492 | } 493 | 494 | function touchstart( event ) { 495 | 496 | if ( _this.enabled === false ) return; 497 | 498 | switch ( event.touches.length ) { 499 | 500 | case 1: 501 | _state = STATE.TOUCH_ROTATE; 502 | _rotateStart.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 503 | _rotateEnd.copy( _rotateStart ); 504 | break; 505 | 506 | case 2: 507 | _state = STATE.TOUCH_ZOOM_PAN; 508 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 509 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 510 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); 511 | 512 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; 513 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; 514 | _panStart.copy( getMouseOnScreen( x, y ) ); 515 | _panEnd.copy( _panStart ); 516 | break; 517 | 518 | default: 519 | _state = STATE.NONE; 520 | 521 | } 522 | _this.dispatchEvent( startEvent ); 523 | 524 | } 525 | 526 | function touchmove( event ) { 527 | 528 | if ( _this.enabled === false ) return; 529 | 530 | event.preventDefault(); 531 | event.stopPropagation(); 532 | 533 | switch ( event.touches.length ) { 534 | 535 | case 1: 536 | _rotateEnd.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 537 | break; 538 | 539 | case 2: 540 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 541 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 542 | _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ); 543 | 544 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; 545 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; 546 | _panEnd.copy( getMouseOnScreen( x, y ) ); 547 | break; 548 | 549 | default: 550 | _state = STATE.NONE; 551 | 552 | } 553 | 554 | } 555 | 556 | function touchend( event ) { 557 | 558 | if ( _this.enabled === false ) return; 559 | 560 | switch ( event.touches.length ) { 561 | 562 | case 1: 563 | _rotateEnd.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 564 | _rotateStart.copy( _rotateEnd ); 565 | break; 566 | 567 | case 2: 568 | _touchZoomDistanceStart = _touchZoomDistanceEnd = 0; 569 | 570 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; 571 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; 572 | _panEnd.copy( getMouseOnScreen( x, y ) ); 573 | _panStart.copy( _panEnd ); 574 | break; 575 | 576 | } 577 | 578 | _state = STATE.NONE; 579 | _this.dispatchEvent( endEvent ); 580 | 581 | } 582 | 583 | function contextmenu( event ) { 584 | 585 | event.preventDefault(); 586 | 587 | } 588 | 589 | this.dispose = function () { 590 | 591 | this.domElement.removeEventListener( 'contextmenu', contextmenu, false ); 592 | this.domElement.removeEventListener( 'mousedown', mousedown, false ); 593 | this.domElement.removeEventListener( 'wheel', mousewheel, false ); 594 | 595 | this.domElement.removeEventListener( 'touchstart', touchstart, false ); 596 | this.domElement.removeEventListener( 'touchend', touchend, false ); 597 | this.domElement.removeEventListener( 'touchmove', touchmove, false ); 598 | 599 | document.removeEventListener( 'mousemove', mousemove, false ); 600 | document.removeEventListener( 'mouseup', mouseup, false ); 601 | 602 | window.removeEventListener( 'keydown', keydown, false ); 603 | window.removeEventListener( 'keyup', keyup, false ); 604 | 605 | }; 606 | 607 | this.domElement.addEventListener( 'contextmenu', contextmenu, false ); 608 | this.domElement.addEventListener( 'mousedown', mousedown, false ); 609 | this.domElement.addEventListener( 'wheel', mousewheel, false ); 610 | 611 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 612 | this.domElement.addEventListener( 'touchend', touchend, false ); 613 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 614 | 615 | window.addEventListener( 'keydown', keydown, false ); 616 | window.addEventListener( 'keyup', keyup, false ); 617 | 618 | this.handleResize(); 619 | 620 | // force an update at start 621 | this.update(); 622 | 623 | }; 624 | 625 | THREE.OrthographicTrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 626 | THREE.OrthographicTrackballControls.prototype.constructor = THREE.OrthographicTrackballControls; 627 | -------------------------------------------------------------------------------- /js/controls/PointerLockControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | * @author Mugen87 / https://github.com/Mugen87 4 | */ 5 | 6 | THREE.PointerLockControls = function ( camera, domElement ) { 7 | 8 | var scope = this; 9 | 10 | this.domElement = domElement || document.body; 11 | this.isLocked = false; 12 | 13 | camera.rotation.set( 0, 0, 0 ); 14 | 15 | var pitchObject = new THREE.Object3D(); 16 | pitchObject.add( camera ); 17 | 18 | var yawObject = new THREE.Object3D(); 19 | yawObject.position.y = 10; 20 | yawObject.add( pitchObject ); 21 | 22 | var PI_2 = Math.PI / 2; 23 | 24 | function onMouseMove( event ) { 25 | 26 | if ( scope.isLocked === false ) return; 27 | 28 | var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0; 29 | var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0; 30 | 31 | yawObject.rotation.y -= movementX * 0.002; 32 | pitchObject.rotation.x -= movementY * 0.002; 33 | 34 | pitchObject.rotation.x = Math.max( - PI_2, Math.min( PI_2, pitchObject.rotation.x ) ); 35 | 36 | } 37 | 38 | function onPointerlockChange() { 39 | 40 | if ( document.pointerLockElement === scope.domElement ) { 41 | 42 | scope.dispatchEvent( { type: 'lock' } ); 43 | 44 | scope.isLocked = true; 45 | 46 | } else { 47 | 48 | scope.dispatchEvent( { type: 'unlock' } ); 49 | 50 | scope.isLocked = false; 51 | 52 | } 53 | 54 | } 55 | 56 | function onPointerlockError() { 57 | 58 | console.error( 'THREE.PointerLockControls: Unable to use Pointer Lock API' ); 59 | 60 | } 61 | 62 | this.connect = function () { 63 | 64 | document.addEventListener( 'mousemove', onMouseMove, false ); 65 | document.addEventListener( 'pointerlockchange', onPointerlockChange, false ); 66 | document.addEventListener( 'pointerlockerror', onPointerlockError, false ); 67 | 68 | }; 69 | 70 | this.disconnect = function () { 71 | 72 | document.removeEventListener( 'mousemove', onMouseMove, false ); 73 | document.removeEventListener( 'pointerlockchange', onPointerlockChange, false ); 74 | document.removeEventListener( 'pointerlockerror', onPointerlockError, false ); 75 | 76 | }; 77 | 78 | this.dispose = function () { 79 | 80 | this.disconnect(); 81 | 82 | }; 83 | 84 | this.getObject = function () { 85 | 86 | return yawObject; 87 | 88 | }; 89 | 90 | this.getDirection = function () { 91 | 92 | // assumes the camera itself is not rotated 93 | 94 | var direction = new THREE.Vector3( 0, 0, - 1 ); 95 | var rotation = new THREE.Euler( 0, 0, 0, 'YXZ' ); 96 | 97 | return function ( v ) { 98 | 99 | rotation.set( pitchObject.rotation.x, yawObject.rotation.y, 0 ); 100 | 101 | v.copy( direction ).applyEuler( rotation ); 102 | 103 | return v; 104 | 105 | }; 106 | 107 | }(); 108 | 109 | this.lock = function () { 110 | 111 | this.domElement.requestPointerLock(); 112 | 113 | }; 114 | 115 | this.unlock = function () { 116 | 117 | document.exitPointerLock(); 118 | 119 | }; 120 | 121 | this.connect(); 122 | 123 | }; 124 | 125 | THREE.PointerLockControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 126 | THREE.PointerLockControls.prototype.constructor = THREE.PointerLockControls; 127 | -------------------------------------------------------------------------------- /js/controls/TrackballControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Eberhard Graether / http://egraether.com/ 3 | * @author Mark Lundin / http://mark-lundin.com 4 | * @author Simone Manini / http://daron1337.github.io 5 | * @author Luca Antiga / http://lantiga.github.io 6 | */ 7 | 8 | THREE.TrackballControls = function ( object, domElement ) { 9 | 10 | var _this = this; 11 | var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 }; 12 | 13 | this.object = object; 14 | this.domElement = ( domElement !== undefined ) ? domElement : document; 15 | 16 | // API 17 | 18 | this.enabled = true; 19 | 20 | this.screen = { left: 0, top: 0, width: 0, height: 0 }; 21 | 22 | this.rotateSpeed = 1.0; 23 | this.zoomSpeed = 1.2; 24 | this.panSpeed = 0.3; 25 | 26 | this.noRotate = false; 27 | this.noZoom = false; 28 | this.noPan = false; 29 | 30 | this.staticMoving = false; 31 | this.dynamicDampingFactor = 0.2; 32 | 33 | this.minDistance = 0; 34 | this.maxDistance = Infinity; 35 | 36 | this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; 37 | 38 | // internals 39 | 40 | this.target = new THREE.Vector3(); 41 | 42 | var EPS = 0.000001; 43 | 44 | var lastPosition = new THREE.Vector3(); 45 | 46 | var _state = STATE.NONE, 47 | _prevState = STATE.NONE, 48 | 49 | _eye = new THREE.Vector3(), 50 | 51 | _movePrev = new THREE.Vector2(), 52 | _moveCurr = new THREE.Vector2(), 53 | 54 | _lastAxis = new THREE.Vector3(), 55 | _lastAngle = 0, 56 | 57 | _zoomStart = new THREE.Vector2(), 58 | _zoomEnd = new THREE.Vector2(), 59 | 60 | _touchZoomDistanceStart = 0, 61 | _touchZoomDistanceEnd = 0, 62 | 63 | _panStart = new THREE.Vector2(), 64 | _panEnd = new THREE.Vector2(); 65 | 66 | // for reset 67 | 68 | this.target0 = this.target.clone(); 69 | this.position0 = this.object.position.clone(); 70 | this.up0 = this.object.up.clone(); 71 | 72 | // events 73 | 74 | var changeEvent = { type: 'change' }; 75 | var startEvent = { type: 'start' }; 76 | var endEvent = { type: 'end' }; 77 | 78 | 79 | // methods 80 | 81 | this.handleResize = function () { 82 | 83 | if ( this.domElement === document ) { 84 | 85 | this.screen.left = 0; 86 | this.screen.top = 0; 87 | this.screen.width = window.innerWidth; 88 | this.screen.height = window.innerHeight; 89 | 90 | } else { 91 | 92 | var box = this.domElement.getBoundingClientRect(); 93 | // adjustments come from similar code in the jquery offset() function 94 | var d = this.domElement.ownerDocument.documentElement; 95 | this.screen.left = box.left + window.pageXOffset - d.clientLeft; 96 | this.screen.top = box.top + window.pageYOffset - d.clientTop; 97 | this.screen.width = box.width; 98 | this.screen.height = box.height; 99 | 100 | } 101 | 102 | }; 103 | 104 | var getMouseOnScreen = ( function () { 105 | 106 | var vector = new THREE.Vector2(); 107 | 108 | return function getMouseOnScreen( pageX, pageY ) { 109 | 110 | vector.set( 111 | ( pageX - _this.screen.left ) / _this.screen.width, 112 | ( pageY - _this.screen.top ) / _this.screen.height 113 | ); 114 | 115 | return vector; 116 | 117 | }; 118 | 119 | }() ); 120 | 121 | var getMouseOnCircle = ( function () { 122 | 123 | var vector = new THREE.Vector2(); 124 | 125 | return function getMouseOnCircle( pageX, pageY ) { 126 | 127 | vector.set( 128 | ( ( pageX - _this.screen.width * 0.5 - _this.screen.left ) / ( _this.screen.width * 0.5 ) ), 129 | ( ( _this.screen.height + 2 * ( _this.screen.top - pageY ) ) / _this.screen.width ) // screen.width intentional 130 | ); 131 | 132 | return vector; 133 | 134 | }; 135 | 136 | }() ); 137 | 138 | this.rotateCamera = ( function () { 139 | 140 | var axis = new THREE.Vector3(), 141 | quaternion = new THREE.Quaternion(), 142 | eyeDirection = new THREE.Vector3(), 143 | objectUpDirection = new THREE.Vector3(), 144 | objectSidewaysDirection = new THREE.Vector3(), 145 | moveDirection = new THREE.Vector3(), 146 | angle; 147 | 148 | return function rotateCamera() { 149 | 150 | moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 ); 151 | angle = moveDirection.length(); 152 | 153 | if ( angle ) { 154 | 155 | _eye.copy( _this.object.position ).sub( _this.target ); 156 | 157 | eyeDirection.copy( _eye ).normalize(); 158 | objectUpDirection.copy( _this.object.up ).normalize(); 159 | objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize(); 160 | 161 | objectUpDirection.setLength( _moveCurr.y - _movePrev.y ); 162 | objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x ); 163 | 164 | moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) ); 165 | 166 | axis.crossVectors( moveDirection, _eye ).normalize(); 167 | 168 | angle *= _this.rotateSpeed; 169 | quaternion.setFromAxisAngle( axis, angle ); 170 | 171 | _eye.applyQuaternion( quaternion ); 172 | _this.object.up.applyQuaternion( quaternion ); 173 | 174 | _lastAxis.copy( axis ); 175 | _lastAngle = angle; 176 | 177 | } else if ( ! _this.staticMoving && _lastAngle ) { 178 | 179 | _lastAngle *= Math.sqrt( 1.0 - _this.dynamicDampingFactor ); 180 | _eye.copy( _this.object.position ).sub( _this.target ); 181 | quaternion.setFromAxisAngle( _lastAxis, _lastAngle ); 182 | _eye.applyQuaternion( quaternion ); 183 | _this.object.up.applyQuaternion( quaternion ); 184 | 185 | } 186 | 187 | _movePrev.copy( _moveCurr ); 188 | 189 | }; 190 | 191 | }() ); 192 | 193 | 194 | this.zoomCamera = function () { 195 | 196 | var factor; 197 | 198 | if ( _state === STATE.TOUCH_ZOOM_PAN ) { 199 | 200 | factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; 201 | _touchZoomDistanceStart = _touchZoomDistanceEnd; 202 | _eye.multiplyScalar( factor ); 203 | 204 | } else { 205 | 206 | factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed; 207 | 208 | if ( factor !== 1.0 && factor > 0.0 ) { 209 | 210 | _eye.multiplyScalar( factor ); 211 | 212 | } 213 | 214 | if ( _this.staticMoving ) { 215 | 216 | _zoomStart.copy( _zoomEnd ); 217 | 218 | } else { 219 | 220 | _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; 221 | 222 | } 223 | 224 | } 225 | 226 | }; 227 | 228 | this.panCamera = ( function () { 229 | 230 | var mouseChange = new THREE.Vector2(), 231 | objectUp = new THREE.Vector3(), 232 | pan = new THREE.Vector3(); 233 | 234 | return function panCamera() { 235 | 236 | mouseChange.copy( _panEnd ).sub( _panStart ); 237 | 238 | if ( mouseChange.lengthSq() ) { 239 | 240 | mouseChange.multiplyScalar( _eye.length() * _this.panSpeed ); 241 | 242 | pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x ); 243 | pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) ); 244 | 245 | _this.object.position.add( pan ); 246 | _this.target.add( pan ); 247 | 248 | if ( _this.staticMoving ) { 249 | 250 | _panStart.copy( _panEnd ); 251 | 252 | } else { 253 | 254 | _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) ); 255 | 256 | } 257 | 258 | } 259 | 260 | }; 261 | 262 | }() ); 263 | 264 | this.checkDistances = function () { 265 | 266 | if ( ! _this.noZoom || ! _this.noPan ) { 267 | 268 | if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) { 269 | 270 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) ); 271 | _zoomStart.copy( _zoomEnd ); 272 | 273 | } 274 | 275 | if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) { 276 | 277 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) ); 278 | _zoomStart.copy( _zoomEnd ); 279 | 280 | } 281 | 282 | } 283 | 284 | }; 285 | 286 | this.update = function () { 287 | 288 | _eye.subVectors( _this.object.position, _this.target ); 289 | 290 | if ( ! _this.noRotate ) { 291 | 292 | _this.rotateCamera(); 293 | 294 | } 295 | 296 | if ( ! _this.noZoom ) { 297 | 298 | _this.zoomCamera(); 299 | 300 | } 301 | 302 | if ( ! _this.noPan ) { 303 | 304 | _this.panCamera(); 305 | 306 | } 307 | 308 | _this.object.position.addVectors( _this.target, _eye ); 309 | 310 | _this.checkDistances(); 311 | 312 | _this.object.lookAt( _this.target ); 313 | 314 | if ( lastPosition.distanceToSquared( _this.object.position ) > EPS ) { 315 | 316 | _this.dispatchEvent( changeEvent ); 317 | 318 | lastPosition.copy( _this.object.position ); 319 | 320 | } 321 | 322 | }; 323 | 324 | this.reset = function () { 325 | 326 | _state = STATE.NONE; 327 | _prevState = STATE.NONE; 328 | 329 | _this.target.copy( _this.target0 ); 330 | _this.object.position.copy( _this.position0 ); 331 | _this.object.up.copy( _this.up0 ); 332 | 333 | _eye.subVectors( _this.object.position, _this.target ); 334 | 335 | _this.object.lookAt( _this.target ); 336 | 337 | _this.dispatchEvent( changeEvent ); 338 | 339 | lastPosition.copy( _this.object.position ); 340 | 341 | }; 342 | 343 | // listeners 344 | 345 | function keydown( event ) { 346 | 347 | if ( _this.enabled === false ) return; 348 | 349 | window.removeEventListener( 'keydown', keydown ); 350 | 351 | _prevState = _state; 352 | 353 | if ( _state !== STATE.NONE ) { 354 | 355 | return; 356 | 357 | } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate ) { 358 | 359 | _state = STATE.ROTATE; 360 | 361 | } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom ) { 362 | 363 | _state = STATE.ZOOM; 364 | 365 | } else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan ) { 366 | 367 | _state = STATE.PAN; 368 | 369 | } 370 | 371 | } 372 | 373 | function keyup( event ) { 374 | 375 | if ( _this.enabled === false ) return; 376 | 377 | _state = _prevState; 378 | 379 | window.addEventListener( 'keydown', keydown, false ); 380 | 381 | } 382 | 383 | function mousedown( event ) { 384 | 385 | if ( _this.enabled === false ) return; 386 | 387 | event.preventDefault(); 388 | event.stopPropagation(); 389 | 390 | if ( _state === STATE.NONE ) { 391 | 392 | _state = event.button; 393 | 394 | } 395 | 396 | if ( _state === STATE.ROTATE && ! _this.noRotate ) { 397 | 398 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); 399 | _movePrev.copy( _moveCurr ); 400 | 401 | } else if ( _state === STATE.ZOOM && ! _this.noZoom ) { 402 | 403 | _zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 404 | _zoomEnd.copy( _zoomStart ); 405 | 406 | } else if ( _state === STATE.PAN && ! _this.noPan ) { 407 | 408 | _panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 409 | _panEnd.copy( _panStart ); 410 | 411 | } 412 | 413 | document.addEventListener( 'mousemove', mousemove, false ); 414 | document.addEventListener( 'mouseup', mouseup, false ); 415 | 416 | _this.dispatchEvent( startEvent ); 417 | 418 | } 419 | 420 | function mousemove( event ) { 421 | 422 | if ( _this.enabled === false ) return; 423 | 424 | event.preventDefault(); 425 | event.stopPropagation(); 426 | 427 | if ( _state === STATE.ROTATE && ! _this.noRotate ) { 428 | 429 | _movePrev.copy( _moveCurr ); 430 | _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); 431 | 432 | } else if ( _state === STATE.ZOOM && ! _this.noZoom ) { 433 | 434 | _zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 435 | 436 | } else if ( _state === STATE.PAN && ! _this.noPan ) { 437 | 438 | _panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); 439 | 440 | } 441 | 442 | } 443 | 444 | function mouseup( event ) { 445 | 446 | if ( _this.enabled === false ) return; 447 | 448 | event.preventDefault(); 449 | event.stopPropagation(); 450 | 451 | _state = STATE.NONE; 452 | 453 | document.removeEventListener( 'mousemove', mousemove ); 454 | document.removeEventListener( 'mouseup', mouseup ); 455 | _this.dispatchEvent( endEvent ); 456 | 457 | } 458 | 459 | function mousewheel( event ) { 460 | 461 | if ( _this.enabled === false ) return; 462 | 463 | if ( _this.noZoom === true ) return; 464 | 465 | event.preventDefault(); 466 | event.stopPropagation(); 467 | 468 | switch ( event.deltaMode ) { 469 | 470 | case 2: 471 | // Zoom in pages 472 | _zoomStart.y -= event.deltaY * 0.025; 473 | break; 474 | 475 | case 1: 476 | // Zoom in lines 477 | _zoomStart.y -= event.deltaY * 0.01; 478 | break; 479 | 480 | default: 481 | // undefined, 0, assume pixels 482 | _zoomStart.y -= event.deltaY * 0.00025; 483 | break; 484 | 485 | } 486 | 487 | _this.dispatchEvent( startEvent ); 488 | _this.dispatchEvent( endEvent ); 489 | 490 | } 491 | 492 | function touchstart( event ) { 493 | 494 | if ( _this.enabled === false ) return; 495 | 496 | event.preventDefault(); 497 | 498 | switch ( event.touches.length ) { 499 | 500 | case 1: 501 | _state = STATE.TOUCH_ROTATE; 502 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 503 | _movePrev.copy( _moveCurr ); 504 | break; 505 | 506 | default: // 2 or more 507 | _state = STATE.TOUCH_ZOOM_PAN; 508 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 509 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 510 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); 511 | 512 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; 513 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; 514 | _panStart.copy( getMouseOnScreen( x, y ) ); 515 | _panEnd.copy( _panStart ); 516 | break; 517 | 518 | } 519 | 520 | _this.dispatchEvent( startEvent ); 521 | 522 | } 523 | 524 | function touchmove( event ) { 525 | 526 | if ( _this.enabled === false ) return; 527 | 528 | event.preventDefault(); 529 | event.stopPropagation(); 530 | 531 | switch ( event.touches.length ) { 532 | 533 | case 1: 534 | _movePrev.copy( _moveCurr ); 535 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 536 | break; 537 | 538 | default: // 2 or more 539 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 540 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 541 | _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ); 542 | 543 | var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2; 544 | var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2; 545 | _panEnd.copy( getMouseOnScreen( x, y ) ); 546 | break; 547 | 548 | } 549 | 550 | } 551 | 552 | function touchend( event ) { 553 | 554 | if ( _this.enabled === false ) return; 555 | 556 | switch ( event.touches.length ) { 557 | 558 | case 0: 559 | _state = STATE.NONE; 560 | break; 561 | 562 | case 1: 563 | _state = STATE.TOUCH_ROTATE; 564 | _moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) ); 565 | _movePrev.copy( _moveCurr ); 566 | break; 567 | 568 | } 569 | 570 | _this.dispatchEvent( endEvent ); 571 | 572 | } 573 | 574 | function contextmenu( event ) { 575 | 576 | if ( _this.enabled === false ) return; 577 | 578 | event.preventDefault(); 579 | 580 | } 581 | 582 | this.dispose = function () { 583 | 584 | this.domElement.removeEventListener( 'contextmenu', contextmenu, false ); 585 | this.domElement.removeEventListener( 'mousedown', mousedown, false ); 586 | this.domElement.removeEventListener( 'wheel', mousewheel, false ); 587 | 588 | this.domElement.removeEventListener( 'touchstart', touchstart, false ); 589 | this.domElement.removeEventListener( 'touchend', touchend, false ); 590 | this.domElement.removeEventListener( 'touchmove', touchmove, false ); 591 | 592 | document.removeEventListener( 'mousemove', mousemove, false ); 593 | document.removeEventListener( 'mouseup', mouseup, false ); 594 | 595 | window.removeEventListener( 'keydown', keydown, false ); 596 | window.removeEventListener( 'keyup', keyup, false ); 597 | 598 | }; 599 | 600 | this.domElement.addEventListener( 'contextmenu', contextmenu, false ); 601 | this.domElement.addEventListener( 'mousedown', mousedown, false ); 602 | this.domElement.addEventListener( 'wheel', mousewheel, false ); 603 | 604 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 605 | this.domElement.addEventListener( 'touchend', touchend, false ); 606 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 607 | 608 | window.addEventListener( 'keydown', keydown, false ); 609 | window.addEventListener( 'keyup', keyup, false ); 610 | 611 | this.handleResize(); 612 | 613 | // force an update at start 614 | this.update(); 615 | 616 | }; 617 | 618 | THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 619 | THREE.TrackballControls.prototype.constructor = THREE.TrackballControls; 620 | -------------------------------------------------------------------------------- /js/loaders/BinaryLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.BinaryLoader = function ( showStatus ) { 6 | 7 | THREE.Loader.call( this, showStatus ); 8 | 9 | }; 10 | 11 | THREE.BinaryLoader.prototype = Object.create( THREE.Loader.prototype ); 12 | 13 | // Load models generated by slim OBJ converter with BINARY option (converter_obj_three_slim.py -t binary) 14 | // - binary models consist of two files: JS and BIN 15 | // - parameters 16 | // - url (required) 17 | // - callback (required) 18 | // - texturePath (optional: if not specified, textures will be assumed to be in the same folder as JS model file) 19 | // - binaryPath (optional: if not specified, binary file will be assumed to be in the same folder as JS model file) 20 | 21 | THREE.BinaryLoader.prototype.load = function( url, callback, texturePath, binaryPath ) { 22 | 23 | // todo: unify load API to for easier SceneLoader use 24 | 25 | texturePath = texturePath && ( typeof texturePath === "string" ) ? texturePath : this.extractUrlBase( url ); 26 | binaryPath = binaryPath && ( typeof binaryPath === "string" ) ? binaryPath : this.extractUrlBase( url ); 27 | 28 | var callbackProgress = this.showProgress ? THREE.Loader.prototype.updateProgress : null; 29 | 30 | this.onLoadStart(); 31 | 32 | // #1 load JS part via web worker 33 | 34 | this.loadAjaxJSON( this, url, callback, texturePath, binaryPath, callbackProgress ); 35 | 36 | }; 37 | 38 | THREE.BinaryLoader.prototype.loadAjaxJSON = function ( context, url, callback, texturePath, binaryPath, callbackProgress ) { 39 | 40 | var xhr = new XMLHttpRequest(); 41 | 42 | xhr.onreadystatechange = function () { 43 | 44 | if ( xhr.readyState == 4 ) { 45 | 46 | if ( xhr.status == 200 || xhr.status == 0 ) { 47 | 48 | var json = JSON.parse( xhr.responseText ); 49 | context.loadAjaxBuffers( json, callback, binaryPath, texturePath, callbackProgress ); 50 | 51 | } else { 52 | 53 | console.error( "THREE.BinaryLoader: Couldn't load [" + url + "] [" + xhr.status + "]" ); 54 | 55 | } 56 | 57 | } 58 | 59 | }; 60 | 61 | xhr.open( "GET", url, true ); 62 | xhr.send( null ); 63 | 64 | }; 65 | 66 | THREE.BinaryLoader.prototype.loadAjaxBuffers = function ( json, callback, binaryPath, texturePath, callbackProgress ) { 67 | 68 | var xhr = new XMLHttpRequest(), 69 | url = binaryPath + "/" + json.buffers; 70 | 71 | var length = 0; 72 | 73 | xhr.onreadystatechange = function () { 74 | 75 | if ( xhr.readyState == 4 ) { 76 | 77 | if ( xhr.status == 200 || xhr.status == 0 ) { 78 | 79 | var buffer = xhr.response; 80 | if ( buffer === undefined ) buffer = ( new Uint8Array( xhr.responseBody ) ).buffer; // IEWEBGL needs this 81 | THREE.BinaryLoader.prototype.createBinModel( buffer, callback, texturePath, json.materials ); 82 | 83 | } else { 84 | 85 | console.error( "THREE.BinaryLoader: Couldn't load [" + url + "] [" + xhr.status + "]" ); 86 | 87 | } 88 | 89 | } else if ( xhr.readyState == 3 ) { 90 | 91 | if ( callbackProgress ) { 92 | 93 | if ( length == 0 ) { 94 | 95 | length = xhr.getResponseHeader( "Content-Length" ); 96 | 97 | } 98 | 99 | callbackProgress( { total: length, loaded: xhr.responseText.length } ); 100 | 101 | } 102 | 103 | } else if ( xhr.readyState == 2 ) { 104 | 105 | length = xhr.getResponseHeader( "Content-Length" ); 106 | 107 | } 108 | 109 | }; 110 | 111 | xhr.open( "GET", url, true ); 112 | xhr.responseType = "arraybuffer"; 113 | xhr.send( null ); 114 | 115 | }; 116 | 117 | // Binary AJAX parser 118 | 119 | THREE.BinaryLoader.prototype.createBinModel = function ( data, callback, texturePath, jsonMaterials ) { 120 | 121 | var Model = function ( texturePath ) { 122 | 123 | var scope = this, 124 | currentOffset = 0, 125 | md, 126 | normals = [], 127 | uvs = [], 128 | start_tri_flat, start_tri_smooth, start_tri_flat_uv, start_tri_smooth_uv, 129 | start_quad_flat, start_quad_smooth, start_quad_flat_uv, start_quad_smooth_uv, 130 | tri_size, quad_size, 131 | len_tri_flat, len_tri_smooth, len_tri_flat_uv, len_tri_smooth_uv, 132 | len_quad_flat, len_quad_smooth, len_quad_flat_uv, len_quad_smooth_uv; 133 | 134 | 135 | THREE.Geometry.call( this ); 136 | 137 | md = parseMetaData( data, currentOffset ); 138 | 139 | currentOffset += md.header_bytes; 140 | /* 141 | md.vertex_index_bytes = Uint32Array.BYTES_PER_ELEMENT; 142 | md.material_index_bytes = Uint16Array.BYTES_PER_ELEMENT; 143 | md.normal_index_bytes = Uint32Array.BYTES_PER_ELEMENT; 144 | md.uv_index_bytes = Uint32Array.BYTES_PER_ELEMENT; 145 | */ 146 | // buffers sizes 147 | 148 | tri_size = md.vertex_index_bytes * 3 + md.material_index_bytes; 149 | quad_size = md.vertex_index_bytes * 4 + md.material_index_bytes; 150 | 151 | len_tri_flat = md.ntri_flat * ( tri_size ); 152 | len_tri_smooth = md.ntri_smooth * ( tri_size + md.normal_index_bytes * 3 ); 153 | len_tri_flat_uv = md.ntri_flat_uv * ( tri_size + md.uv_index_bytes * 3 ); 154 | len_tri_smooth_uv = md.ntri_smooth_uv * ( tri_size + md.normal_index_bytes * 3 + md.uv_index_bytes * 3 ); 155 | 156 | len_quad_flat = md.nquad_flat * ( quad_size ); 157 | len_quad_smooth = md.nquad_smooth * ( quad_size + md.normal_index_bytes * 4 ); 158 | len_quad_flat_uv = md.nquad_flat_uv * ( quad_size + md.uv_index_bytes * 4 ); 159 | len_quad_smooth_uv = md.nquad_smooth_uv * ( quad_size + md.normal_index_bytes * 4 + md.uv_index_bytes * 4 ); 160 | 161 | // read buffers 162 | 163 | currentOffset += init_vertices( currentOffset ); 164 | 165 | currentOffset += init_normals( currentOffset ); 166 | currentOffset += handlePadding( md.nnormals * 3 ); 167 | 168 | currentOffset += init_uvs( currentOffset ); 169 | 170 | start_tri_flat = currentOffset; 171 | start_tri_smooth = start_tri_flat + len_tri_flat + handlePadding( md.ntri_flat * 2 ); 172 | start_tri_flat_uv = start_tri_smooth + len_tri_smooth + handlePadding( md.ntri_smooth * 2 ); 173 | start_tri_smooth_uv = start_tri_flat_uv + len_tri_flat_uv + handlePadding( md.ntri_flat_uv * 2 ); 174 | 175 | start_quad_flat = start_tri_smooth_uv + len_tri_smooth_uv + handlePadding( md.ntri_smooth_uv * 2 ); 176 | start_quad_smooth = start_quad_flat + len_quad_flat + handlePadding( md.nquad_flat * 2 ); 177 | start_quad_flat_uv = start_quad_smooth + len_quad_smooth + handlePadding( md.nquad_smooth * 2 ); 178 | start_quad_smooth_uv= start_quad_flat_uv + len_quad_flat_uv + handlePadding( md.nquad_flat_uv * 2 ); 179 | 180 | // have to first process faces with uvs 181 | // so that face and uv indices match 182 | 183 | init_triangles_flat_uv( start_tri_flat_uv ); 184 | init_triangles_smooth_uv( start_tri_smooth_uv ); 185 | 186 | init_quads_flat_uv( start_quad_flat_uv ); 187 | init_quads_smooth_uv( start_quad_smooth_uv ); 188 | 189 | // now we can process untextured faces 190 | 191 | init_triangles_flat( start_tri_flat ); 192 | init_triangles_smooth( start_tri_smooth ); 193 | 194 | init_quads_flat( start_quad_flat ); 195 | init_quads_smooth( start_quad_smooth ); 196 | 197 | this.computeCentroids(); 198 | this.computeFaceNormals(); 199 | 200 | function handlePadding( n ) { 201 | 202 | return ( n % 4 ) ? ( 4 - n % 4 ) : 0; 203 | 204 | }; 205 | 206 | function parseMetaData( data, offset ) { 207 | 208 | var metaData = { 209 | 210 | 'signature' :parseString( data, offset, 12 ), 211 | 'header_bytes' :parseUChar8( data, offset + 12 ), 212 | 213 | 'vertex_coordinate_bytes' :parseUChar8( data, offset + 13 ), 214 | 'normal_coordinate_bytes' :parseUChar8( data, offset + 14 ), 215 | 'uv_coordinate_bytes' :parseUChar8( data, offset + 15 ), 216 | 217 | 'vertex_index_bytes' :parseUChar8( data, offset + 16 ), 218 | 'normal_index_bytes' :parseUChar8( data, offset + 17 ), 219 | 'uv_index_bytes' :parseUChar8( data, offset + 18 ), 220 | 'material_index_bytes' :parseUChar8( data, offset + 19 ), 221 | 222 | 'nvertices' :parseUInt32( data, offset + 20 ), 223 | 'nnormals' :parseUInt32( data, offset + 20 + 4*1 ), 224 | 'nuvs' :parseUInt32( data, offset + 20 + 4*2 ), 225 | 226 | 'ntri_flat' :parseUInt32( data, offset + 20 + 4*3 ), 227 | 'ntri_smooth' :parseUInt32( data, offset + 20 + 4*4 ), 228 | 'ntri_flat_uv' :parseUInt32( data, offset + 20 + 4*5 ), 229 | 'ntri_smooth_uv' :parseUInt32( data, offset + 20 + 4*6 ), 230 | 231 | 'nquad_flat' :parseUInt32( data, offset + 20 + 4*7 ), 232 | 'nquad_smooth' :parseUInt32( data, offset + 20 + 4*8 ), 233 | 'nquad_flat_uv' :parseUInt32( data, offset + 20 + 4*9 ), 234 | 'nquad_smooth_uv' :parseUInt32( data, offset + 20 + 4*10 ) 235 | 236 | }; 237 | /* 238 | console.log( "signature: " + metaData.signature ); 239 | 240 | console.log( "header_bytes: " + metaData.header_bytes ); 241 | console.log( "vertex_coordinate_bytes: " + metaData.vertex_coordinate_bytes ); 242 | console.log( "normal_coordinate_bytes: " + metaData.normal_coordinate_bytes ); 243 | console.log( "uv_coordinate_bytes: " + metaData.uv_coordinate_bytes ); 244 | 245 | console.log( "vertex_index_bytes: " + metaData.vertex_index_bytes ); 246 | console.log( "normal_index_bytes: " + metaData.normal_index_bytes ); 247 | console.log( "uv_index_bytes: " + metaData.uv_index_bytes ); 248 | console.log( "material_index_bytes: " + metaData.material_index_bytes ); 249 | 250 | console.log( "nvertices: " + metaData.nvertices ); 251 | console.log( "nnormals: " + metaData.nnormals ); 252 | console.log( "nuvs: " + metaData.nuvs ); 253 | 254 | console.log( "ntri_flat: " + metaData.ntri_flat ); 255 | console.log( "ntri_smooth: " + metaData.ntri_smooth ); 256 | console.log( "ntri_flat_uv: " + metaData.ntri_flat_uv ); 257 | console.log( "ntri_smooth_uv: " + metaData.ntri_smooth_uv ); 258 | 259 | console.log( "nquad_flat: " + metaData.nquad_flat ); 260 | console.log( "nquad_smooth: " + metaData.nquad_smooth ); 261 | console.log( "nquad_flat_uv: " + metaData.nquad_flat_uv ); 262 | console.log( "nquad_smooth_uv: " + metaData.nquad_smooth_uv ); 263 | 264 | var total = metaData.header_bytes 265 | + metaData.nvertices * metaData.vertex_coordinate_bytes * 3 266 | + metaData.nnormals * metaData.normal_coordinate_bytes * 3 267 | + metaData.nuvs * metaData.uv_coordinate_bytes * 2 268 | + metaData.ntri_flat * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes ) 269 | + metaData.ntri_smooth * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.normal_index_bytes*3 ) 270 | + metaData.ntri_flat_uv * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.uv_index_bytes*3 ) 271 | + metaData.ntri_smooth_uv * ( metaData.vertex_index_bytes*3 + metaData.material_index_bytes + metaData.normal_index_bytes*3 + metaData.uv_index_bytes*3 ) 272 | + metaData.nquad_flat * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes ) 273 | + metaData.nquad_smooth * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.normal_index_bytes*4 ) 274 | + metaData.nquad_flat_uv * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.uv_index_bytes*4 ) 275 | + metaData.nquad_smooth_uv * ( metaData.vertex_index_bytes*4 + metaData.material_index_bytes + metaData.normal_index_bytes*4 + metaData.uv_index_bytes*4 ); 276 | console.log( "total bytes: " + total ); 277 | */ 278 | 279 | return metaData; 280 | 281 | }; 282 | 283 | function parseString( data, offset, length ) { 284 | 285 | var charArray = new Uint8Array( data, offset, length ); 286 | 287 | var text = ""; 288 | 289 | for ( var i = 0; i < length; i ++ ) { 290 | 291 | text += String.fromCharCode( charArray[ offset + i ] ); 292 | 293 | } 294 | 295 | return text; 296 | 297 | }; 298 | 299 | function parseUChar8( data, offset ) { 300 | 301 | var charArray = new Uint8Array( data, offset, 1 ); 302 | 303 | return charArray[ 0 ]; 304 | 305 | }; 306 | 307 | function parseUInt32( data, offset ) { 308 | 309 | var intArray = new Uint32Array( data, offset, 1 ); 310 | 311 | return intArray[ 0 ]; 312 | 313 | }; 314 | 315 | function init_vertices( start ) { 316 | 317 | var nElements = md.nvertices; 318 | 319 | var coordArray = new Float32Array( data, start, nElements * 3 ); 320 | 321 | var i, x, y, z; 322 | 323 | for( i = 0; i < nElements; i ++ ) { 324 | 325 | x = coordArray[ i * 3 ]; 326 | y = coordArray[ i * 3 + 1 ]; 327 | z = coordArray[ i * 3 + 2 ]; 328 | 329 | vertex( scope, x, y, z ); 330 | 331 | } 332 | 333 | return nElements * 3 * Float32Array.BYTES_PER_ELEMENT; 334 | 335 | }; 336 | 337 | function init_normals( start ) { 338 | 339 | var nElements = md.nnormals; 340 | 341 | if ( nElements ) { 342 | 343 | var normalArray = new Int8Array( data, start, nElements * 3 ); 344 | 345 | var i, x, y, z; 346 | 347 | for( i = 0; i < nElements; i ++ ) { 348 | 349 | x = normalArray[ i * 3 ]; 350 | y = normalArray[ i * 3 + 1 ]; 351 | z = normalArray[ i * 3 + 2 ]; 352 | 353 | normals.push( x/127, y/127, z/127 ); 354 | 355 | } 356 | 357 | } 358 | 359 | return nElements * 3 * Int8Array.BYTES_PER_ELEMENT; 360 | 361 | }; 362 | 363 | function init_uvs( start ) { 364 | 365 | var nElements = md.nuvs; 366 | 367 | if ( nElements ) { 368 | 369 | var uvArray = new Float32Array( data, start, nElements * 2 ); 370 | 371 | var i, u, v; 372 | 373 | for( i = 0; i < nElements; i ++ ) { 374 | 375 | u = uvArray[ i * 2 ]; 376 | v = uvArray[ i * 2 + 1 ]; 377 | 378 | uvs.push( u, v ); 379 | 380 | } 381 | 382 | } 383 | 384 | return nElements * 2 * Float32Array.BYTES_PER_ELEMENT; 385 | 386 | }; 387 | 388 | function init_uvs3( nElements, offset ) { 389 | 390 | var i, uva, uvb, uvc, u1, u2, u3, v1, v2, v3; 391 | 392 | var uvIndexBuffer = new Uint32Array( data, offset, 3 * nElements ); 393 | 394 | for( i = 0; i < nElements; i ++ ) { 395 | 396 | uva = uvIndexBuffer[ i * 3 ]; 397 | uvb = uvIndexBuffer[ i * 3 + 1 ]; 398 | uvc = uvIndexBuffer[ i * 3 + 2 ]; 399 | 400 | u1 = uvs[ uva*2 ]; 401 | v1 = uvs[ uva*2 + 1 ]; 402 | 403 | u2 = uvs[ uvb*2 ]; 404 | v2 = uvs[ uvb*2 + 1 ]; 405 | 406 | u3 = uvs[ uvc*2 ]; 407 | v3 = uvs[ uvc*2 + 1 ]; 408 | 409 | uv3( scope.faceVertexUvs[ 0 ], u1, v1, u2, v2, u3, v3 ); 410 | 411 | } 412 | 413 | }; 414 | 415 | function init_uvs4( nElements, offset ) { 416 | 417 | var i, uva, uvb, uvc, uvd, u1, u2, u3, u4, v1, v2, v3, v4; 418 | 419 | var uvIndexBuffer = new Uint32Array( data, offset, 4 * nElements ); 420 | 421 | for( i = 0; i < nElements; i ++ ) { 422 | 423 | uva = uvIndexBuffer[ i * 4 ]; 424 | uvb = uvIndexBuffer[ i * 4 + 1 ]; 425 | uvc = uvIndexBuffer[ i * 4 + 2 ]; 426 | uvd = uvIndexBuffer[ i * 4 + 3 ]; 427 | 428 | u1 = uvs[ uva*2 ]; 429 | v1 = uvs[ uva*2 + 1 ]; 430 | 431 | u2 = uvs[ uvb*2 ]; 432 | v2 = uvs[ uvb*2 + 1 ]; 433 | 434 | u3 = uvs[ uvc*2 ]; 435 | v3 = uvs[ uvc*2 + 1 ]; 436 | 437 | u4 = uvs[ uvd*2 ]; 438 | v4 = uvs[ uvd*2 + 1 ]; 439 | 440 | uv4( scope.faceVertexUvs[ 0 ], u1, v1, u2, v2, u3, v3, u4, v4 ); 441 | 442 | } 443 | 444 | }; 445 | 446 | function init_faces3_flat( nElements, offsetVertices, offsetMaterials ) { 447 | 448 | var i, a, b, c, m; 449 | 450 | var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 3 * nElements ); 451 | var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); 452 | 453 | for( i = 0; i < nElements; i ++ ) { 454 | 455 | a = vertexIndexBuffer[ i * 3 ]; 456 | b = vertexIndexBuffer[ i * 3 + 1 ]; 457 | c = vertexIndexBuffer[ i * 3 + 2 ]; 458 | 459 | m = materialIndexBuffer[ i ]; 460 | 461 | f3( scope, a, b, c, m ); 462 | 463 | } 464 | 465 | }; 466 | 467 | function init_faces4_flat( nElements, offsetVertices, offsetMaterials ) { 468 | 469 | var i, a, b, c, d, m; 470 | 471 | var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 4 * nElements ); 472 | var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); 473 | 474 | for( i = 0; i < nElements; i ++ ) { 475 | 476 | a = vertexIndexBuffer[ i * 4 ]; 477 | b = vertexIndexBuffer[ i * 4 + 1 ]; 478 | c = vertexIndexBuffer[ i * 4 + 2 ]; 479 | d = vertexIndexBuffer[ i * 4 + 3 ]; 480 | 481 | m = materialIndexBuffer[ i ]; 482 | 483 | f4( scope, a, b, c, d, m ); 484 | 485 | } 486 | 487 | }; 488 | 489 | function init_faces3_smooth( nElements, offsetVertices, offsetNormals, offsetMaterials ) { 490 | 491 | var i, a, b, c, m; 492 | var na, nb, nc; 493 | 494 | var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 3 * nElements ); 495 | var normalIndexBuffer = new Uint32Array( data, offsetNormals, 3 * nElements ); 496 | var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); 497 | 498 | for( i = 0; i < nElements; i ++ ) { 499 | 500 | a = vertexIndexBuffer[ i * 3 ]; 501 | b = vertexIndexBuffer[ i * 3 + 1 ]; 502 | c = vertexIndexBuffer[ i * 3 + 2 ]; 503 | 504 | na = normalIndexBuffer[ i * 3 ]; 505 | nb = normalIndexBuffer[ i * 3 + 1 ]; 506 | nc = normalIndexBuffer[ i * 3 + 2 ]; 507 | 508 | m = materialIndexBuffer[ i ]; 509 | 510 | f3n( scope, normals, a, b, c, m, na, nb, nc ); 511 | 512 | } 513 | 514 | }; 515 | 516 | function init_faces4_smooth( nElements, offsetVertices, offsetNormals, offsetMaterials ) { 517 | 518 | var i, a, b, c, d, m; 519 | var na, nb, nc, nd; 520 | 521 | var vertexIndexBuffer = new Uint32Array( data, offsetVertices, 4 * nElements ); 522 | var normalIndexBuffer = new Uint32Array( data, offsetNormals, 4 * nElements ); 523 | var materialIndexBuffer = new Uint16Array( data, offsetMaterials, nElements ); 524 | 525 | for( i = 0; i < nElements; i ++ ) { 526 | 527 | a = vertexIndexBuffer[ i * 4 ]; 528 | b = vertexIndexBuffer[ i * 4 + 1 ]; 529 | c = vertexIndexBuffer[ i * 4 + 2 ]; 530 | d = vertexIndexBuffer[ i * 4 + 3 ]; 531 | 532 | na = normalIndexBuffer[ i * 4 ]; 533 | nb = normalIndexBuffer[ i * 4 + 1 ]; 534 | nc = normalIndexBuffer[ i * 4 + 2 ]; 535 | nd = normalIndexBuffer[ i * 4 + 3 ]; 536 | 537 | m = materialIndexBuffer[ i ]; 538 | 539 | f4n( scope, normals, a, b, c, d, m, na, nb, nc, nd ); 540 | 541 | } 542 | 543 | }; 544 | 545 | function init_triangles_flat( start ) { 546 | 547 | var nElements = md.ntri_flat; 548 | 549 | if ( nElements ) { 550 | 551 | var offsetMaterials = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 552 | init_faces3_flat( nElements, start, offsetMaterials ); 553 | 554 | } 555 | 556 | }; 557 | 558 | function init_triangles_flat_uv( start ) { 559 | 560 | var nElements = md.ntri_flat_uv; 561 | 562 | if ( nElements ) { 563 | 564 | var offsetUvs = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 565 | var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 566 | 567 | init_faces3_flat( nElements, start, offsetMaterials ); 568 | init_uvs3( nElements, offsetUvs ); 569 | 570 | } 571 | 572 | }; 573 | 574 | function init_triangles_smooth( start ) { 575 | 576 | var nElements = md.ntri_smooth; 577 | 578 | if ( nElements ) { 579 | 580 | var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 581 | var offsetMaterials = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 582 | 583 | init_faces3_smooth( nElements, start, offsetNormals, offsetMaterials ); 584 | 585 | } 586 | 587 | }; 588 | 589 | function init_triangles_smooth_uv( start ) { 590 | 591 | var nElements = md.ntri_smooth_uv; 592 | 593 | if ( nElements ) { 594 | 595 | var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 596 | var offsetUvs = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 597 | var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 3; 598 | 599 | init_faces3_smooth( nElements, start, offsetNormals, offsetMaterials ); 600 | init_uvs3( nElements, offsetUvs ); 601 | 602 | } 603 | 604 | }; 605 | 606 | function init_quads_flat( start ) { 607 | 608 | var nElements = md.nquad_flat; 609 | 610 | if ( nElements ) { 611 | 612 | var offsetMaterials = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 613 | init_faces4_flat( nElements, start, offsetMaterials ); 614 | 615 | } 616 | 617 | }; 618 | 619 | function init_quads_flat_uv( start ) { 620 | 621 | var nElements = md.nquad_flat_uv; 622 | 623 | if ( nElements ) { 624 | 625 | var offsetUvs = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 626 | var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 627 | 628 | init_faces4_flat( nElements, start, offsetMaterials ); 629 | init_uvs4( nElements, offsetUvs ); 630 | 631 | } 632 | 633 | }; 634 | 635 | function init_quads_smooth( start ) { 636 | 637 | var nElements = md.nquad_smooth; 638 | 639 | if ( nElements ) { 640 | 641 | var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 642 | var offsetMaterials = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 643 | 644 | init_faces4_smooth( nElements, start, offsetNormals, offsetMaterials ); 645 | 646 | } 647 | 648 | }; 649 | 650 | function init_quads_smooth_uv( start ) { 651 | 652 | var nElements = md.nquad_smooth_uv; 653 | 654 | if ( nElements ) { 655 | 656 | var offsetNormals = start + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 657 | var offsetUvs = offsetNormals + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 658 | var offsetMaterials = offsetUvs + nElements * Uint32Array.BYTES_PER_ELEMENT * 4; 659 | 660 | init_faces4_smooth( nElements, start, offsetNormals, offsetMaterials ); 661 | init_uvs4( nElements, offsetUvs ); 662 | 663 | } 664 | 665 | }; 666 | 667 | }; 668 | 669 | function vertex ( scope, x, y, z ) { 670 | 671 | scope.vertices.push( new THREE.Vector3( x, y, z ) ); 672 | 673 | }; 674 | 675 | function f3 ( scope, a, b, c, mi ) { 676 | 677 | scope.faces.push( new THREE.Face3( a, b, c, null, null, mi ) ); 678 | 679 | }; 680 | 681 | function f4 ( scope, a, b, c, d, mi ) { 682 | 683 | scope.faces.push( new THREE.Face4( a, b, c, d, null, null, mi ) ); 684 | 685 | }; 686 | 687 | function f3n ( scope, normals, a, b, c, mi, na, nb, nc ) { 688 | 689 | var nax = normals[ na*3 ], 690 | nay = normals[ na*3 + 1 ], 691 | naz = normals[ na*3 + 2 ], 692 | 693 | nbx = normals[ nb*3 ], 694 | nby = normals[ nb*3 + 1 ], 695 | nbz = normals[ nb*3 + 2 ], 696 | 697 | ncx = normals[ nc*3 ], 698 | ncy = normals[ nc*3 + 1 ], 699 | ncz = normals[ nc*3 + 2 ]; 700 | 701 | scope.faces.push( new THREE.Face3( a, b, c, 702 | [new THREE.Vector3( nax, nay, naz ), 703 | new THREE.Vector3( nbx, nby, nbz ), 704 | new THREE.Vector3( ncx, ncy, ncz )], 705 | null, 706 | mi ) ); 707 | 708 | }; 709 | 710 | function f4n ( scope, normals, a, b, c, d, mi, na, nb, nc, nd ) { 711 | 712 | var nax = normals[ na*3 ], 713 | nay = normals[ na*3 + 1 ], 714 | naz = normals[ na*3 + 2 ], 715 | 716 | nbx = normals[ nb*3 ], 717 | nby = normals[ nb*3 + 1 ], 718 | nbz = normals[ nb*3 + 2 ], 719 | 720 | ncx = normals[ nc*3 ], 721 | ncy = normals[ nc*3 + 1 ], 722 | ncz = normals[ nc*3 + 2 ], 723 | 724 | ndx = normals[ nd*3 ], 725 | ndy = normals[ nd*3 + 1 ], 726 | ndz = normals[ nd*3 + 2 ]; 727 | 728 | scope.faces.push( new THREE.Face4( a, b, c, d, 729 | [new THREE.Vector3( nax, nay, naz ), 730 | new THREE.Vector3( nbx, nby, nbz ), 731 | new THREE.Vector3( ncx, ncy, ncz ), 732 | new THREE.Vector3( ndx, ndy, ndz )], 733 | null, 734 | mi ) ); 735 | 736 | }; 737 | 738 | function uv3 ( where, u1, v1, u2, v2, u3, v3 ) { 739 | 740 | where.push( [ 741 | new THREE.Vector2( u1, v1 ), 742 | new THREE.Vector2( u2, v2 ), 743 | new THREE.Vector2( u3, v3 ) 744 | ] ); 745 | 746 | }; 747 | 748 | function uv4 ( where, u1, v1, u2, v2, u3, v3, u4, v4 ) { 749 | 750 | where.push( [ 751 | new THREE.Vector2( u1, v1 ), 752 | new THREE.Vector2( u2, v2 ), 753 | new THREE.Vector2( u3, v3 ), 754 | new THREE.Vector2( u4, v4 ) 755 | ] ); 756 | }; 757 | 758 | Model.prototype = Object.create( THREE.Geometry.prototype ); 759 | 760 | var geometry = new Model( texturePath ); 761 | var materials = this.initMaterials( jsonMaterials, texturePath ); 762 | 763 | if ( this.needsTangents( materials ) ) geometry.computeTangents(); 764 | 765 | callback( geometry, materials ); 766 | 767 | }; 768 | -------------------------------------------------------------------------------- /js/loaders/VTKLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.VTKLoader = function () { 6 | 7 | THREE.EventDispatcher.call( this ); 8 | 9 | }; 10 | 11 | THREE.VTKLoader.prototype = { 12 | 13 | constructor: THREE.VTKLoader, 14 | 15 | load: function ( url, callback ) { 16 | 17 | var scope = this; 18 | var request = new XMLHttpRequest(); 19 | 20 | request.addEventListener( 'load', function ( event ) { 21 | 22 | var geometry = scope.parse( event.target.responseText ); 23 | 24 | scope.dispatchEvent( { type: 'load', content: geometry } ); 25 | 26 | if ( callback ) callback( geometry ); 27 | 28 | }, false ); 29 | 30 | request.addEventListener( 'progress', function ( event ) { 31 | 32 | scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } ); 33 | 34 | }, false ); 35 | 36 | request.addEventListener( 'error', function () { 37 | 38 | scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } ); 39 | 40 | }, false ); 41 | 42 | request.open( 'GET', url, true ); 43 | request.send( null ); 44 | 45 | }, 46 | 47 | parse: function ( data ) { 48 | 49 | var geometry = new THREE.Geometry(); 50 | 51 | function vertex( x, y, z ) { 52 | 53 | geometry.vertices.push( new THREE.Vector3( x, y, z ) ); 54 | 55 | } 56 | 57 | function face3( a, b, c ) { 58 | 59 | geometry.faces.push( new THREE.Face3( a, b, c ) ); 60 | 61 | } 62 | 63 | function face4( a, b, c, d ) { 64 | 65 | geometry.faces.push( new THREE.Face4( a, b, c, d ) ); 66 | 67 | } 68 | 69 | var pattern, result; 70 | 71 | // float float float 72 | 73 | pattern = /([\+|\-]?[\d]+[\.][\d|\-|e]+)[ ]+([\+|\-]?[\d]+[\.][\d|\-|e]+)[ ]+([\+|\-]?[\d]+[\.][\d|\-|e]+)/g; 74 | 75 | while ( ( result = pattern.exec( data ) ) != null ) { 76 | 77 | // ["1.0 2.0 3.0", "1.0", "2.0", "3.0"] 78 | 79 | vertex( parseFloat( result[ 1 ] ), parseFloat( result[ 2 ] ), parseFloat( result[ 3 ] ) ); 80 | 81 | } 82 | 83 | // 3 int int int 84 | 85 | pattern = /3[ ]+([\d]+)[ ]+([\d]+)[ ]+([\d]+)/g; 86 | 87 | while ( ( result = pattern.exec( data ) ) != null ) { 88 | 89 | // ["3 1 2 3", "1", "2", "3"] 90 | 91 | face3( parseInt( result[ 1 ] ), parseInt( result[ 2 ] ), parseInt( result[ 3 ] ) ); 92 | 93 | } 94 | 95 | // 4 int int int int 96 | 97 | pattern = /4[ ]+([\d]+)[ ]+([\d]+)[ ]+([\d]+)[ ]+([\d]+)/g; 98 | 99 | while ( ( result = pattern.exec( data ) ) != null ) { 100 | 101 | // ["4 1 2 3 4", "1", "2", "3", "4"] 102 | 103 | face4( parseInt( result[ 1 ] ), parseInt( result[ 2 ] ), parseInt( result[ 3 ] ), parseInt( result[ 4 ] ) ); 104 | 105 | } 106 | 107 | geometry.computeCentroids(); 108 | geometry.computeFaceNormals(); 109 | geometry.computeVertexNormals(); 110 | geometry.computeBoundingSphere(); 111 | 112 | return geometry; 113 | 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /js/tween.min.js: -------------------------------------------------------------------------------- 1 | // tween.js - http://github.com/sole/tween.js 2 | 'use strict';var TWEEN=TWEEN||function(){var a=[];return{REVISION:"10",getAll:function(){return a},removeAll:function(){a=[]},add:function(c){a.push(c)},remove:function(c){c=a.indexOf(c);-1!==c&&a.splice(c,1)},update:function(c){if(0===a.length)return!1;for(var b=0,d=a.length,c=void 0!==c?c:void 0!==window.performance&&void 0!==window.performance.now?window.performance.now():Date.now();b(a*=2)?0.5*a*a:-0.5*(--a*(a-2)-1)}},Cubic:{In:function(a){return a*a*a},Out:function(a){return--a*a*a+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*a:0.5*((a-=2)*a*a+2)}},Quartic:{In:function(a){return a*a*a*a},Out:function(a){return 1- --a*a*a*a},InOut:function(a){return 1>(a*=2)?0.5*a*a*a*a:-0.5*((a-=2)*a*a*a-2)}},Quintic:{In:function(a){return a*a*a* 7 | a*a},Out:function(a){return--a*a*a*a*a+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*a*a*a:0.5*((a-=2)*a*a*a*a+2)}},Sinusoidal:{In:function(a){return 1-Math.cos(a*Math.PI/2)},Out:function(a){return Math.sin(a*Math.PI/2)},InOut:function(a){return 0.5*(1-Math.cos(Math.PI*a))}},Exponential:{In:function(a){return 0===a?0:Math.pow(1024,a-1)},Out:function(a){return 1===a?1:1-Math.pow(2,-10*a)},InOut:function(a){return 0===a?0:1===a?1:1>(a*=2)?0.5*Math.pow(1024,a-1):0.5*(-Math.pow(2,-10*(a-1))+2)}},Circular:{In:function(a){return 1- 8 | Math.sqrt(1-a*a)},Out:function(a){return Math.sqrt(1- --a*a)},InOut:function(a){return 1>(a*=2)?-0.5*(Math.sqrt(1-a*a)-1):0.5*(Math.sqrt(1-(a-=2)*a)+1)}},Elastic:{In:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return-(b*Math.pow(2,10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4))},Out:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return b*Math.pow(2,-10*a)*Math.sin((a-c)* 9 | 2*Math.PI/0.4)+1},InOut:function(a){var c,b=0.1;if(0===a)return 0;if(1===a)return 1;!b||1>b?(b=1,c=0.1):c=0.4*Math.asin(1/b)/(2*Math.PI);return 1>(a*=2)?-0.5*b*Math.pow(2,10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4):0.5*b*Math.pow(2,-10*(a-=1))*Math.sin((a-c)*2*Math.PI/0.4)+1}},Back:{In:function(a){return a*a*(2.70158*a-1.70158)},Out:function(a){return--a*a*(2.70158*a+1.70158)+1},InOut:function(a){return 1>(a*=2)?0.5*a*a*(3.5949095*a-2.5949095):0.5*((a-=2)*a*(3.5949095*a+2.5949095)+2)}},Bounce:{In:function(a){return 1- 10 | TWEEN.Easing.Bounce.Out(1-a)},Out:function(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+0.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+0.9375:7.5625*(a-=2.625/2.75)*a+0.984375},InOut:function(a){return 0.5>a?0.5*TWEEN.Easing.Bounce.In(2*a):0.5*TWEEN.Easing.Bounce.Out(2*a-1)+0.5}}}; 11 | TWEEN.Interpolation={Linear:function(a,c){var b=a.length-1,d=b*c,e=Math.floor(d),g=TWEEN.Interpolation.Utils.Linear;return 0>c?g(a[0],a[1],d):1b?b:e+1],d-e)},Bezier:function(a,c){var b=0,d=a.length-1,e=Math.pow,g=TWEEN.Interpolation.Utils.Bernstein,i;for(i=0;i<=d;i++)b+=e(1-c,d-i)*e(c,i)*a[i]*g(d,i);return b},CatmullRom:function(a,c){var b=a.length-1,d=b*c,e=Math.floor(d),g=TWEEN.Interpolation.Utils.CatmullRom;return a[0]===a[b]?(0>c&&(e=Math.floor(d=b*(1+c))),g(a[(e- 12 | 1+b)%b],a[e],a[(e+1)%b],a[(e+2)%b],d-e)):0>c?a[0]-(g(a[0],a[0],a[1],a[1],-d)-a[0]):1