// === HOLOGRAPHIC EARTH === import * as THREE from 'three'; import { NEXUS } from './constants.js'; import { scene } from './scene-setup.js'; export const EARTH_RADIUS = 2.8; export const EARTH_Y = 20.0; export const EARTH_ROTATION_SPEED = 0.035; const EARTH_AXIAL_TILT = 23.4 * (Math.PI / 180); export const earthGroup = new THREE.Group(); earthGroup.position.set(0, EARTH_Y, 0); earthGroup.rotation.z = EARTH_AXIAL_TILT; scene.add(earthGroup); export const earthSurfaceMat = new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0.0 }, uOceanColor: { value: new THREE.Color(0x003d99) }, uLandColor: { value: new THREE.Color(0x1a5c2a) }, uGlowColor: { value: new THREE.Color(NEXUS.colors.accent) }, }, vertexShader: ` varying vec3 vNormal; varying vec3 vWorldPos; varying vec2 vUv; void main() { vNormal = normalize(normalMatrix * normal); vWorldPos = (modelMatrix * vec4(position, 1.0)).xyz; vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform float uTime; uniform vec3 uOceanColor; uniform vec3 uLandColor; uniform vec3 uGlowColor; varying vec3 vNormal; varying vec3 vWorldPos; varying vec2 vUv; vec3 _m3(vec3 x){ return x - floor(x*(1./289.))*289.; } vec4 _m4(vec4 x){ return x - floor(x*(1./289.))*289.; } vec4 _p4(vec4 x){ return _m4((x*34.+1.)*x); } float snoise(vec3 v){ const vec2 C = vec2(1./6., 1./3.); vec3 i = floor(v + dot(v, C.yyy)); vec3 x0 = v - i + dot(i, C.xxx); vec3 g = step(x0.yzx, x0.xyz); vec3 l = 1.0 - g; vec3 i1 = min(g.xyz, l.zxy); vec3 i2 = max(g.xyz, l.zxy); vec3 x1 = x0 - i1 + C.xxx; vec3 x2 = x0 - i2 + C.yyy; vec3 x3 = x0 - 0.5; i = _m3(i); vec4 p = _p4(_p4(_p4( i.z+vec4(0.,i1.z,i2.z,1.))+ i.y+vec4(0.,i1.y,i2.y,1.))+ i.x+vec4(0.,i1.x,i2.x,1.)); float n_ = .142857142857; vec3 ns = n_*vec3(2.,0.,-1.)+vec3(0.,-.5,1.); vec4 j = p - 49.*floor(p*ns.z*ns.z); vec4 x_ = floor(j*ns.z); vec4 y_ = floor(j - 7.*x_); vec4 h = 1. - abs(x_*(2./7.)) - abs(y_*(2./7.)); vec4 b0 = vec4(x_.xy,y_.xy)*(2./7.); vec4 b1 = vec4(x_.zw,y_.zw)*(2./7.); vec4 s0 = floor(b0)*2.+1.; vec4 s1 = floor(b1)*2.+1.; vec4 sh = -step(h, vec4(0.)); vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy; vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww; vec3 p0=vec3(a0.xy,h.x); vec3 p1=vec3(a0.zw,h.y); vec3 p2=vec3(a1.xy,h.z); vec3 p3=vec3(a1.zw,h.w); vec4 nm = max(0.6-vec4(dot(x0,x0),dot(x1,x1),dot(x2,x2),dot(x3,x3)),0.); vec4 nr = 1.79284291400159-0.85373472095314*nm; p0*=nr.x; p1*=nr.y; p2*=nr.z; p3*=nr.w; nm = nm*nm; return 42.*dot(nm*nm, vec4(dot(p0,x0),dot(p1,x1),dot(p2,x2),dot(p3,x3))); } void main() { vec3 n = normalize(vNormal); vec3 vd = normalize(cameraPosition - vWorldPos); float lat = (vUv.y - 0.5) * 3.14159265; float lon = vUv.x * 6.28318530; vec3 sp = vec3(cos(lat)*cos(lon), sin(lat), cos(lat)*sin(lon)); float c = snoise(sp*1.8)*0.60 + snoise(sp*3.6)*0.30 + snoise(sp*7.2)*0.10; float land = smoothstep(0.05, 0.30, c); vec3 surf = mix(uOceanColor, uLandColor, land); surf = mix(surf, uGlowColor * 0.45, 0.38); float scan = 0.5 + 0.5*sin(vUv.y * 220.0 + uTime * 1.8); scan = smoothstep(0.30, 0.70, scan) * 0.14; float fresnel = pow(1.0 - max(dot(n, vd), 0.0), 4.0); vec3 col = surf + scan*uGlowColor*0.9 + fresnel*uGlowColor*1.5; float alpha = 0.48 + fresnel * 0.42; gl_FragColor = vec4(col, alpha); } `, transparent: true, depthWrite: false, side: THREE.FrontSide, }); const earthSphere = new THREE.SphereGeometry(EARTH_RADIUS, 64, 32); export const earthMesh = new THREE.Mesh(earthSphere, earthSurfaceMat); earthMesh.userData.zoomLabel = 'Planet Earth'; earthGroup.add(earthMesh); // Lat/lon grid lines (function buildEarthGrid() { const lineMat = new THREE.LineBasicMaterial({ color: 0x2266bb, transparent: true, opacity: 0.30, }); const r = EARTH_RADIUS + 0.015; const SEG = 64; for (let lat = -60; lat <= 60; lat += 30) { const phi = lat * (Math.PI / 180); const pts = []; for (let i = 0; i <= SEG; i++) { const th = (i / SEG) * Math.PI * 2; pts.push(new THREE.Vector3( Math.cos(phi) * Math.cos(th) * r, Math.sin(phi) * r, Math.cos(phi) * Math.sin(th) * r )); } earthGroup.add(new THREE.Line( new THREE.BufferGeometry().setFromPoints(pts), lineMat )); } for (let lon = 0; lon < 360; lon += 30) { const th = lon * (Math.PI / 180); const pts = []; for (let i = 0; i <= SEG; i++) { const phi = (i / SEG) * Math.PI - Math.PI / 2; pts.push(new THREE.Vector3( Math.cos(phi) * Math.cos(th) * r, Math.sin(phi) * r, Math.cos(phi) * Math.sin(th) * r )); } earthGroup.add(new THREE.Line( new THREE.BufferGeometry().setFromPoints(pts), lineMat )); } })(); // Atmosphere shell const atmMat = new THREE.MeshBasicMaterial({ color: 0x1144cc, transparent: true, opacity: 0.07, side: THREE.BackSide, depthWrite: false, blending: THREE.AdditiveBlending, }); earthGroup.add(new THREE.Mesh( new THREE.SphereGeometry(EARTH_RADIUS * 1.14, 32, 16), atmMat )); export const earthGlowLight = new THREE.PointLight(NEXUS.colors.accent, 0.4, 25); earthGroup.add(earthGlowLight); earthGroup.traverse(obj => { if (obj.isMesh || obj.isLine) obj.userData.zoomLabel = 'Planet Earth'; }); // Tether beam (function buildEarthTetherBeam() { const pts = [ new THREE.Vector3(0, EARTH_Y - EARTH_RADIUS * 1.15, 0), new THREE.Vector3(0, 0.5, 0), ]; const beamGeo = new THREE.BufferGeometry().setFromPoints(pts); const beamMat = new THREE.LineBasicMaterial({ color: NEXUS.colors.accent, transparent: true, opacity: 0.08, depthWrite: false, blending: THREE.AdditiveBlending, }); scene.add(new THREE.Line(beamGeo, beamMat)); })();