1function Earth( element, options ) { 2 3 if ( typeof element == "string" ) { 4 element = document.getElementById( element ); 5 } 6 7 if ( ! element ) { 8 return false; 9 } 10 11 12 // check webgl support 13 14 if ( ! Earth.isSupported( options.legacySupportIE11 ) ) { 15 element.classList.add( "earth-show-fallback" ); 16 return false; 17 } 18 19 20 // remove fallback 21 22 var fallback = element.querySelector( ".earth-fallback" ); 23 if ( fallback ) fallback.style.display = "none"; 24 25 26 // add required css styles 27 28 if ( ! Earth.cssAdded && Earth.css ) { 29 Earth.addCss(); 30 Earth.cssAdded = true; 31 } 32 33 34 // add default meshes 35 36 if ( ! Earth.meshesAdded && Earth.markerObj ) { 37 Earth.addMesh( Earth.markerObj ); 38 Earth.meshesAdded = true; 39 } 40 41 42 // add container css class if not present 43 44 element.classList.add( "earth-container" ); 45 46 47 // reference to EarthInstance 48 element.earth = this; 49 50 // reference to dom element 51 this.element = element; 52 53 54 // default options 55 56 var defaults = { 57 58 isEarth : true, 59 quality: this.getQuality(), 60 61 location: { lat: 0, lng: 0 }, 62 63 mapLandColor : '#F4F4F4', 64 mapSeaColor : '#0099FF', 65 mapBorderColor : '', 66 mapBorderWidth : 0.3, 67 mapStyles : '', 68 69 mapSvg : '', 70 mapImage : '', 71 72 draggable : true, 73 grabCursor : true, 74 dragMomentum : true, 75 dragDamping : 0.7, 76 dragPolarLimit : 0.3, 77 78 polarLimit : 0.3, 79 80 autoRotate : false, 81 autoRotateSpeed : 1, 82 autoRotateSpeedUp : 0, 83 autoRotateDelay : 1000, 84 autoRotateStart : 1000, 85 autoRotateEasing : 'in-quad', 86 87 zoom: 1, 88 zoomable : false, 89 zoomMin: 0.5, 90 zoomMax: 1.25, 91 zoomSpeed : 1, 92 93 light: 'simple', // none, simple, sun 94 lightAmbience: ( options.light == 'none' ) ? 1 : 0.5, 95 lightIntensity: 0.5, 96 lightColor: '#FFFFFF', 97 lightGroundColor: '#999999', 98 sunLocation : { lat: 0, lng: 0 }, 99 sunDirection : false, 100 shadows: ( options.light == 'sun' ), 101 102 shininess: 0.1, 103 transparent : false, 104 innerOpacity : 1, 105 innerColor : '#FFFFFF', 106 107 paused : false, 108 showHotspots : false, 109 mapHitTest : false, 110 111 }; 112 113 this.options = Object.assign(defaults, options); 114 115 this.animations = []; 116 this.overlays = []; 117 this.occludables = []; 118 119 this.ready = false; 120 this.deltaTime = 0; 121 122 this.goAnimation = null; 123 this.zoomAnimation = null; 124 125 this.dragging = false; 126 127 this.autoRotating = false; 128 this.autoRotateTime = 0; 129 130 this.mouseOver = false; 131 this.mouseOverEarth = false; 132 this.mouseOverObject = null; 133 134 this.docMousePosition = false; 135 this.lastDocMousePosition = false; 136 this.mousePosition = false; 137 this.lastMousePosition = false; 138 this.lastMouseTime = 0; 139 this.mouseVelocity = new THREE.Vector2(); 140 141 this.momentum = new THREE.Vector2(); 142 143 this.elementSize = new THREE.Vector2(); 144 this.elementCenter = new THREE.Vector2(); 145 this.bounds = { 146 left: 0, 147 top: 0, 148 bottom: 0, 149 right: 0, 150 width: 1, 151 height: 1 152 }; 153 this.radius = 0; 154 155 this.init(); 156 157 return this; 158 159} 160 161 162 163 164Earth.ClassicEventDispatcher = function() {}; 165 166Object.assign( Earth.ClassicEventDispatcher.prototype, { 167 168 addEventListener: function ( type, listener ) { 169 170 if ( this._listeners === undefined ) this._listeners = {}; 171 172 const listeners = this._listeners; 173 174 if ( listeners[ type ] === undefined ) { 175 176 listeners[ type ] = []; 177 178 } 179 180 if ( listeners[ type ].indexOf( listener ) === - 1 ) { 181 182 listeners[ type ].push( listener ); 183 184 } 185 186 }, 187 188 hasEventListener: function ( type, listener ) { 189 190 if ( this._listeners === undefined ) return false; 191 192 const listeners = this._listeners; 193 194 return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1; 195 196 }, 197 198 removeEventListener: function ( type, listener ) { 199 200 if ( this._listeners === undefined ) return; 201 202 const listeners = this._listeners; 203 const listenerArray = listeners[ type ]; 204 205 if ( listenerArray !== undefined ) { 206 207 const index = listenerArray.indexOf( listener ); 208 209 if ( index !== - 1 ) { 210 211 listenerArray.splice( index, 1 ); 212 213 } 214 215 } 216 217 }, 218 219 dispatchEvent: function ( event ) { 220 221 if ( this._listeners === undefined ) return; 222 223 const listeners = this._listeners; 224 const listenerArray = listeners[ event.type ]; 225 226 if ( listenerArray !== undefined ) { 227 228 event.target = this; 229 230 // Make a copy, in case listeners are removed while iterating. 231 const array = listenerArray.slice( 0 ); 232 233 for ( let i = 0, l = array.length; i < l; i ++ ) { 234 235 array[ i ].call( this, event ); 236 237 } 238 239 } 240 241 } 242 243} ); 244 245 246 247 248 249Object.assign( Earth.prototype, Earth.ClassicEventDispatcher.prototype ); 250 251 252// private 253 254Earth.prototype.init = function() { 255 256 this.clock = new THREE.Clock(); 257 this.raycaster = new THREE.Raycaster(); 258 259 // scene 260 this.scene = new THREE.Scene(); 261 262 // camera 263 this.camera = new THREE.PerspectiveCamera( 50, 1, 1, 2000 ); 264 this.camera.position.z = Earth.camDistance; 265 this.scene.add( this.camera ); 266 267 // renderer 268 this.renderer = new THREE.WebGLRenderer( { 269 alpha: true, 270 antialias: true, 271 preserveDrawingBuffer: ( this.options.preserveDrawingBuffer ) ? true : false 272 } ); 273 274 if ( this.options.shadows ) { 275 this.renderer.shadowMap.enabled = true; 276 this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; 277 } 278 279 this.renderer.setPixelRatio( window.devicePixelRatio ); 280 281 this.canvas = this.renderer.domElement; 282 this.element.appendChild( this.canvas ); 283 284 285 // check capabilities for first instance on page 286 if ( ! Earth.capabilitiesChecked ) Earth.checkCapabilities( this.renderer ); 287 288 289 // earth 290 291 this.sphere = new THREE.Mesh( 292 new THREE.SphereBufferGeometry( Earth.earthRadius, this.options.quality*16, this.options.quality*12 ), 293 new THREE.MeshBasicMaterial( { visible: false } ) 294 ); 295 296 this.sphere.renderOrder = -2; 297 298 if ( this.options.shadows ) {
299 this.sphere.receiveShadow = true; 300 this.sphere.castShadow = true; 301 } 302 303 this.scene.add( this.sphere ); 304 305 306 // inner earth 307 308 if ( this.options.transparent ) { 309 this.innerSphere = new THREE.Mesh( 310 new THREE.SphereBufferGeometry( Earth.earthRadius, this.options.quality*16, this.options.quality*12 ), // - 0.001 311 new THREE.MeshBasicMaterial( { visible: false } ) 312 ); 313 314 this.innerSphere.renderOrder = -3; 315 this.sphere.add( this.innerSphere ); 316 } 317 318 319 this.loadTexture(); 320 321 322 // light 323 324 this.ambientLight = new THREE.AmbientLight( 0xFFFFFF, this.options.lightAmbience ); 325 this.scene.add( this.ambientLight ); 326 327 if ( this.options.light == 'simple' ) { 328 329 this.primaryLight = new THREE.HemisphereLight( new THREE.Color(this.options.lightColor), new THREE.Color(this.options.lightGroundColor), this.options.lightIntensity ); 330 this.scene.add( this.primaryLight ); 331 332 } else if ( this.options.light == 'sun' ) { 333 334 this.primaryLight = new THREE.DirectionalLight( new THREE.Color(this.options.lightColor), this.options.lightIntensity ); 335 336 if ( this.options.shadows ) { 337 this.primaryLight.castShadow = true; 338 339 this.primaryLight.shadow.mapSize.width = 340 this.primaryLight.shadow.mapSize.height = Earth.shadowSize[ this.options.quality ]; 341 342 var d = 25; 343 this.primaryLight.shadow.camera.left = -d; 344 this.primaryLight.shadow.camera.right = d; 345 this.primaryLight.shadow.camera.top = d; 346 this.primaryLight.shadow.camera.bottom = -d; 347 this.primaryLight.shadow.camera.far = 3000; 348 this.primaryLight.shadow.bias = 0.0001; 349 } 350 351 this.scene.add( this.primaryLight ); 352 353 } 354 355 356 // events 357 358 var dragStartMousePosition; 359 var ignoreClicks = false; 360 var lastTouchStart; 361 362 var clickHandler = (function( event ){ 363 364 if ( this.paused ) return; 365 366 if ( ignoreClicks && event.type == 'click' ) return; 367 368 369 // touch to mousevents 370 371 var touchEndClick = false; 372 373 if ( event.type == 'touchstart' || event.type == 'touchend' ) { 374 375 if ( event.touches.length > 1 ) return; 376 377 var x = event.changedTouches[0].clientX, y = event.changedTouches[0].clientY; 378 379 if ( event.type == 'touchstart' ) { 380 381 var type = 'mousedown'; 382 lastTouchStart = new THREE.Vector2( x, y ); 383 384 } else if ( event.type == 'touchend' ) { 385 386 var type = 'mouseup'; 387 touchEndClick = ! ignoreClicks && lastTouchStart && lastTouchStart.distanceTo( new THREE.Vector2( x, y ) ) < Earth.maxClickDistance; 388 389 } 390 391 } else { 392 393 var type = event.type; 394 var x = event.clientX, y = event.clientY; 395 396 } 397 398 399 var mouse = Earth.normalizeMouse( this, x, y ); 400 var intersects = this.raycast( mouse ); 401 var intersect_sphere = false; 402 403 for ( var i = 0; i < intersects.length; i++ ) { 404 405 if ( intersects[ i ].object == this.sphere ) { 406 var uv = intersects[ i ].uv; 407 intersects[ i ].object.material.map.transformUv( uv ); 408 409 var mouseEvent = { type: type, x : mouse.x, y : mouse.y, location : Earth.uvToLatLng( uv ), originalEvent: event }; 410 411 if ( this.mapHitTest ) { 412 mouseEvent.id = this.hitTestMap( uv ); 413 } 414 415 this.dispatchEvent( mouseEvent ); 416 417 if ( touchEndClick ) { 418 mouseEvent.type = 'click'; 419 this.dispatchEvent( mouseEvent ); 420 } 421 422 intersect_sphere = true; 423 424 } else if ( intersects[ i ].object.userData.marker && intersects[ i ].object.userData.marker.hotspot ) { 425 426 if ( ! intersect_sphere || (intersects[ i ].object.userData.marker.isSprite && ! intersects[ i ].object.userData.marker.occluded) ) { 427 428 if ( type == 'mousedown' && Earth.hasEvent( intersects[ i ].object.userData.marker, ['mousedown'] ) ) { 429 intersects[ i ].object.userData.marker.dispatchEvent( { type: "mousedown", originalEvent: event } ); 430 event.stopImmediatePropagation(); 431 event.preventDefault(); 432 break; 433 434 } else if ( type == 'mouseup' && Earth.hasEvent( intersects[ i ].object.userData.marker, ['mouseup'] ) ) { 435 intersects[ i ].object.userData.marker.dispatchEvent( { type: "mouseup" } ); 436 if ( touchEndClick ) { 437 intersects[ i ].object.userData.marker.dispatchEvent( { type: "click", originalEvent: event } ); 438 } 439 event.stopImmediatePropagation(); 440 event.preventDefault(); 441 break; 442 443 } else if ( ( type == 'click' || touchEndClick ) && Earth.hasEvent( intersects[ i ].object.userData.marker, ['click'] ) ) { 444 intersects[ i ].object.userData.marker.dispatchEvent( { type: "click", originalEvent: event } ); 445 event.stopImmediatePropagation(); 446 event.preventDefault(); 447 break; 448 449 } 450 451 } 452 453 454 } 455 456 } 457 458 }).bind( this ); 459 460 this.canvas.addEventListener( 'click', clickHandler ); 461 this.canvas.addEventListener( 'mousedown', clickHandler ); 462 this.canvas.addEventListener( 'mouseup', clickHandler ); 463 this.canvas.addEventListener( 'touchstart', clickHandler ); 464 this.canvas.addEventListener( 'touchend', clickHandler ); 465 466 this.canvas.addEventListener( 'mouseover', (function(event){ 467 468 if ( this.paused ) return; 469 this.mouseOver = true; 470 471 }).bind( this ) ); 472 473 this.canvas.addEventListener( 'mouseout', (function(event){ 474 475 if ( this.paused ) return; 476 this.mouseOver = false;
477 this.mouseOverEarth = false; 478 479 if ( this.mouseOverObject ) { 480 this.mouseOverObject.dispatchEvent( { type: "mouseout", originalEvent: event } ); 481 this.mouseOverObject = null; 482 } 483 484 }).bind( this ) ); 485 486 this.canvas.addEventListener( 'mousemove', (function(event) { 487 488 if ( this.paused ) return; 489 this.mousePosition = Earth.normalizeMouse( this, event.clientX, event.clientY ); 490 491 }).bind( this ) ); 492 493 this.canvas.addEventListener( 'touchmove', (function(event) { 494 495 if ( this.paused ) return; 496 this.mousePosition = Earth.normalizeMouse( this, event.touches[0].clientX, event.touches[0].clientY ); 497 498 }).bind( this ) ); 499 500 501 502 document.addEventListener( 'mousemove', (function(event) { 503 504 if ( this.paused ) return; 505 this.docMousePosition = Earth.getEventPosition( event ); 506 507 }).bind( this ) ); 508 509 document.body.addEventListener( 'touchmove', (function(event) { 510 511 if ( this.paused ) return; 512 this.docMousePosition = Earth.getEventPosition( event ); 513 514 }).bind( this ) ); 515 516 517 518 // orbit 519 520 this.orbit = new Earth.Orbit( this.camera, this ); 521 522 this.orbit.addEventListener( "start", (function() { 523 524 this.dragging = true; 525 if ( this.grabCursor ) document.documentElement.classList.add( 'earth-dragging' ); 526 527 if ( this.goAnimation ) { 528 this.goAnimation.stop(); 529 } 530 531 if ( this.ready ) this.dispatchEvent( { type: 'dragstart' } ); 532 533 // save mouse position 534 dragStartMousePosition = this.docMousePosition; 535 536 }).bind( this ) ); 537 538 this.orbit.addEventListener( "change", (function( event ) { 539 540 if ( this.ready ) this.dispatchEvent( { type: 'change' } ); 541 542 this.radius = this.getRadius(); 543 544 // beginn to ignore clicks if dragged a little distance 545 if ( this.dragging && ! ignoreClicks ) { 546 ignoreClicks = dragStartMousePosition && dragStartMousePosition.distanceTo( this.docMousePosition ) > Earth.maxClickDistance; 547 } 548 549 }).bind( this ) ); 550 551 this.orbit.addEventListener( "end", (function() { 552 553 this.momentum.copy( this.mouseVelocity ); 554 this.mouseVelocity.set(0, 0); 555 this.lastDocMousePosition = false; 556 557 document.documentElement.classList.remove( 'earth-dragging' ); 558 559 if ( this.dragging ) { 560 this.dragging = false; 561 if ( this.ready ) this.dispatchEvent( { type: 'dragend' } ); 562 } 563 564 // enable clicks after dragend 565 if ( ignoreClicks ) { 566 setTimeout( function(){ ignoreClicks = false; }, 1 ); 567 } 568 569 }).bind( this ) ); 570 571 572 // set properties 573 574 Object.assign( this, this.options ); 575 576 this.update(); 577
578 setTimeout( (function() { 579 this.ready = true; 580 this.element.classList.add( 'earth-ready' ); 581 this.dispatchEvent( { type: 'ready' } ); 582 this.dispatchEvent( { type: 'change' } ); 583 }).bind( this ), 1 ); 584 585}; 586 587 588// PUBLIC 589// properties 590 591Object.defineProperties( Earth.prototype, { 592 593 location : { 594 get: function() { 595 596 return Earth.worldToLatLng( this.camera.position ); 597 598 }, 599 set: function( v ) { 600 var latlng = Object.assign( {}, v ); 601 latlng = Earth.formatLatLng( latlng ); 602 603 // limit lat 604 if ( ! this.goAnimation ) { // no limits during rotation animation 605 latlng.lat = Math.min( this.maxLat(), Math.max( this.minLat(), latlng.lat ) ); 606 } 607 608 this.orbit.setPosition( Earth.latLngToWorld( latlng, this.camera.position.length() ) ); 609 this.resetAutoRotate(); 610 611 } 612 }, 613 614 paused : { 615 get: function() { 616 return this.options.paused; 617 }, 618 set: function( v ) { 619 if ( v ) { 620 this.options.paused = true; 621 622 } else if ( this.options.paused ) { // resume if paused 623 this.options.paused = false; 624 625 if ( this.ready ) { // if not ready, update will be called when ready 626 this.update(); 627 } 628 629 } 630 } 631 }, 632 633 mapLandColor : { 634 get: function() { 635 return this.options.mapLandColor; 636 }, 637 set: function( v ) { 638 this.options.mapLandColor = v; 639 } 640 }, 641 mapSeaColor : { 642 get: function() { 643 return this.options.mapSeaColor; 644 }, 645 set: function( v ) { 646 this.options.mapSeaColor = v; 647 } 648 }, 649 mapBorderColor : { 650 get: function() { 651 return this.options.mapBorderColor; 652 }, 653 set: function( v ) { 654 this.options.mapBorderColor = v; 655 } 656 }, 657 mapBorderWidth : { 658 get: function() { 659 return this.options.mapBorderWidth; 660 }, 661 set: function( v ) { 662 this.options.mapBorderWidth = v; 663 } 664 }, 665 mapStyles : { 666 get: function() { 667 return this.options.mapStyles; 668 }, 669 set: function( v ) { 670 this.options.mapStyles = v; 671 } 672 }, 673 mapSvg : { 674 get: function() { 675 return this.options.mapSvg; 676 }, 677 set: function( v ) { 678 this.options.mapSvg = v; 679 this.removeHitTester(); 680 } 681 }, 682 mapImage : { 683 get: function() { 684 return this.options.mapImage; 685 }, 686 set: function( v ) { 687 this.options.mapImage = v; 688 } 689 }, 690 691 draggable : { 692 get: function() { 693 return this.orbit.enableRotate; 694 }, 695 set: function( v ) { 696 this.orbit.enableRotate = v; 697 698 if ( ! v && this.dragging ) { // stop dragging 699 this.orbit.cancel(); 700 //this.orbit.enabled = false; 701 } 702 703 } 704 }, 705 706 dragPolarLimit : { 707 get: function() { 708 return this.options.dragPolarLimit; 709 }, 710 set: function( v ) { 711 this.options.dragPolarLimit = Math.max( 0, Math.min( 1, v ) ); 712 this.orbit.minPolarAngle = this.options.dragPolarLimit/2 * Math.PI; // radians 713 this.orbit.maxPolarAngle = (1-this.options.dragPolarLimit/2) * Math.PI; // radians 714 } 715 }, 716 717 polarLimit : { 718 get: function() { 719 return this.options.polarLimit; 720 }, 721 set: function( v ) { 722 this.options.polarLimit = Math.max( 0, Math.min( 1, v ) ); 723 } 724 }, 725 726 727 autoRotate : { 728 get: function() { 729 return this.options.autoRotate; 730 }, 731 set: function( v ) { 732 this.options.autoRotate = v; 733 if ( ! v ) { 734 this.resetAutoRotate(); 735 } 736 } 737 }, 738 739 740 zoom : { 741 get: function() { 742 return Earth.camDistance / this.camera.position.length(); 743 }, 744 set: function( v ) { 745 this.orbit.setPosition( this.camera.position.normalize().multiplyScalar( Earth.camDistance / v ) ); 746 } 747 }, 748 zoomable : { 749 get: function() { 750 return this.orbit.enableZoom; 751 }, 752 set: function( v ) { 753 this.orbit.enableZoom = v; 754 } 755 }, 756 zoomMin : { 757 get: function() { 758 return 1 / (this.orbit.maxDistance / Earth.camDistance); 759 }, 760 set: function( v ) { 761 this.orbit.maxDistance = Earth.camDistance * (1/v); 762 } 763 }, 764 zoomMax : { 765 get: function() { 766 return 1 / (this.orbit.minDistance / Earth.camDistance); 767 }, 768 set: function( v ) { 769 this.orbit.minDistance = Earth.camDistance * (1/v); 770 } 771 }, 772 zoomSpeed : { 773 get: function() { 774 return this.orbit.zoomSpeed; 775 }, 776 set: function( v ) { 777 this.orbit.zoomSpeed = v; 778 } 779 }, 780 781 782 lightAmbience : { 783 get: function() { 784 return this.ambientLight.intensity; 785 }, 786 set: function( v ) { 787 this.ambientLight.intensity = v; 788 } 789 }, 790 791 lightIntensity : { 792 get: function() { 793 if ( ! this.primaryLight ) return 1; 794 return this.primaryLight.intensity; 795 }, 796 set: function( v ) { 797 if ( ! this.primaryLight ) return; 798 this.primaryLight.intensity = v; 799 } 800 }, 801 lightColor : { 802 get: function() { 803 if ( ! this.primaryLight ) return '#FFFFFF'; 804 return '#' + this.primaryLight.color.getHexString(); 805 }, 806 set: function( v ) { 807 if ( ! this.primaryLight ) return; 808 this.primaryLight.color = new THREE.Color(v); 809 } 810 }, 811 lightGroundColor : { 812 get: function() { 813 if ( ! this.primaryLight || ! this.primaryLight.isHemisphereLight ) return '#FFFFFF'; 814 return '#' + this.primaryLight.groundColor.getHexString(); 815 }, 816 set: function( v ) { 817 if ( ! this.primaryLight || ! this.primaryLight.isHemisphereLight ) return; 818 this.primaryLight.groundColor = new THREE.Color(v); 819 } 820 }, 821 822 sunLocation : { 823 get: function() { 824 if ( ! this.primaryLight || ! this.primaryLight.isDirectionalLight ) return { lat: 0, lng: 0 }; 825 return Earth.worldToLatLng( this.primaryLight.position ); 826 }, 827 set: function( v ) { 828 if ( ! this.primaryLight || ! this.primaryLight.isDirectionalLight ) return; 829 this.primaryLight.position.copy( Earth.latLngToWorld( Earth.formatLatLng(v), Earth.camDistance ) ); 830 } 831 }, 832 833 sunDirection : { 834 get: function() { 835 if ( ! this.primaryLight || ! this.primaryLight.isDirectionalLight ) return false;
836 return this.options.sunDirection; 837 }, 838 set: function( v ) { 839 if ( ! this.primaryLight || ! this.primaryLight.isDirectionalLight ) return; 840 if ( ! v || typeof v.x == 'undefined' ) return; 841 if ( this.primaryLight.parent !== this.camera ) { 842 this.camera.add( this.primaryLight ); 843 } 844 this.primaryLight.position.set( v.x*50, v.y*50, 0); 845 } 846 }, 847 848 849} ); 850 851 852// private 853// determine quality based on container size 854 855Earth.prototype.getQuality = function() { 856 857 return ( this.element.offsetWidth >= 720 ) ? 4 : 3; 858 859}; 860 861 862// private 863// get min and max latitude according to polarLimit 864 865Earth.prototype.minLat = function() { 866 return (1 - this.options.polarLimit) * -90; 867}; 868Earth.prototype.maxLat = function() { 869 return (1 - this.options.polarLimit) * 90; 870}; 871 872 873 874// private 875// handle autoRotate 876 877Earth.prototype.updateAutoRotate = function() { 878 879 this.autoRotateTime += this.deltaTime; 880 881 if ( this.autoRotateTime > this.autoRotateDelay ) { 882 883 if ( ! this.autoRotate ) { // start autoRotate 884 this.dispatchEvent( { type: 'autorotate' } ); 885 this.autoRotate = true; 886 } 887 888 var t = ( this.autoRotateTime - this.autoRotateDelay ) / this.autoRotateStart; 889 890 if ( t > 1 ) { 891 this.orbit.autoRotateSpeed = this.autoRotateSpeed; 892 this.orbit.autoRotateSpeedUp = this.autoRotateSpeedUp; 893 } else { 894 this.orbit.autoRotateSpeed = THREE.Math.lerp( 0, this.autoRotateSpeed, Earth.Animation.Easing[ this.autoRotateEasing ](t) ); 895 this.orbit.autoRotateSpeedUp = THREE.Math.lerp( 0, this.autoRotateSpeedUp, Earth.Animation.Easing[ this.autoRotateEasing ](t) ); 896 } 897 898 } else { 899 900 this.orbit.autoRotateSpeed = 0; 901 this.orbit.autoRotateSpeedUp = 0; 902 903 } 904 905}; 906 907 908// PUBLIC 909// start autoRotate without delay 910 911Earth.prototype.startAutoRotate = function( easeIn ) { 912 this.autoRotateTime = this.autoRotateDelay + ( ( easeIn ) ? 0 : this.autoRotateStart ); 913 this.autoRotate = true; 914 this.autoRotating = true; 915}; 916 917// PUBLIC 918// interrupt rotation 919 920Earth.prototype.resetAutoRotate = function() { 921 this.autoRotateTime = 0; 922 this.autoRotating = false; 923}; 924 925 926// private 927// handle momentum 928 929Earth.prototype.updateMomentum = function() { 930 931 if ( this.dragging ) { 932 933 this.resetAutoRotate(); 934 this.orbit.autoRotateSpeed = 0; 935 this.orbit.autoRotateSpeedUp = 0; 936 this.momentum.set( 0, 0 ); 937 938 return; 939 940 } 941 942 if ( this.momentum.equals( Earth.zeroMomentum ) ) { 943 944 if ( this.autoRotate ) { 945 946 this.updateAutoRotate(); 947 948 } else { 949 950 this.orbit.autoRotateSpeed = 0; 951 this.orbit.autoRotateSpeedUp = 0; 952 953 } 954 955 return; 956 957 } 958 959 this.resetAutoRotate(); 960 961 this.orbit.autoRotateSpeed = this.momentum.x * 10000; 962 this.orbit.autoRotateSpeedUp = this.momentum.y * 10000; 963 964 this.momentum.set( 965 THREE.Math.lerp( this.momentum.x, 0, this.deltaTime / (2000 - (this.dragDamping*1999)) ), 966 THREE.Math.lerp( this.momentum.y, 0, this.deltaTime / (2000 - (this.dragDamping*1999)) ) 967 ); 968 969 970 if ( Math.abs(this.momentum.x) < 0.00005 ) { 971 this.momentum.x = 0; 972 } 973 if ( Math.abs(this.momentum.y) < 0.00005 ) { 974 this.momentum.y = 0; 975 } 976 977}; 978 979 980// private 981// calculate velocity for momentum 982 983Earth.prototype.updatePointerVelocity = function() { 984 985 if ( this.dragging && this.dragMomentum && this.docMousePosition && this.lastDocMousePosition ) { 986 987 var moveVelocity = this.docMousePosition.clone().sub( 988 this.lastDocMousePosition 989 ).multiplyScalar( 0.00005 ); 990 991 var max_velocity_x = 0.002; 992 var max_velocity_y = 0.0015; 993 // limit velocity 994 if ( moveVelocity.x < -max_velocity_x ) moveVelocity.x = -max_velocity_x; 995 else if ( moveVelocity.x > max_velocity_x ) moveVelocity.x = max_velocity_x; 996 if ( moveVelocity.y < -max_velocity_y ) moveVelocity.y = -max_velocity_y; 997 else if ( moveVelocity.y > max_velocity_y ) moveVelocity.y = max_velocity_y; 998 999 1000 //this.mouseVelocity.lerp( moveVelocity, Math.min(1, this.deltaTime/50) ); 1001 this.mouseVelocity.copy( moveVelocity ); 1002 1003 } else { 1004 1005 this.mouseVelocity.set( 0, 0 ); 1006 1007 } 1008 1009 this.lastDocMousePosition = ( this.docMousePosition ) ? this.docMousePosition.clone() : false; 1010 1011}; 1012 1013 1014// private 1015// handle mouse over/out events and cursors 1016 1017Earth.prototype.updatePointer = function() { 1018 1019 // mouse over / out 1020 1021 var overObject = false;
1022 this.mouseOverEarth = false; 1023 1024 1025 if ( this.mouseOver && ! this.dragging ) { 1026 1027 var intersects = this.raycast( this.mousePosition ); 1028 var intersect_sphere = false; 1029 1030 for ( var i = 0; i < intersects.length; i++ ) { 1031 1032 var obj = intersects[ i ].object; 1033 1034 if ( obj == this.sphere ) { 1035 this.mouseOverEarth = true; 1036 intersect_sphere = true; 1037 1038 } else if ( ! overObject && obj.userData.marker && obj.userData.marker.hotspot && Earth.hasEvent( obj.userData.marker, ['click','mousedown','mouseup','mouseover','mouseout'] ) ) { 1039 1040 if ( ! intersect_sphere || (intersects[ i ].object.userData.marker.isSprite && ! intersects[ i ].object.userData.marker.occluded) ) { 1041 overObject = intersects[ i ].object.userData.marker; 1042 } 1043 1044 } 1045 } 1046 1047 } 1048 1049 1050 if ( overObject ) { 1051 1052 if ( this.mouseOverObject && this.mouseOverObject != overObject ) { 1053 this.mouseOverObject.dispatchEvent( { type: "mouseout" } ); 1054 } 1055 1056 this.mouseOverObject = overObject; 1057 this.mouseOverObject.dispatchEvent( { type: "mouseover" } ); 1058 1059 } else if ( this.mouseOverObject ) { 1060 1061 this.mouseOverObject.dispatchEvent( { type: "mouseout" } ); 1062 this.mouseOverObject = null; 1063 1064 } 1065 1066 1067 if ( this.mouseOverObject && Earth.hasEvent( this.mouseOverObject, ['click'] ) ) { 1068 this.element.classList.add( 'earth-clickable' ); 1069 } else { 1070 this.element.classList.remove( 'earth-clickable' ); 1071 } 1072 1073 1074 if ( this.draggable && this.grabCursor && this.mouseOverEarth ) { 1075 this.element.classList.add( 'earth-draggable' ); 1076 } else { 1077 this.element.classList.remove( 'earth-draggable' ); 1078 } 1079 1080}; 1081 1082 1083 1084// PUBLIC 1085 1086Earth.prototype.hitTest = function( latlng ) { 1087 1088 return this.hitTestMap( Earth.latLngToUv( latlng ) ); 1089 1090}; 1091 1092// private 1093// hit test a uv position and return path id 1094 1095Earth.prototype.hitTestMap = function( uv ) { 1096 1097 if ( ! this.mapSvg ) return ''; 1098 1099 if ( ! this.hitTester ) { 1100 this.hitTester = document.createElement( 'div' ); 1101 document.body.appendChild( this.hitTester ); 1102 this.hitTester.className = 'earth-hittest'; 1103 this.hitTester.innerHTML = String( this.mapSvg ).replace(/\sid\s*=/gi, ' data-id='); // escape IDs 1104 } 1105 1106 var svgElement = this.hitTester.querySelector('svg'); 1107 if ( ! svgElement ) return ''; 1108 1109 this.hitTester.style.display = 'block'; 1110 1111 var rect = svgElement.getBoundingClientRect(); 1112 var elem = document.elementFromPoint( uv.x * rect.width, uv.y * rect.height ); 1113 1114 this.hitTester.style.display = 'none'; 1115 1116 if ( elem && elem.dataset && elem.dataset.id ) { 1117 return elem.dataset.id; 1118 } else { 1119 return ''; 1120 } 1121 1122}; 1123 1124// private 1125// removes hitTester if earth is destroyed or mapSvg is changed 1126 1127Earth.prototype.removeHitTester = function() { 1128 if ( this.hitTester ) { 1129 this.hitTester.remove(); 1130 this.hitTester = null; 1131 } 1132}; 1133 1134 1135// private 1136// raycast of mouse position 1137 1138Earth.prototype.raycast = function( mouse ) { 1139 1140 this.raycaster.setFromCamera( Earth.normalizeRaycast(mouse), this.camera ); 1141 return this.raycaster.intersectObjects( this.scene.children ); 1142 1143}; 1144 1145 1146// PUBLIC 1147// call redrawMap if map properties have been changed 1148 1149Earth.prototype.redrawMap = function() { 1150 1151 this.loadTexture(); 1152 1153}; 1154 1155 1156// PUBLIC 1157// updates the map texture after drawing to the mapCanvas 1158 1159Earth.prototype.updateMap = function() { 1160 1161 if ( this.mapTexture ) this.mapTexture.needsUpdate = true; 1162 1163}; 1164 1165 1166// private 1167// is svg? 1168 1169Earth.prototype.mapImageIsSvg = function( url ) { 1170 1171 return String(url).toLowerCase().indexOf('.svg') != -1; 1172 1173}; 1174 1175 1176 1177// private 1178// load map texture 1179 1180Earth.prototype.loadTexture = function( useMapSvg, isDefaultMap ) { 1181 1182 if ( useMapSvg ) { // svg map 1183 1184 var svg = this.mapSvg; 1185 1186 if ( isDefaultMap ) { 1187 var style = '<style type="text/css">'; 1188 style += '#SEA { fill:'+ this.options.mapSeaColor +'; }'; 1189 style += 'path { fill:'+ this.options.mapLandColor +'; '; 1190 style += 'stroke:'+ ( this.options.mapBorderColor ? this.options.mapBorderColor : this.options.mapLandColor ) +'; '; 1191 style += 'stroke-width:'+ this.options.mapBorderWidth +'; stroke-miterlimit:1; }'; 1192 style += this.options.mapStyles; 1193 style += '</style>'; 1194 svg = svg.replace( /(<svg[^>]+>)/i, '$1 ' + style ); 1195 } 1196 1197 this.mapImageElem = document.createElement("img"); 1198 this.mapImageElem.setAttribute( "src", "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svg))) ); 1199 this.mapImageElem.onload = this.drawTexture.bind( this ); 1200
1201 1202 } else if ( this.options.legacySupportIE11 && Earth.isIE11() && this.options.fallbackMapUrlIE11 ) { // IE fallback map image 1203 1204 this.mapImageElem = document.createElement("img"); 1205 this.mapImageElem.setAttribute( "src", this.options.fallbackMapUrlIE11 ); 1206 this.mapImageElem.onload = this.drawTexture.bind( this ); 1207 1208 1209 } else if ( this.options.mapImage && ! this.mapImageIsSvg( this.options.mapImage ) ) { // pixel image 1210 1211 this.mapImageElem = document.createElement("img"); 1212 this.mapImageElem.setAttribute( "src", this.options.mapImage ); 1213 this.mapImageElem.onload = this.drawTexture.bind( this ); 1214 1215 1216 } else if ( this.options.mapImage ) { // custom svg 1217 1218 var thisEarth = this; 1219 var ajax = new XMLHttpRequest(); 1220 1221 ajax.onreadystatechange = function() { 1222 if ( this.readyState == 4 ) { 1223 thisEarth.mapSvg = Earth.fixSvgNamespace( this.responseText ); 1224 thisEarth.loadTexture( true, false ); 1225 } 1226 }; 1227 1228 ajax.open("GET", this.options.mapImage, true); 1229 ajax.send(); 1230 1231 1232 } else if ( Earth.mapSvg ) { // default svg 1233 1234 this.mapSvg = Earth.fixSvgNamespace( Earth.mapSvg ); 1235 this.loadTexture( true, true ); 1236 1237 1238 } else { 1239 1240 setTimeout( this.drawTexture.bind( this ), 1 ); 1241 1242 1243 } 1244 1245}; 1246 1247 1248// private 1249// draw the map texture, called by loadTexture 1250 1251Earth.prototype.drawTexture = function() { 1252 1253 if ( ! this.mapCanvas ) { 1254 this.mapCanvas = document.createElement( "canvas" ); 1255 this.mapCanvas.width = Earth.textureSize[ this.quality ]; 1256 this.mapCanvas.height = Earth.textureSize[ this.quality ] / 2; 1257 this.mapContext = this.mapCanvas.getContext("2d"); 1258 1259 } else { // already exits 1260 this.mapContext.clearRect(0, 0, this.mapCanvas.width, this.mapCanvas.height); 1261 1262 } 1263 1264 1265 // draw map image 1266 1267 if ( this.mapImageElem ) { 1268 this.mapContext.drawImage( this.mapImageElem, 0, 0, this.mapImageElem.width, this.mapImageElem.height, 0, 0, this.mapCanvas.width, this.mapCanvas.height ); 1269 } 1270 1271 this.dispatchEvent( { type: 'drawtexture', canvas: this.mapCanvas, context : this.mapContext } ); 1272 1273 1274 if ( ! this.mapTexture ) { 1275 1276 this.mapTexture = new THREE.CanvasTexture( this.mapCanvas ); 1277 this.mapTexture.wrapS = THREE.RepeatWrapping; // this.mapTexture.wrapT = 1278 this.mapTexture.anisotropy = Earth.anisotropy[ this.quality ]; 1279 this.mapTexture.offset = new THREE.Vector2( -0.25, 0 ); 1280 1281 var earthMaterial = { 1282 map: this.mapTexture, 1283 shininess: this.shininess * 100, 1284 dithering : (this.light != 'none') 1285 }; 1286 1287 // inner earth 1288 if ( this.transparent ) { 1289 1290 earthMaterial.transparent = true; 1291 earthMaterial.depthWrite = false; 1292 1293 this.innerSphere.material = new THREE.MeshBasicMaterial( { 1294 dithering : (this.light != 'none'), 1295 transparent : true, 1296 color : this.innerColor, 1297 opacity : this.innerOpacity, 1298 side: THREE.BackSide, 1299 depthWrite : false, 1300 map: this.mapTexture 1301 } ); 1302 1303 } 1304 1305 this.sphere.material = new THREE.MeshPhongMaterial( earthMaterial ); 1306 1307 1308 } else { // already exists 1309 1310 this.mapTexture.needsUpdate = true; 1311 1312 } 1313 1314}; 1315 1316 1317// private 1318// watch for bounding rect changes 1319 1320Earth.prototype.updateBounds = function() { 1321 1322 this.bounds = this.element.getBoundingClientRect(); 1323 1324 this.outOfView = this.bounds.right < 0 || this.bounds.bottom < 0 || 1325 this.bounds.left > window.innerWidth || this.bounds.top > window.innerHeight; 1326 1327 this.isVisible = this.bounds.width && this.bounds.height; 1328 1329 1330 if ( this.elementSize.x == this.bounds.width && this.elementSize.y == this.bounds.height ) return; // not resized 1331 1332 this.elementSize.set( this.bounds.width, this.bounds.height ); 1333 this.elementCenter.set( this.bounds.width / 2, this.bounds.height / 2 ); 1334 this.containerScale = Math.min( this.elementSize.x, this.elementSize.y ) / 1000; // for overlay scaling 1335 1336 this.camera.aspect = this.bounds.width / this.bounds.height; 1337 this.camera.updateProjectionMatrix(); 1338 this.renderer.setSize( this.bounds.width, this.bounds.height ); 1339 1340 this.radius = this.getRadius(); 1341 1342}; 1343 1344 1345// private 1346// main loop 1347 1348Earth.prototype.update = function() { 1349 1350 if ( this.paused ) return; 1351 1352 requestAnimationFrame( this.update.bind(this) ); 1353 1354 if ( ! this.ready ) return; 1355 1356 this.deltaTime = Math.min( 100, this.clock.getDelta() * 1000 ); 1357 1358 this.updateBounds(); 1359 this.updatePointerVelocity(); 1360 this.updatePointer(); 1361 this.updateMomentum();
1362 this.orbit.update(); 1363 1364 /* >>> */ 1365 this.updateAnimations(); 1366 /* <<< */ 1367 1368 this.dispatchEvent( { type: "update" } ); 1369 1370 1371 if ( ! this.outOfView && this.isVisible ) { 1372 1373 this.renderer.render( this.scene, this.camera ); 1374 1375 /* >>> */ 1376 this.updateOccludables(); 1377 /* <<< */ 1378 1379 this.updateOverlays(); 1380 1381 } 1382 1383}; 1384 1385 1386 1387// PUBLIC 1388// create and add objects 1389 1390Earth.prototype.addMarker = function( options ) { 1391 return new Earth.Marker( options, this ); 1392}; 1393 1394/* >>> */ 1395 1396Earth.prototype.addImage = function( options ) { 1397 return new Earth.Image( options, this ); 1398}; 1399 1400 1401Earth.prototype.addSprite = function( options ) { 1402 return new Earth.Sprite( options, this ); 1403}; 1404 1405 1406Earth.prototype.addPoints = function( options ) { 1407 return new Earth.Points( options, this ); 1408}; 1409 1410 1411Earth.prototype.addLine = function( options ) { 1412 return new Earth.Line( options, this ); 1413}; 1414 1415/* <<< */ 1416 1417Earth.prototype.addOverlay = function( options ) { 1418 return new Earth.Overlay( options, this ); 1419}; 1420 1421 1422// private 1423// handle overlays 1424 1425Earth.prototype.updateOverlays = function() { 1426 1427 var updateOrder = false; 1428 var camDistance = this.camera.position.length(); 1429 1430 for (var i=0; i < this.overlays.length; i++) { 1431 1432 var overlay = this.overlays[i]; 1433 1434 1435 updateOrder = true; 1436 1437 // position 1438 overlay.updatePositions(); 1439 1440 if ( overlay.elementPosition.y < overlay.earth.elementSize.y / 2 ) { 1441 overlay.element.classList.add('earth-overlay-top'); 1442 } else { 1443 overlay.element.classList.remove('earth-overlay-top'); 1444 } 1445 1446 if ( overlay.elementPosition.x < overlay.earth.elementSize.x / 2 ) { 1447 overlay.element.classList.add('earth-overlay-left'); 1448 } else { 1449 overlay.element.classList.remove('earth-overlay-left'); 1450 } 1451 1452 // occlusion 1453 overlay.updateOcclusion(); 1454 1455 if ( overlay.occluded ) { 1456 overlay.element.classList.add('earth-occluded'); 1457 } else { 1458 overlay.element.classList.remove('earth-occluded'); 1459 } 1460 1461 1462 if ( ! overlay.visible ) continue; 1463 1464 1465 // scaling 1466 var scale = 1; 1467 1468 if ( overlay.depthScale ) { 1469 scale *= Math.max( 0, 1 - overlay.depthScale + (camDistance-overlay.distance) / Earth.earthRadius * overlay.depthScale ); 1470 } 1471 if ( overlay.zoomScale ) { 1472 scale *= THREE.Math.lerp( 1, this.zoom, overlay.zoomScale ); 1473 } 1474 if ( overlay.containerScale ) { 1475 scale *= THREE.Math.lerp( 1, this.containerScale, overlay.containerScale ); 1476 } 1477 1478 scale = Math.max(0, scale); 1479 var scaleTransform = ( scale!=1 ) ? ' scale('+ scale +')' : ''; 1480 1481 1482 // apply transform 1483 overlay.element.style.transform = 'translate('+ overlay.elementPosition.x +'px, '+ overlay.elementPosition.y +'px)' + scaleTransform; 1484 1485 1486 if ( ! overlay.ready ) { 1487 overlay.ready = true; 1488 overlay.visible = true; 1489 } 1490 1491 } 1492 1493 1494 if ( updateOrder ) { 1495 1496 // sort by cam distance for z-index 1497 1498 this.overlays.sort( function(a, b) { 1499 return b.distance - a.distance; 1500 } ); 1501 1502 for (var i=0; i < this.overlays.length; i++) { 1503 this.overlays[i].element.style.zIndex = (( this.overlays[i].occluded && this.overlays[i].occlude ) ? 10 : 1010) + i; 1504 } 1505 1506 } 1507 1508}; 1509 1510 1511// private 1512// handle occlusion 1513 1514Earth.prototype.updateOccludables = function() { 1515 1516 for ( var i=0; i < this.occludables.length; i++ ) { 1517 1518 var occludable = this.occludables[i]; 1519 1520 occludable.updatePositions(); 1521 occludable.updateOcclusion(); 1522 1523 } 1524 1525}; 1526 1527 1528// private 1529// handle all animations on this earth 1530/* >>> */ 1531 1532Earth.prototype.updateAnimations = function() { 1533 1534 var complete_animations = []; 1535 1536 for ( var i in this.animations ) { 1537 var ani = this.animations[i]; 1538 1539 if ( ani.paused ) continue; 1540 1541 if ( ani.target.removed ) { 1542 complete_animations.push( ani ); 1543 continue; 1544 } 1545 1546 ani.time += this.deltaTime; 1547 var p = ani.time / ani.duration; // progress 0-1 1548 1549 ani.step( Math.min( p, 1 ) ); 1550 1551 if ( p >= 1 ) { // end of animation 1552 1553 if ( ani.loop ) { 1554 1555 if ( ani.oscillate ) { 1556 ani.time = Math.max( 0, ani.duration - ani.time ); 1557 var current = ani.to; 1558 ani.to = ani.from; 1559 ani.from = current; 1560 } else { 1561 ani.time = Math.max( 0, ani.duration - ani.time ); 1562 } 1563 1564 ani.dispatchEvents( true ); 1565 1566 } else { 1567 complete_animations.push( ani ); 1568 } 1569 1570 } 1571 1572 } 1573 1574 for ( var i in complete_animations ) { 1575 complete_animations[i].stop( true ); 1576 } 1577 1578}; 1579 1580/* <<< */ 1581 1582 1583 1584// PUBLIC 1585// animate earth location and zoom 1586// special options: zoom, approachAngle 1587// returns false or aniInstance 1588 1589Earth.prototype.goTo = function( location, options ) { 1590 1591 if ( ! options ) options = {}; 1592 1593 if ( options.approachAngle ) { 1594 1595 var toPos = Earth.latLngToWorld( location, Earth.earthRadius ); 1596 var camPos = this.camera.position.clone().normalize().multiplyScalar( Earth.earthRadius ); 1597 1598 if ( THREE.Math.radToDeg( toPos.angleTo( camPos ) ) > options.approachAngle ) { 1599 1600 for ( var i=1; i <= 32; i++ ) { 1601 1602 var midPos = new THREE.Vector3().lerpVectors( camPos, toPos, i/32 ).normalize().multiplyScalar( Earth.earthRadius ); 1603 1604 if ( THREE.Math.radToDeg( toPos.angleTo( midPos ) ) <= options.approachAngle ) { 1605 1606 location = Earth.worldToLatLng( midPos ); 1607 break; 1608 1609 } 1610 1611 } 1612 1613 } else { 1614 1615 return false;
1616 1617 } 1618 1619 } 1620 1621 1622 // cancel previous animation 1623 1624 if ( this.goAnimation ) { 1625 this.goAnimation.stop(); 1626 } 1627 1628 1629 var to = Object.assign( {}, location ); 1630 1631 // limit lat 1632 to.lat = Math.min( this.maxLat(), to.lat ); 1633 to.lat = Math.max( this.minLat(), to.lat ); 1634 1635 1636 var ani = { 1637 _end : function() { 1638 this.goAnimation = null; 1639 if ( this.zoomAnimation ) this.zoomAnimation.stop(); 1640 this.zoomAnimation = null; 1641 }, 1642 lerpLatLng : true 1643 }; 1644 1645 Object.assign( ani, options ); 1646 1647 this.goAnimation = this.animate( 'location', to, ani ); 1648 1649 1650 if ( options.zoom ) { 1651 1652 this.zoomAnimation = this.animate( 'zoom', options.zoom, { 1653 duration : this.goAnimation.duration 1654 } ); 1655 1656 } 1657 1658 return this.goAnimation; 1659 1660}; 1661 1662 1663 1664// PUBLIC 1665// get position in element container for a lat/lng location with optional earth offset 1666 1667Earth.prototype.getPoint = function( location, offset ) { 1668 1669 if ( ! offset ) offset = 0; 1670 1671 var worldPos = Earth.latLngToWorld( location, Earth.earthRadius + offset ); 1672 1673 return Earth.worldToElement( worldPos, this.elementSize, this.camera ); 1674 1675}; 1676 1677 1678 1679// PUBLIC 1680// get lat/lng location for a mouse/touch position 1681 1682Earth.prototype.getLocation = function( point ) { 1683 1684 var mouse = Earth.normalizeMouse( this, point.x, point.y ); 1685 1686 this.raycaster.setFromCamera( Earth.normalizeRaycast(mouse), this.camera ); 1687 var intersects = this.raycaster.intersectObjects( [this.sphere] ); 1688 1689 for ( var i = 0; i < intersects.length; i++ ) { 1690 1691 var uv = intersects[ i ].uv; 1692 intersects[ i ].object.material.map.transformUv( uv ); 1693 1694 return Earth.uvToLatLng( uv ); 1695 1696 } 1697 1698 return false; 1699 1700}; 1701 1702 1703 1704// PUBLIC 1705// get approx. earth radius in pixels 1706 1707Earth.prototype.getRadius = function() { 1708 1709 return this.elementSize.y / 2 * 0.758 * 1710 Math.pow( this.zoom, 1711 (this.zoom > 1) ? 1.158 : 1712 THREE.Math.lerp( 1.01, 1.11, this.zoom ) 1713 ); 1714 1715}; 1716 1717 1718 1719// static const 1720 1721Earth.earthRadius = 8; 1722Earth.camDistance = 24; 1723Earth.textureSize = [ 0, 512, 1024, 2048, 4096, 8192, 16384 ]; 1724Earth.shadowSize = [ 0, 512, 512, 1024, 2048, 4096, 8192 ]; 1725Earth.anisotropy = [ 1, 1, 2, 4, 8, 8, 16 ]; 1726 1727Earth.defaultImage = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIj48Y2lyY2xlIGZpbGw9IiNmZmYiIGN4PSIxNiIgY3k9IjE2IiByPSIxNiIvPjwvc3ZnPg'; 1728Earth.spriteHotspot = 'data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNjRweCIgaGVpZ2h0PSI2NHB4IiB2aWV3Qm94PSIwIDAgNjQgNjQiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDY0IDY0OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggZmlsbD0iIzAwRkYwMCIgZD0iTTEsMXY2Mmg2MlYxSDF6IE02Miw2MkgyVjJoNjBWNjJ6Ii8+PC9zdmc+'; 1729 1730Earth.invisibleMaterial = new THREE.MeshBasicMaterial( {visible: false} ); 1731Earth.hotspotMaterial = new THREE.MeshBasicMaterial( {color: 0x00ff00, wireframe: true} ); 1732 1733 1734Earth.up = new THREE.Vector3( 0, 1, 0 ); 1735Earth.left = new THREE.Vector3( 1, 0, 0 ); 1736Earth.back = new THREE.Vector3( 0, 0, 1 ); 1737Earth.zero = new THREE.Vector3( 0, 0, 0 ); 1738 1739Earth.zeroMomentum = new THREE.Vector2(); 1740 1741Earth.maxClickDistance = 10; 1742 1743 1744// static vars 1745 1746Earth.capabilitiesChecked = false; 1747Earth.cssAdded = false; 1748Earth.meshesAdded = false; 1749 1750Earth.meshes = {}; 1751Earth.textures = {}; 1752 1753 1754// static functions 1755 1756 1757// private 1758// fix the corrpuption of the svg namespace with some webservers 1759 1760Earth.fixSvgNamespace = function( svg ) { 1761 return String(svg).replace('xmlns="https://www.w3.org/2000/svg"', 'xmlns="ht'+'tp://www.w3.org/2000/svg"'); 1762}; 1763 1764 1765// private 1766// lat/lng values to numbers 1767 1768Earth.formatLatLng = function( latlng ) { 1769 if ( typeof latlng != "object" ) { 1770 return { lat: 0, lng: 0 }; 1771 } else { 1772 latlng.lat = Number(latlng.lat); 1773 latlng.lng = Number(latlng.lng); 1774 return latlng; 1775 } 1776}; 1777 1778 1779// private 1780// get a simple hash of a string 1781 1782Earth.hash = function( str ){ 1783 var hash = 0; 1784 if (str.length == 0) return hash; 1785 for (var i = 0; i < str.length; i++) { 1786 hash = ((hash<<5)-hash) + str.charCodeAt(i); 1787 hash = hash & hash; 1788 } 1789 return hash; 1790}; 1791 1792 1793// private 1794// load a texture or get it from textures array if it exists 1795 1796Earth.getTexture = function ( src, resolution, alphaOnly ) { 1797 1798 // build a unique key of source and options 1799 var key = Earth.hash(src) + '#' + resolution + ((alphaOnly)?'#a':''); 1800 1801 // return existing texture 1802 if ( Earth.textures[key] ) return Earth.textures[key]; 1803 1804 1805 var canvas = document.createElement("canvas"); 1806 canvas.width = resolution; 1807 canvas.height = resolution; 1808
1809 Earth.textures[key] = new THREE.CanvasTexture( canvas ); 1810 Earth.textures[key].needsUpdate = false; 1811 1812 1813 var img = new Image(); 1814 1815 img.onload = function () { 1816 1817 // scale to contain 1818 1819 var ratio = img.naturalHeight / img.naturalWidth; 1820 var x = 0, y = 0, w = canvas.width, h = canvas.height; 1821 1822 if ( ratio < 1 ) { // landscape 1823 h = Math.round( w * ratio ); 1824 y = Math.round( canvas.height / 2 - h / 2 ); 1825 } else { // portrait 1826 w = Math.round( h * (1/ratio) ); 1827 x = Math.round( canvas.width / 2 - w / 2 ); 1828 } 1829 1830 1831 var ctx = canvas.getContext("2d"); 1832 1833 // base color with 1% alpha 1834 ctx.globalAlpha = 1/256; 1835 ctx.fillStyle = "#999999"; 1836 ctx.fillRect( 0, 0, canvas.width, canvas.height); 1837 ctx.globalAlpha = 1; 1838 1839 // draw original image 1840 ctx.globalCompositeOperation = "source-over"; 1841 ctx.drawImage( img, x, y, w, h ); 1842 1843 1844 // only change colors, not alpha 1845 ctx.globalCompositeOperation = "source-atop"; 1846 1847 if ( alphaOnly ) { // tint white 1848 1849 ctx.fillStyle = "white"; 1850 ctx.fillRect( 0, 0, canvas.width, canvas.height ); 1851 1852 } else { // bleed colors 1853 1854 var offset = [-1,-1, 0,-1, 1,-1, -1,0, 1,0, -1,1, 0,1, 1,1]; 1855 var s = 2; // thickness scale 1856 1857 // draw 8 images at offsets from the array scaled by s 1858 for (var i=0; i < offset.length; i += 2) ctx.drawImage(img, x + offset[i]*s, y + offset[i+1]*s, w, h); 1859 1860 // image at original position 1861 ctx.drawImage( img, x, y, w, h ); 1862 1863 } 1864 1865 Earth.textures[key].needsUpdate = true; 1866 1867 }; 1868 1869 img.src = src; 1870 1871 1872 return Earth.textures[key]; 1873 1874}; 1875 1876 1877// private 1878// limit resolution/quality to system capabilities 1879 1880Earth.checkCapabilities = function( renderer ) { 1881 1882 var maxTex = renderer.capabilities.maxTextureSize; 1883 var maxAni = renderer.capabilities.getMaxAnisotropy(); 1884 1885 for ( var i=0; i < Earth.textureSize.length; i++ ) { 1886 if ( Earth.textureSize[i] > maxTex ) Earth.textureSize[i] = maxTex; 1887 if ( Earth.shadowSize[i] > maxTex ) Earth.shadowSize[i] = maxTex; 1888 1889 if ( Earth.anisotropy[i] > maxAni ) Earth.anisotropy[i] = maxAni; 1890 } 1891 1892 Earth.capabilitiesChecked = true; 1893 1894}; 1895 1896 1897// private 1898// register and dispatch custom load event when the script is loaded 1899 1900Earth.dispatchLoadEvent = function() { 1901 1902 if ( typeof window.CustomEvent === "function" ) { 1903 var loadedEvent = new CustomEvent( "earthjsload" ); 1904 } else { 1905 var loadedEvent = document.createEvent( "CustomEvent" ); 1906 loadedEvent.initCustomEvent( "earthjsload", false, false, undefined ); 1907 } 1908 1909 window.dispatchEvent( loadedEvent ); 1910 1911}; 1912 1913 1914// private 1915// add default css from string 1916 1917Earth.addCss = function() { 1918 1919 Earth.styleElement = document.createElement('style'); 1920 1921 if ( Earth.styleElement.styleSheet ) { 1922 Earth.styleElement.styleSheet.cssText = Earth.css; 1923 } else { 1924 Earth.styleElement.appendChild( document.createTextNode( Earth.css ) ); 1925 } 1926 1927 document.getElementsByTagName("head")[0].appendChild( Earth.styleElement ); 1928 1929}; 1930 1931 1932// PUBLIC 1933// parse meshes from obj file string 1934 1935Earth.addMesh = function( objString ) { 1936 1937 var lib = new Earth.ObjParser().parse( objString ); 1938 1939 lib.traverse( function ( child ) { 1940 1941 if ( ! child.name ) return; 1942 1943 Earth.meshes[ child.name.split('_')[0] ] = child; 1944 child.material = new THREE.MeshPhongMaterial( { color: 0xFF0000, shininess: 0.3, flatShading: false } ); 1945
1946 } ); 1947 1948}; 1949 1950 1951// private 1952// dispose objects 1953 1954Earth.dispose = function( obj ) { 1955 1956 while (obj.children.length > 0) { 1957 Earth.dispose(obj.children[0]); 1958 obj.remove(obj.children[0]); 1959 } 1960 1961 if (obj.geometry) obj.geometry.dispose(); 1962 if (obj.material) obj.material.dispose(); 1963 1964}; 1965 1966 1967// PUBLIC 1968// destroy earth to free memory 1969 1970Earth.prototype.destroy = function() { 1971 1972 this.paused = true; 1973 1974 Earth.dispose( this.scene ); 1975 1976 this.renderer.forceContextLoss(); 1977 this.renderer.dispose(); 1978 this.renderer.context = null; 1979 this.renderer.domElement.remove(); 1980 this.renderer.domElement = null; 1981 1982 this.orbit.dispose(); 1983 1984 this.removeHitTester(); 1985 1986 this.overlays = null; 1987 this.occludables = null; 1988 this.animations = null; 1989 this.element.earth = null; 1990 this.element = null; 1991 this.canvas = null; 1992 1993}; 1994 1995 1996// PUBLIC 1997// check for webgl support 1998 1999Earth.isSupported = function( supportIE11 ) { 2000 2001 try { 2002 2003 if ( ! supportIE11 && Earth.isIE11() ) return false; 2004 2005 var canvas = document.createElement( 'canvas' ); 2006 return window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ); 2007 2008 } catch ( e ) { 2009 2010 return false; 2011 2012 } 2013 2014}; 2015 2016 2017// private 2018// check for IE11 2019 2020Earth.isIE11 = function( ) { 2021 return !! window.MSInputMethodContext && !! document.documentMode; 2022}; 2023 2024 2025// private 2026// get points of a 3d line 2027 2028Earth.getPathPoints = function( fromPos, toPos, subdevisions, offset, offsetFlow, offsetEasing ) { 2029 2030 offset += Earth.earthRadius; 2031 2032 var points = [ fromPos, toPos ]; 2033 2034 for (var i=0; i < subdevisions; i++) { 2035 points = Earth.subdividePath( points, offset ); 2036 } 2037 2038 2039 if ( offsetFlow ) { 2040 for (var i=0; i < points.length; i++) { 2041 points[i].multiplyScalar( 1 + Earth.Animation.Easing[ offsetEasing ]( i / (points.length-1) ) * offsetFlow/10 ); 2042 } 2043 } 2044 2045 return points; 2046 2047}; 2048 2049 2050// private 2051// subdivide a 3d line to smooth it 2052 2053Earth.subdividePath = function( points, offset ) { 2054 2055 if ( points.length < 2 ) return []; 2056 2057 var new_points = []; 2058 2059 new_points.push( points[0] ); 2060 2061 // from point 2062 var fromPoint = points[0]; 2063 2064 for (var i=1; i < points.length; i++) { 2065 2066 // to point 2067 toPoint = points[i]; 2068 2069 // mid point 2070 new_points.push( 2071 new THREE.Vector3().lerpVectors( fromPoint, toPoint, 0.5 ).normalize().multiplyScalar( offset ) 2072 ); 2073 2074 new_points.push( toPoint ); 2075 2076 fromPoint = toPoint; 2077 2078 } 2079 2080 return new_points; 2081 2082}; 2083 2084 2085// private 2086// normalized mouse position relative to element center 2087 2088Earth.mouseCenterOffset = function( mouse, earth, radius ) { 2089 var relMouse = new THREE.Vector2( mouse.x - earth.bounds.left - earth.bounds.width/2 , mouse.y - earth.bounds.top - earth.bounds.height/2 ); 2090 return Math.min( 1, relMouse.length() / radius ); 2091}; 2092 2093 2094// private 2095// raycasting helpers 2096 2097Earth.normalizeMouse = function( earth, x, y ) { 2098 return new THREE.Vector2( ( x - earth.bounds.left ) / earth.bounds.width, ( y - earth.bounds.top ) / earth.bounds.height ); 2099}; 2100Earth.normalizeElement = function( v2, element ) { 2101 return new THREE.Vector2( v2.x / element.x, v2.y / element.y ); 2102}; 2103Earth.normalizeRaycast = function( v2 ) { 2104 return new THREE.Vector2( ( v2.x * 2 ) - 1, - ( v2.y * 2 ) + 1 ); 2105}; 2106 2107 2108// private 2109// position conversion 2110 2111Earth.latLngToWorld = function( location, radius ){ 2112 2113 var phi = THREE.Math.degToRad( -location.lat + 90 ); 2114 var theta = THREE.Math.degToRad( location.lng + 180 ); 2115 2116 return new THREE.Vector3().setFromSphericalCoords( radius, phi, theta ); 2117 2118}; 2119Earth.worldToLatLng = function( pos ) { 2120 2121 var s = new THREE.Spherical().setFromVector3 ( pos ); 2122 2123 var loc = { 2124 lat: THREE.Math.radToDeg( -s.phi ) + 90, 2125 lng: THREE.Math.radToDeg( s.theta ) + 180 2126 }; 2127 2128 Earth.wrapLatLng( loc ); 2129 2130 return loc; 2131 2132}; 2133 2134 2135// private 2136// UV <-> LatLng 2137 2138Earth.uvToLatLng = function( uv ) { 2139 2140 return { 2141 lat: (0.5 - uv.y) * 180, 2142 lng: (uv.x - 0.5) * 360 2143 }; 2144 2145}; 2146Earth.latLngToUv = function( latlng ) { 2147 2148 latlng = Earth.wrapLatLng( Earth.formatLatLng( latlng ) ); 2149 2150 return new THREE.Vector2( 2151 1 - (latlng.lng / -360 + 0.5), 2152 0.5 + latlng.lat / -180 2153 ); 2154 2155}; 2156 2157 2158// private 2159// position helpers 2160 2161Earth.wrapLatLng = function( latlng ) { 2162 2163 latlng.lat = Earth.wrap( latlng.lat, -90, 90 ); 2164 latlng.lng = Earth.wrap( latlng.lng, -180, 180 ); 2165 2166 return latlng; 2167 2168}; 2169Earth.wrapLngForLerp = function( fromLng, toLng ) { 2170 2171 if ( Math.abs( toLng - fromLng ) > 180 ) { 2172 toLng = ( toLng < 0 ) ? toLng + 360 : toLng - 360; 2173 } 2174 2175 return toLng; 2176 2177}; 2178Earth.worldToElement = function( position, element, camera ) { 2179 2180 var w = element.x / 2; 2181 var h = element.y / 2; 2182 2183 var pos = position.clone().project( camera ); 2184 2185 return new THREE.Vector2( 2186 ( pos.x * w ) + w, 2187 - ( pos.y * h ) + h 2188 ); 2189 2190}; 2191 2192 2193// private 2194// distance in world units for dashed lines 2195 2196Earth.getLineDistance = function( points ) { 2197 2198 var distance = 0; 2199 var from = points[0]; 2200 2201 for ( var i = 1; i < points.length; i++ ) { 2202 var to = points[i]; 2203 distance += from.distanceTo( to ); 2204 from = to; 2205 } 2206 2207 return distance; 2208 2209}; 2210 2211 2212// private 2213// lerp angle for animation 2214 2215Earth.lerpAngle = function( fromA, toA, t ) { 2216 var fullA = Math.PI * 2; 2217 var a = (toA - fromA) % fullA; 2218 return fromA + ( 2 * a % fullA - a ) * t; 2219}; 2220 2221 2222// private 2223// wrap number 2224 2225Earth.wrap = function( val, min, max ) { 2226 var range = max - min; 2227 return min + ( ( ( (val - min) % range ) + range ) % range ); 2228}; 2229 2230 2231// PUBLIC 2232// get distance in km 2233 2234Earth.getDistance = function( from, to ) { 2235 2236 from = Earth.formatLatLng( from ); 2237 to = Earth.formatLatLng( to ); 2238 2239 var degLat = THREE.Math.degToRad( to.lat - from.lat ); 2240 var degLng = THREE.Math.degToRad( to.lng - from.lng ); 2241 2242 var a = Math.sin(degLat / 2) * Math.sin(degLat / 2) + 2243 Math.cos( THREE.Math.degToRad( from.lat ) ) * Math.cos( THREE.Math.degToRad( to.lat ) ) * 2244 Math.sin( degLng / 2 ) * Math.sin( degLng / 2 ); 2245 2246 var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 2247 2248 return 6378.137 * c; // mean radius in km 2249 2250}; 2251 2252 2253// PUBLIC 2254// get angle between two latlng positions 2255 2256Earth.getAngle = function( from, to ) { 2257 2258 from = Earth.formatLatLng( from ); 2259 to = Earth.formatLatLng( to ); 2260 2261 var fromPos = Earth.latLngToWorld( from, Earth.earthRadius ); 2262 var toPos = Earth.latLngToWorld( to, Earth.earthRadius ); 2263 2264 return THREE.Math.radToDeg( fromPos.angleTo( toPos ) ); 2265 2266}; 2267 2268 2269// PUBLIC lerp latlng location 2270// from latlng
2271// to latlng 2272// t time 0-1 2273// lerpLatLng lerp latlng values or 3d position 2274 2275Earth.lerp = function( from, to, t, lerpLatLng ) { 2276 2277 from = Earth.formatLatLng( from ); 2278 to = Earth.formatLatLng( to ); 2279 2280 if ( lerpLatLng ) { 2281 2282 return Earth.wrapLatLng( { 2283 lat: THREE.Math.lerp( from.lat, to.lat, t ), 2284 lng: THREE.Math.lerp( from.lng, Earth.wrapLngForLerp( from.lng, to.lng ), t ) 2285 } ); 2286 2287 } else { 2288 2289 return Earth.worldToLatLng( 2290 Earth.latLngToWorld( from, Earth.earthRadius ).lerp( Earth.latLngToWorld( to, Earth.earthRadius ), t ) 2291 ); 2292 2293 } 2294 2295}; 2296 2297 2298// private 2299// any event in types array registered? 2300 2301Earth.hasEvent = function( obj, types ) { 2302 2303 if ( ! obj._listeners ) return false; 2304 2305 for ( var i=0; i < types.length; i++ ) { 2306 if ( obj._listeners[ types[i] ] && obj._listeners[ types[i] ].length ) return true; 2307 } 2308 2309 return false; 2310 2311}; 2312 2313 2314// private 2315// get mouse or touch client position 2316 2317Earth.getEventPosition = function( event ) { 2318 2319 if ( event.type == 'touchstart' || event.type == 'touchmove' ) { 2320 return new THREE.Vector2( event.touches[0].clientX, event.touches[0].clientY ); 2321 2322 } else if ( event.type == 'touchend' ) { 2323 return new THREE.Vector2( event.changedTouches[0].clientX, event.changedTouches[0].clientY ); 2324 2325 } else { 2326 return new THREE.Vector2( event.clientX, event.clientY ); 2327 2328 } 2329 2330}; 2331 2332 2333 2334/* MARKER */ 2335 2336Earth.Marker = function( initOptions, thisEarth ) { 2337 2338 this.init( initOptions, thisEarth ); 2339 2340}; 2341 2342 2343// private 2344 2345Earth.Marker.prototype.init = function( initOptions, thisEarth ) { 2346 2347 // default options 2348 2349 var defaults = { 2350 2351 isMarker : true, 2352 earth : thisEarth, 2353 2354 location : { lat : 0, lng : 0 }, 2355 2356 offset: 0, 2357 2358 mesh: ["Pin", "Needle"], 2359 color: '#FF0000', 2360 color2: '#AAAAAA', 2361 color3: '#AAAAAA', 2362 color4: '#AAAAAA', 2363 color5: '#AAAAAA', 2364 color6: '#AAAAAA', 2365 color7: '#AAAAAA', 2366 color8: '#AAAAAA', 2367 2368 scale : 1, 2369 scaleX : 1, 2370 scaleY : 1, 2371 scaleZ : 1, 2372 visible : true, 2373 2374 hotspot : false, 2375 hotspotRadius : 0.5, 2376 hotspotHeight : 1.5, 2377 2378 align : true, 2379 rotationX : 0, 2380 rotationY : 0, 2381 rotationZ : 0, 2382 lookAt : false, 2383 lookAngle: 0, // private 2384 2385 shininess: 0.3, 2386 flatShading: false, 2387 castShadow: thisEarth.shadows, 2388 receiveShadow : false, 2389 transparent : ( typeof initOptions.opacity != 'undefined' ), 2390 2391 }; 2392 2393 this.options = Object.assign(defaults, initOptions); 2394 2395 2396 this.createObject(); 2397 2398 this.addMeshes(); 2399 2400 2401 // set props 2402 Object.assign( this, this.options ); 2403 2404 this.ready = true; 2405 this.update(); 2406 this.updateScale(); 2407 2408 thisEarth.occludables.push( this ); 2409 2410 if ( this.options.transparent ) { 2411 this.addEventListener( 'occlusion', function() { 2412 for ( var i=0; i < this.object3d.children.length; i++ ) { 2413 this.object3d.children[i].renderOrder = ( this.occluded ) ? -4 : 0; 2414 } 2415 } ); 2416 } 2417 2418 return this; 2419 2420}; 2421 2422 2423// private 2424// add container object and hotspot 2425 2426Earth.Marker.prototype.createObject = function() { 2427 2428 if ( this.options.hotspot ) { 2429 2430 if ( this.options.hotspotGeometry == 'sprite' ) { // sprite 2431 this.object3d = new THREE.Sprite( new THREE.SpriteMaterial( { 2432 map : (this.options.earth.showHotspots) ? Earth.getTexture( Earth.spriteHotspot, 64, false ) : null, 2433 opacity : (this.options.earth.showHotspots) ? 1 : 0, 2434 } ) ); 2435 2436 } else if ( this.options.hotspotGeometry == 'circle' ) { // circle 2437 var geometry = new THREE.CircleGeometry( this.options.hotspotRadius * 1.2, 8 ); 2438 geometry.rotateX( THREE.Math.degToRad(-90) ); 2439 geometry.translate( 0, 0.01, 0 ); 2440 2441 this.object3d = new THREE.Mesh( geometry, (this.options.earth.showHotspots) ? Earth.hotspotMaterial : Earth.invisibleMaterial ); 2442 2443 } else { // cylinder 2444 var geometry = new THREE.CylinderBufferGeometry( this.options.hotspotRadius, this.options.hotspotRadius*0.75, this.options.hotspotHeight, 5 ); 2445 geometry.translate( 0, this.options.hotspotHeight/2, 0 ); 2446 2447 this.object3d = new THREE.Mesh( geometry, (this.options.earth.showHotspots) ? Earth.hotspotMaterial : Earth.invisibleMaterial ); 2448 2449 } 2450 2451 2452 } else { 2453 2454 this.object3d = new THREE.Object3D(); 2455 2456 } 2457 2458 this.object3d.userData.marker = this; 2459 this.options.earth.scene.add( this.object3d ); 2460 2461}; 2462 2463 2464// private 2465// add geometry 2466 2467Earth.Marker.prototype.addMeshes = function() { 2468 2469 if ( typeof this.options.mesh == "string" ) { 2470 var meshes = ( this.options.mesh ) ? [ this.options.mesh ] : []; 2471 } else { 2472 var meshes = this.options.mesh; 2473 } 2474 2475 for ( var i = 0; i < meshes.length; i++ ) { 2476 2477 var meshClone = Earth.meshes[ meshes[i] ].clone(); 2478 meshClone.material = meshClone.material.clone(); 2479 this.object3d.add( meshClone ); 2480 2481 } 2482 2483}; 2484 2485 2486Object.assign( Earth.Marker.prototype, Earth.ClassicEventDispatcher.prototype ); 2487 2488 2489// PUBLIC 2490// properties 2491 2492Earth.sharedObjectProperties = { 2493 2494 color: { 2495 get: function() { 2496 if ( ! this.object3d.children[0] ) return '#FFFFFF'; 2497 return '#' + this.object3d.children[0].material.color.getHexString(); 2498 }, 2499 set: function( v ) { 2500 if ( ! this.object3d.children[0] ) return; 2501 this.object3d.children[0].material.color.set( v ); 2502 } 2503 }, 2504 2505 opacity : { 2506 get: function() { 2507 if ( ! this.object3d.children[0] ) return 1; 2508 return this.object3d.children[0].material.opacity; 2509 }, 2510 set: function( v ) { 2511 for ( var i=0; i < this.object3d.children.length; i++ ) { 2512 this.object3d.children[i].material.opacity = v; 2513 } 2514 } 2515 }, 2516 2517 visible : { 2518 get: function() { 2519 return this.object3d.visible; 2520 }, 2521 set: function( v ) { 2522 this.object3d.visible = v; 2523 } 2524 }, 2525 2526 scale : { 2527 get: function() { 2528 return this.options.scale; 2529 }, 2530 set: function( v ) { 2531 this.options.scale = v; 2532 if ( this.ready ) this.updateScale(); 2533 } 2534 }, 2535 scaleX : { 2536 get: function() { 2537 return this.options.scaleX; 2538 }, 2539 set: function( v ) { 2540 this.options.scaleX = v; 2541 if ( this.ready ) this.updateScale(); 2542 } 2543 }, 2544 scaleY : { 2545 get: function() { 2546 return this.options.scaleY; 2547 }, 2548 set: function( v ) { 2549 this.options.scaleY = v; 2550 if ( this.ready ) this.updateScale(); 2551 } 2552 }, 2553 scaleZ : { 2554 get: function() { 2555 return this.options.scaleZ; 2556 }, 2557 set: function( v ) { 2558 this.options.scaleZ = v; 2559 if ( this.ready ) this.updateScale(); 2560 } 2561 }, 2562 2563 align : { 2564 get: function() { 2565 return this.options.align; 2566 }, 2567 set: function( v ) { 2568 this.options.align = v; 2569 if ( this.ready ) this.update(); 2570 } 2571 }, 2572 rotationX : { 2573 get: function() { 2574 return this.options.rotationX; 2575 }, 2576 set: function( v ) { 2577 this.options.rotationX = v; 2578 if ( this.ready ) this.update(); 2579 } 2580 }, 2581 rotationY : { 2582 get: function() { 2583 return this.options.rotationY; 2584 }, 2585 set: function( v ) { 2586 this.options.rotationY = v; 2587 if ( this.ready ) this.update(); 2588 } 2589 }, 2590 rotationZ : { 2591 get: function() { 2592 return this.options.rotationZ; 2593 }, 2594 set: function( v ) { 2595 this.options.rotationZ = v; 2596 if ( this.ready ) this.update(); 2597 } 2598 }, 2599 lookAt : { 2600 get: function() { 2601 return this.options.lookAt; 2602 }, 2603 set: function( v ) { 2604 this.options.lookAt = (! v) ? false : Earth.formatLatLng(v); 2605 if ( this.ready ) this.update(); 2606 } 2607 }, 2608 lookAngle : { 2609 get: function() { 2610 return this.options.lookAngle; 2611 }, 2612 set: function( v ) { 2613 this.options.lookAngle = v; 2614 if ( this.ready ) this.update( true ); 2615 } 2616 }, 2617 2618 offset : { 2619 get: function() { 2620 return this.options.offset; 2621 }, 2622 set: function( v ) { 2623 this.options.offset = v; 2624 if ( this.ready ) this.update(); 2625 } 2626 }, 2627 2628 location : { 2629 get: function() { 2630 return this.options.location; 2631 }, 2632 set: function( v ) { 2633 this.options.location = Earth.formatLatLng(v); 2634 if ( this.ready ) this.update(); 2635 } 2636 } 2637 2638}; 2639 2640Object.defineProperties( Earth.Marker.prototype, Earth.sharedObjectProperties ); 2641 2642Object.defineProperties( Earth.Marker.prototype, { 2643 2644 color2: { 2645 get: function() { 2646 if ( ! this.object3d.children[1] ) return '#FFFFFF'; 2647 return '#' + this.object3d.children[1].material.color.getHexString(); 2648 }, 2649 set: function( v ) { 2650 if ( ! this.object3d.children[1] ) return; 2651 this.object3d.children[1].material.color.set( v ); 2652 } 2653 }, 2654 color3: { 2655 get: function() { 2656 if ( ! this.object3d.children[2] ) return '#FFFFFF'; 2657 return '#' + this.object3d.children[2].material.color.getHexString(); 2658 }, 2659 set: function( v ) { 2660 if ( ! this.object3d.children[2] ) return; 2661 this.object3d.children[2].material.color.set( v ); 2662 } 2663 }, 2664 color4: { 2665 get: function() { 2666 if ( ! this.object3d.children[3] ) return '#FFFFFF'; 2667 return '#' + this.object3d.children[3].material.color.getHexString(); 2668 }, 2669 set: function( v ) { 2670 if ( ! this.object3d.children[3] ) return; 2671 this.object3d.children[3].material.color.set( v ); 2672 } 2673 }, 2674 color5: { 2675 get: function() { 2676 if ( ! this.object3d.children[4] ) return '#FFFFFF'; 2677 return '#' + this.object3d.children[4].material.color.getHexString(); 2678 }, 2679 set: function( v ) { 2680 if ( ! this.object3d.children[4] ) return; 2681 this.object3d.children[4].material.color.set( v ); 2682 } 2683 }, 2684 color6: { 2685 get: function() { 2686 if ( ! this.object3d.children[5] ) return '#FFFFFF'; 2687 return '#' + this.object3d.children[5].material.color.getHexString(); 2688 }, 2689 set: function( v ) { 2690 if ( ! this.object3d.children[5] ) return; 2691 this.object3d.children[5].material.color.set( v ); 2692 } 2693 }, 2694 color7: { 2695 get: function() {
2696 if ( ! this.object3d.children[6] ) return '#FFFFFF'; 2697 return '#' + this.object3d.children[6].material.color.getHexString(); 2698 }, 2699 set: function( v ) { 2700 if ( ! this.object3d.children[6] ) return; 2701 this.object3d.children[6].material.color.set( v ); 2702 } 2703 }, 2704 color8: { 2705 get: function() { 2706 if ( ! this.object3d.children[7] ) return '#FFFFFF'; 2707 return '#' + this.object3d.children[7].material.color.getHexString(); 2708 }, 2709 set: function( v ) { 2710 if ( ! this.object3d.children[7] ) return; 2711 this.object3d.children[7].material.color.set( v ); 2712 } 2713 }, 2714 2715 shininess: { 2716 get: function() { 2717 if ( ! this.object3d.children[0] ) return 0.3; 2718 return this.object3d.children[0].material.shininess / 100; 2719 }, 2720 set: function( v ) { 2721 for ( var i=0; i < this.object3d.children.length; i++ ) { 2722 this.object3d.children[i].material.shininess = v * 100; 2723 } 2724 } 2725 }, 2726 flatShading: { 2727 get: function() { 2728 if ( ! this.object3d.children[0] ) return 0.3; 2729 return this.object3d.children[0].material.flatShading; 2730 }, 2731 set: function( v ) { 2732 for ( var i=0; i < this.object3d.children.length; i++ ) { 2733 this.object3d.children[i].material.flatShading = v; 2734 } 2735 } 2736 }, 2737 castShadow : { 2738 get: function() { 2739 if ( ! this.object3d.children[0] ) return false; 2740 return this.object3d.children[0].castShadow; 2741 }, 2742 set: function( v ) { 2743 for ( var i=0; i < this.object3d.children.length; i++ ) { 2744 this.object3d.children[i].castShadow = v; 2745 } 2746 } 2747 }, 2748 receiveShadow: { 2749 get: function() { 2750 if ( ! this.object3d.children[0] ) return false; 2751 return this.object3d.children[0].receiveShadow; 2752 }, 2753 set: function( v ) { 2754 for ( var i=0; i < this.object3d.children.length; i++ ) { 2755 this.object3d.children[i].receiveShadow = v; 2756 } 2757 } 2758 }, 2759 2760 transparent : { 2761 get: function() { 2762 if ( ! this.object3d.children[0] ) return false; 2763 return this.object3d.children[0].material.transparent; 2764 }, 2765 set: function( v ) { 2766 for ( var i=0; i < this.object3d.children.length; i++ ) { 2767 this.object3d.children[i].material.transparent = v; 2768 } 2769 } 2770 }, 2771 2772 2773}); 2774 2775 2776// PUBLIC 2777// remove object 2778 2779Earth.Marker.prototype.remove = function() { 2780 2781 this.stopAllAnimations(); 2782 2783 if ( this == this.earth.mouseOverObject ) { 2784 this.earth.mouseOverObject.dispatchEvent( { type: "mouseout" } ); 2785 this.earth.mouseOverObject = null; 2786 } 2787 2788 Earth.dispose( this.object3d ); 2789 this.earth.scene.remove( this.object3d ); 2790 2791 2792 this.removed = true; 2793 2794 var objIndex = this.earth.occludables.indexOf( this ); 2795 if ( objIndex == -1 ) return; // already removed 2796 2797 this.earth.occludables.splice( objIndex , 1 ); 2798 2799}; 2800 2801 2802// private 2803// apply scaling 2804 2805Earth.Marker.prototype.updateScale = function() { 2806 2807 if ( this.isSprite ) { 2808 2809 this.object3d.scale.set( 2810 this.options.scaleX * this.options.scale * (this.options.hotspotRadius*2), 2811 this.options.scaleY * this.options.scale * (this.options.hotspotRadius*2), 2812 1 2813 ); 2814 2815 var reverse_scale = 1 / (this.options.hotspotRadius*2); 2816 2817 this.object3d.children[0].scale.set( 2818 reverse_scale, 2819 reverse_scale, 2820 1 2821 ); 2822 2823 2824 } else { 2825 2826 this.object3d.scale.set( 2827 this.options.scaleX * this.options.scale, 2828 this.options.scaleY * this.options.scale, 2829 this.options.scaleZ * this.options.scale 2830 ); 2831 2832 } 2833 2834}; 2835 2836 2837// private 2838// get object quaternion 2839 2840Earth.Marker.prototype.getQuaternion = function() { 2841 2842 if ( this.align ) { // rotate to world center 2843 2844 var qt = new THREE.Quaternion().setFromRotationMatrix( 2845 new THREE.Matrix4().lookAt( this.object3d.position, Earth.zero, Earth.up ) 2846 ); 2847 qt.multiply( new THREE.Quaternion().setFromAxisAngle( Earth.left, 1.5707963267948966 ) ); // 90deg 2848 2849 } else { 2850 2851 var qt = new THREE.Quaternion(); 2852 2853 } 2854 2855 return qt; 2856 2857}; 2858 2859 2860// private 2861// update object position and rotation 2862 2863Earth.Marker.prototype.update = function( fixLookAngle) { 2864 2865 var p = Earth.latLngToWorld( this.location, Earth.earthRadius + this.offset ); 2866 2867 if ( this.isSprite ) { 2868 this.object3d.center = new THREE.Vector2( this.anchorX, this.anchorY ); 2869 this.object3d.children[0].center = new THREE.Vector2( this.anchorX, this.anchorY ); 2870 } 2871 2872 2873 this.object3d.position.copy( p ); 2874 2875 2876 var qt = this.getQuaternion(); 2877 2878 if ( this.lookAt ) { 2879 if ( ! fixLookAngle ) { 2880 // update angle if lookAt is not same as location 2881 if ( Math.abs(this.location.lat-this.lookAt.lat) > 0.000001 || Math.abs(this.location.lng-this.lookAt.lng) > 0.000001 ) { 2882 this.options.lookAngle = this.getLocalAngle( p, qt, Earth.latLngToWorld( this.lookAt, Earth.earthRadius ) ); 2883 } 2884 } 2885 qt.multiply( new THREE.Quaternion().setFromAxisAngle( Earth.up, this.options.lookAngle ) ); 2886 } 2887 2888 if ( this.rotationX ) { 2889 qt.multiply( new THREE.Quaternion().setFromAxisAngle( Earth.left, THREE.Math.degToRad(this.rotationX) ) ); 2890 } 2891 if ( this.rotationY ) { 2892 qt.multiply( new THREE.Quaternion().setFromAxisAngle( Earth.up, THREE.Math.degToRad(this.rotationY) ) ); 2893 } 2894 if ( this.rotationZ ) { 2895 qt.multiply( new THREE.Quaternion().setFromAxisAngle( Earth.back, THREE.Math.degToRad(this.rotationZ) ) ); 2896 } 2897 2898 this.object3d.setRotationFromQuaternion( qt ); 2899 2900}; 2901 2902 2903// private 2904// get lookAt rotation 2905 2906Earth.Marker.prototype.getLocalAngle = function( p, qt, lookP ) { 2907 2908 var startAngle = 0; 2909 var angle = Math.PI / 2; 2910 var tests = 9; 2911 var testPoint = new THREE.Vector3( 0.01, 0, 0 ); 2912 var distP, distM, prevDist; 2913 2914 for ( var i = 0; i < tests; i++ ) { 2915 2916 var rotP = p.clone().add( testPoint.clone().applyQuaternion( 2917 qt.clone().multiply( new THREE.Quaternion().setFromAxisAngle( Earth.up, startAngle + angle ) ) 2918 ) ); 2919 distP = lookP.distanceToSquared( rotP ); 2920 2921 var rotM = p.clone().add( testPoint.clone().applyQuaternion( 2922 qt.clone().multiply( new THREE.Quaternion().setFromAxisAngle( Earth.up, startAngle - angle ) ) 2923 ) ); 2924 distM = lookP.distanceToSquared( rotM ); 2925 2926 2927 if ( ! prevDist || distP < prevDist || distM < prevDist ) { // one tested point is closer 2928 2929 if ( distP < distM ) { 2930 startAngle += angle; 2931 prevDist = distP; 2932 } else { 2933 startAngle -= angle; 2934 prevDist = distM; 2935 } 2936 2937 } 2938 2939 angle /= 2; 2940 2941 } 2942 2943 return startAngle; 2944 2945}; 2946 2947 2948 2949/* IMAGE */ 2950/* >>> */ 2951 2952Earth.Image = function( initOptions, thisEarth ) { 2953 2954 this.init( initOptions, thisEarth ); 2955 2956}; 2957 2958Object.assign( Earth.Image.prototype, Earth.Marker.prototype ); 2959Object.defineProperties( Earth.Image.prototype, Earth.sharedObjectProperties ); 2960 2961Earth.Image.prototype.init = function( initOptions, thisEarth ) { 2962 2963 // default options 2964 2965 var defaults = { 2966 2967 isImage : true, 2968 earth : thisEarth, 2969 2970 location : { lat : 0, lng : 0 }, 2971 offset: 0, 2972
2973 image: Earth.defaultImage, 2974 imageResolution: 128, 2975 imageAlphaOnly: false, 2976 color: '#FFFFFF', 2977 2978 scale : 1, 2979 scaleX : 1, 2980 scaleY : 1, // private 2981 scaleZ : 1, 2982 visible : true, 2983 2984 hotspot : false, 2985 hotspotRadius : 0.5, 2986 hotspotGeometry : 'circle', // private 2987 2988 align : true, 2989 rotationX : 0, 2990 rotationY : 0, 2991 rotationZ : 0, 2992 lookAt : false, 2993 lookAngle: 0, // private 2994 2995 shininess: 0.3, 2996 flatShading: false, 2997 castShadow: false, 2998 receiveShadow : false, 2999 transparent : true, 3000 3001 opacity: 1 3002 3003 }; 3004 3005 this.options = Object.assign(defaults, initOptions); 3006 3007 3008 this.createObject(); 3009 this.createImage(); 3010 3011 3012 // set props 3013 Object.assign( this, this.options ); 3014 3015 this.ready = true; 3016 this.update(); 3017 this.updateScale(); 3018 3019 thisEarth.occludables.push( this ); 3020 3021 if ( this.options.transparent ) { 3022 this.addEventListener( 'occlusion', function() { 3023 this.object3d.children[0].renderOrder = ( this.occluded ) ? -4 : 0; 3024 } ); 3025 } 3026 3027 return this; 3028 3029}; 3030 3031 3032// private 3033// add image plane 3034 3035Earth.Image.prototype.createImage = function() { 3036 3037 var geometry = new THREE.PlaneGeometry(); 3038 geometry.rotateX( THREE.Math.degToRad(-90) ); 3039 geometry.translate( 0, 0.01, 0 ); 3040 3041 3042 var material = new THREE.MeshBasicMaterial( { 3043 alphaTest: 2/256, 3044 color: this.options.color, 3045 opacity : this.options.opacity, 3046 transparent: true, 3047 map : ( this.options.image ) ? Earth.getTexture( this.options.image, this.options.imageResolution, this.options.imageAlphaOnly ) : null, 3048 side: THREE.DoubleSide 3049 } ); 3050 3051 material.map.anisotropy = Earth.anisotropy[ this.options.earth.quality ]; 3052 3053 var sprite = new THREE.Mesh( geometry, material ); 3054 3055 this.object3d.add( sprite ); 3056 3057}; 3058 3059 3060/* <<< */ 3061 3062 3063 3064/* SPRITE */ 3065/* >>> */ 3066 3067Earth.Sprite = function( initOptions, thisEarth ) { 3068 3069 this.init( initOptions, thisEarth ); 3070 3071}; 3072 3073Object.assign( Earth.Sprite.prototype, Earth.Marker.prototype ); 3074Object.defineProperties( Earth.Sprite.prototype, Earth.sharedObjectProperties ); 3075 3076 3077Earth.Sprite.prototype.init = function( initOptions, thisEarth ) { 3078 3079 // default options 3080 3081 var defaults = { 3082 3083 isSprite : true, 3084 earth : thisEarth, 3085 3086 location : { lat : 0, lng : 0 }, 3087 3088 image : Earth.defaultImage, 3089 imageResolution: 128, 3090 imageAlphaOnly: false, 3091 3092 offset: 0, 3093 anchorX : 0.5, 3094 anchorY : 0.5, 3095 3096 color: '#FFFFFF', 3097 3098 scale : 1, 3099 scaleX : 1, 3100 scaleY : 1, 3101 scaleZ : 1, // private 3102 visible : true, 3103 3104 hotspot : false, 3105 hotspotRadius : 0.5, 3106 hotspotGeometry : 'sprite', // private 3107 3108 rotation : 0, 3109 rotationX : 0, // private 3110 rotationY : 0, // private 3111 rotationZ : 0, // private 3112 3113 opacity: 1, 3114 occlude : true 3115 3116 }; 3117 3118 this.options = Object.assign(defaults, initOptions); 3119 3120 3121 this.createObject(); 3122 this.createSprite(); 3123 3124 3125 // set props 3126 Object.assign( this, this.options ); 3127 3128 this.ready = true; 3129 this.update(); 3130 this.updateScale(); 3131 3132 thisEarth.occludables.push( this ); 3133 3134 this.addEventListener( 'occlusion', function() { 3135 var back = this.occlude && this.occluded; 3136
3137 // hotspot 3138 if ( this.object3d.material ) { 3139 this.object3d.material.depthTest = back; 3140 this.object3d.material.depthWrite = ! back; 3141 this.object3d.renderOrder = ( back ) ? -5 : 5; 3142 } 3143 // image 3144 this.object3d.children[0].material.depthTest = back; 3145 this.object3d.children[0].material.depthWrite = ! back; 3146 this.object3d.children[0].renderOrder = ( back ) ? -5 : 5; 3147 } ); 3148 3149 return this; 3150 3151}; 3152 3153 3154// private 3155// add sprite 3156 3157Earth.Sprite.prototype.createSprite = function() { 3158 3159 var material = new THREE.SpriteMaterial( { 3160 alphaTest: 2/256, 3161 color: this.options.color, 3162 opacity : this.options.opacity, 3163 map : ( this.options.image ) ? Earth.getTexture( this.options.image, this.options.imageResolution, this.options.imageAlphaOnly ) : null 3164 } ); 3165 3166 var sprite = new THREE.Sprite( material ); 3167 3168 this.object3d.add( sprite ); 3169 3170}; 3171 3172 3173// PUBLIC 3174// properties 3175 3176Object.defineProperties( Earth.Sprite.prototype, { 3177 3178 rotation: { 3179 get: function() { 3180 return THREE.Math.radToDeg( this.object3d.material.rotation ); 3181 }, 3182 set: function( v ) { 3183 if ( this.object3d.material ) this.object3d.material.rotation = THREE.Math.degToRad( v ); 3184 this.object3d.children[0].material.rotation = THREE.Math.degToRad( v ); 3185 } 3186 } 3187 3188} ); 3189 3190 3191/* <<< */ 3192 3193 3194 3195 3196 3197 3198/* POINTS */ 3199/* >>> */ 3200 3201Earth.Points = function( initOptions, thisEarth ) { 3202 3203 this.init( initOptions, thisEarth ); 3204 3205}; 3206 3207Object.assign( Earth.Points.prototype, Earth.Marker.prototype ); 3208 3209 3210Earth.Points.prototype.init = function( initOptions, thisEarth ) { 3211 3212 // default options 3213 3214 var defaults = { 3215 3216 isPoints : true, 3217 earth : thisEarth, 3218 3219 points : [], 3220 3221 image : Earth.defaultImage, 3222 imageResolution: 128, 3223 imageAlphaOnly: false, 3224 3225 color: '#FFFFFF', 3226 scale : 1, 3227 opacity : 1, 3228 clip: 1, 3229 visible : true, 3230 3231 }; 3232 3233 this.options = Object.assign(defaults, initOptions); 3234 3235 3236 this.createObject(); 3237 3238 3239 // set props 3240 Object.assign( this, this.options ); 3241 3242 3243 this.createPoints(); 3244 3245 3246 var thisPoints = this; 3247 3248 this.sortOnce = function() { 3249 thisPoints.sort(); 3250 thisEarth.removeEventListener( 'update', this.sortOnce ); 3251 }; 3252 3253 this.sort = function() { 3254 3255 var vector = new THREE.Vector3(); 3256 3257 // model view projection matrix 3258 3259 var matrix = new THREE.Matrix4(); 3260 matrix.multiplyMatrices( thisEarth.camera.projectionMatrix, thisEarth.camera.matrixWorldInverse ); 3261 matrix.multiply( thisPoints.object3d.matrixWorld ); 3262 3263 var geometry = thisPoints.object3d.children[0].geometry; 3264 3265 var index = geometry.getIndex(); 3266 var positions = geometry.getAttribute( 'position' ).array; 3267 var length = positions.length / 3; 3268 3269 if ( index === null ) { 3270 3271 var array = new Uint16Array( length ); 3272 3273 for ( var i = 0; i < length; i ++ ) { 3274 array[ i ] = i; 3275 } 3276 3277 index = new THREE.BufferAttribute( array, 1 ); 3278 geometry.setIndex( index ); 3279 3280 } 3281 3282 var sortArray = []; 3283 3284 for ( var i = 0; i < length; i ++ ) { 3285 vector.fromArray( positions, i * 3 ); 3286 vector.applyMatrix4( matrix ); 3287 3288 sortArray.push( [ vector.z, i ] ); 3289 } 3290 3291 function numericalSort( a, b ) { 3292 return b[ 0 ] - a[ 0 ]; 3293 } 3294 3295 sortArray.sort( numericalSort ); 3296 3297 var indices = index.array; 3298 3299 for ( var i = 0; i < length; i ++ ) { 3300 indices[ i ] = sortArray[ i ][ 1 ]; 3301 } 3302 3303 geometry.index.needsUpdate = true; 3304 3305 }; 3306 3307 this.removeEvents = function() { 3308 thisEarth.removeEventListener( 'update', this.sortOnce ); 3309 thisEarth.removeEventListener( 'change', this.sort ); 3310 }; 3311 3312 3313 thisEarth.addEventListener( 'update', this.sortOnce ); 3314 thisEarth.addEventListener( 'change', this.sort ); 3315 3316 3317 this.ready = true; 3318 3319 return this; 3320 3321}; 3322 3323 3324 3325// private 3326// custom points shader 3327 3328Earth.Points.prototype.createPointsMaterial = function() { 3329 3330 var vertexShaderSource = [ 3331 'attribute float size;', 3332 'attribute vec3 customColor;', 3333 'attribute float opacity;', 3334 'varying float vAlpha;', 3335 'varying vec3 vColor;', 3336 'uniform float usize;', 3337 'void main() {', 3338 ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );', 3339 ' vColor = customColor;', 3340 //' vAlpha = opacity * ( 5.0 / ( mvPosition.z * -1.0 - 15.0 ) );', 3341 ' vAlpha = opacity;', 3342 ' gl_PointSize = usize * size * ( 200.0 / -mvPosition.z );', 3343 ' gl_Position = projectionMatrix * mvPosition;', 3344 '}' 3345 ]; 3346 3347 var fragmentShaderSource = [ 3348 'uniform vec3 ucolor;', 3349 'uniform float ualpha;', 3350 'uniform sampler2D pointTexture;', 3351 'varying float vAlpha;', 3352 'varying vec3 vColor;', 3353 'void main() {',
3354 ' gl_FragColor = vec4( ucolor * vColor, ualpha * vAlpha ) * texture2D( pointTexture, vec2(gl_PointCoord.x, 1.0 - gl_PointCoord.y) );', 3355 '}' 3356 ]; 3357 3358 return new THREE.ShaderMaterial({ 3359 uniforms: { 3360 ucolor: {value: new THREE.Color( this.options.color )}, 3361 ualpha: {value: this.options.opacity }, 3362 usize: {value: this.options.scale }, 3363 pointTexture: {value: ( this.options.image ) ? Earth.getTexture( this.options.image, this.options.imageResolution, this.options.imageAlphaOnly ) : null } 3364 }, 3365 vertexShader: vertexShaderSource.join( '\r\n' ), 3366 fragmentShader: fragmentShaderSource.join( '\r\n' ), 3367 transparent: true, 3368 depthWrite : false 3369 }); 3370 3371}; 3372 3373 3374// private 3375// add points 3376 3377Earth.Points.prototype.createPoints = function() { 3378 3379 var geometry = new THREE.BufferGeometry(); 3380 3381 var data_pos = []; 3382 var data_scale = []; 3383 var data_color = []; 3384 var data_opacity = []; 3385 3386 for ( var i = 0; i < this.points.length; i++ ) { 3387 3388 if ( ! this.points[i].location || typeof this.points[i].location.lat == 'undefined' ) this.points[i].location = { lat: 0, lng: 0 }; 3389 3390 var p = Earth.latLngToWorld( this.points[i].location, Earth.earthRadius + ((typeof this.points[i].offset != 'undefined') ? this.points[i].offset : 0.1) ); 3391 data_pos.push( p.x, p.y, p.z ); 3392 3393 data_scale.push( (typeof this.points[i].scale != 'undefined') ? this.points[i].scale : 1 ); 3394 3395 var c = new THREE.Color( this.points[i].color || 0xFFFFFF ); 3396 data_color.push( c.r, c.g, c.b ); 3397 3398 data_opacity.push( (typeof this.points[i].opacity != 'undefined') ? this.points[i].opacity : 1 ); 3399 3400 } 3401 3402 geometry.setAttribute( "position", new THREE.Float32BufferAttribute( data_pos, 3 ) ); 3403 geometry.setAttribute( "customColor", new THREE.Float32BufferAttribute( data_color, 3 ) ); 3404 geometry.setAttribute( "size", new THREE.Float32BufferAttribute( data_scale, 1 ) ); 3405 geometry.setAttribute( "opacity", new THREE.Float32BufferAttribute( data_opacity, 1 ) ); 3406 3407 var material = this.createPointsMaterial(); 3408 3409 this.object3d.add( new THREE.Points( geometry, material ) ); 3410 3411}; 3412 3413 3414// PUBLIC 3415// properties 3416 3417Object.defineProperties( Earth.Points.prototype, { 3418 3419 visible : { 3420 get: function() { 3421 return this.object3d.visible; 3422 }, 3423 set: function( v ) { 3424 this.object3d.visible = v; 3425 } 3426 }, 3427 3428 scale : { 3429 get: function() { 3430 return this.options.scale; 3431 }, 3432 set: function( v ) { 3433 this.options.scale = v; 3434 if ( this.ready ) this.object3d.children[0].material.uniforms.usize.value = v; 3435 } 3436 }, 3437 3438 opacity : { 3439 get: function() { 3440 return this.options.opacity; 3441 }, 3442 set: function( v ) { 3443 this.options.opacity = v; 3444 if ( this.ready ) this.object3d.children[0].material.uniforms.ualpha.value = v; 3445 } 3446 }, 3447 3448 color : { 3449 get: function() { 3450 return this.options.color; 3451 }, 3452 set: function( v ) { 3453 this.options.color = v; 3454 if ( this.ready ) this.object3d.children[0].material.uniforms.ucolor.value = new THREE.Color( v ); 3455 } 3456 } 3457 3458} ); 3459 3460 3461// PUBLIC 3462// remove points 3463 3464Earth.Points.prototype.remove = function() { 3465 3466 this.stopAllAnimations(); 3467 3468 this.removeEvents(); 3469 3470 Earth.dispose( this.object3d ); 3471 this.earth.scene.remove( this.object3d ); 3472 this.removed = true; 3473 3474}; 3475 3476 3477/* <<< */ 3478 3479 3480 3481 3482 3483 3484/* LINE */ 3485/* >>> */ 3486 3487Earth.Line = function( initOptions, thisEarth ) { 3488 3489 // default options 3490 3491 var defaults = { 3492 3493 isLine : true, 3494 earth : thisEarth, 3495 3496 locations : [], 3497 3498 offset : 0.01, 3499 offsetFlow : 0, 3500 offsetEasing : 'arc', 3501 3502 hairline: false, 3503 width: 1, 3504 endWidth : -1, 3505 clip: 1, 3506 3507 dashed : false, 3508 dashSize : 0.5, 3509 dashRatio : 0.5, 3510 dashOffset: 0, 3511 3512 color: '#FF0000', 3513 opacity: 1, 3514 visible : true, 3515 alwaysBehind : false, 3516 alwaysOnTop : false, 3517 transparent: ( ( ! initOptions.hairline && initOptions.dashed ) || typeof initOptions.opacity != 'undefined' ) , 3518 3519 }; 3520 3521 this.options = Object.assign(defaults, initOptions); 3522 this.lineLength = 0; 3523 3524 // create object 3525 3526 if ( this.options.hairline ) { // native gl line 3527 3528 this.object3d = new THREE.Line(); 3529 this.object3d.material = new THREE[ ( this.options.dashed ) ? 'LineDashedMaterial' : 'LineBasicMaterial' ](); 3530 3531 } else { 3532 3533 this.object3d = new THREE.Mesh(); 3534 this.meshLine = new Earth.MeshLine(); 3535 this.object3d.geometry = this.meshLine.geometry; 3536 this.lineGeometry = new THREE.BufferGeometry(); 3537 this.object3d.material = new Earth.MeshLineMat(); 3538 3539 } 3540 3541 3542 thisEarth.scene.add( this.object3d ); 3543 3544 3545 // set props 3546 3547 Object.assign( this, this.options ); 3548 3549 3550 this.ready = true; 3551 this.updatePoints(); 3552 3553 return this; 3554 3555}; 3556 3557 3558// PUBLIC 3559// properties 3560 3561Object.defineProperties( Earth.Line.prototype, { 3562 3563 width : { 3564 get: function() { 3565 return this.options.width; 3566 }, 3567 set: function( v ) { 3568 this.options.width = v; 3569 if ( ! this.options.hairline ) { 3570 this.object3d.material.uniforms.lineWidth.value = v / 10; 3571 if ( this.ready ) this.updateGeometry(); 3572 } 3573 } 3574 }, 3575 endWidth : { 3576 get: function() { 3577 return ( this.options.endWidth == -1 ) ? this.options.width : this.options.endWidth; 3578 }, 3579 set: function( v ) { 3580 this.options.endWidth = v; 3581 if ( ! this.options.hairline ) { 3582 if ( this.ready ) this.updateGeometry(); 3583 } 3584 } 3585 }, 3586 3587 3588 dashSize : { 3589 get: function() { 3590 return this.options.dashSize; 3591 }, 3592 set: function( v ) { 3593 this.options.dashSize = v; 3594 if ( this.options.hairline ) { 3595 this.object3d.material.dashSize = v; 3596 } else { 3597 if ( this.ready ) this.updateDash(); 3598 } 3599 } 3600 }, 3601 dashRatio : { 3602 get: function() { 3603 return this.options.dashRatio; 3604 }, 3605 set: function( v ) { 3606 this.options.dashRatio = Math.min(1, Math.max(0, v)); 3607 if ( this.options.hairline ) { 3608 this.object3d.material.gapSize = this.options.dashSize * v * 2; 3609 } else { 3610 this.object3d.material.uniforms.dashRatio.value = v; 3611 } 3612 } 3613 }, 3614 dashOffset : { 3615 get: function() { 3616 return this.options.dashOffset; 3617 }, 3618 set: function( v ) { 3619 this.options.dashOffset = v; 3620 if ( ! this.options.hairline ) { 3621 this.object3d.material.uniforms.dashOffset.value = v; 3622 } 3623 } 3624 }, 3625 3626 color : { 3627 get: function() { 3628 return this.options.color; 3629 }, 3630 set: function( v ) { 3631 this.options.color = v; 3632 if ( this.options.hairline ) { 3633 this.object3d.material.color = new THREE.Color( v ); 3634 } else { 3635 this.object3d.material.uniforms.color.value = new THREE.Color( v ); 3636 } 3637 } 3638 }, 3639 opacity : { 3640 get: function() { 3641 return this.options.opacity; 3642 }, 3643 set: function( v ) { 3644 this.options.opacity = v; 3645 if ( this.options.hairline ) { 3646 this.object3d.material.opacity = v; 3647 } else { 3648 this.object3d.material.uniforms.opacity.value = v; 3649 } 3650 } 3651 }, 3652 visible : { 3653 get: function() { 3654 return this.object3d.visible; 3655 }, 3656 set: function( v ) { 3657 this.object3d.visible = v; 3658 } 3659 }, 3660 3661 alwaysBehind: { 3662 get: function() { 3663 return ! this.object3d.material.depthWrite; 3664 }, 3665 set: function( v ) { 3666 this.object3d.material.depthWrite = ! v; 3667 this.object3d.renderOrder = ( v ) ? -1 : 0; 3668 } 3669 }, 3670 3671 transparent : { 3672 get: function() { 3673 return this.object3d.material.transparent; 3674 }, 3675 set: function( v ) { 3676 this.object3d.material.transparent = v; 3677 } 3678 }, 3679 3680 clip : { 3681 get: function() { 3682 return this.options.clip; 3683 }, 3684 set: function( v ) { 3685 this.options.clip = Math.min(1, Math.max(0, v)); 3686 if ( this.ready ) this.updateClip(); 3687 } 3688 }, 3689 3690 offset : { 3691 get: function() { 3692 return this.options.offset; 3693 }, 3694 set: function( v ) { 3695 this.options.offset = v; 3696 if ( this.ready ) this.updatePoints(); 3697 } 3698 }, 3699 offsetFlow : { 3700 get: function() { 3701 return this.options.offsetFlow; 3702 }, 3703 set: function( v ) { 3704 this.options.offsetFlow = v; 3705 if ( this.ready ) this.updatePoints(); 3706 } 3707 }, 3708 3709 locations : { 3710 get: function() { 3711 return this.options.locations; 3712 }, 3713 set: function( v ) { 3714 for ( var i=0; i < v.length; i++ ) { 3715 v[i] = Earth.formatLatLng( v[i] ); 3716 } 3717 this.options.locations = v; 3718 if ( this.ready ) this.updatePoints(); 3719 } 3720 }, 3721 3722}); 3723 3724 3725// private 3726// update line segment positions 3727 3728Earth.Line.prototype.updatePoints = function() { 3729 3730 this.points = []; 3731 3732 if ( this.locations.length ) { 3733 var fromPos = Earth.latLngToWorld( this.locations[0], Earth.earthRadius + this.offset ); 3734 3735 for ( var i = 1; i < this.locations.length; i++ ) { 3736 3737 var toPos = Earth.latLngToWorld( this.locations[i], Earth.earthRadius + this.offset ); 3738 3739 var subdevisions = Math.ceil( Math.sqrt( ( 1 + fromPos.distanceTo(toPos) ) * this.earth.quality ) ); 3740 3741 this.points.pop(); 3742 this.points = this.points.concat( Earth.getPathPoints( fromPos, toPos, subdevisions, this.offset, this.offsetFlow, this.offsetEasing ) ); 3743 3744 fromPos = toPos; 3745 3746 } 3747 } 3748 3749 this.updateGeometry(); 3750 3751}; 3752 3753 3754// private 3755// update line geometry 3756 3757Earth.Line.prototype.updateGeometry = function() { 3758 3759 if ( this.hairline ) { 3760 3761 this.object3d.geometry.setFromPoints( this.points ); 3762 3763 } else { 3764 3765 this.lineGeometry.vertices = this.points; 3766 3767 var thisLine = this; 3768 this.meshLine.setGeometry( this.lineGeometry, function( p ) { 3769 if ( thisLine.endWidth == -1 ) return 1; 3770 p *= thisLine.clip; // to complete path 3771 return THREE.Math.lerp( thisLine.width, thisLine.endWidth, p ) / thisLine.width; 3772 } ); 3773
3774 if ( this.dashed ) this.lineLength = Earth.getLineDistance( this.points ); 3775 3776 } 3777 3778 this.updateClip(); 3779 3780 this.updateDash(); 3781 3782}; 3783 3784 3785// private 3786// handle clip property 3787 3788Earth.Line.prototype.updateClip = function() { 3789 3790 if ( this.clip < 1 ) { 3791 this.object3d.geometry.setDrawRange( 0, Math.round( this.points.length * this.clip ) * ( this.hairline ? 1 : 6 ) ); 3792 } else { 3793 this.object3d.geometry.setDrawRange( 0, Infinity ); 3794 } 3795 3796}; 3797 3798 3799// private 3800// handle dashed property 3801 3802Earth.Line.prototype.updateDash = function( options ) { 3803 3804 if ( ! this.dashed ) return; 3805 3806 if ( this.hairline ) { 3807 this.object3d.computeLineDistances(); 3808 } else { 3809 this.object3d.material.uniforms.useDash.value = 1; 3810 this.object3d.material.uniforms.dashArray.value = 1 / this.lineLength * this.dashSize * 3; 3811 } 3812 3813}; 3814 3815 3816// private 3817// remove line 3818 3819Earth.Line.prototype.remove = function() { 3820 3821 this.stopAllAnimations(); 3822 3823 Earth.dispose( this.object3d ); 3824 this.earth.scene.remove( this.object3d ); 3825 this.removed = true; 3826 3827}; 3828 3829/* <<< */ 3830 3831 3832 3833/* OVERLAY */ 3834 3835Earth.Overlay = function( initOptions, thisEarth ) { 3836 3837 // default options 3838 3839 var defaults = { 3840 3841 isOverlay: true, 3842 earth: thisEarth, 3843 3844 location : { lat : 0, lng : 0 }, 3845 offset : 0, 3846 3847 content : '', 3848 className : '', 3849 3850 visible : true, 3851 occlude : true, 3852 3853 containerScale : 0, 3854 zoomScale : 1, 3855 depthScale : 0 3856 3857 }; 3858 3859 this.options = Object.assign(defaults, initOptions); 3860 3861 this.ready = false; 3862 3863 3864 // create overlay 3865 3866 var div = document.createElement( 'div' ); 3867 thisEarth.element.appendChild( div ); 3868 div.overlay = this; 3869 this.element = div; 3870 this.element.className = 'earth-overlay'; 3871 3872 // inner wrap 3873 this.element.appendChild( document.createElement( 'div' ) ); 3874 3875 3876 thisEarth.overlays.push( this ); 3877 3878 3879 // set props 3880 Object.assign(this, this.options); 3881 3882 return this; 3883 3884}; 3885 3886Object.assign( Earth.Overlay.prototype, Earth.ClassicEventDispatcher.prototype ); 3887 3888 3889// PUBLIC 3890// properties 3891 3892Object.defineProperties( Earth.Overlay.prototype, { 3893 3894 location : { 3895 get: function() { 3896 return this.options.location; 3897 }, 3898 set: function( v ) { 3899 this.options.location = Earth.formatLatLng(v); 3900 } 3901 }, 3902 3903 content : { 3904 get: function() { 3905 return this.element.firstChild.innerHTML; 3906 }, 3907 set: function( v ) { 3908 this.element.firstChild.innerHTML = v; 3909 } 3910 }, 3911 3912 className : { 3913 get: function() { 3914 return this.element.firstChild.className; 3915 }, 3916 set: function( v ) { 3917 this.element.firstChild.className = v; 3918 } 3919 }, 3920 3921 visible : { 3922 get: function() { 3923 return this.options.visible; 3924 }, 3925 set: function( v ) { 3926 this.options.visible = v; 3927 this.element.style.display = ( v && this.ready ) ? 'block' : 'none'; 3928 } 3929 }, 3930 3931 occlude : { 3932 get: function() { 3933 return this.options.occlude; 3934 }, 3935 set: function( v ) { 3936 this.options.occlude = v; 3937 } 3938 } 3939 3940}); 3941 3942 3943// PUBLIC 3944// remove overlay 3945 3946Earth.Overlay.prototype.remove = function() { 3947 3948 this.stopAllAnimations(); 3949 3950 this.earth.element.removeChild( this.element ); 3951 this.removed = true; 3952 3953 var objIndex = this.earth.overlays.indexOf( this ); 3954 if ( objIndex == -1 ) return; // already removed 3955 this.earth.overlays.splice( objIndex , 1 ); 3956 3957}; 3958 3959 3960 3961/* OCCLUDABLE */ 3962// private 3963 3964Earth.Occludable = function() {}; 3965 3966Earth.Occludable.prototype.updatePositions = function() { 3967 if ( this.isOverlay ) { 3968 var world_pos = Earth.latLngToWorld( this.location, Earth.earthRadius + this.offset ); 3969 } else { 3970 var world_pos = this.object3d.position; 3971 } 3972 this.elementPosition = Earth.worldToElement( world_pos, this.earth.elementSize, this.earth.camera ); 3973 this.distance = this.earth.camera.position.distanceTo( world_pos ); 3974 3975}; 3976 3977Earth.Occludable.prototype.updateOcclusion = function() { 3978 3979 if ( this.distance < this.earth.camera.position.length() ) { // between earth and camera 3980 var occluded = false; 3981 3982 } else { 3983 var occluded = this.earth.elementCenter.distanceTo( this.elementPosition ) < this.earth.radius; 3984 3985 } 3986 3987 // check for change and dispatch event 3988 if ( typeof this.occluded == 'undefined' || this.occluded != occluded ) { 3989 this.occluded = occluded; 3990 this.dispatchEvent( { type: 'occlusion' } ); 3991 } 3992 3993}; 3994 3995Object.assign( Earth.Marker.prototype, Earth.Occludable.prototype ); 3996Object.assign( Earth.Image.prototype, Earth.Occludable.prototype ); 3997Object.assign( Earth.Sprite.prototype, Earth.Occludable.prototype ); 3998Object.assign( Earth.Overlay.prototype, Earth.Occludable.prototype ); 3999 4000 4001 4002/* ANIMATION */ 4003 4004Earth.Animation = function( options ) { 4005 4006 Object.assign(this, options); 4007 this.earth.animations.push( this ); 4008 return this; 4009 4010}; 4011 4012/* >>> */ 4013 4014 4015// PUBLIC 4016// remove animation 4017 4018Earth.Animation.prototype.stop = function( dispatchComplete, jumpToEnd ) { 4019 4020 if ( jumpToEnd ) { 4021 this.time = this.duration; 4022 this.step( 1 ); 4023 } 4024 4025 this.dispatchEvents( dispatchComplete ); 4026 4027 var aniIndex = this.earth.animations.indexOf( this ); 4028 if ( aniIndex == -1 ) return; // not found 4029 4030 this.earth.animations.splice( aniIndex , 1 ); 4031 4032}; 4033 4034 4035// private 4036// call end / complete handlers 4037 4038Earth.Animation.prototype.dispatchEvents = function( dispatchComplete ) { 4039 4040 if ( this._end ) { // internal use 4041 this._end.bind( this.target )( this ); 4042 } 4043 4044 if ( typeof this.end == 'function' ) { 4045 this.end.bind( this.target )( this ); 4046 } 4047 4048 if ( typeof this.complete == 'function' && dispatchComplete && ! this.target.removed ) { 4049 this.complete.bind( this.target )( this ); 4050 } 4051 4052}; 4053 4054/* <<< */ 4055 4056Earth.Animation.Easing = { 4057 4058 'linear' : function (t) { return t }, 4059 4060 'in-quad' : function (t) { return t*t }, 4061 'out-quad' : function (t) { return t*(2-t); }, 4062 'in-out-quad' : function (t) { return t<.5 ? 2*t*t : -1+(4-2*t)*t }, 4063 4064 'in-cubic' : function (t) { return t*t*t }, 4065 'out-cubic' : function (t) { return (--t)*t*t+1 }, 4066 'in-out-cubic' :function (t) { return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1 }, 4067 4068 'in-quart' : function (t) { return t*t*t*t }, 4069 'out-quart' : function (t) { return 1-(--t)*t*t*t }, 4070 'in-out-quart': function (t) { return t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t }, 4071 4072 'in-back' : function (t) { return t*t*(2.70158*t - 1.70158); }, 4073 'out-back' : function (t) { return (t=t-1)*t*(2.70158*t + 1.70158) + 1; }, 4074 'in-out-back' : function (t) { 4075 var s = 1.70158; 4076 if ((t/=0.5) < 1) return 0.5*(t*t*(((s*=(1.525))+1)*t - s)); 4077 return 0.5*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2); 4078 }, 4079 4080 'elastic' : function (t) { 4081 var p=.3; 4082 if (t==0) return 0; if (t==1) return 1; 4083 var s = p/(2*Math.PI) * Math.asin (1); 4084 return Math.pow(2,-10*t) * Math.sin( (t-s)*(2*Math.PI)/p ) + 1; 4085 }, 4086 4087 'bounce' : function (t) { 4088 if (t < (1/2.75)) { 4089 return (7.5625*t*t); 4090 } else if (t < (2/2.75)) { 4091 return (7.5625*(t-=(1.5/2.75))*t + .75); 4092 } else if (t < (2.5/2.75)) { 4093 return (7.5625*(t-=(2.25/2.75))*t + .9375); 4094 } else { 4095 return (7.5625*(t-=(2.625/2.75))*t + .984375); 4096 } 4097 }, 4098 4099 'arc' : function(t) { 4100 return t<.5 ? this['out-quad'](t*2) : this['out-quad'](2 - t*2); 4101 } 4102 4103}; 4104 4105 4106 4107/* ANIMATABLE */ 4108
4109/* >>> */ 4110 4111Earth.Animatable = function() {}; 4112 4113Earth.Animatable.colorProp = [ 'color', 'color2', 'color3', 'color4', 'color5', 'color6', 'color7', 'color8', 'lightColor', 'lightGroundColor' ]; 4114Earth.Animatable.latlngProp = [ 'location', 'sunLocation' ]; 4115Earth.Animatable.angleProp = [ 'lookAt' ]; 4116 4117 4118// PUBLIC 4119// start object animation 4120 4121Earth.Animatable.prototype.animate = function( prop, val, options ) { 4122 4123 var ani = { 4124 target: this, 4125 earth: (this.isEarth) ? this : this.earth, 4126 prop: prop, 4127 val: val, 4128 time : 0, 4129 duration : 400, 4130 relativeDuration : 0, 4131 easing : 'in-out-quad', 4132 lerpLatLng : false, 4133 loop : false, 4134 oscillate : false, 4135 paused : false 4136 }; 4137 4138 Object.assign( ani , options ); 4139 4140 4141 // value type for lerp 4142 4143 if ( Earth.Animatable.colorProp.indexOf( ani.prop ) != -1 ) { 4144 ani.type = 'color'; 4145 ani.from = new THREE.Color( this[ ani.prop ] ); 4146 ani.to = new THREE.Color( ani.val ); 4147 4148 } else if ( Earth.Animatable.latlngProp.indexOf( ani.prop ) != -1 ) { 4149 ani.type = 'latlng'; 4150 ani.from = Earth.formatLatLng( Object.assign( {}, this[ ani.prop ] ) ); 4151 ani.to = Earth.formatLatLng( Object.assign( {}, ani.val ) ); 4152 4153 } else if ( Earth.Animatable.angleProp.indexOf( ani.prop ) != -1 ) { 4154 this.options.lookAt = Earth.formatLatLng( Object.assign( {}, ani.val ) ); 4155 ani.type = 'angle'; 4156 ani.from = this.lookAngle; 4157 ani.to = this.getLocalAngle( this.object3d.position, this.getQuaternion(), Earth.latLngToWorld( this.options.lookAt, Earth.earthRadius ) ); 4158 ani.prop = 'lookAngle'; 4159 4160 } else { 4161 ani.type = 'number'; 4162 ani.from = this[ ani.prop ]; 4163 ani.to = ani.val; 4164 4165 } 4166 4167 4168 // ani duration 4169 4170 if ( ani.relativeDuration ) { 4171 4172 if ( ani.type == 'number' ) { 4173 ani.duration += Math.abs( ani.from - ani.to ) * ani.relativeDuration; 4174 4175 } else if ( ani.type == 'color' ) { 4176 ani.duration += ( Math.abs( ani.from.r - ani.to.r ) + Math.abs( ani.from.g - ani.to.g ) + Math.abs( ani.from.b - ani.to.b ) ) / 3 * ani.relativeDuration; 4177 4178 } else if ( ani.type == 'latlng' ) { 4179 ani.duration += Earth.getDistance( ani.from, ani.to ) / 1000 * ani.relativeDuration; 4180 4181 } else if ( ani.type == 'angle' ) { 4182 ani.duration += Math.abs( Earth.wrap( ani.from, 0, 2 * Math.PI ) - Earth.wrap( ani.to, 0, 2 * Math.PI ) ) * ani.relativeDuration; 4183 4184 } 4185 4186 } 4187 4188 4189 ani.step = function( t ) { 4190 4191 t = Earth.Animation.Easing[ this.easing ]( t ); 4192 4193 if ( this.type == 'number' ) { 4194 ani.target[ this.prop ] = THREE.Math.lerp( this.from, this.to, t ); 4195 4196 } else if ( this.type == 'color' ) { 4197 ani.target[ this.prop ] = this.from.clone().lerp( this.to, t ); 4198 4199 } else if ( this.type == 'latlng' ) { 4200 ani.target[ this.prop ] = Earth.lerp( this.from, this.to, t, ani.lerpLatLng ); 4201 4202 } else if ( this.type == 'angle' ) { 4203 ani.target[ this.prop ] = Earth.lerpAngle( this.from, this.to, t ); 4204 } 4205 4206 }; 4207 4208 4209 return new Earth.Animation( ani ); 4210 4211}; 4212 4213 4214// PUBLIC 4215// stop all animations 4216 4217Earth.Animatable.prototype.stopAllAnimations = function( dispatchComplete, jumpToEnd ) { 4218 4219 var stop_animations = []; 4220 4221 for ( var i=0; i < this.earth.animations.length; i++ ) { 4222 if ( this.earth.animations[i].target == this ) stop_animations.push( this.earth.animations[i] ); 4223 } 4224 4225 for ( var i in stop_animations ) { 4226 stop_animations[i].stop( dispatchComplete, jumpToEnd ); 4227 } 4228 4229}; 4230 4231Object.assign( Earth.prototype, Earth.Animatable.prototype ); 4232Object.assign( Earth.Marker.prototype, Earth.Animatable.prototype ); 4233Object.assign( Earth.Image.prototype, Earth.Animatable.prototype ); 4234Object.assign( Earth.Sprite.prototype, Earth.Animatable.prototype ); 4235Object.assign( Earth.Points.prototype, Earth.Animatable.prototype ); 4236Object.assign( Earth.Line.prototype, Earth.Animatable.prototype ); 4237Object.assign( Earth.Overlay.prototype, Earth.Animatable.prototype ); 4238 4239/* <<< */ 4240 4241 4242/* ORBIT */ 4243// private 4244// based on THREE.OrbitControls 4245 4246Earth.Orbit = function ( camera, earth ) { 4247 4248 this.camera = camera; 4249 this.earth = earth; 4250 4251 this.enableRotate = true; 4252 this.enableZoom = true; 4253 this.zoomSpeed = 1.0; 4254 4255 4256 // How far you can dolly in and out ( PerspectiveCamera only ) 4257 this.minDistance = 0; 4258 this.maxDistance = Infinity; 4259 4260 // How far you can orbit vertically, upper and lower limits. 4261 // Range is 0 to Math.PI radians. 4262 this.minPolarAngle = 0; // radians 4263 this.maxPolarAngle = Math.PI; // radians 4264 4265 // automatically rotate 4266 this.autoRotateSpeed = 0; 4267 this.autoRotateSpeedUp = 0; 4268 4269 4270 // public methods 4271 4272 this.setPosition = function ( pos ) { 4273 4274 scope.camera.position.copy( pos ); 4275 4276 scope.camera.updateProjectionMatrix();
4277 scope.update(); 4278 4279 state = STATE.NONE; 4280 4281 scope.dispatchEvent( changeEvent ); 4282 4283 }; 4284 4285 4286 this.cancel = function () { 4287 onMouseUp(); 4288 }; 4289 4290 4291 this.update = function ( ) { 4292 4293 var offset = new THREE.Vector3(); 4294 4295 // so camera.up is the orbit axis 4296 var quat = new THREE.Quaternion().setFromUnitVectors( camera.up, new THREE.Vector3( 0, 1, 0 ) ); 4297 var quatInverse = quat.clone().invert(); 4298 4299 var lastPosition = new THREE.Vector3(); 4300 var lastQuaternion = new THREE.Quaternion(); 4301 4302 4303 return function update( ) { 4304 4305 var position = scope.camera.position; 4306 4307 offset.copy( position ); 4308 // rotate offset to "y-axis-is-up" space 4309 offset.applyQuaternion( quat ); 4310 4311 // angle from z-axis around y-axis 4312 spherical.setFromVector3( offset ); 4313 4314 4315 if ( state == STATE.NONE && scope.autoRotateSpeed != 0 ) { 4316 rotateLeft( getAutoRotationAngle() * earth.deltaTime / 30 ); 4317 } 4318 4319 if ( state == STATE.NONE && scope.autoRotateSpeedUp != 0 ) { 4320 rotateUp( getAutoRotationAngleY() * earth.deltaTime / 30 ); 4321 } 4322 4323 4324 spherical.theta += sphericalDelta.theta; 4325 spherical.phi += sphericalDelta.phi; 4326 4327 4328 // restrict phi to be between desired limits 4329 spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); 4330 4331 spherical.makeSafe(); 4332 4333 4334 spherical.radius *= scale; 4335 4336 // restrict radius to be between desired limits 4337 spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); 4338 4339 4340 offset.setFromSpherical( spherical ); 4341 4342 // rotate offset back to "camera-up-vector-is-up" space 4343 offset.applyQuaternion( quatInverse ); 4344 4345 4346 position.copy( offset ); 4347 4348 scope.camera.lookAt( 0, 0, 0 ); 4349 4350 sphericalDelta.set( 0, 0, 0 ); 4351 4352 scale = 1; 4353 4354 4355 // has changed? 4356 4357 if ( zoomChanged || 4358 lastPosition.distanceToSquared( scope.camera.position ) > EPS || 4359 8 * ( 1 - lastQuaternion.dot( scope.camera.quaternion ) ) > EPS ) { 4360 4361 scope.dispatchEvent( changeEvent ); 4362 4363 lastPosition.copy( scope.camera.position ); 4364 lastQuaternion.copy( scope.camera.quaternion ); 4365 zoomChanged = false; 4366 4367 return true; 4368 4369 } 4370 4371 return false; 4372 4373 }; 4374 4375 }(); 4376 4377 4378 this.dispose = function () { 4379 4380 scope.earth.canvas.removeEventListener( 'mousedown', onMouseDown, false ); 4381 scope.earth.canvas.removeEventListener( 'wheel', onMouseWheel, false ); 4382 4383 scope.earth.canvas.removeEventListener( 'touchstart', onTouchStart, false ); 4384 scope.earth.canvas.removeEventListener( 'touchend', onTouchEnd, false ); 4385 document.removeEventListener( 'touchmove', onTouchMove, false ); 4386 4387 document.removeEventListener( 'mousemove', onMouseMove, false ); 4388 document.removeEventListener( 'mouseup', onMouseUp, false ); 4389 4390 }; 4391 4392 4393 // internals 4394 4395 var scope = this; 4396 4397 var changeEvent = { type: 'change' }; 4398 var startEvent = { type: 'start' }; 4399 var endEvent = { type: 'end' }; 4400 4401 var STATE = { NONE: - 1, ROTATE: 0, TOUCHZOOM: 1 }; 4402 var state = STATE.NONE; 4403 4404 var EPS = 0.000001; 4405 4406 // current position in spherical coordinates 4407 var spherical = new THREE.Spherical(); 4408 var sphericalDelta = new THREE.Spherical(); 4409 4410 var scale = 1; 4411 var zoomChanged = false; 4412 4413 var rotateStart = new THREE.Vector2(); 4414 var rotateEnd = new THREE.Vector2(); 4415 var rotateDelta = new THREE.Vector2(); 4416 4417 var dollyStart = new THREE.Vector2(); 4418 var dollyEnd = new THREE.Vector2(); 4419 var dollyDelta = new THREE.Vector2(); 4420 4421 4422 function getAutoRotationAngle() { 4423 return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 4424 } 4425 function getAutoRotationAngleY() { 4426 return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeedUp; 4427 } 4428 4429 function getZoomScale() { 4430 return Math.pow( 0.95, scope.zoomSpeed ); 4431 } 4432 4433 function rotateLeft( angle ) { 4434 sphericalDelta.theta -= angle; 4435 } 4436 function rotateUp( angle ) { 4437 sphericalDelta.phi -= angle; 4438 } 4439 4440 function dollyIn( dollyScale ) { 4441 scale /= dollyScale; 4442 } 4443 function dollyOut( dollyScale ) { 4444 scale *= dollyScale; 4445 } 4446 4447 4448 4449 // event callbacks - update the object state 4450 4451 function handleMouseDownRotate( event ) { 4452 4453 rotateStart = Earth.getEventPosition( event ); 4454 4455 } 4456 4457 function handleMouseMoveRotate( event ) { 4458 4459 rotateEnd = Earth.getEventPosition( event ); 4460 4461 var earthRadiusPx = scope.earth.elementSize.y / 2 * 0.75 * scope.earth.zoom; 4462 4463 var mouseCenterOffset = Earth.mouseCenterOffset( rotateEnd, scope.earth, earthRadiusPx ); 4464 mouseCenterOffset = Earth.Animation.Easing['in-cubic']( mouseCenterOffset ); 4465 4466 var speed = 0.75 / earthRadiusPx; 4467 4468 // compensate earth's curvature 4469 speed *= 1 + mouseCenterOffset * 0.85; 4470 4471 rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( speed ); 4472 4473 rotateLeft( rotateDelta.x ); 4474 rotateUp( rotateDelta.y ); 4475 4476 rotateStart.copy( rotateEnd );
4477 scope.update(); 4478 4479 } 4480 4481 4482 function handleMouseWheel( event ) { 4483 4484 if ( event.deltaY < 0 ) { 4485 dollyOut( getZoomScale() ); 4486 } else if ( event.deltaY > 0 ) { 4487 dollyIn( getZoomScale() ); 4488 } 4489 4490 scope.update(); 4491 4492 } 4493 4494 4495 function handleTouchStartDollyPan( event ) { 4496 4497 if ( scope.enableZoom ) { 4498 4499 var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 4500 var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 4501 var distance = Math.sqrt( dx * dx + dy * dy ); 4502 dollyStart.set( 0, distance ); 4503 4504 } 4505 4506 } 4507 4508 function handleTouchMoveDollyPan( event ) { 4509 4510 if ( scope.enableZoom ) { 4511 4512 var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 4513 var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 4514 var distance = Math.sqrt( dx * dx + dy * dy ); 4515 4516 dollyEnd.set( 0, distance ); 4517 dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); 4518 dollyIn( dollyDelta.y ); 4519 dollyStart.copy( dollyEnd ); 4520 scope.update(); 4521 4522 } 4523 4524 } 4525 4526 4527 // event handlers 4528 4529 function onMouseDown( event ) { 4530 4531 event.preventDefault(); 4532 4533 switch ( event.button ) { 4534 4535 case THREE.MOUSE.LEFT: 4536 4537 if ( scope.enableRotate === false ) return; 4538 handleMouseDownRotate( event ); 4539 state = STATE.ROTATE; 4540 break; 4541 4542 } 4543 4544 if ( state !== STATE.NONE ) { 4545 document.addEventListener( 'mousemove', onMouseMove, false ); 4546 document.addEventListener( 'mouseup', onMouseUp, false ); 4547 scope.dispatchEvent( startEvent ); 4548 } 4549 4550 } 4551 4552 function onMouseMove( event ) { 4553 4554 event.preventDefault(); 4555 4556 switch ( state ) { 4557 4558 case STATE.ROTATE: 4559 4560 if ( ! scope.enableRotate ) return; 4561 handleMouseMoveRotate( event ); 4562 break; 4563 4564 } 4565 4566 } 4567 4568 function onMouseUp( event ) { 4569 4570 document.removeEventListener( 'mousemove', onMouseMove, false ); 4571 document.removeEventListener( 'mouseup', onMouseUp, false ); 4572 scope.dispatchEvent( endEvent ); 4573 state = STATE.NONE; 4574 4575 } 4576 4577 function onMouseWheel( event ) { 4578 4579 if ( ! scope.enableZoom ) return; 4580 4581 event.preventDefault(); 4582 event.stopPropagation(); 4583 4584 handleMouseWheel( event ); 4585 4586 } 4587 4588 4589 function onTouchStart( event ) { 4590 4591 event.preventDefault(); 4592 4593 switch ( event.touches.length ) { 4594 4595 case 1: // one-fingered touch: rotate 4596 4597 if ( scope.enableRotate === false ) return; 4598 handleMouseDownRotate( event ); 4599 state = STATE.ROTATE; 4600 break; 4601 4602 case 2: // two-fingered touch: dolly-pan 4603 4604 if ( scope.enableZoom === false ) return; 4605 handleTouchStartDollyPan( event ); 4606 state = STATE.TOUCHZOOM; 4607 break; 4608 4609 default: 4610 state = STATE.NONE; 4611 4612 } 4613 4614 if ( state !== STATE.NONE ) { 4615 scope.dispatchEvent( startEvent ); 4616 } 4617 4618 } 4619 4620 function onTouchMove( event ) { 4621 4622 //event.preventDefault(); // removed because of chrome console warning 4623 event.stopPropagation(); 4624 4625 switch ( event.touches.length ) { 4626 4627 case 1: // one-fingered touch: rotate 4628 4629 if ( ! scope.enableRotate ) return; 4630 if ( state !== STATE.ROTATE ) return; // is this needed? 4631 4632 handleMouseMoveRotate( event ); 4633 break; 4634 4635 case 2: // two-fingered touch: dolly-pan 4636 4637 if ( ! scope.enableZoom ) return; 4638 if ( state !== STATE.TOUCHZOOM ) return; // is this needed? 4639 4640 handleTouchMoveDollyPan( event ); 4641 break; 4642 4643 default: 4644 4645 state = STATE.NONE; 4646 4647 } 4648 4649 } 4650 4651 function onTouchEnd( event ) { 4652 4653 scope.dispatchEvent( endEvent ); 4654 state = STATE.NONE; 4655 4656 } 4657 4658 4659 scope.earth.canvas.addEventListener( 'mousedown', onMouseDown, false ); 4660 scope.earth.canvas.addEventListener( 'wheel', onMouseWheel, false ); 4661 4662 scope.earth.canvas.addEventListener( 'touchstart', onTouchStart, false ); 4663 scope.earth.canvas.addEventListener( 'touchend', onTouchEnd, false ); 4664 document.addEventListener( 'touchmove', onTouchMove, false ); 4665 4666 // force an update at start 4667 this.update(); 4668 4669}; 4670 4671Object.assign( Earth.Orbit.prototype, Earth.ClassicEventDispatcher.prototype ); 4672 4673 4674 4675/* MeshLine */ 4676// private 4677// based on THREE.MeshLine 4678 4679Earth.MeshLine = function() { 4680 4681 this.positions = []; 4682 4683 this.previous = []; 4684 this.next = []; 4685 this.side = []; 4686 this.width = []; 4687 this.indices_array = []; 4688 this.uvs = []; 4689 this.counters = []; 4690 this.geometry = new THREE.BufferGeometry(); 4691 4692 this.widthCallback = null; 4693 4694}; 4695 4696Earth.MeshLine.prototype.setGeometry = function( g, c ) { 4697 4698 this.widthCallback = c; 4699 4700 this.positions = []; 4701 this.counters = []; 4702 4703 if( g instanceof THREE.BufferGeometry ) { 4704 for( var j = 0; j < g.vertices.length; j++ ) { 4705 var v = g.vertices[ j ]; 4706 var c = j/g.vertices.length; 4707 this.positions.push( v.x, v.y, v.z ); 4708 this.positions.push( v.x, v.y, v.z ); 4709 this.counters.push(c); 4710 this.counters.push(c); 4711 } 4712 } 4713 4714 if( g instanceof Float32Array || g instanceof Array ) { 4715 for( var j = 0; j < g.length; j += 3 ) { 4716 var c = j/g.length; 4717 this.positions.push( g[ j ], g[ j + 1 ], g[ j + 2 ] ); 4718 this.positions.push( g[ j ], g[ j + 1 ], g[ j + 2 ] ); 4719 this.counters.push(c); 4720 this.counters.push(c); 4721 } 4722 } 4723 4724 this.process(); 4725 4726} 4727 4728Earth.MeshLine.prototype.compareV3 = function( a, b ) { 4729 4730 var aa = a * 6; 4731 var ab = b * 6; 4732 return ( this.positions[ aa ] === this.positions[ ab ] ) && ( this.positions[ aa + 1 ] === this.positions[ ab + 1 ] ) && ( this.positions[ aa + 2 ] === this.positions[ ab + 2 ] ); 4733 4734} 4735 4736Earth.MeshLine.prototype.copyV3 = function( a ) { 4737 4738 var aa = a * 6; 4739 return [ this.positions[ aa ], this.positions[ aa + 1 ], this.positions[ aa + 2 ] ]; 4740 4741} 4742 4743Earth.MeshLine.prototype.process = function() { 4744 4745 var l = this.positions.length / 6; 4746 4747 this.previous = []; 4748 this.next = []; 4749 this.side = []; 4750 this.width = []; 4751 this.indices_array = []; 4752 this.uvs = []; 4753 4754 for( var j = 0; j < l; j++ ) { 4755 this.side.push( 1 ); 4756 this.side.push( -1 ); 4757 } 4758 4759 var w; 4760 for( var j = 0; j < l; j++ ) { 4761 if( this.widthCallback ) w = this.widthCallback( j / ( l -1 ) ); 4762 else w = 1; 4763 this.width.push( w ); 4764 this.width.push( w ); 4765 } 4766 4767 for( var j = 0; j < l; j++ ) { 4768 this.uvs.push( j / ( l - 1 ), 0 ); 4769 this.uvs.push( j / ( l - 1 ), 1 ); 4770 } 4771 4772 var v; 4773 4774 if( this.compareV3( 0, l - 1 ) ){ 4775 v = this.copyV3( l - 2 ); 4776 } else { 4777 v = this.copyV3( 0 ); 4778 } 4779 this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 4780 this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 4781 for( var j = 0; j < l - 1; j++ ) { 4782 v = this.copyV3( j ); 4783 this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 4784 this.previous.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 4785 } 4786 4787 for( var j = 1; j < l; j++ ) { 4788 v = this.copyV3( j ); 4789 this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 4790 this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 4791 } 4792 4793 if( this.compareV3( l - 1, 0 ) ){ 4794 v = this.copyV3( 1 ); 4795 } else { 4796 v = this.copyV3( l - 1 ); 4797 } 4798 this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 4799 this.next.push( v[ 0 ], v[ 1 ], v[ 2 ] ); 4800 4801 for( var j = 0; j < l - 1; j++ ) { 4802 var n = j * 2; 4803 this.indices_array.push( n, n + 1, n + 2 ); 4804 this.indices_array.push( n + 2, n + 1, n + 3 ); 4805 } 4806 4807 //if (! this.attributes) { 4808 4809 this.attributes = { 4810 position: new THREE.BufferAttribute( new Float32Array( this.positions ), 3 ), 4811 previous: new THREE.BufferAttribute( new Float32Array( this.previous ), 3 ), 4812 next: new THREE.BufferAttribute( new Float32Array( this.next ), 3 ), 4813 side: new THREE.BufferAttribute( new Float32Array( this.side ), 1 ), 4814 width: new THREE.BufferAttribute( new Float32Array( this.width ), 1 ), 4815 uv: new THREE.BufferAttribute( new Float32Array( this.uvs ), 2 ), 4816 index: new THREE.BufferAttribute( new Uint16Array( this.indices_array ), 1 ),
4817 counters: new THREE.BufferAttribute( new Float32Array( this.counters ), 1 ) 4818 }; 4819 4820 this.geometry.setAttribute( 'position', this.attributes.position ); 4821 this.geometry.setAttribute( 'previous', this.attributes.previous ); 4822 this.geometry.setAttribute( 'next', this.attributes.next ); 4823 this.geometry.setAttribute( 'side', this.attributes.side ); 4824 this.geometry.setAttribute( 'width', this.attributes.width ); 4825 this.geometry.setAttribute( 'uv', this.attributes.uv ); 4826 this.geometry.setIndex( this.attributes.index ); 4827 this.geometry.setAttribute( 'counters', this.attributes.counters ); 4828 4829/* } else { 4830 4831 4832 this.attributes.position.setArray( new Float32Array(this.positions) ); 4833 this.attributes.position.needsUpdate = true; 4834 this.attributes.previous.setArray(new Float32Array(this.previous)); 4835 this.attributes.previous.needsUpdate = true; 4836 this.attributes.next.setArray(new Float32Array(this.next)); 4837 this.attributes.next.needsUpdate = true; 4838 this.attributes.side.setArray(new Float32Array(this.side)); 4839 this.attributes.side.needsUpdate = true; 4840 this.attributes.width.setArray(new Float32Array(this.width)); 4841 this.attributes.width.needsUpdate = true; 4842 this.attributes.uv.setArray(new Float32Array(this.uvs)); 4843 this.attributes.uv.needsUpdate = true; 4844 this.attributes.index.setArray(new Uint16Array(this.indices_array)); 4845 this.attributes.index.needsUpdate = true; 4846 this.attributes.counters.setArray(new Float32Array( this.counters )); 4847 this.attributes.counters.needsUpdate = true; 4848 4849 4850 }*/ 4851 4852} 4853 4854 4855Earth.MeshLineMat = function( parameters ) { 4856 4857 var vertexShaderSource = [ 4858'precision highp float;', 4859'', 4860'attribute vec3 position;', 4861'attribute vec3 previous;', 4862'attribute vec3 next;', 4863'attribute float side;', 4864'attribute float width;', 4865'attribute vec2 uv;', 4866'attribute float counters;', 4867'', 4868'uniform mat4 projectionMatrix;', 4869'uniform mat4 modelViewMatrix;', 4870'uniform vec2 resolution;', 4871'uniform float lineWidth;', 4872'uniform vec3 color;', 4873'uniform float opacity;', 4874'uniform float near;', 4875'uniform float far;', 4876'uniform float sizeAttenuation;', 4877'', 4878'varying vec2 vUV;', 4879'varying vec4 vColor;', 4880'varying float vCounters;', 4881'', 4882'vec2 fix( vec4 i, float aspect ) {', 4883'', 4884' vec2 res = i.xy / i.w;', 4885' res.x *= aspect;', 4886' vCounters = counters;', 4887' return res;', 4888'', 4889'}', 4890'', 4891'void main() {', 4892'', 4893' float aspect = resolution.x / resolution.y;', 4894' float pixelWidthRatio = 1. / (resolution.x * projectionMatrix[0][0]);', 4895'', 4896' vColor = vec4( color, opacity );', 4897' vUV = uv;', 4898'', 4899' mat4 m = projectionMatrix * modelViewMatrix;', 4900' vec4 finalPosition = m * vec4( position, 1.0 );', 4901' vec4 prevPos = m * vec4( previous, 1.0 );', 4902' vec4 nextPos = m * vec4( next, 1.0 );', 4903'', 4904' vec2 currentP = fix( finalPosition, aspect );', 4905' vec2 prevP = fix( prevPos, aspect );', 4906' vec2 nextP = fix( nextPos, aspect );', 4907'', 4908' float pixelWidth = finalPosition.w * pixelWidthRatio;', 4909' float w = 1.8 * pixelWidth * lineWidth * width;', 4910'', 4911' if( sizeAttenuation == 1. ) {', 4912' w = 1.8 * lineWidth * width;', 4913' }', 4914'', 4915' vec2 dir;', 4916' if( nextP == currentP ) dir = normalize( currentP - prevP );', 4917' else if( prevP == currentP ) dir = normalize( nextP - currentP );', 4918' else {', 4919' vec2 dir1 = normalize( currentP - prevP );', 4920' vec2 dir2 = normalize( nextP - currentP );', 4921' dir = normalize( dir1 + dir2 );', 4922'', 4923' vec2 perp = vec2( -dir1.y, dir1.x );', 4924' vec2 miter = vec2( -dir.y, dir.x );', 4925' //w = clamp( w / dot( miter, perp ), 0., 4. * lineWidth * width );', 4926'', 4927' }', 4928'', 4929' //vec2 normal = ( cross( vec3( dir, 0. ), vec3( 0., 0., 1. ) ) ).xy;', 4930' vec2 normal = vec2( -dir.y, dir.x );', 4931' normal.x /= aspect;', 4932' normal *= .5 * w;', 4933'', 4934' vec4 offset = vec4( normal * side, 0.0, 1.0 );', 4935' finalPosition.xy += offset.xy;', 4936'', 4937' gl_Position = finalPosition;', 4938'', 4939'}' ]; 4940 4941 var fragmentShaderSource = [ 4942 '#extension GL_OES_standard_derivatives : enable', 4943'precision mediump float;', 4944'', 4945'uniform sampler2D map;', 4946'uniform sampler2D alphaMap;', 4947'uniform float useMap;', 4948'uniform float useAlphaMap;', 4949'uniform float useDash;', 4950'uniform float dashArray;', 4951'uniform float dashOffset;', 4952'uniform float dashRatio;', 4953'uniform float visibility;', 4954'uniform float alphaTest;', 4955'uniform vec2 repeat;', 4956'', 4957'varying vec2 vUV;', 4958'varying vec4 vColor;', 4959'varying float vCounters;', 4960'', 4961'void main() {', 4962'', 4963' vec4 c = vColor;', 4964' if( useMap == 1. ) c *= texture2D( map, vUV * repeat );', 4965' if( useAlphaMap == 1. ) c.a *= texture2D( alphaMap, vUV * repeat ).a;', 4966' if( c.a < alphaTest ) discard;', 4967' if( useDash == 1. ){', 4968' c.a *= ceil(mod(vCounters + dashOffset, dashArray) - (dashArray * dashRatio));', 4969' }', 4970' gl_FragColor = c;', 4971' gl_FragColor.a *= step(vCounters, visibility);', 4972'}' ]; 4973 4974 function check( v, d ) { 4975 if( v === undefined ) return d; 4976 return v; 4977 } 4978 4979 //THREE.Material.call( this ); 4980 4981 parameters = parameters || {}; 4982 4983 this.lineWidth = check( parameters.lineWidth, 1 ); 4984 this.map = check( parameters.map, null ); 4985 this.useMap = check( parameters.useMap, 0 ); 4986 this.alphaMap = check( parameters.alphaMap, null ); 4987 this.useAlphaMap = check( parameters.useAlphaMap, 0 ); 4988 this.color = check( parameters.color, new THREE.Color( 0xffffff ) ); 4989 this.opacity = check( parameters.opacity, 1 ); 4990 this.resolution = check( parameters.resolution, new THREE.Vector2( 1, 1 ) ); 4991 this.sizeAttenuation = check( parameters.sizeAttenuation, 1 ); 4992 this.near = check( parameters.near, 1 ); 4993 this.far = check( parameters.far, 1 ); 4994 this.dashArray = check( parameters.dashArray, 0 ); 4995 this.dashOffset = check( parameters.dashOffset, 0 ); 4996 this.dashRatio = check( parameters.dashRatio, 0.5 ); 4997 this.useDash = ( this.dashArray !== 0 ) ? 1 : 0; 4998 this.visibility = check( parameters.visibility, 1 ); 4999 this.alphaTest = check( parameters.alphaTest, 0 ); 5000 this.repeat = check( parameters.repeat, new THREE.Vector2( 1, 1 ) ); 5001 5002 var material = new THREE.RawShaderMaterial( { 5003 uniforms:{ 5004 lineWidth: { type: 'f', value: this.lineWidth }, 5005 map: { type: 't', value: this.map }, 5006 useMap: { type: 'f', value: this.useMap }, 5007 alphaMap: { type: 't', value: this.alphaMap }, 5008 useAlphaMap: { type: 'f', value: this.useAlphaMap }, 5009 color: { type: 'c', value: this.color }, 5010 opacity: { type: 'f', value: this.opacity }, 5011 resolution: { type: 'v2', value: this.resolution }, 5012 sizeAttenuation: { type: 'f', value: this.sizeAttenuation }, 5013 near: { type: 'f', value: this.near }, 5014 far: { type: 'f', value: this.far }, 5015 dashArray: { type: 'f', value: this.dashArray }, 5016 dashOffset: { type: 'f', value: this.dashOffset }, 5017 dashRatio: { type: 'f', value: this.dashRatio }, 5018 useDash: { type: 'f', value: this.useDash }, 5019 visibility: {type: 'f', value: this.visibility}, 5020 alphaTest: {type: 'f', value: this.alphaTest}, 5021 repeat: { type: 'v2', value: this.repeat } 5022 }, 5023 vertexShader: vertexShaderSource.join( '\r\n' ), 5024 fragmentShader: fragmentShaderSource.join( '\r\n' ) 5025 }); 5026 5027 material.type = 'MeshLineMaterial'; 5028 5029 material.setValues( parameters ); 5030 5031 return material; 5032 5033}; 5034 5035Earth.MeshLineMat.prototype = Object.create( THREE.Material.prototype ); 5036Earth.MeshLineMat.prototype.constructor = Earth.MeshLineMat; 5037 5038 5039 5040/* ObjParser */ 5041// private 5042// based on THREE.OBJLoader 5043 5044Earth.ObjParser = ( function () { 5045 5046 // o object_name | g group_name 5047 var object_pattern = /^[og]\s*(.+)?/; 5048 5049 function ParserState() { 5050 5051 var state = { 5052 objects: [], 5053 object: {}, 5054 5055 vertices: [], 5056 normals: [], 5057 5058 startObject: function ( name, fromDeclaration ) { 5059 5060 if ( this.object && this.object.fromDeclaration === false ) { 5061 this.object.name = name; 5062 this.object.fromDeclaration = ( fromDeclaration !== false ); 5063 return; 5064 } 5065 5066 var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); 5067 5068 if ( this.object && typeof this.object._finalize === 'function' ) { 5069 this.object._finalize( true ); 5070 } 5071 5072 this.object = { 5073 name: name || '', 5074 fromDeclaration: ( fromDeclaration !== false ), 5075 5076 geometry: { 5077 vertices: [], 5078 normals: [] 5079 }, 5080 materials: [], 5081 smooth: true, 5082 5083 currentMaterial: function () { 5084 if ( this.materials.length > 0 ) { 5085 return this.materials[ this.materials.length - 1 ]; 5086 } 5087 return undefined; 5088 }, 5089 5090 _finalize: function ( end ) { 5091 5092 var lastMultiMaterial = this.currentMaterial(); 5093 if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) { 5094 5095 lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; 5096 lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; 5097 lastMultiMaterial.inherited = false;
5098 5099 } 5100 5101 // Ignore objects tail materials if no face declarations followed them before a new o/g started. 5102 if ( end && this.materials.length > 1 ) { 5103 for ( var mi = this.materials.length - 1; mi >= 0; mi -- ) { 5104 if ( this.materials[ mi ].groupCount <= 0 ) { 5105 this.materials.splice( mi, 1 ); 5106 } 5107 } 5108 } 5109 5110 // Guarantee at least one empty material, this makes the creation later more straight forward. 5111 if ( end && this.materials.length === 0 ) { 5112 this.materials.push( { 5113 name: '', 5114 smooth: this.smooth 5115 } ); 5116 } 5117 5118 return lastMultiMaterial; 5119 5120 } 5121 }; 5122 5123 // Inherit previous objects material. 5124 if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) { 5125 var declared = previousMaterial.clone( 0 ); 5126 declared.inherited = true; 5127 this.object.materials.push( declared ); 5128 } 5129 5130 this.objects.push( this.object ); 5131 5132 }, 5133 5134 finalize: function () { 5135 if ( this.object && typeof this.object._finalize === 'function' ) { 5136 this.object._finalize( true ); 5137 } 5138 }, 5139 5140 parseVertexIndex: function ( value, len ) { 5141 var index = parseInt( value, 10 ); 5142 return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 5143 }, 5144 5145 parseNormalIndex: function ( value, len ) { 5146 var index = parseInt( value, 10 ); 5147 return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; 5148 }, 5149 5150 addVertex: function ( a, b, c ) { 5151 var src = this.vertices; 5152 var dst = this.object.geometry.vertices; 5153 5154 dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); 5155 dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); 5156 dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); 5157 }, 5158 5159 addNormal: function ( a, b, c ) { 5160 var src = this.normals; 5161 var dst = this.object.geometry.normals; 5162 5163 dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); 5164 dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); 5165 dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); 5166 }, 5167 5168 addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) { 5169 var vLen = this.vertices.length; 5170 5171 var ia = this.parseVertexIndex( a, vLen ); 5172 var ib = this.parseVertexIndex( b, vLen ); 5173 var ic = this.parseVertexIndex( c, vLen ); 5174 5175 this.addVertex( ia, ib, ic ); 5176 5177 5178 if ( na !== undefined && na !== '' ) { 5179 5180 // Normals are many times the same. If so, skip function call and parseInt. 5181 var nLen = this.normals.length; 5182 ia = this.parseNormalIndex( na, nLen ); 5183 5184 ib = na === nb ? ia : this.parseNormalIndex( nb, nLen ); 5185 ic = na === nc ? ia : this.parseNormalIndex( nc, nLen ); 5186 5187 this.addNormal( ia, ib, ic ); 5188 5189 } 5190 }, 5191 5192 }; 5193 5194 state.startObject( '', false ); 5195 5196 return state; 5197 5198 } 5199 5200 5201 5202 function OBJLoader( manager ) { 5203 5204 this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 5205 this.materials = null; 5206 5207 } 5208 5209 OBJLoader.prototype = { 5210 5211 constructor: OBJLoader, 5212 5213 parse: function ( text ) { 5214 5215 var state = new ParserState(); 5216 5217 if ( text.indexOf( '\r\n' ) !== - 1 ) { 5218 // This is faster than String.split with regex that splits on both 5219 text = text.replace( /\r\n/g, '\n' ); 5220 } 5221 5222 if ( text.indexOf( '\\\n' ) !== - 1 ) { 5223 // join lines separated by a line continuation character (\) 5224 text = text.replace( /\\\n/g, '' ); 5225 } 5226 5227 var lines = text.split( '\n' ); 5228 var line = '', lineFirstChar = ''; 5229 var lineLength = 0; 5230 var result = []; 5231 5232 // Faster to just trim left side of the line. Use if available. 5233 var trimLeft = ( typeof ''.trimLeft === 'function' ); 5234 5235 for ( var i = 0, l = lines.length; i < l; i ++ ) { 5236 5237 line = lines[ i ]; 5238 line = trimLeft ? line.trimLeft() : line.trim(); 5239 lineLength = line.length; 5240 5241 if ( lineLength === 0 ) continue; 5242 5243 lineFirstChar = line.charAt( 0 ); 5244 5245 // @todo invoke passed in handler if any 5246 if ( lineFirstChar === '#' ) continue; 5247 5248 if ( lineFirstChar === 'v' ) { 5249 5250 var data = line.split( /\s+/ ); 5251 5252 switch ( data[ 0 ] ) { 5253 5254 case 'v': 5255 state.vertices.push( 5256 parseFloat( data[ 1 ] ), 5257 parseFloat( data[ 2 ] ), 5258 parseFloat( data[ 3 ] ) 5259 ); 5260 break; 5261 5262 case 'vn': 5263 state.normals.push( 5264 parseFloat( data[ 1 ] ), 5265 parseFloat( data[ 2 ] ), 5266 parseFloat( data[ 3 ] ) 5267 ); 5268 break; 5269 5270 } 5271 5272 } else if ( lineFirstChar === 'f' ) { 5273 5274 var lineData = line.substr( 1 ).trim(); 5275 var vertexData = lineData.split( /\s+/ ); 5276 var faceVertices = []; 5277 5278 // Parse the face vertex data into an easy to work with format 5279 5280 for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) { 5281 var vertex = vertexData[ j ]; 5282 5283 if ( vertex.length > 0 ) { 5284 var vertexParts = vertex.split( '/' ); 5285 faceVertices.push( vertexParts ); 5286 } 5287 } 5288 5289 // Draw an edge between the first vertex and all subsequent vertices to form an n-gon 5290 5291 var v1 = faceVertices[ 0 ]; 5292 5293 for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) { 5294 var v2 = faceVertices[ j ]; 5295 var v3 = faceVertices[ j + 1 ]; 5296 5297 state.addFace( 5298 v1[ 0 ], v2[ 0 ], v3[ 0 ], 5299 v1[ 1 ], v2[ 1 ], v3[ 1 ],
5300 v1[ 2 ], v2[ 2 ], v3[ 2 ] 5301 ); 5302 } 5303 5304 } else if ( ( result = object_pattern.exec( line ) ) !== null ) { 5305 5306 // o object_name or g group_name 5307 var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); 5308 state.startObject( name ); 5309 5310 5311 } else if ( lineFirstChar === 's' ) { 5312 5313 result = line.split( ' ' ); 5314 5315 // smooth shading 5316 if ( result.length > 1 ) { 5317 var value = result[ 1 ].trim().toLowerCase(); 5318 state.object.smooth = ( value !== '0' && value !== 'off' ); 5319 5320 } else { 5321 state.object.smooth = true; 5322 5323 } 5324 5325 var material = state.object.currentMaterial(); 5326 if ( material ) material.smooth = state.object.smooth; 5327 5328 } else { 5329 5330 // Handle null terminated files 5331 if ( line === '\0' ) continue; 5332 5333 } 5334 5335 } 5336 5337 state.finalize(); 5338 5339 var container = new THREE.Group(); 5340 5341 for ( var i = 0, l = state.objects.length; i < l; i ++ ) { 5342 5343 var object = state.objects[ i ]; 5344 var geometry = object.geometry; 5345 5346 // Skip o/g line declarations that did not follow with any faces 5347 if ( geometry.vertices.length === 0 ) continue; 5348 5349 var buffergeometry = new THREE.BufferGeometry(); 5350 5351 buffergeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) ); 5352 5353 if ( geometry.normals.length > 0 ) { 5354 buffergeometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) ); 5355 } else { 5356 buffergeometry.computeVertexNormals(); 5357 } 5358 5359 5360 // Create mesh 5361 5362 var mesh = new THREE.Mesh( buffergeometry ); 5363 mesh.name = object.name; 5364 container.add( mesh ); 5365 5366 } 5367 5368 return container; 5369 5370 } 5371 5372 }; 5373 5374 return OBJLoader; 5375 5376} )(); 5377 5378 5379 5380 5381 5382/* Earth INIT */ 5383 5384if ( document.readyState == 'loading' ) { // page is still loading 5385 5386 document.addEventListener( "DOMContentLoaded", function() { 5387 Earth.dispatchLoadEvent(); 5388 } ); 5389 5390} else { // async load 5391 5392 setTimeout( function(){ 5393 Earth.dispatchLoadEvent(); 5394 }, 1 ); 5395 5396}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.