PageSourceSearch

https://aymuos15.github.io/js/research-ocean-bg.js

js aymuos15.github.io collected 2026-10-03 08:41:10 UTC 5,379 bytes, 178 lines download raw bytes

1// WebGL ocean background for the Segmentation research card.
2// Adapted from the three.js webgl_shaders_ocean example (classic Water + Sky on
3// the WebGL renderer). We deliberately avoid the WebGPU renderer here: pairing
4// WebGPURenderer with the example WaterMesh/SkyMesh pulls in a second Three.js
5// instance whose node materials never bind to the renderer, and on many
6// GPU/driver combos (e.g. Mesa Intel) WebGPU reports an adapter yet paints
7// nothing — so the card silently came up blank. Plain WebGL is the same
8// single-instance path the Instance Metrics card already uses reliably.
9// The camera slowly orbits so the water and sky drift behind the card content.
10// Renders only while the Research section is showing, and holds still under
11// reduced motion.
12import * as THREE from "https://esm.sh/[email protected]";
13import { Sky } from "https://esm.sh/[email protected]/examples/jsm/objects/Sky.js";
14import { Water } from "https://esm.sh/[email protected]/examples/jsm/objects/Water.js";
15
16const card = document.querySelector('.research-card[data-id="segmentation"]');
17const WATER_NORMALS_URL =
18  "https://cdn.jsdelivr.net/gh/mrdoob/three.js@r169/examples/textures/waternormals.jpg";
19
20const reduceMotion = window.matchMedia(
21  "(prefers-reduced-motion: reduce)"
22).matches;
23
24let renderer,
25  scene,
26  camera,
27  water,
28  running = false,
29  ready = false;
30
31function inResearch() {
32  return (
33    document.documentElement.getAttribute("data-section") === "research" ||
34    document.querySelector("#research.section.active") !== null
35  );
36}
37
38let sizedW = 0,
39  sizedH = 0;
40
41function sizeToCard() {
42  const w = Math.max(1, card.clientWidth);
43  const h = Math.max(1, card.clientHeight);
44  if (w === sizedW && h === sizedH) {
45    return;
46  }
47  sizedW = w;
48  sizedH = h;
49  renderer.setSize(w, h, false);
50  camera.aspect = w / h;
51  camera.updateProjectionMatrix();
52}
53
54function init(canvas) {
55  renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true, canvas });
56  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
57  renderer.toneMapping = THREE.ACESFilmicToneMapping;
58  renderer.toneMappingExposure = 0.35;
59
60  scene = new THREE.Scene();
61  camera = new THREE.PerspectiveCamera(55, 1, 1, 20_000);
62
63  const sun = new THREE.Vector3();
64
65  // Water
66  const waterNormals = new THREE.TextureLoader().load(WATER_NORMALS_URL);
67  waterNormals.wrapS = THREE.RepeatWrapping;
68  waterNormals.wrapT = THREE.RepeatWrapping;
69
70  water = new Water(new THREE.PlaneGeometry(10_000, 10_000), {
71    distortionScale: 3.7,
72    fog: false,
73    sunColor: 0xff_ff_ff,
74    sunDirection: new THREE.Vector3(),
75    textureHeight: 512,
76    textureWidth: 512,
77    waterColor: 0x00_1e_2f,
78    waterNormals,
79  });
80  water.rotation.x = -Math.PI / 2;
81  scene.add(water);
82
83  // Sky
84  const sky = new Sky();
85  sky.scale.setScalar(10_000);
86  scene.add(sky);
87  const skyU = sky.material.uniforms;
88  skyU.turbidity.value = 10;
89  skyU.rayleigh.value = 2;
90  skyU.mieCoefficient.value = 0.005;
91  skyU.mieDirectionalG.value = 0.8;
92
93  const elevation = 3,
94    azimuth = 175;
95  const pmrem = new THREE.PMREMGenerator(renderer);
96  const sceneEnv = new THREE.Scene();
97  let renderTarget;
98
99  function updateSun() {
100    const phi = THREE.MathUtils.degToRad(90 - elevation);
101    const theta = THREE.MathUtils.degToRad(azimuth);
102    sun.setFromSphericalCoords(1, phi, theta);
103    skyU.sunPosition.value.copy(sun);
104    water.material.uniforms.sunDirection.value.copy(sun).normalize();
105
106    if (renderTarget) {
107      renderTarget.dispose();
108    }
109    sceneEnv.add(sky);
110    renderTarget = pmrem.fromScene(sceneEnv);
111    scene.add(sky);
112    scene.environment = renderTarget.texture;
113  }
114
115  updateSun();
116  ready = true;
117  sizeToCard();
118  // First paint runs at the card's tiny home-section size; don't let a hiccup
119  // here abort init (and with it the section-switch observer set up below).
120  try {
121    renderer.render(scene, camera);
122  } catch {
123    /* the loop paints properly once Research is shown */
124  }
125  ensureRunning();
126
127  window.addEventListener("resize", () => {
128    if (ready) {
129      sizeToCard();
130    }
131  });
132}
133
134function frame() {
135  if (!(ready && inResearch())) {
136    running = false;
137    return;
138  }
139  sizeToCard(); // card has real dimensions once its section is visible
140  const t = performance.now() * 0.001;
141  const ang = reduceMotion ? 2.6 : t * 0.04;
142  const radius = 120;
143  camera.position.set(Math.cos(ang) * radius, 22, Math.sin(ang) * radius);
144  camera.lookAt(0, 6, 0);
145  water.material.uniforms.time.value = reduceMotion ? 0 : t;
146  renderer.render(scene, camera);
147  if (reduceMotion) {
148    running = false;
149    return;
150  }
151  requestAnimationFrame(frame);
152}
153
154function ensureRunning() {
155  if (running || !ready || !inResearch()) {
156    return;
157  }
158  running = true;
159  requestAnimationFrame(frame);
160}
161
162if (card) {
163  const canvas = document.createElement("canvas");
164  canvas.className = "research-card-bg-canvas research-card-ocean";
165  canvas.setAttribute("aria-hidden", "true");
166  card.insertBefore(canvas, card.firstChild);
167  card.classList.add("research-card--bg");
168
169  // Register the section-switch observer *before* init so the render loop can
170  // still be kicked off even if init throws partway through.
171  const observer = new MutationObserver(ensureRunning);
172  observer.observe(document.documentElement, {
173    attributeFilter: ["data-section"],
174    attributes: true,
175  });
176
177  init(canvas);
178}

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.