PageSourceSearch

https://aymuos15.github.io/js/theme-earth.js

js aymuos15.github.io collected 2026-10-03 08:41:07 UTC 4,176 bytes, 140 lines download raw bytes

1// Rotating Earth ornament for the Gallery section's theme toggle.
2// A WebGL take on the three.js webgpu_tsl_earth example: a day-textured
3// globe whose dark side lights up with city lights, wrapped in a thin
4// cloud layer and a soft atmospheric rim. Rendered into the .theme-earth
5// canvas inside the theme button, so clicking it still toggles the theme
6// (handled by theme.js). Runs only while Gallery is the active section.
7import * as THREE from "https://esm.sh/[email protected]";
8
9const canvas = document.querySelector(".theme-earth");
10
11// Served from the three.js repo (the npm package omits example textures).
12const TEX =
13  "https://cdn.jsdelivr.net/gh/mrdoob/three.js@r169/examples/textures/planets/";
14
15let renderer, scene, camera, earth, clouds;
16let started = false;
17let running = false;
18
19// Day/night shader: blend the daytime map into the night-lights map
20// across the terminator, driven by the sun direction.
21const earthVertex = /* glsl */ `
22    varying vec2 vUv;
23    varying vec3 vNormal;
24    void main() {
25        vUv = uv;
26        vNormal = normalize( normalMatrix * normal );
27        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
28    }
29`;
30
31const earthFragment = /* glsl */ `
32    uniform sampler2D dayMap;
33    uniform sampler2D nightMap;
34    uniform vec3 sunDirection;
35    varying vec2 vUv;
36    varying vec3 vNormal;
37    void main() {
38        float intensity = dot( normalize( vNormal ), normalize( sunDirection ) );
39        float mixAmount = smoothstep( -0.3, 0.2, intensity );
40        vec3 day = texture2D( dayMap, vUv ).rgb * 1.2;
41        vec3 night = texture2D( nightMap, vUv ).rgb * 1.7;
42        vec3 color = mix( night, day, mixAmount );
43        color += 0.06; // gentle overall lift
44        // Subtle rim / atmosphere glow toward the edges.
45        float rim = pow( 1.0 - max( dot( normalize( vNormal ), vec3( 0.0, 0.0, 1.0 ) ), 0.0 ), 3.0 );
46        color += vec3( 0.25, 0.4, 0.7 ) * rim * 0.6;
47        gl_FragColor = vec4( color, 1.0 );
48    }
49`;
50
51function build() {
52  renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, canvas });
53  renderer.setPixelRatio(window.devicePixelRatio);
54  renderer.setSize(canvas.width, canvas.height, false);
55
56  scene = new THREE.Scene();
57
58  camera = new THREE.PerspectiveCamera(40, 1, 0.1, 100);
59  camera.position.set(0, 0, 3);
60  camera.lookAt(0, 0, 0);
61
62  const loader = new THREE.TextureLoader();
63  loader.setCrossOrigin("anonymous");
64  const dayMap = loader.load(`${TEX}earth_atmos_2048.jpg`);
65  const nightMap = loader.load(`${TEX}earth_lights_2048.png`);
66  const cloudMap = loader.load(`${TEX}earth_clouds_1024.png`);
67  dayMap.colorSpace = THREE.SRGBColorSpace;
68  nightMap.colorSpace = THREE.SRGBColorSpace;
69
70  const geometry = new THREE.SphereGeometry(1, 48, 48);
71
72  const material = new THREE.ShaderMaterial({
73    fragmentShader: earthFragment,
74    uniforms: {
75      dayMap: { value: dayMap },
76      nightMap: { value: nightMap },
77      sunDirection: { value: new THREE.Vector3(1, 0.3, 1).normalize() },
78    },
79    vertexShader: earthVertex,
80  });
81
82  earth = new THREE.Mesh(geometry, material);
83  earth.rotation.z = 0.41; // ~23.5° axial tilt
84  scene.add(earth);
85
86  clouds = new THREE.Mesh(
87    new THREE.SphereGeometry(1.015, 48, 48),
88    new THREE.MeshBasicMaterial({
89      depthWrite: false,
90      map: cloudMap,
91      opacity: 0.55,
92      transparent: true,
93    })
94  );
95  earth.add(clouds);
96}
97
98function tick() {
99  if (!running) {
100    return;
101  }
102  earth.rotation.y += 0.0025;
103  clouds.rotation.y += 0.0006;
104  renderer.render(scene, camera);
105  requestAnimationFrame(tick);
106}
107
108function start() {
109  if (!started) {
110    build();
111    started = true;
112  }
113  if (running) {
114    return;
115  }
116  running = true;
117  requestAnimationFrame(tick);
118}
119
120function stop() {
121  running = false;
122}
123
124// Run the WebGL loop only while the Gallery section is showing.
125function sync() {
126  if (document.documentElement.getAttribute("data-section") === "gallery") {
127    start();
128  } else {
129    stop();
130  }
131}
132
133if (canvas) {
134  const observer = new MutationObserver(sync);
135  observer.observe(document.documentElement, {
136    attributeFilter: ["data-section"],
137    attributes: true,
138  });
139  sync();
140}

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.