UNPKG

three-globe

Version:

Globe data visualization as a ThreeJS reusable 3D object

168 lines (147 loc) 5.47 kB
<head> <script type="importmap">{ "imports": { "three": "https://esm.sh/three", "three/": "https://esm.sh/three/" }}</script> <!-- <script type="module"> import * as THREE from 'three'; window.THREE = THREE;</script>--> <!-- <script src="../../dist/three-globe.js" defer></script>--> <style> body { margin: 0; } #time { position: absolute; bottom: 8px; left: 8px; color: lightblue; font-family: monospace; } </style> </head> <body> <div id="globeViz"></div> <div id="time"></div> <script type="module"> import ThreeGlobe from 'https://esm.sh/three-globe?external=three'; import * as THREE from 'https://esm.sh/three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js?external=three'; import * as solar from 'https://esm.sh/solar-calculator'; const VELOCITY = 1; // minutes per frame // Custom shader: Blends night and day images to simulate day/night cycle const dayNightShader = { vertexShader: ` varying vec3 vNormal; varying vec2 vUv; void main() { vNormal = normalize(normalMatrix * normal); vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` #define PI 3.141592653589793 uniform sampler2D dayTexture; uniform sampler2D nightTexture; uniform vec2 sunPosition; uniform vec2 globeRotation; varying vec3 vNormal; varying vec2 vUv; float toRad(in float a) { return a * PI / 180.0; } vec3 Polar2Cartesian(in vec2 c) { // [lng, lat] float theta = toRad(90.0 - c.x); float phi = toRad(90.0 - c.y); return vec3( // x,y,z sin(phi) * cos(theta), cos(phi), sin(phi) * sin(theta) ); } void main() { float invLon = toRad(globeRotation.x); float invLat = -toRad(globeRotation.y); mat3 rotX = mat3( 1, 0, 0, 0, cos(invLat), -sin(invLat), 0, sin(invLat), cos(invLat) ); mat3 rotY = mat3( cos(invLon), 0, sin(invLon), 0, 1, 0, -sin(invLon), 0, cos(invLon) ); vec3 rotatedSunDirection = rotX * rotY * Polar2Cartesian(sunPosition); float intensity = dot(normalize(vNormal), normalize(rotatedSunDirection)); vec4 dayColor = texture2D(dayTexture, vUv); vec4 nightColor = texture2D(nightTexture, vUv); float blendFactor = smoothstep(-0.1, 0.1, intensity); gl_FragColor = mix(nightColor, dayColor, blendFactor); } ` }; const sunPosAt = dt => { const day = new Date(+dt).setUTCHours(0, 0, 0, 0); const t = solar.century(dt); const longitude = (day - dt) / 864e5 * 360 - 180; return [longitude - solar.equationOfTime(t) / 4, solar.declination(t)]; }; let dt = +new Date(); const timeEl = document.getElementById('time'); const Globe = new ThreeGlobe(); let globeMaterial; Promise.all([ new THREE.TextureLoader().loadAsync('//cdn.jsdelivr.net/npm/three-globe/example/img/earth-day.jpg'), new THREE.TextureLoader().loadAsync('//cdn.jsdelivr.net/npm/three-globe/example/img/earth-night.jpg') ]).then(([dayTexture, nightTexture]) => { const material = globeMaterial = new THREE.ShaderMaterial({ uniforms: { dayTexture: { value: dayTexture }, nightTexture: { value: nightTexture }, sunPosition: { value: new THREE.Vector2() }, globeRotation: { value: new THREE.Vector2() } }, vertexShader: dayNightShader.vertexShader, fragmentShader: dayNightShader.fragmentShader }); Globe.globeMaterial(material); requestAnimationFrame(() => (function animate() { // animate time of day dt += VELOCITY * 60 * 1000; timeEl.textContent = new Date(dt).toLocaleString(); material.uniforms.sunPosition.value.set(...sunPosAt(dt)); requestAnimationFrame(animate); })() ); }); // Setup renderer const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); document.getElementById('globeViz').appendChild(renderer.domElement); // Setup scene const scene = new THREE.Scene(); scene.add(Globe); scene.add(new THREE.AmbientLight(0xcccccc, Math.PI)); scene.add(new THREE.DirectionalLight(0xffffff, 0.6 * Math.PI)); // Setup camera const camera = new THREE.PerspectiveCamera(); camera.aspect = window.innerWidth/ window.innerHeight; camera.updateProjectionMatrix(); camera.position.z = 500; // Add camera controls const obControls = new OrbitControls(camera, renderer.domElement); obControls.minDistance = 101; obControls.addEventListener('change', () => { // Update globe rotation on shader const { lng, lat } = Globe.toGeoCoords(camera.position); globeMaterial?.uniforms.globeRotation.value.set(lng, lat); }); // Kick-off renderer (function animate() { // IIFE // Frame cycle obControls.update(); renderer.render(scene, camera); requestAnimationFrame(animate); })(); </script> </body>