├── 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 | 
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