├── .gitignore ├── README.md ├── audio ├── 235428__allanz10d__calm-ocean-breeze-simulation.ogg └── 52450__inchadney__craw.wav ├── images ├── background-overlay.png ├── background.jpg ├── louvre-overlay.png ├── louvre.jpg ├── puydesancy-overlay.png ├── puydesancy.jpg ├── tintamarre-overlay.png └── tintamarre.jpg ├── index.html ├── js ├── OrbitControls.js ├── VRControls.js ├── VREffect.js ├── lib │ └── fetch.js ├── main.js ├── three.min.js ├── tween.min.js └── webvr-polyfill.js └── panos.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 360 VR panorama 2 | 3 | Use arrow keys to move between panorama photo's. 4 | 5 | [Click here to view project on Mozvr.com](http://mozvr.com/projects/panorama-viewer) 6 | 7 | -------------------------------------------------------------------------------- /audio/235428__allanz10d__calm-ocean-breeze-simulation.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MozillaReality/panorama-viewer/81929a6d46b41e54dcbf7ad3a1c191bedb47fb0d/audio/235428__allanz10d__calm-ocean-breeze-simulation.ogg -------------------------------------------------------------------------------- /audio/52450__inchadney__craw.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MozillaReality/panorama-viewer/81929a6d46b41e54dcbf7ad3a1c191bedb47fb0d/audio/52450__inchadney__craw.wav -------------------------------------------------------------------------------- /images/background-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MozillaReality/panorama-viewer/81929a6d46b41e54dcbf7ad3a1c191bedb47fb0d/images/background-overlay.png -------------------------------------------------------------------------------- /images/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MozillaReality/panorama-viewer/81929a6d46b41e54dcbf7ad3a1c191bedb47fb0d/images/background.jpg -------------------------------------------------------------------------------- /images/louvre-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MozillaReality/panorama-viewer/81929a6d46b41e54dcbf7ad3a1c191bedb47fb0d/images/louvre-overlay.png -------------------------------------------------------------------------------- /images/louvre.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MozillaReality/panorama-viewer/81929a6d46b41e54dcbf7ad3a1c191bedb47fb0d/images/louvre.jpg -------------------------------------------------------------------------------- /images/puydesancy-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MozillaReality/panorama-viewer/81929a6d46b41e54dcbf7ad3a1c191bedb47fb0d/images/puydesancy-overlay.png -------------------------------------------------------------------------------- /images/puydesancy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MozillaReality/panorama-viewer/81929a6d46b41e54dcbf7ad3a1c191bedb47fb0d/images/puydesancy.jpg -------------------------------------------------------------------------------- /images/tintamarre-overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MozillaReality/panorama-viewer/81929a6d46b41e54dcbf7ad3a1c191bedb47fb0d/images/tintamarre-overlay.png -------------------------------------------------------------------------------- /images/tintamarre.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MozillaReality/panorama-viewer/81929a6d46b41e54dcbf7ad3a1c191bedb47fb0d/images/tintamarre.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Panoramas 5 | 6 | 7 | 8 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /js/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 | /*global THREE, console */ 9 | 10 | ( function () { 11 | 12 | function OrbitConstraint ( object ) { 13 | 14 | this.object = object; 15 | 16 | // "target" sets the location of focus, where the object orbits around 17 | // and where it pans with respect to. 18 | this.target = new THREE.Vector3(); 19 | 20 | // Limits to how far you can dolly in and out ( PerspectiveCamera only ) 21 | this.minDistance = 0; 22 | this.maxDistance = Infinity; 23 | 24 | // Limits to how far you can zoom in and out ( OrthographicCamera only ) 25 | this.minZoom = 0; 26 | this.maxZoom = Infinity; 27 | 28 | // How far you can orbit vertically, upper and lower limits. 29 | // Range is 0 to Math.PI radians. 30 | this.minPolarAngle = 0; // radians 31 | this.maxPolarAngle = Math.PI; // radians 32 | 33 | // How far you can orbit horizontally, upper and lower limits. 34 | // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. 35 | this.minAzimuthAngle = - Infinity; // radians 36 | this.maxAzimuthAngle = Infinity; // radians 37 | 38 | // Set to true to enable damping (inertia) 39 | // If damping is enabled, you must call controls.update() in your animation loop 40 | this.enableDamping = false; 41 | this.dampingFactor = 0.25; 42 | 43 | //////////// 44 | // internals 45 | 46 | var scope = this; 47 | 48 | var EPS = 0.000001; 49 | 50 | // Current position in spherical coordinate system. 51 | var theta; 52 | var phi; 53 | 54 | // Pending changes 55 | var phiDelta = 0; 56 | var thetaDelta = 0; 57 | var scale = 1; 58 | var panOffset = new THREE.Vector3(); 59 | var zoomChanged = false; 60 | 61 | // API 62 | 63 | this.getPolarAngle = function () { 64 | 65 | return phi; 66 | 67 | }; 68 | 69 | this.getAzimuthalAngle = function () { 70 | 71 | return theta; 72 | 73 | }; 74 | 75 | this.rotateLeft = function ( angle ) { 76 | 77 | thetaDelta -= angle; 78 | 79 | }; 80 | 81 | this.rotateUp = function ( angle ) { 82 | 83 | phiDelta -= angle; 84 | 85 | }; 86 | 87 | // pass in distance in world space to move left 88 | this.panLeft = function() { 89 | 90 | var v = new THREE.Vector3(); 91 | 92 | return function panLeft ( distance ) { 93 | 94 | var te = this.object.matrix.elements; 95 | 96 | // get X column of matrix 97 | v.set( te[ 0 ], te[ 1 ], te[ 2 ] ); 98 | v.multiplyScalar( - distance ); 99 | 100 | panOffset.add( v ); 101 | 102 | }; 103 | 104 | }(); 105 | 106 | // pass in distance in world space to move up 107 | this.panUp = function() { 108 | 109 | var v = new THREE.Vector3(); 110 | 111 | return function panUp ( distance ) { 112 | 113 | var te = this.object.matrix.elements; 114 | 115 | // get Y column of matrix 116 | v.set( te[ 4 ], te[ 5 ], te[ 6 ] ); 117 | v.multiplyScalar( distance ); 118 | 119 | panOffset.add( v ); 120 | 121 | }; 122 | 123 | }(); 124 | 125 | // pass in x,y of change desired in pixel space, 126 | // right and down are positive 127 | this.pan = function ( deltaX, deltaY, screenWidth, screenHeight ) { 128 | 129 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 130 | 131 | // perspective 132 | var position = scope.object.position; 133 | var offset = position.clone().sub( scope.target ); 134 | var targetDistance = offset.length(); 135 | 136 | // half of the fov is center to top of screen 137 | targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); 138 | 139 | // we actually don't use screenWidth, since perspective camera is fixed to screen height 140 | scope.panLeft( 2 * deltaX * targetDistance / screenHeight ); 141 | scope.panUp( 2 * deltaY * targetDistance / screenHeight ); 142 | 143 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 144 | 145 | // orthographic 146 | scope.panLeft( deltaX * ( scope.object.right - scope.object.left ) / screenWidth ); 147 | scope.panUp( deltaY * ( scope.object.top - scope.object.bottom ) / screenHeight ); 148 | 149 | } else { 150 | 151 | // camera neither orthographic or perspective 152 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); 153 | 154 | } 155 | 156 | }; 157 | 158 | this.dollyIn = function ( dollyScale ) { 159 | 160 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 161 | 162 | scale /= dollyScale; 163 | 164 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 165 | 166 | scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom * dollyScale ) ); 167 | scope.object.updateProjectionMatrix(); 168 | zoomChanged = true; 169 | 170 | } else { 171 | 172 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 173 | 174 | } 175 | 176 | }; 177 | 178 | this.dollyOut = function ( dollyScale ) { 179 | 180 | if ( scope.object instanceof THREE.PerspectiveCamera ) { 181 | 182 | scale *= dollyScale; 183 | 184 | } else if ( scope.object instanceof THREE.OrthographicCamera ) { 185 | 186 | scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / dollyScale ) ); 187 | scope.object.updateProjectionMatrix(); 188 | zoomChanged = true; 189 | 190 | } else { 191 | 192 | console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); 193 | 194 | } 195 | 196 | }; 197 | 198 | this.update = function() { 199 | 200 | var offset = new THREE.Vector3(); 201 | 202 | // so camera.up is the orbit axis 203 | var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); 204 | var quatInverse = quat.clone().inverse(); 205 | 206 | var lastPosition = new THREE.Vector3(); 207 | var lastQuaternion = new THREE.Quaternion(); 208 | 209 | return function () { 210 | 211 | var position = this.object.position; 212 | 213 | offset.copy( position ).sub( this.target ); 214 | 215 | // rotate offset to "y-axis-is-up" space 216 | offset.applyQuaternion( quat ); 217 | 218 | // angle from z-axis around y-axis 219 | 220 | theta = Math.atan2( offset.x, offset.z ); 221 | 222 | // angle from y-axis 223 | 224 | phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y ); 225 | 226 | theta += thetaDelta; 227 | phi += phiDelta; 228 | 229 | // restrict theta to be between desired limits 230 | theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) ); 231 | 232 | // restrict phi to be between desired limits 233 | phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) ); 234 | 235 | // restrict phi to be betwee EPS and PI-EPS 236 | phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) ); 237 | 238 | var radius = offset.length() * scale; 239 | 240 | // restrict radius to be between desired limits 241 | radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) ); 242 | 243 | // move target to panned location 244 | this.target.add( panOffset ); 245 | 246 | offset.x = radius * Math.sin( phi ) * Math.sin( theta ); 247 | offset.y = radius * Math.cos( phi ); 248 | offset.z = radius * Math.sin( phi ) * Math.cos( theta ); 249 | 250 | // rotate offset back to "camera-up-vector-is-up" space 251 | offset.applyQuaternion( quatInverse ); 252 | 253 | position.copy( this.target ).add( offset ); 254 | 255 | this.object.lookAt( this.target ); 256 | 257 | if ( this.enableDamping === true ) { 258 | 259 | thetaDelta *= ( 1 - this.dampingFactor ); 260 | phiDelta *= ( 1 - this.dampingFactor ); 261 | 262 | } else { 263 | 264 | thetaDelta = 0; 265 | phiDelta = 0; 266 | 267 | } 268 | 269 | scale = 1; 270 | panOffset.set( 0, 0, 0 ); 271 | 272 | // update condition is: 273 | // min(camera displacement, camera rotation in radians)^2 > EPS 274 | // using small-angle approximation cos(x/2) = 1 - x^2 / 8 275 | 276 | if ( zoomChanged || 277 | lastPosition.distanceToSquared( this.object.position ) > EPS || 278 | 8 * ( 1 - lastQuaternion.dot( this.object.quaternion ) ) > EPS ) { 279 | 280 | lastPosition.copy( this.object.position ); 281 | lastQuaternion.copy( this.object.quaternion ); 282 | zoomChanged = false; 283 | 284 | return true; 285 | 286 | } 287 | 288 | return false; 289 | 290 | }; 291 | 292 | }(); 293 | 294 | }; 295 | 296 | 297 | // This set of controls performs orbiting, dollying (zooming), and panning. It maintains 298 | // the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is 299 | // supported. 300 | // 301 | // Orbit - left mouse / touch: one finger move 302 | // Zoom - middle mouse, or mousewheel / touch: two finger spread or squish 303 | // Pan - right mouse, or arrow keys / touch: three finter swipe 304 | 305 | THREE.OrbitControls = function ( object, domElement ) { 306 | 307 | var constraint = new OrbitConstraint( object ); 308 | 309 | this.domElement = ( domElement !== undefined ) ? domElement : document; 310 | 311 | // API 312 | 313 | Object.defineProperty( this, 'constraint', { 314 | 315 | get: function() { 316 | 317 | return constraint; 318 | 319 | } 320 | 321 | } ); 322 | 323 | this.getPolarAngle = function () { 324 | 325 | return constraint.getPolarAngle(); 326 | 327 | }; 328 | 329 | this.getAzimuthalAngle = function () { 330 | 331 | return constraint.getAzimuthalAngle(); 332 | 333 | }; 334 | 335 | // Set to false to disable this control 336 | this.enabled = true; 337 | 338 | // center is old, deprecated; use "target" instead 339 | this.center = this.target; 340 | 341 | // This option actually enables dollying in and out; left as "zoom" for 342 | // backwards compatibility. 343 | // Set to false to disable zooming 344 | this.enableZoom = true; 345 | this.zoomSpeed = 1.0; 346 | 347 | // Set to false to disable rotating 348 | this.enableRotate = true; 349 | this.rotateSpeed = 1.0; 350 | 351 | // Set to false to disable panning 352 | this.enablePan = true; 353 | this.keyPanSpeed = 7.0; // pixels moved per arrow key push 354 | 355 | // Set to true to automatically rotate around the target 356 | // If auto-rotate is enabled, you must call controls.update() in your animation loop 357 | this.autoRotate = false; 358 | this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 359 | 360 | // Set to false to disable use of the keys 361 | this.enableKeys = true; 362 | 363 | // The four arrow keys 364 | this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; 365 | 366 | // Mouse buttons 367 | this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; 368 | 369 | //////////// 370 | // internals 371 | 372 | var scope = this; 373 | 374 | var rotateStart = new THREE.Vector2(); 375 | var rotateEnd = new THREE.Vector2(); 376 | var rotateDelta = new THREE.Vector2(); 377 | 378 | var panStart = new THREE.Vector2(); 379 | var panEnd = new THREE.Vector2(); 380 | var panDelta = new THREE.Vector2(); 381 | 382 | var dollyStart = new THREE.Vector2(); 383 | var dollyEnd = new THREE.Vector2(); 384 | var dollyDelta = new THREE.Vector2(); 385 | 386 | var STATE = { NONE : - 1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 }; 387 | 388 | var state = STATE.NONE; 389 | 390 | // for reset 391 | 392 | this.target0 = this.target.clone(); 393 | this.position0 = this.object.position.clone(); 394 | this.zoom0 = this.object.zoom; 395 | 396 | // events 397 | 398 | var changeEvent = { type: 'change' }; 399 | var startEvent = { type: 'start' }; 400 | var endEvent = { type: 'end' }; 401 | 402 | // pass in x,y of change desired in pixel space, 403 | // right and down are positive 404 | function pan( deltaX, deltaY ) { 405 | 406 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 407 | 408 | constraint.pan( deltaX, deltaY, element.clientWidth, element.clientHeight ); 409 | 410 | } 411 | 412 | this.update = function () { 413 | 414 | if ( this.autoRotate && state === STATE.NONE ) { 415 | 416 | constraint.rotateLeft( getAutoRotationAngle() ); 417 | 418 | } 419 | 420 | if ( constraint.update() === true ) { 421 | 422 | this.dispatchEvent( changeEvent ); 423 | 424 | } 425 | 426 | }; 427 | 428 | this.reset = function () { 429 | 430 | state = STATE.NONE; 431 | 432 | this.target.copy( this.target0 ); 433 | this.object.position.copy( this.position0 ); 434 | this.object.zoom = this.zoom0; 435 | 436 | this.object.updateProjectionMatrix(); 437 | this.dispatchEvent( changeEvent ); 438 | 439 | this.update(); 440 | 441 | }; 442 | 443 | function getAutoRotationAngle() { 444 | 445 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 446 | 447 | } 448 | 449 | function getZoomScale() { 450 | 451 | return Math.pow( 0.95, scope.zoomSpeed ); 452 | 453 | } 454 | 455 | function onMouseDown( event ) { 456 | 457 | if ( scope.enabled === false ) return; 458 | 459 | event.preventDefault(); 460 | 461 | if ( event.button === scope.mouseButtons.ORBIT ) { 462 | 463 | if ( scope.enableRotate === false ) return; 464 | 465 | state = STATE.ROTATE; 466 | 467 | rotateStart.set( event.clientX, event.clientY ); 468 | 469 | } else if ( event.button === scope.mouseButtons.ZOOM ) { 470 | 471 | if ( scope.enableZoom === false ) return; 472 | 473 | state = STATE.DOLLY; 474 | 475 | dollyStart.set( event.clientX, event.clientY ); 476 | 477 | } else if ( event.button === scope.mouseButtons.PAN ) { 478 | 479 | if ( scope.enablePan === false ) return; 480 | 481 | state = STATE.PAN; 482 | 483 | panStart.set( event.clientX, event.clientY ); 484 | 485 | } 486 | 487 | if ( state !== STATE.NONE ) { 488 | 489 | document.addEventListener( 'mousemove', onMouseMove, false ); 490 | document.addEventListener( 'mouseup', onMouseUp, false ); 491 | scope.dispatchEvent( startEvent ); 492 | 493 | } 494 | 495 | } 496 | 497 | function onMouseMove( event ) { 498 | 499 | if ( scope.enabled === false ) return; 500 | 501 | event.preventDefault(); 502 | 503 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 504 | 505 | if ( state === STATE.ROTATE ) { 506 | 507 | if ( scope.enableRotate === false ) return; 508 | 509 | rotateEnd.set( event.clientX, event.clientY ); 510 | rotateDelta.subVectors( rotateEnd, rotateStart ); 511 | 512 | // rotating across whole screen goes 360 degrees around 513 | constraint.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 514 | 515 | // rotating up and down along whole screen attempts to go 360, but limited to 180 516 | constraint.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 517 | 518 | rotateStart.copy( rotateEnd ); 519 | 520 | } else if ( state === STATE.DOLLY ) { 521 | 522 | if ( scope.enableZoom === false ) return; 523 | 524 | dollyEnd.set( event.clientX, event.clientY ); 525 | dollyDelta.subVectors( dollyEnd, dollyStart ); 526 | 527 | if ( dollyDelta.y > 0 ) { 528 | 529 | constraint.dollyIn( getZoomScale() ); 530 | 531 | } else if ( dollyDelta.y < 0 ) { 532 | 533 | constraint.dollyOut( getZoomScale() ); 534 | 535 | } 536 | 537 | dollyStart.copy( dollyEnd ); 538 | 539 | } else if ( state === STATE.PAN ) { 540 | 541 | if ( scope.enablePan === false ) return; 542 | 543 | panEnd.set( event.clientX, event.clientY ); 544 | panDelta.subVectors( panEnd, panStart ); 545 | 546 | pan( panDelta.x, panDelta.y ); 547 | 548 | panStart.copy( panEnd ); 549 | 550 | } 551 | 552 | if ( state !== STATE.NONE ) scope.update(); 553 | 554 | } 555 | 556 | function onMouseUp( /* event */ ) { 557 | 558 | if ( scope.enabled === false ) return; 559 | 560 | document.removeEventListener( 'mousemove', onMouseMove, false ); 561 | document.removeEventListener( 'mouseup', onMouseUp, false ); 562 | scope.dispatchEvent( endEvent ); 563 | state = STATE.NONE; 564 | 565 | } 566 | 567 | function onMouseWheel( event ) { 568 | 569 | if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return; 570 | 571 | event.preventDefault(); 572 | event.stopPropagation(); 573 | 574 | var delta = 0; 575 | 576 | if ( event.wheelDelta !== undefined ) { 577 | 578 | // WebKit / Opera / Explorer 9 579 | 580 | delta = event.wheelDelta; 581 | 582 | } else if ( event.detail !== undefined ) { 583 | 584 | // Firefox 585 | 586 | delta = - event.detail; 587 | 588 | } 589 | 590 | if ( delta > 0 ) { 591 | 592 | constraint.dollyOut( getZoomScale() ); 593 | 594 | } else if ( delta < 0 ) { 595 | 596 | constraint.dollyIn( getZoomScale() ); 597 | 598 | } 599 | 600 | scope.update(); 601 | scope.dispatchEvent( startEvent ); 602 | scope.dispatchEvent( endEvent ); 603 | 604 | } 605 | 606 | function onKeyDown( event ) { 607 | 608 | if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; 609 | 610 | switch ( event.keyCode ) { 611 | 612 | case scope.keys.UP: 613 | pan( 0, scope.keyPanSpeed ); 614 | scope.update(); 615 | break; 616 | 617 | case scope.keys.BOTTOM: 618 | pan( 0, - scope.keyPanSpeed ); 619 | scope.update(); 620 | break; 621 | 622 | case scope.keys.LEFT: 623 | pan( scope.keyPanSpeed, 0 ); 624 | scope.update(); 625 | break; 626 | 627 | case scope.keys.RIGHT: 628 | pan( - scope.keyPanSpeed, 0 ); 629 | scope.update(); 630 | break; 631 | 632 | } 633 | 634 | } 635 | 636 | function touchstart( event ) { 637 | 638 | if ( scope.enabled === false ) return; 639 | 640 | switch ( event.touches.length ) { 641 | 642 | case 1: // one-fingered touch: rotate 643 | 644 | if ( scope.enableRotate === false ) return; 645 | 646 | state = STATE.TOUCH_ROTATE; 647 | 648 | rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 649 | break; 650 | 651 | case 2: // two-fingered touch: dolly 652 | 653 | if ( scope.enableZoom === false ) return; 654 | 655 | state = STATE.TOUCH_DOLLY; 656 | 657 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 658 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 659 | var distance = Math.sqrt( dx * dx + dy * dy ); 660 | dollyStart.set( 0, distance ); 661 | break; 662 | 663 | case 3: // three-fingered touch: pan 664 | 665 | if ( scope.enablePan === false ) return; 666 | 667 | state = STATE.TOUCH_PAN; 668 | 669 | panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 670 | break; 671 | 672 | default: 673 | 674 | state = STATE.NONE; 675 | 676 | } 677 | 678 | if ( state !== STATE.NONE ) scope.dispatchEvent( startEvent ); 679 | 680 | } 681 | 682 | function touchmove( event ) { 683 | 684 | if ( scope.enabled === false ) return; 685 | 686 | event.preventDefault(); 687 | event.stopPropagation(); 688 | 689 | var element = scope.domElement === document ? scope.domElement.body : scope.domElement; 690 | 691 | switch ( event.touches.length ) { 692 | 693 | case 1: // one-fingered touch: rotate 694 | 695 | if ( scope.enableRotate === false ) return; 696 | if ( state !== STATE.TOUCH_ROTATE ) return; 697 | 698 | rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 699 | rotateDelta.subVectors( rotateEnd, rotateStart ); 700 | 701 | // rotating across whole screen goes 360 degrees around 702 | constraint.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); 703 | // rotating up and down along whole screen attempts to go 360, but limited to 180 704 | constraint.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); 705 | 706 | rotateStart.copy( rotateEnd ); 707 | 708 | scope.update(); 709 | break; 710 | 711 | case 2: // two-fingered touch: dolly 712 | 713 | if ( scope.enableZoom === false ) return; 714 | if ( state !== STATE.TOUCH_DOLLY ) return; 715 | 716 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 717 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 718 | var distance = Math.sqrt( dx * dx + dy * dy ); 719 | 720 | dollyEnd.set( 0, distance ); 721 | dollyDelta.subVectors( dollyEnd, dollyStart ); 722 | 723 | if ( dollyDelta.y > 0 ) { 724 | 725 | constraint.dollyOut( getZoomScale() ); 726 | 727 | } else if ( dollyDelta.y < 0 ) { 728 | 729 | constraint.dollyIn( getZoomScale() ); 730 | 731 | } 732 | 733 | dollyStart.copy( dollyEnd ); 734 | 735 | scope.update(); 736 | break; 737 | 738 | case 3: // three-fingered touch: pan 739 | 740 | if ( scope.enablePan === false ) return; 741 | if ( state !== STATE.TOUCH_PAN ) return; 742 | 743 | panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 744 | panDelta.subVectors( panEnd, panStart ); 745 | 746 | pan( panDelta.x, panDelta.y ); 747 | 748 | panStart.copy( panEnd ); 749 | 750 | scope.update(); 751 | break; 752 | 753 | default: 754 | 755 | state = STATE.NONE; 756 | 757 | } 758 | 759 | } 760 | 761 | function touchend( /* event */ ) { 762 | 763 | if ( scope.enabled === false ) return; 764 | 765 | scope.dispatchEvent( endEvent ); 766 | state = STATE.NONE; 767 | 768 | } 769 | 770 | function contextmenu( event ) { 771 | 772 | event.preventDefault(); 773 | 774 | } 775 | 776 | this.dispose = function() { 777 | 778 | this.domElement.removeEventListener( 'contextmenu', contextmenu, false ); 779 | this.domElement.removeEventListener( 'mousedown', onMouseDown, false ); 780 | this.domElement.removeEventListener( 'mousewheel', onMouseWheel, false ); 781 | this.domElement.removeEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox 782 | 783 | this.domElement.removeEventListener( 'touchstart', touchstart, false ); 784 | this.domElement.removeEventListener( 'touchend', touchend, false ); 785 | this.domElement.removeEventListener( 'touchmove', touchmove, false ); 786 | 787 | document.removeEventListener( 'mousemove', onMouseMove, false ); 788 | document.removeEventListener( 'mouseup', onMouseUp, false ); 789 | 790 | window.removeEventListener( 'keydown', onKeyDown, false ); 791 | 792 | } 793 | 794 | this.domElement.addEventListener( 'contextmenu', contextmenu, false ); 795 | 796 | this.domElement.addEventListener( 'mousedown', onMouseDown, false ); 797 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 798 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox 799 | 800 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 801 | this.domElement.addEventListener( 'touchend', touchend, false ); 802 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 803 | 804 | window.addEventListener( 'keydown', onKeyDown, false ); 805 | 806 | // force an update at start 807 | this.update(); 808 | 809 | }; 810 | 811 | THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); 812 | THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; 813 | 814 | Object.defineProperties( THREE.OrbitControls.prototype, { 815 | 816 | object: { 817 | 818 | get: function () { 819 | 820 | return this.constraint.object; 821 | 822 | } 823 | 824 | }, 825 | 826 | target: { 827 | 828 | get: function () { 829 | 830 | return this.constraint.target; 831 | 832 | }, 833 | 834 | set: function ( value ) { 835 | 836 | console.warn( 'THREE.OrbitControls: target is now immutable. Use target.set() instead.' ); 837 | this.constraint.target.copy( value ); 838 | 839 | } 840 | 841 | }, 842 | 843 | minDistance : { 844 | 845 | get: function () { 846 | 847 | return this.constraint.minDistance; 848 | 849 | }, 850 | 851 | set: function ( value ) { 852 | 853 | this.constraint.minDistance = value; 854 | 855 | } 856 | 857 | }, 858 | 859 | maxDistance : { 860 | 861 | get: function () { 862 | 863 | return this.constraint.maxDistance; 864 | 865 | }, 866 | 867 | set: function ( value ) { 868 | 869 | this.constraint.maxDistance = value; 870 | 871 | } 872 | 873 | }, 874 | 875 | minZoom : { 876 | 877 | get: function () { 878 | 879 | return this.constraint.minZoom; 880 | 881 | }, 882 | 883 | set: function ( value ) { 884 | 885 | this.constraint.minZoom = value; 886 | 887 | } 888 | 889 | }, 890 | 891 | maxZoom : { 892 | 893 | get: function () { 894 | 895 | return this.constraint.maxZoom; 896 | 897 | }, 898 | 899 | set: function ( value ) { 900 | 901 | this.constraint.maxZoom = value; 902 | 903 | } 904 | 905 | }, 906 | 907 | minPolarAngle : { 908 | 909 | get: function () { 910 | 911 | return this.constraint.minPolarAngle; 912 | 913 | }, 914 | 915 | set: function ( value ) { 916 | 917 | this.constraint.minPolarAngle = value; 918 | 919 | } 920 | 921 | }, 922 | 923 | maxPolarAngle : { 924 | 925 | get: function () { 926 | 927 | return this.constraint.maxPolarAngle; 928 | 929 | }, 930 | 931 | set: function ( value ) { 932 | 933 | this.constraint.maxPolarAngle = value; 934 | 935 | } 936 | 937 | }, 938 | 939 | minAzimuthAngle : { 940 | 941 | get: function () { 942 | 943 | return this.constraint.minAzimuthAngle; 944 | 945 | }, 946 | 947 | set: function ( value ) { 948 | 949 | this.constraint.minAzimuthAngle = value; 950 | 951 | } 952 | 953 | }, 954 | 955 | maxAzimuthAngle : { 956 | 957 | get: function () { 958 | 959 | return this.constraint.maxAzimuthAngle; 960 | 961 | }, 962 | 963 | set: function ( value ) { 964 | 965 | this.constraint.maxAzimuthAngle = value; 966 | 967 | } 968 | 969 | }, 970 | 971 | enableDamping : { 972 | 973 | get: function () { 974 | 975 | return this.constraint.enableDamping; 976 | 977 | }, 978 | 979 | set: function ( value ) { 980 | 981 | this.constraint.enableDamping = value; 982 | 983 | } 984 | 985 | }, 986 | 987 | dampingFactor : { 988 | 989 | get: function () { 990 | 991 | return this.constraint.dampingFactor; 992 | 993 | }, 994 | 995 | set: function ( value ) { 996 | 997 | this.constraint.dampingFactor = value; 998 | 999 | } 1000 | 1001 | }, 1002 | 1003 | // backward compatibility 1004 | 1005 | noZoom: { 1006 | 1007 | get: function () { 1008 | 1009 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 1010 | return ! this.enableZoom; 1011 | 1012 | }, 1013 | 1014 | set: function ( value ) { 1015 | 1016 | console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); 1017 | this.enableZoom = ! value; 1018 | 1019 | } 1020 | 1021 | }, 1022 | 1023 | noRotate: { 1024 | 1025 | get: function () { 1026 | 1027 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 1028 | return ! this.enableRotate; 1029 | 1030 | }, 1031 | 1032 | set: function ( value ) { 1033 | 1034 | console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); 1035 | this.enableRotate = ! value; 1036 | 1037 | } 1038 | 1039 | }, 1040 | 1041 | noPan: { 1042 | 1043 | get: function () { 1044 | 1045 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 1046 | return ! this.enablePan; 1047 | 1048 | }, 1049 | 1050 | set: function ( value ) { 1051 | 1052 | console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); 1053 | this.enablePan = ! value; 1054 | 1055 | } 1056 | 1057 | }, 1058 | 1059 | noKeys: { 1060 | 1061 | get: function () { 1062 | 1063 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1064 | return ! this.enableKeys; 1065 | 1066 | }, 1067 | 1068 | set: function ( value ) { 1069 | 1070 | console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); 1071 | this.enableKeys = ! value; 1072 | 1073 | } 1074 | 1075 | }, 1076 | 1077 | staticMoving : { 1078 | 1079 | get: function () { 1080 | 1081 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1082 | return ! this.constraint.enableDamping; 1083 | 1084 | }, 1085 | 1086 | set: function ( value ) { 1087 | 1088 | console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); 1089 | this.constraint.enableDamping = ! value; 1090 | 1091 | } 1092 | 1093 | }, 1094 | 1095 | dynamicDampingFactor : { 1096 | 1097 | get: function () { 1098 | 1099 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1100 | return this.constraint.dampingFactor; 1101 | 1102 | }, 1103 | 1104 | set: function ( value ) { 1105 | 1106 | console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); 1107 | this.constraint.dampingFactor = value; 1108 | 1109 | } 1110 | 1111 | } 1112 | 1113 | } ); 1114 | 1115 | }() ); 1116 | -------------------------------------------------------------------------------- /js/VRControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author dmarcos / https://github.com/dmarcos 3 | * @author mrdoob / http://mrdoob.com 4 | */ 5 | 6 | THREE.VRControls = function ( object, onError ) { 7 | 8 | var scope = this; 9 | 10 | var vrInputs = []; 11 | 12 | function filterInvalidDevices( devices ) { 13 | 14 | // Exclude Cardboard position sensor if Oculus exists. 15 | 16 | var oculusDevices = devices.filter( function ( device ) { 17 | 18 | return device.deviceName.toLowerCase().indexOf( 'oculus' ) !== - 1; 19 | 20 | } ); 21 | 22 | if ( oculusDevices.length >= 1 ) { 23 | 24 | return devices.filter( function ( device ) { 25 | 26 | return device.deviceName.toLowerCase().indexOf( 'cardboard' ) === - 1; 27 | 28 | } ); 29 | 30 | } else { 31 | 32 | return devices; 33 | 34 | } 35 | 36 | } 37 | 38 | function gotVRDevices( devices ) { 39 | 40 | devices = filterInvalidDevices( devices ); 41 | 42 | for ( var i = 0; i < devices.length; i ++ ) { 43 | 44 | if ( devices[ i ] instanceof PositionSensorVRDevice ) { 45 | 46 | vrInputs.push( devices[ i ] ); 47 | 48 | } 49 | 50 | } 51 | 52 | if ( onError ) onError( 'HMD not available' ); 53 | 54 | } 55 | 56 | if ( navigator.getVRDevices ) { 57 | 58 | navigator.getVRDevices().then( gotVRDevices ); 59 | 60 | } 61 | 62 | // the Rift SDK returns the position in meters 63 | // this scale factor allows the user to define how meters 64 | // are converted to scene units. 65 | 66 | this.scale = 1; 67 | 68 | this.update = function () { 69 | 70 | for ( var i = 0; i < vrInputs.length; i ++ ) { 71 | 72 | var vrInput = vrInputs[ i ]; 73 | 74 | var state = vrInput.getState(); 75 | 76 | if ( state.orientation !== null ) { 77 | 78 | object.quaternion.copy( state.orientation ); 79 | 80 | } 81 | 82 | if ( state.position !== null ) { 83 | 84 | object.position.copy( state.position ).multiplyScalar( scope.scale ); 85 | 86 | } 87 | 88 | } 89 | 90 | }; 91 | 92 | this.resetSensor = function () { 93 | 94 | for ( var i = 0; i < vrInputs.length; i ++ ) { 95 | 96 | var vrInput = vrInputs[ i ]; 97 | 98 | if ( vrInput.resetSensor !== undefined ) { 99 | 100 | vrInput.resetSensor(); 101 | 102 | } else if ( vrInput.zeroSensor !== undefined ) { 103 | 104 | vrInput.zeroSensor(); 105 | 106 | } 107 | 108 | } 109 | 110 | }; 111 | 112 | this.zeroSensor = function () { 113 | 114 | console.warn( 'THREE.VRControls: .zeroSensor() is now .resetSensor().' ); 115 | this.resetSensor(); 116 | 117 | }; 118 | 119 | this.dispose = function () { 120 | 121 | vrInputs = []; 122 | 123 | }; 124 | 125 | }; 126 | -------------------------------------------------------------------------------- /js/VREffect.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author dmarcos / https://github.com/dmarcos 3 | * @author mrdoob / http://mrdoob.com 4 | * 5 | * WebVR Spec: http://mozvr.github.io/webvr-spec/webvr.html 6 | * 7 | * Firefox: http://mozvr.com/downloads/ 8 | * Chromium: https://drive.google.com/folderview?id=0BzudLt22BqGRbW9WTHMtOWMzNjQ&usp=sharing#list 9 | * 10 | */ 11 | 12 | THREE.VREffect = function ( renderer, onError ) { 13 | 14 | var vrHMD; 15 | var eyeTranslationL, eyeFOVL; 16 | var eyeTranslationR, eyeFOVR; 17 | 18 | function gotVRDevices( devices ) { 19 | 20 | for ( var i = 0; i < devices.length; i ++ ) { 21 | 22 | if ( devices[ i ] instanceof HMDVRDevice ) { 23 | 24 | vrHMD = devices[ i ]; 25 | 26 | if ( vrHMD.getEyeParameters !== undefined ) { 27 | 28 | var eyeParamsL = vrHMD.getEyeParameters( 'left' ); 29 | var eyeParamsR = vrHMD.getEyeParameters( 'right' ); 30 | 31 | eyeTranslationL = eyeParamsL.eyeTranslation; 32 | eyeTranslationR = eyeParamsR.eyeTranslation; 33 | eyeFOVL = eyeParamsL.recommendedFieldOfView; 34 | eyeFOVR = eyeParamsR.recommendedFieldOfView; 35 | 36 | } else { 37 | 38 | // TODO: This is an older code path and not spec compliant. 39 | // It should be removed at some point in the near future. 40 | eyeTranslationL = vrHMD.getEyeTranslation( 'left' ); 41 | eyeTranslationR = vrHMD.getEyeTranslation( 'right' ); 42 | eyeFOVL = vrHMD.getRecommendedEyeFieldOfView( 'left' ); 43 | eyeFOVR = vrHMD.getRecommendedEyeFieldOfView( 'right' ); 44 | 45 | } 46 | 47 | break; // We keep the first we encounter 48 | 49 | } 50 | 51 | } 52 | 53 | if ( vrHMD === undefined ) { 54 | 55 | if ( onError ) onError( 'HMD not available' ); 56 | 57 | } 58 | 59 | } 60 | 61 | if ( navigator.getVRDevices ) { 62 | 63 | navigator.getVRDevices().then( gotVRDevices ); 64 | 65 | } 66 | 67 | // 68 | 69 | this.scale = 1; 70 | 71 | this.setSize = function( width, height ) { 72 | 73 | renderer.setSize( width, height ); 74 | 75 | }; 76 | 77 | // fullscreen 78 | 79 | var isFullscreen = false; 80 | 81 | var canvas = renderer.domElement; 82 | var fullscreenchange = canvas.mozRequestFullScreen ? 'mozfullscreenchange' : 'webkitfullscreenchange'; 83 | 84 | document.addEventListener( fullscreenchange, function ( event ) { 85 | 86 | isFullscreen = document.mozFullScreenElement || document.webkitFullscreenElement; 87 | 88 | }, false ); 89 | 90 | this.setFullScreen = function ( boolean ) { 91 | 92 | if ( vrHMD === undefined ) return; 93 | if ( isFullscreen === boolean ) return; 94 | 95 | if ( canvas.mozRequestFullScreen ) { 96 | 97 | canvas.mozRequestFullScreen( { vrDisplay: vrHMD } ); 98 | 99 | } else if ( canvas.webkitRequestFullscreen ) { 100 | 101 | canvas.webkitRequestFullscreen( { vrDisplay: vrHMD } ); 102 | 103 | } 104 | 105 | }; 106 | 107 | // render 108 | 109 | var cameraL = new THREE.PerspectiveCamera(); 110 | var cameraR = new THREE.PerspectiveCamera(); 111 | 112 | this.render = function ( scene, camera ) { 113 | 114 | if ( vrHMD ) { 115 | 116 | var sceneL, sceneR; 117 | 118 | if ( Array.isArray( scene ) ) { 119 | 120 | sceneL = scene[ 0 ]; 121 | sceneR = scene[ 1 ]; 122 | 123 | } else { 124 | 125 | sceneL = scene; 126 | sceneR = scene; 127 | 128 | } 129 | 130 | var size = renderer.getSize(); 131 | size.width /= 2; 132 | 133 | renderer.enableScissorTest( true ); 134 | renderer.clear(); 135 | 136 | if ( camera.parent === null ) camera.updateMatrixWorld(); 137 | 138 | cameraL.projectionMatrix = fovToProjection( eyeFOVL, true, camera.near, camera.far ); 139 | cameraR.projectionMatrix = fovToProjection( eyeFOVR, true, camera.near, camera.far ); 140 | 141 | camera.matrixWorld.decompose( cameraL.position, cameraL.quaternion, cameraL.scale ); 142 | camera.matrixWorld.decompose( cameraR.position, cameraR.quaternion, cameraR.scale ); 143 | 144 | cameraL.translateX( eyeTranslationL.x * this.scale ); 145 | cameraR.translateX( eyeTranslationR.x * this.scale ); 146 | 147 | // render left eye 148 | renderer.setViewport( 0, 0, size.width, size.height ); 149 | renderer.setScissor( 0, 0, size.width, size.height ); 150 | renderer.render( sceneL, cameraL ); 151 | 152 | // render right eye 153 | renderer.setViewport( size.width, 0, size.width, size.height ); 154 | renderer.setScissor( size.width, 0, size.width, size.height ); 155 | renderer.render( sceneR, cameraR ); 156 | 157 | renderer.enableScissorTest( false ); 158 | 159 | return; 160 | 161 | } 162 | 163 | // Regular render mode if not HMD 164 | 165 | if ( Array.isArray( scene ) ) scene = scene[ 0 ]; 166 | 167 | renderer.render( scene, camera ); 168 | 169 | }; 170 | 171 | // 172 | 173 | function fovToNDCScaleOffset( fov ) { 174 | 175 | var pxscale = 2.0 / ( fov.leftTan + fov.rightTan ); 176 | var pxoffset = ( fov.leftTan - fov.rightTan ) * pxscale * 0.5; 177 | var pyscale = 2.0 / ( fov.upTan + fov.downTan ); 178 | var pyoffset = ( fov.upTan - fov.downTan ) * pyscale * 0.5; 179 | return { scale: [ pxscale, pyscale ], offset: [ pxoffset, pyoffset ] }; 180 | 181 | } 182 | 183 | function fovPortToProjection( fov, rightHanded, zNear, zFar ) { 184 | 185 | rightHanded = rightHanded === undefined ? true : rightHanded; 186 | zNear = zNear === undefined ? 0.01 : zNear; 187 | zFar = zFar === undefined ? 10000.0 : zFar; 188 | 189 | var handednessScale = rightHanded ? - 1.0 : 1.0; 190 | 191 | // start with an identity matrix 192 | var mobj = new THREE.Matrix4(); 193 | var m = mobj.elements; 194 | 195 | // and with scale/offset info for normalized device coords 196 | var scaleAndOffset = fovToNDCScaleOffset( fov ); 197 | 198 | // X result, map clip edges to [-w,+w] 199 | m[ 0 * 4 + 0 ] = scaleAndOffset.scale[ 0 ]; 200 | m[ 0 * 4 + 1 ] = 0.0; 201 | m[ 0 * 4 + 2 ] = scaleAndOffset.offset[ 0 ] * handednessScale; 202 | m[ 0 * 4 + 3 ] = 0.0; 203 | 204 | // Y result, map clip edges to [-w,+w] 205 | // Y offset is negated because this proj matrix transforms from world coords with Y=up, 206 | // but the NDC scaling has Y=down (thanks D3D?) 207 | m[ 1 * 4 + 0 ] = 0.0; 208 | m[ 1 * 4 + 1 ] = scaleAndOffset.scale[ 1 ]; 209 | m[ 1 * 4 + 2 ] = - scaleAndOffset.offset[ 1 ] * handednessScale; 210 | m[ 1 * 4 + 3 ] = 0.0; 211 | 212 | // Z result (up to the app) 213 | m[ 2 * 4 + 0 ] = 0.0; 214 | m[ 2 * 4 + 1 ] = 0.0; 215 | m[ 2 * 4 + 2 ] = zFar / ( zNear - zFar ) * - handednessScale; 216 | m[ 2 * 4 + 3 ] = ( zFar * zNear ) / ( zNear - zFar ); 217 | 218 | // W result (= Z in) 219 | m[ 3 * 4 + 0 ] = 0.0; 220 | m[ 3 * 4 + 1 ] = 0.0; 221 | m[ 3 * 4 + 2 ] = handednessScale; 222 | m[ 3 * 4 + 3 ] = 0.0; 223 | 224 | mobj.transpose(); 225 | 226 | return mobj; 227 | 228 | } 229 | 230 | function fovToProjection( fov, rightHanded, zNear, zFar ) { 231 | 232 | var DEG2RAD = Math.PI / 180.0; 233 | 234 | var fovPort = { 235 | upTan: Math.tan( fov.upDegrees * DEG2RAD ), 236 | downTan: Math.tan( fov.downDegrees * DEG2RAD ), 237 | leftTan: Math.tan( fov.leftDegrees * DEG2RAD ), 238 | rightTan: Math.tan( fov.rightDegrees * DEG2RAD ) 239 | }; 240 | 241 | return fovPortToProjection( fovPort, rightHanded, zNear, zFar ); 242 | 243 | } 244 | 245 | }; 246 | -------------------------------------------------------------------------------- /js/lib/fetch.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | if (self.fetch) { 5 | return 6 | } 7 | 8 | function normalizeName(name) { 9 | if (typeof name !== 'string') { 10 | name = name.toString(); 11 | } 12 | if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) { 13 | throw new TypeError('Invalid character in header field name') 14 | } 15 | return name.toLowerCase() 16 | } 17 | 18 | function normalizeValue(value) { 19 | if (typeof value !== 'string') { 20 | value = value.toString(); 21 | } 22 | return value 23 | } 24 | 25 | function Headers(headers) { 26 | this.map = {} 27 | 28 | if (headers instanceof Headers) { 29 | headers.forEach(function(value, name) { 30 | this.append(name, value) 31 | }, this) 32 | 33 | } else if (headers) { 34 | Object.getOwnPropertyNames(headers).forEach(function(name) { 35 | this.append(name, headers[name]) 36 | }, this) 37 | } 38 | } 39 | 40 | Headers.prototype.append = function(name, value) { 41 | name = normalizeName(name) 42 | value = normalizeValue(value) 43 | var list = this.map[name] 44 | if (!list) { 45 | list = [] 46 | this.map[name] = list 47 | } 48 | list.push(value) 49 | } 50 | 51 | Headers.prototype['delete'] = function(name) { 52 | delete this.map[normalizeName(name)] 53 | } 54 | 55 | Headers.prototype.get = function(name) { 56 | var values = this.map[normalizeName(name)] 57 | return values ? values[0] : null 58 | } 59 | 60 | Headers.prototype.getAll = function(name) { 61 | return this.map[normalizeName(name)] || [] 62 | } 63 | 64 | Headers.prototype.has = function(name) { 65 | return this.map.hasOwnProperty(normalizeName(name)) 66 | } 67 | 68 | Headers.prototype.set = function(name, value) { 69 | this.map[normalizeName(name)] = [normalizeValue(value)] 70 | } 71 | 72 | Headers.prototype.forEach = function(callback, thisArg) { 73 | Object.getOwnPropertyNames(this.map).forEach(function(name) { 74 | this.map[name].forEach(function(value) { 75 | callback.call(thisArg, value, name, this) 76 | }, this) 77 | }, this) 78 | } 79 | 80 | function consumed(body) { 81 | if (body.bodyUsed) { 82 | return Promise.reject(new TypeError('Already read')) 83 | } 84 | body.bodyUsed = true 85 | } 86 | 87 | function fileReaderReady(reader) { 88 | return new Promise(function(resolve, reject) { 89 | reader.onload = function() { 90 | resolve(reader.result) 91 | } 92 | reader.onerror = function() { 93 | reject(reader.error) 94 | } 95 | }) 96 | } 97 | 98 | function readBlobAsArrayBuffer(blob) { 99 | var reader = new FileReader() 100 | reader.readAsArrayBuffer(blob) 101 | return fileReaderReady(reader) 102 | } 103 | 104 | function readBlobAsText(blob) { 105 | var reader = new FileReader() 106 | reader.readAsText(blob) 107 | return fileReaderReady(reader) 108 | } 109 | 110 | var support = { 111 | blob: 'FileReader' in self && 'Blob' in self && (function() { 112 | try { 113 | new Blob(); 114 | return true 115 | } catch(e) { 116 | return false 117 | } 118 | })(), 119 | formData: 'FormData' in self 120 | } 121 | 122 | function Body() { 123 | this.bodyUsed = false 124 | 125 | 126 | this._initBody = function(body) { 127 | this._bodyInit = body 128 | if (typeof body === 'string') { 129 | this._bodyText = body 130 | } else if (support.blob && Blob.prototype.isPrototypeOf(body)) { 131 | this._bodyBlob = body 132 | } else if (support.formData && FormData.prototype.isPrototypeOf(body)) { 133 | this._bodyFormData = body 134 | } else if (!body) { 135 | this._bodyText = '' 136 | } else { 137 | throw new Error('unsupported BodyInit type') 138 | } 139 | } 140 | 141 | if (support.blob) { 142 | this.blob = function() { 143 | var rejected = consumed(this) 144 | if (rejected) { 145 | return rejected 146 | } 147 | 148 | if (this._bodyBlob) { 149 | return Promise.resolve(this._bodyBlob) 150 | } else if (this._bodyFormData) { 151 | throw new Error('could not read FormData body as blob') 152 | } else { 153 | return Promise.resolve(new Blob([this._bodyText])) 154 | } 155 | } 156 | 157 | this.arrayBuffer = function() { 158 | return this.blob().then(readBlobAsArrayBuffer) 159 | } 160 | 161 | this.text = function() { 162 | var rejected = consumed(this) 163 | if (rejected) { 164 | return rejected 165 | } 166 | 167 | if (this._bodyBlob) { 168 | return readBlobAsText(this._bodyBlob) 169 | } else if (this._bodyFormData) { 170 | throw new Error('could not read FormData body as text') 171 | } else { 172 | return Promise.resolve(this._bodyText) 173 | } 174 | } 175 | } else { 176 | this.text = function() { 177 | var rejected = consumed(this) 178 | return rejected ? rejected : Promise.resolve(this._bodyText) 179 | } 180 | } 181 | 182 | if (support.formData) { 183 | this.formData = function() { 184 | return this.text().then(decode) 185 | } 186 | } 187 | 188 | this.json = function() { 189 | return this.text().then(JSON.parse) 190 | } 191 | 192 | return this 193 | } 194 | 195 | // HTTP methods whose capitalization should be normalized 196 | var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] 197 | 198 | function normalizeMethod(method) { 199 | var upcased = method.toUpperCase() 200 | return (methods.indexOf(upcased) > -1) ? upcased : method 201 | } 202 | 203 | function Request(url, options) { 204 | options = options || {} 205 | this.url = url 206 | 207 | this.credentials = options.credentials || 'omit' 208 | this.headers = new Headers(options.headers) 209 | this.method = normalizeMethod(options.method || 'GET') 210 | this.mode = options.mode || null 211 | this.referrer = null 212 | 213 | if ((this.method === 'GET' || this.method === 'HEAD') && options.body) { 214 | throw new TypeError('Body not allowed for GET or HEAD requests') 215 | } 216 | this._initBody(options.body) 217 | } 218 | 219 | function decode(body) { 220 | var form = new FormData() 221 | body.trim().split('&').forEach(function(bytes) { 222 | if (bytes) { 223 | var split = bytes.split('=') 224 | var name = split.shift().replace(/\+/g, ' ') 225 | var value = split.join('=').replace(/\+/g, ' ') 226 | form.append(decodeURIComponent(name), decodeURIComponent(value)) 227 | } 228 | }) 229 | return form 230 | } 231 | 232 | function headers(xhr) { 233 | var head = new Headers() 234 | var pairs = xhr.getAllResponseHeaders().trim().split('\n') 235 | pairs.forEach(function(header) { 236 | var split = header.trim().split(':') 237 | var key = split.shift().trim() 238 | var value = split.join(':').trim() 239 | head.append(key, value) 240 | }) 241 | return head 242 | } 243 | 244 | Body.call(Request.prototype) 245 | 246 | function Response(bodyInit, options) { 247 | if (!options) { 248 | options = {} 249 | } 250 | 251 | this._initBody(bodyInit) 252 | this.type = 'default' 253 | this.url = null 254 | this.status = options.status 255 | this.ok = this.status >= 200 && this.status < 300 256 | this.statusText = options.statusText 257 | this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers) 258 | this.url = options.url || '' 259 | } 260 | 261 | Body.call(Response.prototype) 262 | 263 | self.Headers = Headers; 264 | self.Request = Request; 265 | self.Response = Response; 266 | 267 | self.fetch = function(input, init) { 268 | // TODO: Request constructor should accept input, init 269 | var request 270 | if (Request.prototype.isPrototypeOf(input) && !init) { 271 | request = input 272 | } else { 273 | request = new Request(input, init) 274 | } 275 | 276 | return new Promise(function(resolve, reject) { 277 | var xhr = new XMLHttpRequest() 278 | 279 | function responseURL() { 280 | if ('responseURL' in xhr) { 281 | return xhr.responseURL 282 | } 283 | 284 | // Avoid security warnings on getResponseHeader when not allowed by CORS 285 | if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) { 286 | return xhr.getResponseHeader('X-Request-URL') 287 | } 288 | 289 | return; 290 | } 291 | 292 | xhr.onload = function() { 293 | var status = (xhr.status === 1223) ? 204 : xhr.status 294 | if (status < 100 || status > 599) { 295 | reject(new TypeError('Network request failed')) 296 | return 297 | } 298 | var options = { 299 | status: status, 300 | statusText: xhr.statusText, 301 | headers: headers(xhr), 302 | url: responseURL() 303 | } 304 | var body = 'response' in xhr ? xhr.response : xhr.responseText; 305 | resolve(new Response(body, options)) 306 | } 307 | 308 | xhr.onerror = function() { 309 | reject(new TypeError('Network request failed')) 310 | } 311 | 312 | xhr.open(request.method, request.url, true) 313 | 314 | if (request.credentials === 'include') { 315 | xhr.withCredentials = true 316 | } 317 | 318 | if ('responseType' in xhr && support.blob) { 319 | xhr.responseType = 'blob' 320 | } 321 | 322 | request.headers.forEach(function(value, name) { 323 | xhr.setRequestHeader(name, value) 324 | }) 325 | 326 | xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit) 327 | }) 328 | } 329 | self.fetch.polyfill = true 330 | })(); 331 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | // url parameters 2 | var parameters = (function() { 3 | var parameters = {}; 4 | var parts = window.location.search.substr(1).split('&'); 5 | for (var i = 0; i < parts.length; i++) { 6 | var parameter = parts[i].split('='); 7 | parameters[parameter[0]] = parameter[1]; 8 | } 9 | return parameters; 10 | })(); 11 | 12 | (function () { 13 | var panosList = fetchPanos(); 14 | 15 | function fetchPanos() { 16 | return fetch('panos.json').then(function (response) { 17 | return response.json(); 18 | }); 19 | } 20 | 21 | self.panosList = panosList; 22 | })(); 23 | 24 | var isMobile = function () { 25 | var check = false; 26 | (function (a) { 27 | if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) { 28 | check = true; 29 | } 30 | })(navigator.userAgent || navigator.vendor || window.opera); 31 | return check; 32 | }; 33 | 34 | var camera; 35 | var clock = new THREE.Clock(); 36 | var vrControls; 37 | var counter = 0; 38 | var effect; 39 | var manager; 40 | var overlay; 41 | var pano; 42 | var panoCurrent; 43 | var renderer; 44 | var scene; 45 | var vrMode = false; 46 | 47 | function bend( group, amount, multiMaterialObject ) { 48 | function bendVertices( mesh, amount, parent ) { 49 | var vertices = mesh.geometry.vertices; 50 | 51 | if (!parent) { 52 | parent = mesh; 53 | } 54 | 55 | for (var i = 0; i < vertices.length; i++) { 56 | var vertex = vertices[i]; 57 | 58 | // apply bend calculations on vertexes from world coordinates 59 | parent.updateMatrixWorld(); 60 | 61 | var worldVertex = parent.localToWorld(vertex); 62 | 63 | var worldX = Math.sin( worldVertex.x / amount) * amount; 64 | var worldZ = - Math.cos( worldVertex.x / amount ) * amount; 65 | var worldY = worldVertex.y ; 66 | 67 | // convert world coordinates back into local object coordinates. 68 | var localVertex = parent.worldToLocal(new THREE.Vector3(worldX, worldY, worldZ)); 69 | vertex.x = localVertex.x; 70 | vertex.z = localVertex.z+amount; 71 | vertex.y = localVertex.y; 72 | } 73 | 74 | mesh.geometry.computeBoundingSphere(); 75 | mesh.geometry.verticesNeedUpdate = true; 76 | } 77 | 78 | for ( var i = 0; i < group.children.length; i ++ ) { 79 | var element = group.children[ i ]; 80 | 81 | if (element.geometry.vertices) { 82 | if (multiMaterialObject) { 83 | bendVertices( element, amount, group); 84 | } else { 85 | bendVertices( element, amount); 86 | } 87 | } 88 | } 89 | } 90 | 91 | function loadPano() { 92 | panosList.then(function (panos) { 93 | 94 | panoCurrent = panos[counter]; 95 | var imgPano = panoCurrent.image; 96 | var imgOverlay = panoCurrent.overlay; 97 | 98 | // fade out current panorama. 99 | new TWEEN.Tween(pano.material) 100 | .to({opacity: 0}, 300) 101 | .onComplete(function () { 102 | // load in new panorama texture. 103 | pano.material.map = THREE.ImageUtils.loadTexture(imgPano, THREE.UVMapping, fadeIn); 104 | }) 105 | .start(); 106 | 107 | // fade out current title. 108 | new TWEEN.Tween(overlay.children[0].material) 109 | .to({opacity: 0}, 300) 110 | .onComplete(function () { 111 | // load in new title. 112 | overlay.children[0].material.map = THREE.ImageUtils.loadTexture(imgOverlay, THREE.UVMapping); 113 | }) 114 | .start(); 115 | 116 | // fade in newly loaded panorama. 117 | function fadeIn() { 118 | new TWEEN.Tween(pano.material) 119 | .to({opacity: 1}, 1000) 120 | .onComplete(fadeInOverlay) 121 | .start(); 122 | } 123 | 124 | // fade in newly loaded title. 125 | function fadeInOverlay() { 126 | new TWEEN.Tween(overlay.children[0].material) 127 | .to({opacity: 1}, 300) 128 | .start(); 129 | } 130 | 131 | }); 132 | } 133 | 134 | 135 | // initialize scene 136 | function init() { 137 | renderer = new THREE.WebGLRenderer({ antialias: true }); 138 | renderer.autoClear = false; 139 | renderer.setClearColor( 0x000000 ); 140 | document.body.appendChild( renderer.domElement ); 141 | 142 | scene = new THREE.Scene(); 143 | 144 | camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 ); 145 | camera.position.z = 0.01; // set camera position so that OrbitControls works properly 146 | scene.add(camera); 147 | 148 | // effect and controls for VR 149 | effect = new THREE.VREffect(renderer); 150 | vrControls = new THREE.VRControls(camera); 151 | 152 | // Fetch the JSON list of panos 153 | function loadMaterial() { 154 | return new Promise(function (resolve) { 155 | var material = new THREE.MeshBasicMaterial({ 156 | side: THREE.DoubleSide, 157 | transparent: true, 158 | map: THREE.ImageUtils.loadTexture( 159 | 'images/background.jpg', // load placeholder rexture 160 | THREE.UVMapping, 161 | resolve 162 | ) 163 | }); 164 | 165 | pano = new THREE.Mesh( geometry, material ); 166 | pano.renderDepth = 2; 167 | pano.rotation.set( 0, -90 * Math.PI / 180, 0 ); 168 | scene.add(pano); 169 | }); 170 | } 171 | panosList.then(loadMaterial).then(loadPano); 172 | 173 | // panorma mesh 174 | var geometry = new THREE.SphereGeometry( 1000, 60, 60 ); 175 | geometry.applyMatrix( new THREE.Matrix4().makeScale( -1, 1, 1 ) ); 176 | 177 | // title text 178 | overlay = new THREE.Object3D(); 179 | var mesh = new THREE.Mesh( 180 | new THREE.PlaneGeometry( 63, 30, 20, 20 ), 181 | new THREE.MeshBasicMaterial({ 182 | transparent: true, 183 | alphaTest: 0.5, 184 | side: THREE.FrontSide, 185 | map: new THREE.TextureLoader().load('images/background-overlay.png') 186 | })); 187 | overlay.add( mesh ); 188 | overlay.position.set( 0, -3, -5 ); 189 | overlay.scale.set( 0.1, 0.1, 0.1 ); 190 | bend(overlay, 100); 191 | mesh.renderOrder = 1; 192 | scene.add( overlay ); 193 | 194 | // trigger function that begins to animate the scene. 195 | new TWEEN.Tween() 196 | .delay(400) 197 | .start(); 198 | 199 | // kick off animation 200 | animate(); 201 | onWindowResize(); 202 | } 203 | 204 | function requestFullscreen() { 205 | var el = renderer.domElement; 206 | 207 | if (!isMobile()) { 208 | effect.setFullScreen(true); 209 | return; 210 | } 211 | 212 | if (el.requestFullscreen) { 213 | el.requestFullscreen(); 214 | } else if (el.mozRequestFullScreen) { 215 | el.mozRequestFullScreen(); 216 | } else if (el.webkitRequestFullscreen) { 217 | el.webkitRequestFullscreen(); 218 | } 219 | } 220 | 221 | function onWindowResize() { 222 | camera.aspect = window.innerWidth / window.innerHeight; 223 | camera.updateProjectionMatrix(); 224 | if (vrMode) { 225 | effect.setSize(window.innerWidth, window.innerHeight); 226 | } else { 227 | renderer.setSize(window.innerWidth, window.innerHeight); 228 | } 229 | } 230 | 231 | function onFullscreenChange(e) { 232 | var fsElement = document.fullscreenElement || 233 | document.mozFullScreenElement || 234 | document.webkitFullscreenElement; 235 | 236 | if (!fsElement) { 237 | vrMode = false; 238 | } else { 239 | // lock screen if mobile 240 | window.screen.orientation.lock('landscape'); 241 | } 242 | } 243 | 244 | function onkey(e) { 245 | panosList.then(function (panos) { 246 | if (e.keyCode == '90') { 247 | vrControls.zeroSensor(); 248 | } else if (e.keyCode == '37') { // left arrow - prev panorama 249 | counter --; 250 | if (counter < 0) { 251 | counter = panos.length - 1; 252 | } 253 | loadPano(); 254 | } else if (e.keyCode == '39') { // right arrow - next panorama 255 | counter ++; 256 | if (counter == panos.length) { 257 | counter = 0; 258 | } 259 | loadPano(); 260 | } 261 | }); 262 | e.stopPropagation(); 263 | } 264 | 265 | function animate() { 266 | TWEEN.update(); 267 | 268 | if (vrMode) { 269 | effect.render(scene, camera); 270 | } else { 271 | renderer.render(scene, camera); 272 | } 273 | 274 | vrControls.update(); 275 | 276 | requestAnimationFrame(animate); 277 | } 278 | 279 | document.querySelector('#enterVr').addEventListener('click', function() { 280 | vrMode = vrMode ? false : true; 281 | requestFullscreen(); 282 | onWindowResize(); 283 | }); 284 | document.addEventListener('fullscreenchange', onFullscreenChange); 285 | document.addEventListener('mozfullscreenchange', onFullscreenChange); 286 | window.addEventListener('keydown', onkey, true); 287 | window.addEventListener('resize', onWindowResize, false ); 288 | 289 | init(); 290 | -------------------------------------------------------------------------------- /js/tween.min.js: -------------------------------------------------------------------------------- 1 | // tween.js v.0.15.0 https://github.com/sole/tween.js 2 | void 0===Date.now&&(Date.now=function(){return(new Date).valueOf()});var TWEEN=TWEEN||function(){var n=[];return{REVISION:"14",getAll:function(){return n},removeAll:function(){n=[]},add:function(t){n.push(t)},remove:function(t){var r=n.indexOf(t);-1!==r&&n.splice(r,1)},update:function(t){if(0===n.length)return!1;var r=0;for(t=void 0!==t?t:"undefined"!=typeof window&&void 0!==window.performance&&void 0!==window.performance.now?window.performance.now():Date.now();rn;n++)E[n].stop()},this.delay=function(n){return s=n,this},this.repeat=function(n){return e=n,this},this.yoyo=function(n){return a=n,this},this.easing=function(n){return l=n,this},this.interpolation=function(n){return p=n,this},this.chain=function(){return E=arguments,this},this.onStart=function(n){return d=n,this},this.onUpdate=function(n){return I=n,this},this.onComplete=function(n){return w=n,this},this.onStop=function(n){return M=n,this},this.update=function(n){var f;if(h>n)return!0;v===!1&&(null!==d&&d.call(t),v=!0);var M=(n-h)/o;M=M>1?1:M;var O=l(M);for(f in i){var m=r[f]||0,N=i[f];N instanceof Array?t[f]=p(N,O):("string"==typeof N&&(N=m+parseFloat(N,10)),"number"==typeof N&&(t[f]=m+(N-m)*O))}if(null!==I&&I.call(t,O),1==M){if(e>0){isFinite(e)&&e--;for(f in u){if("string"==typeof i[f]&&(u[f]=u[f]+parseFloat(i[f],10)),a){var T=u[f];u[f]=i[f],i[f]=T}r[f]=u[f]}return a&&(c=!c),h=n+s,!0}null!==w&&w.call(t);for(var g=0,W=E.length;W>g;g++)E[g].start(n);return!1}return!0}},TWEEN.Easing={Linear:{None:function(n){return n}},Quadratic:{In:function(n){return n*n},Out:function(n){return n*(2-n)},InOut:function(n){return(n*=2)<1?.5*n*n:-.5*(--n*(n-2)-1)}},Cubic:{In:function(n){return n*n*n},Out:function(n){return--n*n*n+1},InOut:function(n){return(n*=2)<1?.5*n*n*n:.5*((n-=2)*n*n+2)}},Quartic:{In:function(n){return n*n*n*n},Out:function(n){return 1- --n*n*n*n},InOut:function(n){return(n*=2)<1?.5*n*n*n*n:-.5*((n-=2)*n*n*n-2)}},Quintic:{In:function(n){return n*n*n*n*n},Out:function(n){return--n*n*n*n*n+1},InOut:function(n){return(n*=2)<1?.5*n*n*n*n*n:.5*((n-=2)*n*n*n*n+2)}},Sinusoidal:{In:function(n){return 1-Math.cos(n*Math.PI/2)},Out:function(n){return Math.sin(n*Math.PI/2)},InOut:function(n){return.5*(1-Math.cos(Math.PI*n))}},Exponential:{In:function(n){return 0===n?0:Math.pow(1024,n-1)},Out:function(n){return 1===n?1:1-Math.pow(2,-10*n)},InOut:function(n){return 0===n?0:1===n?1:(n*=2)<1?.5*Math.pow(1024,n-1):.5*(-Math.pow(2,-10*(n-1))+2)}},Circular:{In:function(n){return 1-Math.sqrt(1-n*n)},Out:function(n){return Math.sqrt(1- --n*n)},InOut:function(n){return(n*=2)<1?-.5*(Math.sqrt(1-n*n)-1):.5*(Math.sqrt(1-(n-=2)*n)+1)}},Elastic:{In:function(n){var t,r=.1,i=.4;return 0===n?0:1===n?1:(!r||1>r?(r=1,t=i/4):t=i*Math.asin(1/r)/(2*Math.PI),-(r*Math.pow(2,10*(n-=1))*Math.sin(2*(n-t)*Math.PI/i)))},Out:function(n){var t,r=.1,i=.4;return 0===n?0:1===n?1:(!r||1>r?(r=1,t=i/4):t=i*Math.asin(1/r)/(2*Math.PI),r*Math.pow(2,-10*n)*Math.sin(2*(n-t)*Math.PI/i)+1)},InOut:function(n){var t,r=.1,i=.4;return 0===n?0:1===n?1:(!r||1>r?(r=1,t=i/4):t=i*Math.asin(1/r)/(2*Math.PI),(n*=2)<1?-.5*r*Math.pow(2,10*(n-=1))*Math.sin(2*(n-t)*Math.PI/i):r*Math.pow(2,-10*(n-=1))*Math.sin(2*(n-t)*Math.PI/i)*.5+1)}},Back:{In:function(n){var t=1.70158;return n*n*((t+1)*n-t)},Out:function(n){var t=1.70158;return--n*n*((t+1)*n+t)+1},InOut:function(n){var t=2.5949095;return(n*=2)<1?.5*n*n*((t+1)*n-t):.5*((n-=2)*n*((t+1)*n+t)+2)}},Bounce:{In:function(n){return 1-TWEEN.Easing.Bounce.Out(1-n)},Out:function(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375},InOut:function(n){return.5>n?.5*TWEEN.Easing.Bounce.In(2*n):.5*TWEEN.Easing.Bounce.Out(2*n-1)+.5}}},TWEEN.Interpolation={Linear:function(n,t){var r=n.length-1,i=r*t,u=Math.floor(i),o=TWEEN.Interpolation.Utils.Linear;return 0>t?o(n[0],n[1],i):t>1?o(n[r],n[r-1],r-i):o(n[u],n[u+1>r?r:u+1],i-u)},Bezier:function(n,t){var r,i=0,u=n.length-1,o=Math.pow,e=TWEEN.Interpolation.Utils.Bernstein;for(r=0;u>=r;r++)i+=o(1-t,u-r)*o(t,r)*n[r]*e(u,r);return i},CatmullRom:function(n,t){var r=n.length-1,i=r*t,u=Math.floor(i),o=TWEEN.Interpolation.Utils.CatmullRom;return n[0]===n[r]?(0>t&&(u=Math.floor(i=r*(1+t))),o(n[(u-1+r)%r],n[u],n[(u+1)%r],n[(u+2)%r],i-u)):0>t?n[0]-(o(n[0],n[0],n[1],n[1],-i)-n[0]):t>1?n[r]-(o(n[r],n[r],n[r-1],n[r-1],i-r)-n[r]):o(n[u?u-1:0],n[u],n[u+1>r?r:u+1],n[u+2>r?r:u+2],i-u)},Utils:{Linear:function(n,t,r){return(t-n)*r+n},Bernstein:function(n,t){var r=TWEEN.Interpolation.Utils.Factorial;return r(n)/r(t)/r(n-t)},Factorial:function(){var n=[1];return function(t){var r,i=1;if(n[t])return n[t];for(r=t;r>1;r--)i*=r;return n[t]=i}}(),CatmullRom:function(n,t,r,i,u){var o=.5*(r-n),e=.5*(i-t),a=u*u,f=u*a;return(2*t-2*r+o+e)*f+(-3*t+3*r-2*o-e)*a+o*u+t}}},"undefined"!=typeof module&&module.exports&&(module.exports=TWEEN); -------------------------------------------------------------------------------- /js/webvr-polyfill.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o Util.MAX_TIMESTEP) { 416 | console.warn('Invalid timestamps detected. Time step between successive ' + 417 | 'gyroscope sensor samples is very small or not monotonic'); 418 | this.previousTimestampS = timestampS; 419 | return; 420 | } 421 | this.accelerometer.set(-accGravity.x, -accGravity.y, -accGravity.z); 422 | this.gyroscope.set(rotRate.alpha, rotRate.beta, rotRate.gamma); 423 | 424 | // With iOS and Firefox Android, rotationRate is reported in degrees, 425 | // so we first convert to radians. 426 | if (this.isIOS || this.isFirefoxAndroid) { 427 | this.gyroscope.multiplyScalar(Math.PI / 180); 428 | } 429 | 430 | this.filter.addAccelMeasurement(this.accelerometer, timestampS); 431 | this.filter.addGyroMeasurement(this.gyroscope, timestampS); 432 | 433 | this.previousTimestampS = timestampS; 434 | }; 435 | 436 | FusionPositionSensorVRDevice.prototype.onScreenOrientationChange_ = 437 | function(screenOrientation) { 438 | this.setScreenTransform_(); 439 | }; 440 | 441 | FusionPositionSensorVRDevice.prototype.setScreenTransform_ = function() { 442 | this.worldToScreenQ.set(0, 0, 0, 1); 443 | switch (window.orientation) { 444 | case 0: 445 | break; 446 | case 90: 447 | this.worldToScreenQ.setFromAxisAngle(new THREE.Vector3(0, 0, 1), -Math.PI/2); 448 | break; 449 | case -90: 450 | this.worldToScreenQ.setFromAxisAngle(new THREE.Vector3(0, 0, 1), Math.PI/2); 451 | break; 452 | case 180: 453 | // TODO. 454 | break; 455 | } 456 | }; 457 | 458 | 459 | module.exports = FusionPositionSensorVRDevice; 460 | 461 | },{"./base.js":1,"./complementary-filter.js":3,"./pose-predictor.js":7,"./three-math.js":9,"./touch-panner.js":10,"./util.js":11}],5:[function(_dereq_,module,exports){ 462 | /* 463 | * Copyright 2015 Google Inc. All Rights Reserved. 464 | * Licensed under the Apache License, Version 2.0 (the "License"); 465 | * you may not use this file except in compliance with the License. 466 | * You may obtain a copy of the License at 467 | * 468 | * http://www.apache.org/licenses/LICENSE-2.0 469 | * 470 | * Unless required by applicable law or agreed to in writing, software 471 | * distributed under the License is distributed on an "AS IS" BASIS, 472 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 473 | * See the License for the specific language governing permissions and 474 | * limitations under the License. 475 | */ 476 | var WebVRPolyfill = _dereq_('./webvr-polyfill.js'); 477 | 478 | // Initialize a WebVRConfig just in case. 479 | window.WebVRConfig = window.WebVRConfig || {}; 480 | new WebVRPolyfill(); 481 | 482 | },{"./webvr-polyfill.js":12}],6:[function(_dereq_,module,exports){ 483 | /* 484 | * Copyright 2015 Google Inc. All Rights Reserved. 485 | * Licensed under the Apache License, Version 2.0 (the "License"); 486 | * you may not use this file except in compliance with the License. 487 | * You may obtain a copy of the License at 488 | * 489 | * http://www.apache.org/licenses/LICENSE-2.0 490 | * 491 | * Unless required by applicable law or agreed to in writing, software 492 | * distributed under the License is distributed on an "AS IS" BASIS, 493 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 494 | * See the License for the specific language governing permissions and 495 | * limitations under the License. 496 | */ 497 | var PositionSensorVRDevice = _dereq_('./base.js').PositionSensorVRDevice; 498 | var THREE = _dereq_('./three-math.js'); 499 | var Util = _dereq_('./util.js'); 500 | 501 | // How much to rotate per key stroke. 502 | var KEY_SPEED = 0.15; 503 | var KEY_ANIMATION_DURATION = 80; 504 | 505 | // How much to rotate for mouse events. 506 | var MOUSE_SPEED_X = 0.5; 507 | var MOUSE_SPEED_Y = 0.3; 508 | 509 | /** 510 | * A virtual position sensor, implemented using keyboard and 511 | * mouse APIs. This is designed as for desktops/laptops where no Device* 512 | * events work. 513 | */ 514 | function MouseKeyboardPositionSensorVRDevice() { 515 | this.deviceId = 'webvr-polyfill:mouse-keyboard'; 516 | this.deviceName = 'VR Position Device (webvr-polyfill:mouse-keyboard)'; 517 | 518 | // Attach to mouse and keyboard events. 519 | window.addEventListener('keydown', this.onKeyDown_.bind(this)); 520 | window.addEventListener('mousemove', this.onMouseMove_.bind(this)); 521 | window.addEventListener('mousedown', this.onMouseDown_.bind(this)); 522 | window.addEventListener('mouseup', this.onMouseUp_.bind(this)); 523 | 524 | this.phi = 0; 525 | this.theta = 0; 526 | 527 | // Variables for keyboard-based rotation animation. 528 | this.targetAngle = null; 529 | 530 | // State variables for calculations. 531 | this.euler = new THREE.Euler(); 532 | this.orientation = new THREE.Quaternion(); 533 | 534 | // Variables for mouse-based rotation. 535 | this.rotateStart = new THREE.Vector2(); 536 | this.rotateEnd = new THREE.Vector2(); 537 | this.rotateDelta = new THREE.Vector2(); 538 | } 539 | MouseKeyboardPositionSensorVRDevice.prototype = new PositionSensorVRDevice(); 540 | 541 | /** 542 | * Returns {orientation: {x,y,z,w}, position: null}. 543 | * Position is not supported for parity with other PositionSensors. 544 | */ 545 | MouseKeyboardPositionSensorVRDevice.prototype.getState = function() { 546 | this.euler.set(this.phi, this.theta, 0, 'YXZ'); 547 | this.orientation.setFromEuler(this.euler); 548 | 549 | return { 550 | hasOrientation: true, 551 | orientation: this.orientation, 552 | hasPosition: false, 553 | position: null 554 | } 555 | }; 556 | 557 | MouseKeyboardPositionSensorVRDevice.prototype.onKeyDown_ = function(e) { 558 | // Track WASD and arrow keys. 559 | if (e.keyCode == 38) { // Up key. 560 | this.animatePhi_(this.phi + KEY_SPEED); 561 | } else if (e.keyCode == 39) { // Right key. 562 | this.animateTheta_(this.theta - KEY_SPEED); 563 | } else if (e.keyCode == 40) { // Down key. 564 | this.animatePhi_(this.phi - KEY_SPEED); 565 | } else if (e.keyCode == 37) { // Left key. 566 | this.animateTheta_(this.theta + KEY_SPEED); 567 | } 568 | }; 569 | 570 | MouseKeyboardPositionSensorVRDevice.prototype.animateTheta_ = function(targetAngle) { 571 | this.animateKeyTransitions_('theta', targetAngle); 572 | }; 573 | 574 | MouseKeyboardPositionSensorVRDevice.prototype.animatePhi_ = function(targetAngle) { 575 | // Prevent looking too far up or down. 576 | targetAngle = Util.clamp(targetAngle, -Math.PI/2, Math.PI/2); 577 | this.animateKeyTransitions_('phi', targetAngle); 578 | }; 579 | 580 | /** 581 | * Start an animation to transition an angle from one value to another. 582 | */ 583 | MouseKeyboardPositionSensorVRDevice.prototype.animateKeyTransitions_ = function(angleName, targetAngle) { 584 | // If an animation is currently running, cancel it. 585 | if (this.angleAnimation) { 586 | clearInterval(this.angleAnimation); 587 | } 588 | var startAngle = this[angleName]; 589 | var startTime = new Date(); 590 | // Set up an interval timer to perform the animation. 591 | this.angleAnimation = setInterval(function() { 592 | // Once we're finished the animation, we're done. 593 | var elapsed = new Date() - startTime; 594 | if (elapsed >= KEY_ANIMATION_DURATION) { 595 | this[angleName] = targetAngle; 596 | clearInterval(this.angleAnimation); 597 | return; 598 | } 599 | // Linearly interpolate the angle some amount. 600 | var percent = elapsed / KEY_ANIMATION_DURATION; 601 | this[angleName] = startAngle + (targetAngle - startAngle) * percent; 602 | }.bind(this), 1000/60); 603 | }; 604 | 605 | MouseKeyboardPositionSensorVRDevice.prototype.onMouseDown_ = function(e) { 606 | this.rotateStart.set(e.clientX, e.clientY); 607 | this.isDragging = true; 608 | }; 609 | 610 | // Very similar to https://gist.github.com/mrflix/8351020 611 | MouseKeyboardPositionSensorVRDevice.prototype.onMouseMove_ = function(e) { 612 | if (!this.isDragging && !this.isPointerLocked_()) { 613 | return; 614 | } 615 | // Support pointer lock API. 616 | if (this.isPointerLocked_()) { 617 | var movementX = e.movementX || e.mozMovementX || 0; 618 | var movementY = e.movementY || e.mozMovementY || 0; 619 | this.rotateEnd.set(this.rotateStart.x - movementX, this.rotateStart.y - movementY); 620 | } else { 621 | this.rotateEnd.set(e.clientX, e.clientY); 622 | } 623 | // Calculate how much we moved in mouse space. 624 | this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart); 625 | this.rotateStart.copy(this.rotateEnd); 626 | 627 | // Keep track of the cumulative euler angles. 628 | var element = document.body; 629 | this.phi += 2 * Math.PI * this.rotateDelta.y / element.clientHeight * MOUSE_SPEED_Y; 630 | this.theta += 2 * Math.PI * this.rotateDelta.x / element.clientWidth * MOUSE_SPEED_X; 631 | 632 | // Prevent looking too far up or down. 633 | this.phi = Util.clamp(this.phi, -Math.PI/2, Math.PI/2); 634 | }; 635 | 636 | MouseKeyboardPositionSensorVRDevice.prototype.onMouseUp_ = function(e) { 637 | this.isDragging = false; 638 | }; 639 | 640 | MouseKeyboardPositionSensorVRDevice.prototype.isPointerLocked_ = function() { 641 | var el = document.pointerLockElement || document.mozPointerLockElement || 642 | document.webkitPointerLockElement; 643 | return el !== undefined; 644 | }; 645 | 646 | MouseKeyboardPositionSensorVRDevice.prototype.resetSensor = function() { 647 | console.error('Not implemented yet.'); 648 | }; 649 | 650 | module.exports = MouseKeyboardPositionSensorVRDevice; 651 | 652 | },{"./base.js":1,"./three-math.js":9,"./util.js":11}],7:[function(_dereq_,module,exports){ 653 | /* 654 | * Copyright 2015 Google Inc. All Rights Reserved. 655 | * Licensed under the Apache License, Version 2.0 (the "License"); 656 | * you may not use this file except in compliance with the License. 657 | * You may obtain a copy of the License at 658 | * 659 | * http://www.apache.org/licenses/LICENSE-2.0 660 | * 661 | * Unless required by applicable law or agreed to in writing, software 662 | * distributed under the License is distributed on an "AS IS" BASIS, 663 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 664 | * See the License for the specific language governing permissions and 665 | * limitations under the License. 666 | */ 667 | var THREE = _dereq_('./three-math.js'); 668 | 669 | var DEBUG = false; 670 | 671 | /** 672 | * Given an orientation and the gyroscope data, predicts the future orientation 673 | * of the head. This makes rendering appear faster. 674 | * 675 | * Also see: http://msl.cs.uiuc.edu/~lavalle/papers/LavYerKatAnt14.pdf 676 | * 677 | * @param {Number} predictionTimeS time from head movement to the appearance of 678 | * the corresponding image. 679 | */ 680 | function PosePredictor(predictionTimeS) { 681 | this.predictionTimeS = predictionTimeS; 682 | 683 | // The quaternion corresponding to the previous state. 684 | this.previousQ = new THREE.Quaternion(); 685 | // Previous time a prediction occurred. 686 | this.previousTimestampS = null; 687 | 688 | // The delta quaternion that adjusts the current pose. 689 | this.deltaQ = new THREE.Quaternion(); 690 | // The output quaternion. 691 | this.outQ = new THREE.Quaternion(); 692 | } 693 | 694 | PosePredictor.prototype.getPrediction = function(currentQ, gyro, timestampS) { 695 | if (!this.previousTimestampS) { 696 | this.previousQ.copy(currentQ); 697 | this.previousTimestampS = timestampS; 698 | return currentQ; 699 | } 700 | 701 | // Calculate axis and angle based on gyroscope rotation rate data. 702 | var axis = new THREE.Vector3(); 703 | axis.copy(gyro); 704 | axis.normalize(); 705 | 706 | var angularSpeed = gyro.length(); 707 | 708 | // If we're rotating slowly, don't do prediction. 709 | if (angularSpeed < THREE.Math.degToRad(20)) { 710 | if (DEBUG) { 711 | console.log('Moving slowly, at %s deg/s: no prediction', 712 | THREE.Math.radToDeg(angularSpeed).toFixed(1)); 713 | } 714 | this.outQ.copy(currentQ); 715 | this.previousQ.copy(currentQ); 716 | return this.outQ; 717 | } 718 | 719 | // Get the predicted angle based on the time delta and latency. 720 | var deltaT = timestampS - this.previousTimestampS; 721 | var predictAngle = angularSpeed * this.predictionTimeS; 722 | 723 | this.deltaQ.setFromAxisAngle(axis, predictAngle); 724 | this.outQ.copy(this.previousQ); 725 | this.outQ.multiply(this.deltaQ); 726 | 727 | this.previousQ.copy(currentQ); 728 | 729 | return this.outQ; 730 | }; 731 | 732 | 733 | module.exports = PosePredictor; 734 | 735 | },{"./three-math.js":9}],8:[function(_dereq_,module,exports){ 736 | function SensorSample(sample, timestampS) { 737 | this.set(sample, timestampS); 738 | }; 739 | 740 | SensorSample.prototype.set = function(sample, timestampS) { 741 | this.sample = sample; 742 | this.timestampS = timestampS; 743 | }; 744 | 745 | SensorSample.prototype.copy = function(sensorSample) { 746 | this.set(sensorSample.sample, sensorSample.timestampS); 747 | }; 748 | 749 | module.exports = SensorSample; 750 | 751 | },{}],9:[function(_dereq_,module,exports){ 752 | /* 753 | * A subset of THREE.js, providing mostly quaternion and euler-related 754 | * operations, manually lifted from 755 | * https://github.com/mrdoob/three.js/tree/master/src/math, as of 9c30286b38df039fca389989ff06ea1c15d6bad1 756 | */ 757 | 758 | // Only use if the real THREE is not provided. 759 | var THREE = window.THREE || {}; 760 | 761 | // If some piece of THREE is missing, fill it in here. 762 | if (!THREE.Quaternion || !THREE.Vector3 || !THREE.Vector2 || !THREE.Euler || !THREE.Math) { 763 | console.log('No THREE.js found.'); 764 | 765 | 766 | /*** START Quaternion ***/ 767 | 768 | /** 769 | * @author mikael emtinger / http://gomo.se/ 770 | * @author alteredq / http://alteredqualia.com/ 771 | * @author WestLangley / http://github.com/WestLangley 772 | * @author bhouston / http://exocortex.com 773 | */ 774 | 775 | THREE.Quaternion = function ( x, y, z, w ) { 776 | 777 | this._x = x || 0; 778 | this._y = y || 0; 779 | this._z = z || 0; 780 | this._w = ( w !== undefined ) ? w : 1; 781 | 782 | }; 783 | 784 | THREE.Quaternion.prototype = { 785 | 786 | constructor: THREE.Quaternion, 787 | 788 | _x: 0,_y: 0, _z: 0, _w: 0, 789 | 790 | get x () { 791 | 792 | return this._x; 793 | 794 | }, 795 | 796 | set x ( value ) { 797 | 798 | this._x = value; 799 | this.onChangeCallback(); 800 | 801 | }, 802 | 803 | get y () { 804 | 805 | return this._y; 806 | 807 | }, 808 | 809 | set y ( value ) { 810 | 811 | this._y = value; 812 | this.onChangeCallback(); 813 | 814 | }, 815 | 816 | get z () { 817 | 818 | return this._z; 819 | 820 | }, 821 | 822 | set z ( value ) { 823 | 824 | this._z = value; 825 | this.onChangeCallback(); 826 | 827 | }, 828 | 829 | get w () { 830 | 831 | return this._w; 832 | 833 | }, 834 | 835 | set w ( value ) { 836 | 837 | this._w = value; 838 | this.onChangeCallback(); 839 | 840 | }, 841 | 842 | set: function ( x, y, z, w ) { 843 | 844 | this._x = x; 845 | this._y = y; 846 | this._z = z; 847 | this._w = w; 848 | 849 | this.onChangeCallback(); 850 | 851 | return this; 852 | 853 | }, 854 | 855 | copy: function ( quaternion ) { 856 | 857 | this._x = quaternion.x; 858 | this._y = quaternion.y; 859 | this._z = quaternion.z; 860 | this._w = quaternion.w; 861 | 862 | this.onChangeCallback(); 863 | 864 | return this; 865 | 866 | }, 867 | 868 | setFromEuler: function ( euler, update ) { 869 | 870 | if ( euler instanceof THREE.Euler === false ) { 871 | 872 | throw new Error( 'THREE.Quaternion: .setFromEuler() now expects a Euler rotation rather than a Vector3 and order.' ); 873 | } 874 | 875 | // http://www.mathworks.com/matlabcentral/fileexchange/ 876 | // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ 877 | // content/SpinCalc.m 878 | 879 | var c1 = Math.cos( euler._x / 2 ); 880 | var c2 = Math.cos( euler._y / 2 ); 881 | var c3 = Math.cos( euler._z / 2 ); 882 | var s1 = Math.sin( euler._x / 2 ); 883 | var s2 = Math.sin( euler._y / 2 ); 884 | var s3 = Math.sin( euler._z / 2 ); 885 | 886 | if ( euler.order === 'XYZ' ) { 887 | 888 | this._x = s1 * c2 * c3 + c1 * s2 * s3; 889 | this._y = c1 * s2 * c3 - s1 * c2 * s3; 890 | this._z = c1 * c2 * s3 + s1 * s2 * c3; 891 | this._w = c1 * c2 * c3 - s1 * s2 * s3; 892 | 893 | } else if ( euler.order === 'YXZ' ) { 894 | 895 | this._x = s1 * c2 * c3 + c1 * s2 * s3; 896 | this._y = c1 * s2 * c3 - s1 * c2 * s3; 897 | this._z = c1 * c2 * s3 - s1 * s2 * c3; 898 | this._w = c1 * c2 * c3 + s1 * s2 * s3; 899 | 900 | } else if ( euler.order === 'ZXY' ) { 901 | 902 | this._x = s1 * c2 * c3 - c1 * s2 * s3; 903 | this._y = c1 * s2 * c3 + s1 * c2 * s3; 904 | this._z = c1 * c2 * s3 + s1 * s2 * c3; 905 | this._w = c1 * c2 * c3 - s1 * s2 * s3; 906 | 907 | } else if ( euler.order === 'ZYX' ) { 908 | 909 | this._x = s1 * c2 * c3 - c1 * s2 * s3; 910 | this._y = c1 * s2 * c3 + s1 * c2 * s3; 911 | this._z = c1 * c2 * s3 - s1 * s2 * c3; 912 | this._w = c1 * c2 * c3 + s1 * s2 * s3; 913 | 914 | } else if ( euler.order === 'YZX' ) { 915 | 916 | this._x = s1 * c2 * c3 + c1 * s2 * s3; 917 | this._y = c1 * s2 * c3 + s1 * c2 * s3; 918 | this._z = c1 * c2 * s3 - s1 * s2 * c3; 919 | this._w = c1 * c2 * c3 - s1 * s2 * s3; 920 | 921 | } else if ( euler.order === 'XZY' ) { 922 | 923 | this._x = s1 * c2 * c3 - c1 * s2 * s3; 924 | this._y = c1 * s2 * c3 - s1 * c2 * s3; 925 | this._z = c1 * c2 * s3 + s1 * s2 * c3; 926 | this._w = c1 * c2 * c3 + s1 * s2 * s3; 927 | 928 | } 929 | 930 | if ( update !== false ) this.onChangeCallback(); 931 | 932 | return this; 933 | 934 | }, 935 | 936 | setFromAxisAngle: function ( axis, angle ) { 937 | 938 | // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm 939 | 940 | // assumes axis is normalized 941 | 942 | var halfAngle = angle / 2, s = Math.sin( halfAngle ); 943 | 944 | this._x = axis.x * s; 945 | this._y = axis.y * s; 946 | this._z = axis.z * s; 947 | this._w = Math.cos( halfAngle ); 948 | 949 | this.onChangeCallback(); 950 | 951 | return this; 952 | 953 | }, 954 | 955 | setFromRotationMatrix: function ( m ) { 956 | 957 | // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm 958 | 959 | // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) 960 | 961 | var te = m.elements, 962 | 963 | m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], 964 | m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], 965 | m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], 966 | 967 | trace = m11 + m22 + m33, 968 | s; 969 | 970 | if ( trace > 0 ) { 971 | 972 | s = 0.5 / Math.sqrt( trace + 1.0 ); 973 | 974 | this._w = 0.25 / s; 975 | this._x = ( m32 - m23 ) * s; 976 | this._y = ( m13 - m31 ) * s; 977 | this._z = ( m21 - m12 ) * s; 978 | 979 | } else if ( m11 > m22 && m11 > m33 ) { 980 | 981 | s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); 982 | 983 | this._w = ( m32 - m23 ) / s; 984 | this._x = 0.25 * s; 985 | this._y = ( m12 + m21 ) / s; 986 | this._z = ( m13 + m31 ) / s; 987 | 988 | } else if ( m22 > m33 ) { 989 | 990 | s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); 991 | 992 | this._w = ( m13 - m31 ) / s; 993 | this._x = ( m12 + m21 ) / s; 994 | this._y = 0.25 * s; 995 | this._z = ( m23 + m32 ) / s; 996 | 997 | } else { 998 | 999 | s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); 1000 | 1001 | this._w = ( m21 - m12 ) / s; 1002 | this._x = ( m13 + m31 ) / s; 1003 | this._y = ( m23 + m32 ) / s; 1004 | this._z = 0.25 * s; 1005 | 1006 | } 1007 | 1008 | this.onChangeCallback(); 1009 | 1010 | return this; 1011 | 1012 | }, 1013 | 1014 | setFromUnitVectors: function () { 1015 | 1016 | // http://lolengine.net/blog/2014/02/24/quaternion-from-two-vectors-final 1017 | 1018 | // assumes direction vectors vFrom and vTo are normalized 1019 | 1020 | var v1, r; 1021 | 1022 | var EPS = 0.000001; 1023 | 1024 | return function ( vFrom, vTo ) { 1025 | 1026 | if ( v1 === undefined ) v1 = new THREE.Vector3(); 1027 | 1028 | r = vFrom.dot( vTo ) + 1; 1029 | 1030 | if ( r < EPS ) { 1031 | 1032 | r = 0; 1033 | 1034 | if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { 1035 | 1036 | v1.set( - vFrom.y, vFrom.x, 0 ); 1037 | 1038 | } else { 1039 | 1040 | v1.set( 0, - vFrom.z, vFrom.y ); 1041 | 1042 | } 1043 | 1044 | } else { 1045 | 1046 | v1.crossVectors( vFrom, vTo ); 1047 | 1048 | } 1049 | 1050 | this._x = v1.x; 1051 | this._y = v1.y; 1052 | this._z = v1.z; 1053 | this._w = r; 1054 | 1055 | this.normalize(); 1056 | 1057 | return this; 1058 | 1059 | } 1060 | 1061 | }(), 1062 | 1063 | inverse: function () { 1064 | 1065 | this.conjugate().normalize(); 1066 | 1067 | return this; 1068 | 1069 | }, 1070 | 1071 | conjugate: function () { 1072 | 1073 | this._x *= - 1; 1074 | this._y *= - 1; 1075 | this._z *= - 1; 1076 | 1077 | this.onChangeCallback(); 1078 | 1079 | return this; 1080 | 1081 | }, 1082 | 1083 | dot: function ( v ) { 1084 | 1085 | return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; 1086 | 1087 | }, 1088 | 1089 | lengthSq: function () { 1090 | 1091 | return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; 1092 | 1093 | }, 1094 | 1095 | length: function () { 1096 | 1097 | return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); 1098 | 1099 | }, 1100 | 1101 | normalize: function () { 1102 | 1103 | var l = this.length(); 1104 | 1105 | if ( l === 0 ) { 1106 | 1107 | this._x = 0; 1108 | this._y = 0; 1109 | this._z = 0; 1110 | this._w = 1; 1111 | 1112 | } else { 1113 | 1114 | l = 1 / l; 1115 | 1116 | this._x = this._x * l; 1117 | this._y = this._y * l; 1118 | this._z = this._z * l; 1119 | this._w = this._w * l; 1120 | 1121 | } 1122 | 1123 | this.onChangeCallback(); 1124 | 1125 | return this; 1126 | 1127 | }, 1128 | 1129 | multiply: function ( q, p ) { 1130 | 1131 | if ( p !== undefined ) { 1132 | 1133 | console.warn( 'THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' ); 1134 | return this.multiplyQuaternions( q, p ); 1135 | 1136 | } 1137 | 1138 | return this.multiplyQuaternions( this, q ); 1139 | 1140 | }, 1141 | 1142 | multiplyQuaternions: function ( a, b ) { 1143 | 1144 | // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm 1145 | 1146 | var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; 1147 | var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; 1148 | 1149 | this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; 1150 | this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; 1151 | this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; 1152 | this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; 1153 | 1154 | this.onChangeCallback(); 1155 | 1156 | return this; 1157 | 1158 | }, 1159 | 1160 | multiplyVector3: function ( vector ) { 1161 | 1162 | console.warn( 'THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' ); 1163 | return vector.applyQuaternion( this ); 1164 | 1165 | }, 1166 | 1167 | slerp: function ( qb, t ) { 1168 | 1169 | if ( t === 0 ) return this; 1170 | if ( t === 1 ) return this.copy( qb ); 1171 | 1172 | var x = this._x, y = this._y, z = this._z, w = this._w; 1173 | 1174 | // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ 1175 | 1176 | var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; 1177 | 1178 | if ( cosHalfTheta < 0 ) { 1179 | 1180 | this._w = - qb._w; 1181 | this._x = - qb._x; 1182 | this._y = - qb._y; 1183 | this._z = - qb._z; 1184 | 1185 | cosHalfTheta = - cosHalfTheta; 1186 | 1187 | } else { 1188 | 1189 | this.copy( qb ); 1190 | 1191 | } 1192 | 1193 | if ( cosHalfTheta >= 1.0 ) { 1194 | 1195 | this._w = w; 1196 | this._x = x; 1197 | this._y = y; 1198 | this._z = z; 1199 | 1200 | return this; 1201 | 1202 | } 1203 | 1204 | var halfTheta = Math.acos( cosHalfTheta ); 1205 | var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta ); 1206 | 1207 | if ( Math.abs( sinHalfTheta ) < 0.001 ) { 1208 | 1209 | this._w = 0.5 * ( w + this._w ); 1210 | this._x = 0.5 * ( x + this._x ); 1211 | this._y = 0.5 * ( y + this._y ); 1212 | this._z = 0.5 * ( z + this._z ); 1213 | 1214 | return this; 1215 | 1216 | } 1217 | 1218 | var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, 1219 | ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; 1220 | 1221 | this._w = ( w * ratioA + this._w * ratioB ); 1222 | this._x = ( x * ratioA + this._x * ratioB ); 1223 | this._y = ( y * ratioA + this._y * ratioB ); 1224 | this._z = ( z * ratioA + this._z * ratioB ); 1225 | 1226 | this.onChangeCallback(); 1227 | 1228 | return this; 1229 | 1230 | }, 1231 | 1232 | equals: function ( quaternion ) { 1233 | 1234 | return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); 1235 | 1236 | }, 1237 | 1238 | fromArray: function ( array, offset ) { 1239 | 1240 | if ( offset === undefined ) offset = 0; 1241 | 1242 | this._x = array[ offset ]; 1243 | this._y = array[ offset + 1 ]; 1244 | this._z = array[ offset + 2 ]; 1245 | this._w = array[ offset + 3 ]; 1246 | 1247 | this.onChangeCallback(); 1248 | 1249 | return this; 1250 | 1251 | }, 1252 | 1253 | toArray: function ( array, offset ) { 1254 | 1255 | if ( array === undefined ) array = []; 1256 | if ( offset === undefined ) offset = 0; 1257 | 1258 | array[ offset ] = this._x; 1259 | array[ offset + 1 ] = this._y; 1260 | array[ offset + 2 ] = this._z; 1261 | array[ offset + 3 ] = this._w; 1262 | 1263 | return array; 1264 | 1265 | }, 1266 | 1267 | onChange: function ( callback ) { 1268 | 1269 | this.onChangeCallback = callback; 1270 | 1271 | return this; 1272 | 1273 | }, 1274 | 1275 | onChangeCallback: function () {}, 1276 | 1277 | clone: function () { 1278 | 1279 | return new THREE.Quaternion( this._x, this._y, this._z, this._w ); 1280 | 1281 | } 1282 | 1283 | }; 1284 | 1285 | THREE.Quaternion.slerp = function ( qa, qb, qm, t ) { 1286 | 1287 | return qm.copy( qa ).slerp( qb, t ); 1288 | 1289 | } 1290 | 1291 | /*** END Quaternion ***/ 1292 | /*** START Vector2 ***/ 1293 | /** 1294 | * @author mrdoob / http://mrdoob.com/ 1295 | * @author philogb / http://blog.thejit.org/ 1296 | * @author egraether / http://egraether.com/ 1297 | * @author zz85 / http://www.lab4games.net/zz85/blog 1298 | */ 1299 | 1300 | THREE.Vector2 = function ( x, y ) { 1301 | 1302 | this.x = x || 0; 1303 | this.y = y || 0; 1304 | 1305 | }; 1306 | 1307 | THREE.Vector2.prototype = { 1308 | 1309 | constructor: THREE.Vector2, 1310 | 1311 | set: function ( x, y ) { 1312 | 1313 | this.x = x; 1314 | this.y = y; 1315 | 1316 | return this; 1317 | 1318 | }, 1319 | 1320 | setX: function ( x ) { 1321 | 1322 | this.x = x; 1323 | 1324 | return this; 1325 | 1326 | }, 1327 | 1328 | setY: function ( y ) { 1329 | 1330 | this.y = y; 1331 | 1332 | return this; 1333 | 1334 | }, 1335 | 1336 | setComponent: function ( index, value ) { 1337 | 1338 | switch ( index ) { 1339 | 1340 | case 0: this.x = value; break; 1341 | case 1: this.y = value; break; 1342 | default: throw new Error( 'index is out of range: ' + index ); 1343 | 1344 | } 1345 | 1346 | }, 1347 | 1348 | getComponent: function ( index ) { 1349 | 1350 | switch ( index ) { 1351 | 1352 | case 0: return this.x; 1353 | case 1: return this.y; 1354 | default: throw new Error( 'index is out of range: ' + index ); 1355 | 1356 | } 1357 | 1358 | }, 1359 | 1360 | copy: function ( v ) { 1361 | 1362 | this.x = v.x; 1363 | this.y = v.y; 1364 | 1365 | return this; 1366 | 1367 | }, 1368 | 1369 | add: function ( v, w ) { 1370 | 1371 | if ( w !== undefined ) { 1372 | 1373 | console.warn( 'THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); 1374 | return this.addVectors( v, w ); 1375 | 1376 | } 1377 | 1378 | this.x += v.x; 1379 | this.y += v.y; 1380 | 1381 | return this; 1382 | 1383 | }, 1384 | 1385 | addVectors: function ( a, b ) { 1386 | 1387 | this.x = a.x + b.x; 1388 | this.y = a.y + b.y; 1389 | 1390 | return this; 1391 | 1392 | }, 1393 | 1394 | addScalar: function ( s ) { 1395 | 1396 | this.x += s; 1397 | this.y += s; 1398 | 1399 | return this; 1400 | 1401 | }, 1402 | 1403 | sub: function ( v, w ) { 1404 | 1405 | if ( w !== undefined ) { 1406 | 1407 | console.warn( 'THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); 1408 | return this.subVectors( v, w ); 1409 | 1410 | } 1411 | 1412 | this.x -= v.x; 1413 | this.y -= v.y; 1414 | 1415 | return this; 1416 | 1417 | }, 1418 | 1419 | subVectors: function ( a, b ) { 1420 | 1421 | this.x = a.x - b.x; 1422 | this.y = a.y - b.y; 1423 | 1424 | return this; 1425 | 1426 | }, 1427 | 1428 | multiply: function ( v ) { 1429 | 1430 | this.x *= v.x; 1431 | this.y *= v.y; 1432 | 1433 | return this; 1434 | 1435 | }, 1436 | 1437 | multiplyScalar: function ( s ) { 1438 | 1439 | this.x *= s; 1440 | this.y *= s; 1441 | 1442 | return this; 1443 | 1444 | }, 1445 | 1446 | divide: function ( v ) { 1447 | 1448 | this.x /= v.x; 1449 | this.y /= v.y; 1450 | 1451 | return this; 1452 | 1453 | }, 1454 | 1455 | divideScalar: function ( scalar ) { 1456 | 1457 | if ( scalar !== 0 ) { 1458 | 1459 | var invScalar = 1 / scalar; 1460 | 1461 | this.x *= invScalar; 1462 | this.y *= invScalar; 1463 | 1464 | } else { 1465 | 1466 | this.x = 0; 1467 | this.y = 0; 1468 | 1469 | } 1470 | 1471 | return this; 1472 | 1473 | }, 1474 | 1475 | min: function ( v ) { 1476 | 1477 | if ( this.x > v.x ) { 1478 | 1479 | this.x = v.x; 1480 | 1481 | } 1482 | 1483 | if ( this.y > v.y ) { 1484 | 1485 | this.y = v.y; 1486 | 1487 | } 1488 | 1489 | return this; 1490 | 1491 | }, 1492 | 1493 | max: function ( v ) { 1494 | 1495 | if ( this.x < v.x ) { 1496 | 1497 | this.x = v.x; 1498 | 1499 | } 1500 | 1501 | if ( this.y < v.y ) { 1502 | 1503 | this.y = v.y; 1504 | 1505 | } 1506 | 1507 | return this; 1508 | 1509 | }, 1510 | 1511 | clamp: function ( min, max ) { 1512 | 1513 | // This function assumes min < max, if this assumption isn't true it will not operate correctly 1514 | 1515 | if ( this.x < min.x ) { 1516 | 1517 | this.x = min.x; 1518 | 1519 | } else if ( this.x > max.x ) { 1520 | 1521 | this.x = max.x; 1522 | 1523 | } 1524 | 1525 | if ( this.y < min.y ) { 1526 | 1527 | this.y = min.y; 1528 | 1529 | } else if ( this.y > max.y ) { 1530 | 1531 | this.y = max.y; 1532 | 1533 | } 1534 | 1535 | return this; 1536 | }, 1537 | 1538 | clampScalar: ( function () { 1539 | 1540 | var min, max; 1541 | 1542 | return function ( minVal, maxVal ) { 1543 | 1544 | if ( min === undefined ) { 1545 | 1546 | min = new THREE.Vector2(); 1547 | max = new THREE.Vector2(); 1548 | 1549 | } 1550 | 1551 | min.set( minVal, minVal ); 1552 | max.set( maxVal, maxVal ); 1553 | 1554 | return this.clamp( min, max ); 1555 | 1556 | }; 1557 | 1558 | } )(), 1559 | 1560 | floor: function () { 1561 | 1562 | this.x = Math.floor( this.x ); 1563 | this.y = Math.floor( this.y ); 1564 | 1565 | return this; 1566 | 1567 | }, 1568 | 1569 | ceil: function () { 1570 | 1571 | this.x = Math.ceil( this.x ); 1572 | this.y = Math.ceil( this.y ); 1573 | 1574 | return this; 1575 | 1576 | }, 1577 | 1578 | round: function () { 1579 | 1580 | this.x = Math.round( this.x ); 1581 | this.y = Math.round( this.y ); 1582 | 1583 | return this; 1584 | 1585 | }, 1586 | 1587 | roundToZero: function () { 1588 | 1589 | this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); 1590 | this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); 1591 | 1592 | return this; 1593 | 1594 | }, 1595 | 1596 | negate: function () { 1597 | 1598 | this.x = - this.x; 1599 | this.y = - this.y; 1600 | 1601 | return this; 1602 | 1603 | }, 1604 | 1605 | dot: function ( v ) { 1606 | 1607 | return this.x * v.x + this.y * v.y; 1608 | 1609 | }, 1610 | 1611 | lengthSq: function () { 1612 | 1613 | return this.x * this.x + this.y * this.y; 1614 | 1615 | }, 1616 | 1617 | length: function () { 1618 | 1619 | return Math.sqrt( this.x * this.x + this.y * this.y ); 1620 | 1621 | }, 1622 | 1623 | normalize: function () { 1624 | 1625 | return this.divideScalar( this.length() ); 1626 | 1627 | }, 1628 | 1629 | distanceTo: function ( v ) { 1630 | 1631 | return Math.sqrt( this.distanceToSquared( v ) ); 1632 | 1633 | }, 1634 | 1635 | distanceToSquared: function ( v ) { 1636 | 1637 | var dx = this.x - v.x, dy = this.y - v.y; 1638 | return dx * dx + dy * dy; 1639 | 1640 | }, 1641 | 1642 | setLength: function ( l ) { 1643 | 1644 | var oldLength = this.length(); 1645 | 1646 | if ( oldLength !== 0 && l !== oldLength ) { 1647 | 1648 | this.multiplyScalar( l / oldLength ); 1649 | } 1650 | 1651 | return this; 1652 | 1653 | }, 1654 | 1655 | lerp: function ( v, alpha ) { 1656 | 1657 | this.x += ( v.x - this.x ) * alpha; 1658 | this.y += ( v.y - this.y ) * alpha; 1659 | 1660 | return this; 1661 | 1662 | }, 1663 | 1664 | equals: function ( v ) { 1665 | 1666 | return ( ( v.x === this.x ) && ( v.y === this.y ) ); 1667 | 1668 | }, 1669 | 1670 | fromArray: function ( array, offset ) { 1671 | 1672 | if ( offset === undefined ) offset = 0; 1673 | 1674 | this.x = array[ offset ]; 1675 | this.y = array[ offset + 1 ]; 1676 | 1677 | return this; 1678 | 1679 | }, 1680 | 1681 | toArray: function ( array, offset ) { 1682 | 1683 | if ( array === undefined ) array = []; 1684 | if ( offset === undefined ) offset = 0; 1685 | 1686 | array[ offset ] = this.x; 1687 | array[ offset + 1 ] = this.y; 1688 | 1689 | return array; 1690 | 1691 | }, 1692 | 1693 | fromAttribute: function ( attribute, index, offset ) { 1694 | 1695 | if ( offset === undefined ) offset = 0; 1696 | 1697 | index = index * attribute.itemSize + offset; 1698 | 1699 | this.x = attribute.array[ index ]; 1700 | this.y = attribute.array[ index + 1 ]; 1701 | 1702 | return this; 1703 | 1704 | }, 1705 | 1706 | clone: function () { 1707 | 1708 | return new THREE.Vector2( this.x, this.y ); 1709 | 1710 | } 1711 | 1712 | }; 1713 | /*** END Vector2 ***/ 1714 | /*** START Vector3 ***/ 1715 | 1716 | /** 1717 | * @author mrdoob / http://mrdoob.com/ 1718 | * @author *kile / http://kile.stravaganza.org/ 1719 | * @author philogb / http://blog.thejit.org/ 1720 | * @author mikael emtinger / http://gomo.se/ 1721 | * @author egraether / http://egraether.com/ 1722 | * @author WestLangley / http://github.com/WestLangley 1723 | */ 1724 | 1725 | THREE.Vector3 = function ( x, y, z ) { 1726 | 1727 | this.x = x || 0; 1728 | this.y = y || 0; 1729 | this.z = z || 0; 1730 | 1731 | }; 1732 | 1733 | THREE.Vector3.prototype = { 1734 | 1735 | constructor: THREE.Vector3, 1736 | 1737 | set: function ( x, y, z ) { 1738 | 1739 | this.x = x; 1740 | this.y = y; 1741 | this.z = z; 1742 | 1743 | return this; 1744 | 1745 | }, 1746 | 1747 | setX: function ( x ) { 1748 | 1749 | this.x = x; 1750 | 1751 | return this; 1752 | 1753 | }, 1754 | 1755 | setY: function ( y ) { 1756 | 1757 | this.y = y; 1758 | 1759 | return this; 1760 | 1761 | }, 1762 | 1763 | setZ: function ( z ) { 1764 | 1765 | this.z = z; 1766 | 1767 | return this; 1768 | 1769 | }, 1770 | 1771 | setComponent: function ( index, value ) { 1772 | 1773 | switch ( index ) { 1774 | 1775 | case 0: this.x = value; break; 1776 | case 1: this.y = value; break; 1777 | case 2: this.z = value; break; 1778 | default: throw new Error( 'index is out of range: ' + index ); 1779 | 1780 | } 1781 | 1782 | }, 1783 | 1784 | getComponent: function ( index ) { 1785 | 1786 | switch ( index ) { 1787 | 1788 | case 0: return this.x; 1789 | case 1: return this.y; 1790 | case 2: return this.z; 1791 | default: throw new Error( 'index is out of range: ' + index ); 1792 | 1793 | } 1794 | 1795 | }, 1796 | 1797 | copy: function ( v ) { 1798 | 1799 | this.x = v.x; 1800 | this.y = v.y; 1801 | this.z = v.z; 1802 | 1803 | return this; 1804 | 1805 | }, 1806 | 1807 | add: function ( v, w ) { 1808 | 1809 | if ( w !== undefined ) { 1810 | 1811 | console.warn( 'THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); 1812 | return this.addVectors( v, w ); 1813 | 1814 | } 1815 | 1816 | this.x += v.x; 1817 | this.y += v.y; 1818 | this.z += v.z; 1819 | 1820 | return this; 1821 | 1822 | }, 1823 | 1824 | addScalar: function ( s ) { 1825 | 1826 | this.x += s; 1827 | this.y += s; 1828 | this.z += s; 1829 | 1830 | return this; 1831 | 1832 | }, 1833 | 1834 | addVectors: function ( a, b ) { 1835 | 1836 | this.x = a.x + b.x; 1837 | this.y = a.y + b.y; 1838 | this.z = a.z + b.z; 1839 | 1840 | return this; 1841 | 1842 | }, 1843 | 1844 | sub: function ( v, w ) { 1845 | 1846 | if ( w !== undefined ) { 1847 | 1848 | console.warn( 'THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); 1849 | return this.subVectors( v, w ); 1850 | 1851 | } 1852 | 1853 | this.x -= v.x; 1854 | this.y -= v.y; 1855 | this.z -= v.z; 1856 | 1857 | return this; 1858 | 1859 | }, 1860 | 1861 | subVectors: function ( a, b ) { 1862 | 1863 | this.x = a.x - b.x; 1864 | this.y = a.y - b.y; 1865 | this.z = a.z - b.z; 1866 | 1867 | return this; 1868 | 1869 | }, 1870 | 1871 | multiply: function ( v, w ) { 1872 | 1873 | if ( w !== undefined ) { 1874 | 1875 | console.warn( 'THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' ); 1876 | return this.multiplyVectors( v, w ); 1877 | 1878 | } 1879 | 1880 | this.x *= v.x; 1881 | this.y *= v.y; 1882 | this.z *= v.z; 1883 | 1884 | return this; 1885 | 1886 | }, 1887 | 1888 | multiplyScalar: function ( scalar ) { 1889 | 1890 | this.x *= scalar; 1891 | this.y *= scalar; 1892 | this.z *= scalar; 1893 | 1894 | return this; 1895 | 1896 | }, 1897 | 1898 | multiplyVectors: function ( a, b ) { 1899 | 1900 | this.x = a.x * b.x; 1901 | this.y = a.y * b.y; 1902 | this.z = a.z * b.z; 1903 | 1904 | return this; 1905 | 1906 | }, 1907 | 1908 | applyEuler: function () { 1909 | 1910 | var quaternion; 1911 | 1912 | return function ( euler ) { 1913 | 1914 | if ( euler instanceof THREE.Euler === false ) { 1915 | 1916 | console.error( 'THREE.Vector3: .applyEuler() now expects a Euler rotation rather than a Vector3 and order.' ); 1917 | 1918 | } 1919 | 1920 | if ( quaternion === undefined ) quaternion = new THREE.Quaternion(); 1921 | 1922 | this.applyQuaternion( quaternion.setFromEuler( euler ) ); 1923 | 1924 | return this; 1925 | 1926 | }; 1927 | 1928 | }(), 1929 | 1930 | applyAxisAngle: function () { 1931 | 1932 | var quaternion; 1933 | 1934 | return function ( axis, angle ) { 1935 | 1936 | if ( quaternion === undefined ) quaternion = new THREE.Quaternion(); 1937 | 1938 | this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) ); 1939 | 1940 | return this; 1941 | 1942 | }; 1943 | 1944 | }(), 1945 | 1946 | applyMatrix3: function ( m ) { 1947 | 1948 | var x = this.x; 1949 | var y = this.y; 1950 | var z = this.z; 1951 | 1952 | var e = m.elements; 1953 | 1954 | this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; 1955 | this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; 1956 | this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; 1957 | 1958 | return this; 1959 | 1960 | }, 1961 | 1962 | applyMatrix4: function ( m ) { 1963 | 1964 | // input: THREE.Matrix4 affine matrix 1965 | 1966 | var x = this.x, y = this.y, z = this.z; 1967 | 1968 | var e = m.elements; 1969 | 1970 | this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ]; 1971 | this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ]; 1972 | this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ]; 1973 | 1974 | return this; 1975 | 1976 | }, 1977 | 1978 | applyProjection: function ( m ) { 1979 | 1980 | // input: THREE.Matrix4 projection matrix 1981 | 1982 | var x = this.x, y = this.y, z = this.z; 1983 | 1984 | var e = m.elements; 1985 | var d = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); // perspective divide 1986 | 1987 | this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * d; 1988 | this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * d; 1989 | this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * d; 1990 | 1991 | return this; 1992 | 1993 | }, 1994 | 1995 | applyQuaternion: function ( q ) { 1996 | 1997 | var x = this.x; 1998 | var y = this.y; 1999 | var z = this.z; 2000 | 2001 | var qx = q.x; 2002 | var qy = q.y; 2003 | var qz = q.z; 2004 | var qw = q.w; 2005 | 2006 | // calculate quat * vector 2007 | 2008 | var ix = qw * x + qy * z - qz * y; 2009 | var iy = qw * y + qz * x - qx * z; 2010 | var iz = qw * z + qx * y - qy * x; 2011 | var iw = - qx * x - qy * y - qz * z; 2012 | 2013 | // calculate result * inverse quat 2014 | 2015 | this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy; 2016 | this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz; 2017 | this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx; 2018 | 2019 | return this; 2020 | 2021 | }, 2022 | 2023 | project: function () { 2024 | 2025 | var matrix; 2026 | 2027 | return function ( camera ) { 2028 | 2029 | if ( matrix === undefined ) matrix = new THREE.Matrix4(); 2030 | 2031 | matrix.multiplyMatrices( camera.projectionMatrix, matrix.getInverse( camera.matrixWorld ) ); 2032 | return this.applyProjection( matrix ); 2033 | 2034 | }; 2035 | 2036 | }(), 2037 | 2038 | unproject: function () { 2039 | 2040 | var matrix; 2041 | 2042 | return function ( camera ) { 2043 | 2044 | if ( matrix === undefined ) matrix = new THREE.Matrix4(); 2045 | 2046 | matrix.multiplyMatrices( camera.matrixWorld, matrix.getInverse( camera.projectionMatrix ) ); 2047 | return this.applyProjection( matrix ); 2048 | 2049 | }; 2050 | 2051 | }(), 2052 | 2053 | transformDirection: function ( m ) { 2054 | 2055 | // input: THREE.Matrix4 affine matrix 2056 | // vector interpreted as a direction 2057 | 2058 | var x = this.x, y = this.y, z = this.z; 2059 | 2060 | var e = m.elements; 2061 | 2062 | this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; 2063 | this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; 2064 | this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; 2065 | 2066 | this.normalize(); 2067 | 2068 | return this; 2069 | 2070 | }, 2071 | 2072 | divide: function ( v ) { 2073 | 2074 | this.x /= v.x; 2075 | this.y /= v.y; 2076 | this.z /= v.z; 2077 | 2078 | return this; 2079 | 2080 | }, 2081 | 2082 | divideScalar: function ( scalar ) { 2083 | 2084 | if ( scalar !== 0 ) { 2085 | 2086 | var invScalar = 1 / scalar; 2087 | 2088 | this.x *= invScalar; 2089 | this.y *= invScalar; 2090 | this.z *= invScalar; 2091 | 2092 | } else { 2093 | 2094 | this.x = 0; 2095 | this.y = 0; 2096 | this.z = 0; 2097 | 2098 | } 2099 | 2100 | return this; 2101 | 2102 | }, 2103 | 2104 | min: function ( v ) { 2105 | 2106 | if ( this.x > v.x ) { 2107 | 2108 | this.x = v.x; 2109 | 2110 | } 2111 | 2112 | if ( this.y > v.y ) { 2113 | 2114 | this.y = v.y; 2115 | 2116 | } 2117 | 2118 | if ( this.z > v.z ) { 2119 | 2120 | this.z = v.z; 2121 | 2122 | } 2123 | 2124 | return this; 2125 | 2126 | }, 2127 | 2128 | max: function ( v ) { 2129 | 2130 | if ( this.x < v.x ) { 2131 | 2132 | this.x = v.x; 2133 | 2134 | } 2135 | 2136 | if ( this.y < v.y ) { 2137 | 2138 | this.y = v.y; 2139 | 2140 | } 2141 | 2142 | if ( this.z < v.z ) { 2143 | 2144 | this.z = v.z; 2145 | 2146 | } 2147 | 2148 | return this; 2149 | 2150 | }, 2151 | 2152 | clamp: function ( min, max ) { 2153 | 2154 | // This function assumes min < max, if this assumption isn't true it will not operate correctly 2155 | 2156 | if ( this.x < min.x ) { 2157 | 2158 | this.x = min.x; 2159 | 2160 | } else if ( this.x > max.x ) { 2161 | 2162 | this.x = max.x; 2163 | 2164 | } 2165 | 2166 | if ( this.y < min.y ) { 2167 | 2168 | this.y = min.y; 2169 | 2170 | } else if ( this.y > max.y ) { 2171 | 2172 | this.y = max.y; 2173 | 2174 | } 2175 | 2176 | if ( this.z < min.z ) { 2177 | 2178 | this.z = min.z; 2179 | 2180 | } else if ( this.z > max.z ) { 2181 | 2182 | this.z = max.z; 2183 | 2184 | } 2185 | 2186 | return this; 2187 | 2188 | }, 2189 | 2190 | clampScalar: ( function () { 2191 | 2192 | var min, max; 2193 | 2194 | return function ( minVal, maxVal ) { 2195 | 2196 | if ( min === undefined ) { 2197 | 2198 | min = new THREE.Vector3(); 2199 | max = new THREE.Vector3(); 2200 | 2201 | } 2202 | 2203 | min.set( minVal, minVal, minVal ); 2204 | max.set( maxVal, maxVal, maxVal ); 2205 | 2206 | return this.clamp( min, max ); 2207 | 2208 | }; 2209 | 2210 | } )(), 2211 | 2212 | floor: function () { 2213 | 2214 | this.x = Math.floor( this.x ); 2215 | this.y = Math.floor( this.y ); 2216 | this.z = Math.floor( this.z ); 2217 | 2218 | return this; 2219 | 2220 | }, 2221 | 2222 | ceil: function () { 2223 | 2224 | this.x = Math.ceil( this.x ); 2225 | this.y = Math.ceil( this.y ); 2226 | this.z = Math.ceil( this.z ); 2227 | 2228 | return this; 2229 | 2230 | }, 2231 | 2232 | round: function () { 2233 | 2234 | this.x = Math.round( this.x ); 2235 | this.y = Math.round( this.y ); 2236 | this.z = Math.round( this.z ); 2237 | 2238 | return this; 2239 | 2240 | }, 2241 | 2242 | roundToZero: function () { 2243 | 2244 | this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); 2245 | this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); 2246 | this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); 2247 | 2248 | return this; 2249 | 2250 | }, 2251 | 2252 | negate: function () { 2253 | 2254 | this.x = - this.x; 2255 | this.y = - this.y; 2256 | this.z = - this.z; 2257 | 2258 | return this; 2259 | 2260 | }, 2261 | 2262 | dot: function ( v ) { 2263 | 2264 | return this.x * v.x + this.y * v.y + this.z * v.z; 2265 | 2266 | }, 2267 | 2268 | lengthSq: function () { 2269 | 2270 | return this.x * this.x + this.y * this.y + this.z * this.z; 2271 | 2272 | }, 2273 | 2274 | length: function () { 2275 | 2276 | return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); 2277 | 2278 | }, 2279 | 2280 | lengthManhattan: function () { 2281 | 2282 | return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); 2283 | 2284 | }, 2285 | 2286 | normalize: function () { 2287 | 2288 | return this.divideScalar( this.length() ); 2289 | 2290 | }, 2291 | 2292 | setLength: function ( l ) { 2293 | 2294 | var oldLength = this.length(); 2295 | 2296 | if ( oldLength !== 0 && l !== oldLength ) { 2297 | 2298 | this.multiplyScalar( l / oldLength ); 2299 | } 2300 | 2301 | return this; 2302 | 2303 | }, 2304 | 2305 | lerp: function ( v, alpha ) { 2306 | 2307 | this.x += ( v.x - this.x ) * alpha; 2308 | this.y += ( v.y - this.y ) * alpha; 2309 | this.z += ( v.z - this.z ) * alpha; 2310 | 2311 | return this; 2312 | 2313 | }, 2314 | 2315 | cross: function ( v, w ) { 2316 | 2317 | if ( w !== undefined ) { 2318 | 2319 | console.warn( 'THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' ); 2320 | return this.crossVectors( v, w ); 2321 | 2322 | } 2323 | 2324 | var x = this.x, y = this.y, z = this.z; 2325 | 2326 | this.x = y * v.z - z * v.y; 2327 | this.y = z * v.x - x * v.z; 2328 | this.z = x * v.y - y * v.x; 2329 | 2330 | return this; 2331 | 2332 | }, 2333 | 2334 | crossVectors: function ( a, b ) { 2335 | 2336 | var ax = a.x, ay = a.y, az = a.z; 2337 | var bx = b.x, by = b.y, bz = b.z; 2338 | 2339 | this.x = ay * bz - az * by; 2340 | this.y = az * bx - ax * bz; 2341 | this.z = ax * by - ay * bx; 2342 | 2343 | return this; 2344 | 2345 | }, 2346 | 2347 | projectOnVector: function () { 2348 | 2349 | var v1, dot; 2350 | 2351 | return function ( vector ) { 2352 | 2353 | if ( v1 === undefined ) v1 = new THREE.Vector3(); 2354 | 2355 | v1.copy( vector ).normalize(); 2356 | 2357 | dot = this.dot( v1 ); 2358 | 2359 | return this.copy( v1 ).multiplyScalar( dot ); 2360 | 2361 | }; 2362 | 2363 | }(), 2364 | 2365 | projectOnPlane: function () { 2366 | 2367 | var v1; 2368 | 2369 | return function ( planeNormal ) { 2370 | 2371 | if ( v1 === undefined ) v1 = new THREE.Vector3(); 2372 | 2373 | v1.copy( this ).projectOnVector( planeNormal ); 2374 | 2375 | return this.sub( v1 ); 2376 | 2377 | } 2378 | 2379 | }(), 2380 | 2381 | reflect: function () { 2382 | 2383 | // reflect incident vector off plane orthogonal to normal 2384 | // normal is assumed to have unit length 2385 | 2386 | var v1; 2387 | 2388 | return function ( normal ) { 2389 | 2390 | if ( v1 === undefined ) v1 = new THREE.Vector3(); 2391 | 2392 | return this.sub( v1.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); 2393 | 2394 | } 2395 | 2396 | }(), 2397 | 2398 | angleTo: function ( v ) { 2399 | 2400 | var theta = this.dot( v ) / ( this.length() * v.length() ); 2401 | 2402 | // clamp, to handle numerical problems 2403 | 2404 | return Math.acos( THREE.Math.clamp( theta, - 1, 1 ) ); 2405 | 2406 | }, 2407 | 2408 | distanceTo: function ( v ) { 2409 | 2410 | return Math.sqrt( this.distanceToSquared( v ) ); 2411 | 2412 | }, 2413 | 2414 | distanceToSquared: function ( v ) { 2415 | 2416 | var dx = this.x - v.x; 2417 | var dy = this.y - v.y; 2418 | var dz = this.z - v.z; 2419 | 2420 | return dx * dx + dy * dy + dz * dz; 2421 | 2422 | }, 2423 | 2424 | setEulerFromRotationMatrix: function ( m, order ) { 2425 | 2426 | console.error( 'THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.' ); 2427 | 2428 | }, 2429 | 2430 | setEulerFromQuaternion: function ( q, order ) { 2431 | 2432 | console.error( 'THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.' ); 2433 | 2434 | }, 2435 | 2436 | getPositionFromMatrix: function ( m ) { 2437 | 2438 | console.warn( 'THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().' ); 2439 | 2440 | return this.setFromMatrixPosition( m ); 2441 | 2442 | }, 2443 | 2444 | getScaleFromMatrix: function ( m ) { 2445 | 2446 | console.warn( 'THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().' ); 2447 | 2448 | return this.setFromMatrixScale( m ); 2449 | }, 2450 | 2451 | getColumnFromMatrix: function ( index, matrix ) { 2452 | 2453 | console.warn( 'THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().' ); 2454 | 2455 | return this.setFromMatrixColumn( index, matrix ); 2456 | 2457 | }, 2458 | 2459 | setFromMatrixPosition: function ( m ) { 2460 | 2461 | this.x = m.elements[ 12 ]; 2462 | this.y = m.elements[ 13 ]; 2463 | this.z = m.elements[ 14 ]; 2464 | 2465 | return this; 2466 | 2467 | }, 2468 | 2469 | setFromMatrixScale: function ( m ) { 2470 | 2471 | var sx = this.set( m.elements[ 0 ], m.elements[ 1 ], m.elements[ 2 ] ).length(); 2472 | var sy = this.set( m.elements[ 4 ], m.elements[ 5 ], m.elements[ 6 ] ).length(); 2473 | var sz = this.set( m.elements[ 8 ], m.elements[ 9 ], m.elements[ 10 ] ).length(); 2474 | 2475 | this.x = sx; 2476 | this.y = sy; 2477 | this.z = sz; 2478 | 2479 | return this; 2480 | }, 2481 | 2482 | setFromMatrixColumn: function ( index, matrix ) { 2483 | 2484 | var offset = index * 4; 2485 | 2486 | var me = matrix.elements; 2487 | 2488 | this.x = me[ offset ]; 2489 | this.y = me[ offset + 1 ]; 2490 | this.z = me[ offset + 2 ]; 2491 | 2492 | return this; 2493 | 2494 | }, 2495 | 2496 | equals: function ( v ) { 2497 | 2498 | return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); 2499 | 2500 | }, 2501 | 2502 | fromArray: function ( array, offset ) { 2503 | 2504 | if ( offset === undefined ) offset = 0; 2505 | 2506 | this.x = array[ offset ]; 2507 | this.y = array[ offset + 1 ]; 2508 | this.z = array[ offset + 2 ]; 2509 | 2510 | return this; 2511 | 2512 | }, 2513 | 2514 | toArray: function ( array, offset ) { 2515 | 2516 | if ( array === undefined ) array = []; 2517 | if ( offset === undefined ) offset = 0; 2518 | 2519 | array[ offset ] = this.x; 2520 | array[ offset + 1 ] = this.y; 2521 | array[ offset + 2 ] = this.z; 2522 | 2523 | return array; 2524 | 2525 | }, 2526 | 2527 | fromAttribute: function ( attribute, index, offset ) { 2528 | 2529 | if ( offset === undefined ) offset = 0; 2530 | 2531 | index = index * attribute.itemSize + offset; 2532 | 2533 | this.x = attribute.array[ index ]; 2534 | this.y = attribute.array[ index + 1 ]; 2535 | this.z = attribute.array[ index + 2 ]; 2536 | 2537 | return this; 2538 | 2539 | }, 2540 | 2541 | clone: function () { 2542 | 2543 | return new THREE.Vector3( this.x, this.y, this.z ); 2544 | 2545 | } 2546 | 2547 | }; 2548 | /*** END Vector3 ***/ 2549 | /*** START Euler ***/ 2550 | /** 2551 | * @author mrdoob / http://mrdoob.com/ 2552 | * @author WestLangley / http://github.com/WestLangley 2553 | * @author bhouston / http://exocortex.com 2554 | */ 2555 | 2556 | THREE.Euler = function ( x, y, z, order ) { 2557 | 2558 | this._x = x || 0; 2559 | this._y = y || 0; 2560 | this._z = z || 0; 2561 | this._order = order || THREE.Euler.DefaultOrder; 2562 | 2563 | }; 2564 | 2565 | THREE.Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ]; 2566 | 2567 | THREE.Euler.DefaultOrder = 'XYZ'; 2568 | 2569 | THREE.Euler.prototype = { 2570 | 2571 | constructor: THREE.Euler, 2572 | 2573 | _x: 0, _y: 0, _z: 0, _order: THREE.Euler.DefaultOrder, 2574 | 2575 | get x () { 2576 | 2577 | return this._x; 2578 | 2579 | }, 2580 | 2581 | set x ( value ) { 2582 | 2583 | this._x = value; 2584 | this.onChangeCallback(); 2585 | 2586 | }, 2587 | 2588 | get y () { 2589 | 2590 | return this._y; 2591 | 2592 | }, 2593 | 2594 | set y ( value ) { 2595 | 2596 | this._y = value; 2597 | this.onChangeCallback(); 2598 | 2599 | }, 2600 | 2601 | get z () { 2602 | 2603 | return this._z; 2604 | 2605 | }, 2606 | 2607 | set z ( value ) { 2608 | 2609 | this._z = value; 2610 | this.onChangeCallback(); 2611 | 2612 | }, 2613 | 2614 | get order () { 2615 | 2616 | return this._order; 2617 | 2618 | }, 2619 | 2620 | set order ( value ) { 2621 | 2622 | this._order = value; 2623 | this.onChangeCallback(); 2624 | 2625 | }, 2626 | 2627 | set: function ( x, y, z, order ) { 2628 | 2629 | this._x = x; 2630 | this._y = y; 2631 | this._z = z; 2632 | this._order = order || this._order; 2633 | 2634 | this.onChangeCallback(); 2635 | 2636 | return this; 2637 | 2638 | }, 2639 | 2640 | copy: function ( euler ) { 2641 | 2642 | this._x = euler._x; 2643 | this._y = euler._y; 2644 | this._z = euler._z; 2645 | this._order = euler._order; 2646 | 2647 | this.onChangeCallback(); 2648 | 2649 | return this; 2650 | 2651 | }, 2652 | 2653 | setFromRotationMatrix: function ( m, order, update ) { 2654 | 2655 | var clamp = THREE.Math.clamp; 2656 | 2657 | // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) 2658 | 2659 | var te = m.elements; 2660 | var m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ]; 2661 | var m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ]; 2662 | var m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; 2663 | 2664 | order = order || this._order; 2665 | 2666 | if ( order === 'XYZ' ) { 2667 | 2668 | this._y = Math.asin( clamp( m13, - 1, 1 ) ); 2669 | 2670 | if ( Math.abs( m13 ) < 0.99999 ) { 2671 | 2672 | this._x = Math.atan2( - m23, m33 ); 2673 | this._z = Math.atan2( - m12, m11 ); 2674 | 2675 | } else { 2676 | 2677 | this._x = Math.atan2( m32, m22 ); 2678 | this._z = 0; 2679 | 2680 | } 2681 | 2682 | } else if ( order === 'YXZ' ) { 2683 | 2684 | this._x = Math.asin( - clamp( m23, - 1, 1 ) ); 2685 | 2686 | if ( Math.abs( m23 ) < 0.99999 ) { 2687 | 2688 | this._y = Math.atan2( m13, m33 ); 2689 | this._z = Math.atan2( m21, m22 ); 2690 | 2691 | } else { 2692 | 2693 | this._y = Math.atan2( - m31, m11 ); 2694 | this._z = 0; 2695 | 2696 | } 2697 | 2698 | } else if ( order === 'ZXY' ) { 2699 | 2700 | this._x = Math.asin( clamp( m32, - 1, 1 ) ); 2701 | 2702 | if ( Math.abs( m32 ) < 0.99999 ) { 2703 | 2704 | this._y = Math.atan2( - m31, m33 ); 2705 | this._z = Math.atan2( - m12, m22 ); 2706 | 2707 | } else { 2708 | 2709 | this._y = 0; 2710 | this._z = Math.atan2( m21, m11 ); 2711 | 2712 | } 2713 | 2714 | } else if ( order === 'ZYX' ) { 2715 | 2716 | this._y = Math.asin( - clamp( m31, - 1, 1 ) ); 2717 | 2718 | if ( Math.abs( m31 ) < 0.99999 ) { 2719 | 2720 | this._x = Math.atan2( m32, m33 ); 2721 | this._z = Math.atan2( m21, m11 ); 2722 | 2723 | } else { 2724 | 2725 | this._x = 0; 2726 | this._z = Math.atan2( - m12, m22 ); 2727 | 2728 | } 2729 | 2730 | } else if ( order === 'YZX' ) { 2731 | 2732 | this._z = Math.asin( clamp( m21, - 1, 1 ) ); 2733 | 2734 | if ( Math.abs( m21 ) < 0.99999 ) { 2735 | 2736 | this._x = Math.atan2( - m23, m22 ); 2737 | this._y = Math.atan2( - m31, m11 ); 2738 | 2739 | } else { 2740 | 2741 | this._x = 0; 2742 | this._y = Math.atan2( m13, m33 ); 2743 | 2744 | } 2745 | 2746 | } else if ( order === 'XZY' ) { 2747 | 2748 | this._z = Math.asin( - clamp( m12, - 1, 1 ) ); 2749 | 2750 | if ( Math.abs( m12 ) < 0.99999 ) { 2751 | 2752 | this._x = Math.atan2( m32, m22 ); 2753 | this._y = Math.atan2( m13, m11 ); 2754 | 2755 | } else { 2756 | 2757 | this._x = Math.atan2( - m23, m33 ); 2758 | this._y = 0; 2759 | 2760 | } 2761 | 2762 | } else { 2763 | 2764 | console.warn( 'THREE.Euler: .setFromRotationMatrix() given unsupported order: ' + order ) 2765 | 2766 | } 2767 | 2768 | this._order = order; 2769 | 2770 | if ( update !== false ) this.onChangeCallback(); 2771 | 2772 | return this; 2773 | 2774 | }, 2775 | 2776 | setFromQuaternion: function () { 2777 | 2778 | var matrix; 2779 | 2780 | return function ( q, order, update ) { 2781 | 2782 | if ( matrix === undefined ) matrix = new THREE.Matrix4(); 2783 | matrix.makeRotationFromQuaternion( q ); 2784 | this.setFromRotationMatrix( matrix, order, update ); 2785 | 2786 | return this; 2787 | 2788 | }; 2789 | 2790 | }(), 2791 | 2792 | setFromVector3: function ( v, order ) { 2793 | 2794 | return this.set( v.x, v.y, v.z, order || this._order ); 2795 | 2796 | }, 2797 | 2798 | reorder: function () { 2799 | 2800 | // WARNING: this discards revolution information -bhouston 2801 | 2802 | var q = new THREE.Quaternion(); 2803 | 2804 | return function ( newOrder ) { 2805 | 2806 | q.setFromEuler( this ); 2807 | this.setFromQuaternion( q, newOrder ); 2808 | 2809 | }; 2810 | 2811 | }(), 2812 | 2813 | equals: function ( euler ) { 2814 | 2815 | return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); 2816 | 2817 | }, 2818 | 2819 | fromArray: function ( array ) { 2820 | 2821 | this._x = array[ 0 ]; 2822 | this._y = array[ 1 ]; 2823 | this._z = array[ 2 ]; 2824 | if ( array[ 3 ] !== undefined ) this._order = array[ 3 ]; 2825 | 2826 | this.onChangeCallback(); 2827 | 2828 | return this; 2829 | 2830 | }, 2831 | 2832 | toArray: function () { 2833 | 2834 | return [ this._x, this._y, this._z, this._order ]; 2835 | 2836 | }, 2837 | 2838 | toVector3: function ( optionalResult ) { 2839 | 2840 | if ( optionalResult ) { 2841 | 2842 | return optionalResult.set( this._x, this._y, this._z ); 2843 | 2844 | } else { 2845 | 2846 | return new THREE.Vector3( this._x, this._y, this._z ); 2847 | 2848 | } 2849 | 2850 | }, 2851 | 2852 | onChange: function ( callback ) { 2853 | 2854 | this.onChangeCallback = callback; 2855 | 2856 | return this; 2857 | 2858 | }, 2859 | 2860 | onChangeCallback: function () {}, 2861 | 2862 | clone: function () { 2863 | 2864 | return new THREE.Euler( this._x, this._y, this._z, this._order ); 2865 | 2866 | } 2867 | 2868 | }; 2869 | /*** END Euler ***/ 2870 | /*** START Math ***/ 2871 | /** 2872 | * @author alteredq / http://alteredqualia.com/ 2873 | * @author mrdoob / http://mrdoob.com/ 2874 | */ 2875 | 2876 | THREE.Math = { 2877 | 2878 | generateUUID: function () { 2879 | 2880 | // http://www.broofa.com/Tools/Math.uuid.htm 2881 | 2882 | var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split( '' ); 2883 | var uuid = new Array( 36 ); 2884 | var rnd = 0, r; 2885 | 2886 | return function () { 2887 | 2888 | for ( var i = 0; i < 36; i ++ ) { 2889 | 2890 | if ( i == 8 || i == 13 || i == 18 || i == 23 ) { 2891 | 2892 | uuid[ i ] = '-'; 2893 | 2894 | } else if ( i == 14 ) { 2895 | 2896 | uuid[ i ] = '4'; 2897 | 2898 | } else { 2899 | 2900 | if ( rnd <= 0x02 ) rnd = 0x2000000 + ( Math.random() * 0x1000000 ) | 0; 2901 | r = rnd & 0xf; 2902 | rnd = rnd >> 4; 2903 | uuid[ i ] = chars[ ( i == 19 ) ? ( r & 0x3 ) | 0x8 : r ]; 2904 | 2905 | } 2906 | } 2907 | 2908 | return uuid.join( '' ); 2909 | 2910 | }; 2911 | 2912 | }(), 2913 | 2914 | // Clamp value to range 2915 | 2916 | clamp: function ( x, a, b ) { 2917 | 2918 | return ( x < a ) ? a : ( ( x > b ) ? b : x ); 2919 | 2920 | }, 2921 | 2922 | // Clamp value to range to range 2931 | 2932 | mapLinear: function ( x, a1, a2, b1, b2 ) { 2933 | 2934 | return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); 2935 | 2936 | }, 2937 | 2938 | // http://en.wikipedia.org/wiki/Smoothstep 2939 | 2940 | smoothstep: function ( x, min, max ) { 2941 | 2942 | if ( x <= min ) return 0; 2943 | if ( x >= max ) return 1; 2944 | 2945 | x = ( x - min ) / ( max - min ); 2946 | 2947 | return x * x * ( 3 - 2 * x ); 2948 | 2949 | }, 2950 | 2951 | smootherstep: function ( x, min, max ) { 2952 | 2953 | if ( x <= min ) return 0; 2954 | if ( x >= max ) return 1; 2955 | 2956 | x = ( x - min ) / ( max - min ); 2957 | 2958 | return x * x * x * ( x * ( x * 6 - 15 ) + 10 ); 2959 | 2960 | }, 2961 | 2962 | // Random float from <0, 1> with 16 bits of randomness 2963 | // (standard Math.random() creates repetitive patterns when applied over larger space) 2964 | 2965 | random16: function () { 2966 | 2967 | return ( 65280 * Math.random() + 255 * Math.random() ) / 65535; 2968 | 2969 | }, 2970 | 2971 | // Random integer from interval 2972 | 2973 | randInt: function ( low, high ) { 2974 | 2975 | return Math.floor( this.randFloat( low, high ) ); 2976 | 2977 | }, 2978 | 2979 | // Random float from interval 2980 | 2981 | randFloat: function ( low, high ) { 2982 | 2983 | return low + Math.random() * ( high - low ); 2984 | 2985 | }, 2986 | 2987 | // Random float from <-range/2, range/2> interval 2988 | 2989 | randFloatSpread: function ( range ) { 2990 | 2991 | return range * ( 0.5 - Math.random() ); 2992 | 2993 | }, 2994 | 2995 | degToRad: function () { 2996 | 2997 | var degreeToRadiansFactor = Math.PI / 180; 2998 | 2999 | return function ( degrees ) { 3000 | 3001 | return degrees * degreeToRadiansFactor; 3002 | 3003 | }; 3004 | 3005 | }(), 3006 | 3007 | radToDeg: function () { 3008 | 3009 | var radianToDegreesFactor = 180 / Math.PI; 3010 | 3011 | return function ( radians ) { 3012 | 3013 | return radians * radianToDegreesFactor; 3014 | 3015 | }; 3016 | 3017 | }(), 3018 | 3019 | isPowerOfTwo: function ( value ) { 3020 | 3021 | return ( value & ( value - 1 ) ) === 0 && value !== 0; 3022 | 3023 | }, 3024 | 3025 | nextPowerOfTwo: function ( value ) { 3026 | 3027 | value --; 3028 | value |= value >> 1; 3029 | value |= value >> 2; 3030 | value |= value >> 4; 3031 | value |= value >> 8; 3032 | value |= value >> 16; 3033 | value ++; 3034 | 3035 | return value; 3036 | } 3037 | 3038 | }; 3039 | 3040 | /*** END Math ***/ 3041 | 3042 | } 3043 | 3044 | module.exports = THREE; 3045 | 3046 | },{}],10:[function(_dereq_,module,exports){ 3047 | /* 3048 | * Copyright 2015 Google Inc. All Rights Reserved. 3049 | * Licensed under the Apache License, Version 2.0 (the "License"); 3050 | * you may not use this file except in compliance with the License. 3051 | * You may obtain a copy of the License at 3052 | * 3053 | * http://www.apache.org/licenses/LICENSE-2.0 3054 | * 3055 | * Unless required by applicable law or agreed to in writing, software 3056 | * distributed under the License is distributed on an "AS IS" BASIS, 3057 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3058 | * See the License for the specific language governing permissions and 3059 | * limitations under the License. 3060 | */ 3061 | var THREE = _dereq_('./three-math.js'); 3062 | var Util = _dereq_('./util.js'); 3063 | 3064 | var ROTATE_SPEED = 0.5; 3065 | /** 3066 | * Provides a quaternion responsible for pre-panning the scene before further 3067 | * transformations due to device sensors. 3068 | */ 3069 | function TouchPanner() { 3070 | window.addEventListener('touchstart', this.onTouchStart_.bind(this)); 3071 | window.addEventListener('touchmove', this.onTouchMove_.bind(this)); 3072 | window.addEventListener('touchend', this.onTouchEnd_.bind(this)); 3073 | 3074 | this.isTouching = false; 3075 | this.rotateStart = new THREE.Vector2(); 3076 | this.rotateEnd = new THREE.Vector2(); 3077 | this.rotateDelta = new THREE.Vector2(); 3078 | 3079 | this.theta = 0; 3080 | this.orientation = new THREE.Quaternion(); 3081 | } 3082 | 3083 | TouchPanner.prototype.getOrientation = function() { 3084 | this.orientation.setFromEuler(new THREE.Euler(0, 0, this.theta)); 3085 | return this.orientation; 3086 | }; 3087 | 3088 | TouchPanner.prototype.resetSensor = function() { 3089 | this.theta = 0; 3090 | }; 3091 | 3092 | TouchPanner.prototype.onTouchStart_ = function(e) { 3093 | // Only respond if there is exactly one touch. 3094 | if (e.touches.length != 1) { 3095 | return; 3096 | } 3097 | this.rotateStart.set(e.touches[0].pageX, e.touches[0].pageY); 3098 | this.isTouching = true; 3099 | }; 3100 | 3101 | TouchPanner.prototype.onTouchMove_ = function(e) { 3102 | if (!this.isTouching) { 3103 | return; 3104 | } 3105 | this.rotateEnd.set(e.touches[0].pageX, e.touches[0].pageY); 3106 | this.rotateDelta.subVectors(this.rotateEnd, this.rotateStart); 3107 | this.rotateStart.copy(this.rotateEnd); 3108 | 3109 | // On iOS, direction is inverted. 3110 | if (Util.isIOS()) { 3111 | this.rotateDelta.x *= -1; 3112 | } 3113 | 3114 | var element = document.body; 3115 | this.theta += 2 * Math.PI * this.rotateDelta.x / element.clientWidth * ROTATE_SPEED; 3116 | }; 3117 | 3118 | TouchPanner.prototype.onTouchEnd_ = function(e) { 3119 | this.isTouching = false; 3120 | }; 3121 | 3122 | module.exports = TouchPanner; 3123 | 3124 | },{"./three-math.js":9,"./util.js":11}],11:[function(_dereq_,module,exports){ 3125 | /* 3126 | * Copyright 2015 Google Inc. All Rights Reserved. 3127 | * Licensed under the Apache License, Version 2.0 (the "License"); 3128 | * you may not use this file except in compliance with the License. 3129 | * You may obtain a copy of the License at 3130 | * 3131 | * http://www.apache.org/licenses/LICENSE-2.0 3132 | * 3133 | * Unless required by applicable law or agreed to in writing, software 3134 | * distributed under the License is distributed on an "AS IS" BASIS, 3135 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3136 | * See the License for the specific language governing permissions and 3137 | * limitations under the License. 3138 | */ 3139 | var Util = window.Util || {}; 3140 | 3141 | Util.MIN_TIMESTEP = 0.001; 3142 | Util.MAX_TIMESTEP = 1; 3143 | 3144 | Util.clamp = function(value, min, max) { 3145 | return Math.min(Math.max(min, value), max); 3146 | }; 3147 | 3148 | Util.isIOS = function() { 3149 | return /iPad|iPhone|iPod/.test(navigator.platform); 3150 | }; 3151 | 3152 | Util.isFirefoxAndroid = function() { 3153 | return navigator.userAgent.indexOf('Firefox') !== -1 && navigator.userAgent.indexOf('Android') !== -1; 3154 | } 3155 | 3156 | // Helper method to validate the time steps of sensor timestamps. 3157 | Util.isTimestampDeltaValid = function(timestampDeltaS) { 3158 | if (isNaN(timestampDeltaS)) { 3159 | return false; 3160 | } 3161 | if (timestampDeltaS <= Util.MIN_TIMESTEP) { 3162 | return false; 3163 | } 3164 | if (timestampDeltaS > Util.MAX_TIMESTEP) { 3165 | return false; 3166 | } 3167 | return true; 3168 | } 3169 | 3170 | module.exports = Util; 3171 | 3172 | },{}],12:[function(_dereq_,module,exports){ 3173 | /* 3174 | * Copyright 2015 Google Inc. All Rights Reserved. 3175 | * Licensed under the Apache License, Version 2.0 (the "License"); 3176 | * you may not use this file except in compliance with the License. 3177 | * You may obtain a copy of the License at 3178 | * 3179 | * http://www.apache.org/licenses/LICENSE-2.0 3180 | * 3181 | * Unless required by applicable law or agreed to in writing, software 3182 | * distributed under the License is distributed on an "AS IS" BASIS, 3183 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3184 | * See the License for the specific language governing permissions and 3185 | * limitations under the License. 3186 | */ 3187 | 3188 | var CardboardHMDVRDevice = _dereq_('./cardboard-hmd-vr-device.js'); 3189 | //var OrientationPositionSensorVRDevice = require('./orientation-position-sensor-vr-device.js'); 3190 | var FusionPositionSensorVRDevice = _dereq_('./fusion-position-sensor-vr-device.js'); 3191 | var MouseKeyboardPositionSensorVRDevice = _dereq_('./mouse-keyboard-position-sensor-vr-device.js'); 3192 | // Uncomment to add positional tracking via webcam. 3193 | //var WebcamPositionSensorVRDevice = require('./webcam-position-sensor-vr-device.js'); 3194 | var HMDVRDevice = _dereq_('./base.js').HMDVRDevice; 3195 | var PositionSensorVRDevice = _dereq_('./base.js').PositionSensorVRDevice; 3196 | 3197 | function WebVRPolyfill() { 3198 | this.devices = []; 3199 | 3200 | if (!this.isWebVRAvailable()) { 3201 | this.enablePolyfill(); 3202 | } 3203 | } 3204 | 3205 | WebVRPolyfill.prototype.isWebVRAvailable = function() { 3206 | return ('getVRDevices' in navigator) || ('mozGetVRDevices' in navigator); 3207 | }; 3208 | 3209 | 3210 | WebVRPolyfill.prototype.enablePolyfill = function() { 3211 | // Initialize our virtual VR devices. 3212 | if (this.isCardboardCompatible()) { 3213 | this.devices.push(new CardboardHMDVRDevice()); 3214 | } 3215 | 3216 | // Polyfill using the right position sensor. 3217 | if (this.isMobile()) { 3218 | //this.devices.push(new OrientationPositionSensorVRDevice()); 3219 | this.devices.push(new FusionPositionSensorVRDevice()); 3220 | } else { 3221 | if (!WebVRConfig.MOUSE_KEYBOARD_CONTROLS_DISABLED) { 3222 | this.devices.push(new MouseKeyboardPositionSensorVRDevice()); 3223 | } 3224 | // Uncomment to add positional tracking via webcam. 3225 | //this.devices.push(new WebcamPositionSensorVRDevice()); 3226 | } 3227 | 3228 | // Provide navigator.getVRDevices. 3229 | navigator.getVRDevices = this.getVRDevices.bind(this); 3230 | 3231 | // Provide the CardboardHMDVRDevice and PositionSensorVRDevice objects. 3232 | window.HMDVRDevice = HMDVRDevice; 3233 | window.PositionSensorVRDevice = PositionSensorVRDevice; 3234 | }; 3235 | 3236 | WebVRPolyfill.prototype.getVRDevices = function() { 3237 | var devices = this.devices; 3238 | return new Promise(function(resolve, reject) { 3239 | try { 3240 | resolve(devices); 3241 | } catch (e) { 3242 | reject(e); 3243 | } 3244 | }); 3245 | }; 3246 | 3247 | /** 3248 | * Determine if a device is mobile. 3249 | */ 3250 | WebVRPolyfill.prototype.isMobile = function() { 3251 | return /Android/i.test(navigator.userAgent) || 3252 | /iPhone|iPad|iPod/i.test(navigator.userAgent); 3253 | }; 3254 | 3255 | WebVRPolyfill.prototype.isCardboardCompatible = function() { 3256 | // For now, support all iOS and Android devices. 3257 | // Also enable the WebVRConfig.FORCE_VR flag for debugging. 3258 | return this.isMobile() || WebVRConfig.FORCE_ENABLE_VR; 3259 | }; 3260 | 3261 | module.exports = WebVRPolyfill; 3262 | 3263 | },{"./base.js":1,"./cardboard-hmd-vr-device.js":2,"./fusion-position-sensor-vr-device.js":4,"./mouse-keyboard-position-sensor-vr-device.js":6}]},{},[5]); 3264 | -------------------------------------------------------------------------------- /panos.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "En route pour le Puy de Sancy", 4 | "details": "Equirectangular panorama built from 10 pictures, shot handheld.", 5 | "owner": "Alexandre Duret-Lutz", 6 | "overlay": "images/puydesancy-overlay.png", 7 | "image": "images/puydesancy.jpg", 8 | "audio": "audio/235428__allanz10d__calm-ocean-breeze-simulation.ogg", 9 | "source": "http://bit.ly/1xk9Vk1", 10 | "license": "Creative Commons" 11 | }, 12 | { 13 | "title": "Musée du Louvre", 14 | "details": "Equirectangular panorama built from 8 pictures.", 15 | "owner": "Alexandre Duret-Lutz", 16 | "overlay": "images/louvre-overlay.png", 17 | "image": "images/louvre.jpg", 18 | "audio": "audio/235428__allanz10d__calm-ocean-breeze-simulation.ogg", 19 | "source": "https://www.flickr.com/photos/gadl/406108908", 20 | "license": "Creative Commons" 21 | }, 22 | { 23 | "title": "Sunrise on Tintamarre", 24 | "details": "Equirectangular panorama built from 8 pictures.", 25 | "owner": "Alexandre Duret-Lutz", 26 | "overlay": "images/tintamarre-overlay.png", 27 | "image": "images/tintamarre.jpg", 28 | "audio": "audio/235428__allanz10d__calm-ocean-breeze-simulation.ogg", 29 | "source": "g", 30 | "license": "Creative Commons" 31 | } 32 | ] 33 | --------------------------------------------------------------------------------