glm-5.2 Code
Model-specific source generated from the shared prompt shown on the gallery page. The prompt itself is intentionally not duplicated here.

JavaScript
src/main.js
1import './styles.css';2import * as THREE from 'three';3 4const MODEL_NAME = 'glm-5-2';5 6const app = document.querySelector('#app');7 8const state = {9 paused: false,10 waveHeight: 0.6,11 speed: 1.0,12 choppiness: 1.0,13 windAngle: 0,14 wireframe: false,15 theta: 0.0,16 phi: 0.32,17 radius: 26,18 autoOrbit: true,19};20 21function buildUI() {22 const panel = document.createElement('div');23 panel.className = 'panel';24 panel.innerHTML = `25 <div class="panel-header">26 <div class="identity">27 <span class="title">Ocean UI</span>28 <span class="meta" id="meta">model run: ${MODEL_NAME} · 0 fps</span>29 </div>30 <button class="collapse-btn" id="collapse" aria-label="Toggle controls">–</button>31 </div>32 <div class="panel-body">33 <div class="btn-group">34 <button class="btn primary" id="pause">Pause</button>35 <button class="btn" id="reset">Reset view</button>36 </div>37 <div class="row">38 <label>Wave height <span class="val" id="vWave">0.60</span></label>39 <input type="range" id="waveHeight" min="0" max="1.6" step="0.01" value="0.6" />40 </div>41 <div class="row">42 <label>Speed <span class="val" id="vSpeed">1.00</span></label>43 <input type="range" id="speed" min="0" max="2.5" step="0.01" value="1" />44 </div>45 <div class="row">46 <label>Choppiness <span class="val" id="vChop">1.00</span></label>47 <input type="range" id="choppiness" min="0.4" max="2.6" step="0.01" value="1" />48 </div>49 <div class="row">50 <label>Wind direction <span class="val" id="vWind">0°</span></label>51 <input type="range" id="windAngle" min="0" max="360" step="1" value="0" />52 </div>53 <div class="row">54 <label>Render mode</label>55 <div class="btn-group">56 <button class="btn active" id="modeSolid">Solid</button>57 <button class="btn" id="modeWire">Wireframe</button>58 </div>59 </div>60 <div class="hint">Drag to orbit · scroll/pinch to zoom · <kbd>Space</kbd> pause</div>61 </div>62 `;63 app.appendChild(panel);64 65 const $ = (id) => document.getElementById(id);66 67 const pauseBtn = $('pause');68 pauseBtn.addEventListener('click', () => {69 state.paused = !state.paused;70 pauseBtn.textContent = state.paused ? 'Resume' : 'Pause';71 pauseBtn.classList.toggle('paused', state.paused);72 });73 74 $('reset').addEventListener('click', () => {75 state.theta = 0;76 state.phi = 0.32;77 state.radius = 26;78 state.autoOrbit = true;79 });80 81 const linkSlider = (id, key, fmt, onChange) => {82 const el = $(id);83 const val = $('v' + id.charAt(0).toUpperCase() + id.slice(1));84 el.addEventListener('input', () => {85 state[key] = parseFloat(el.value);86 if (val) val.textContent = fmt(state[key]);87 if (onChange) onChange(state[key]);88 state.autoOrbit = false;89 });90 };91 linkSlider('waveHeight', 'waveHeight', (v) => v.toFixed(2), (v) => { oceanUniforms.uWaveHeight.value = v; });92 linkSlider('speed', 'speed', (v) => v.toFixed(2), (v) => { oceanUniforms.uSpeed.value = v; });93 linkSlider('choppiness', 'choppiness', (v) => v.toFixed(2), (v) => { oceanUniforms.uChoppiness.value = v; });94 const windEl = $('windAngle');95 const windVal = $('vWind');96 windEl.addEventListener('input', () => {97 state.windAngle = parseFloat(windEl.value);98 windVal.textContent = Math.round(state.windAngle) + '°';99 oceanUniforms.uWindAngle.value = state.windAngle * Math.PI / 180;100 state.autoOrbit = false;101 });102 103 const modeSolid = $('modeSolid');104 const modeWire = $('modeWire');105 modeSolid.addEventListener('click', () => {106 state.wireframe = false;107 oceanMat.wireframe = false;108 modeSolid.classList.add('active');109 modeWire.classList.remove('active');110 });111 modeWire.addEventListener('click', () => {112 state.wireframe = true;113 oceanMat.wireframe = true;114 modeWire.classList.add('active');115 modeSolid.classList.remove('active');116 });117 118 const collapse = $('collapse');119 collapse.addEventListener('click', () => {120 panel.classList.toggle('collapsed');121 collapse.textContent = panel.classList.contains('collapsed') ? '+' : '–';122 });123 124 window.addEventListener('keydown', (e) => {125 if (e.code === 'Space') {126 e.preventDefault();127 pauseBtn.click();128 }129 });130 131 return $('meta');132}133 134const scene = new THREE.Scene();135const horizonColor = new THREE.Color(0xbfe3f0);136scene.fog = new THREE.Fog(horizonColor, 45, 130);137 138const camera = new THREE.PerspectiveCamera(58, window.innerWidth / window.innerHeight, 0.1, 600);139camera.position.set(0, 8, 26);140camera.lookAt(0, 1.5, 0);141 142const renderer = new THREE.WebGLRenderer({ antialias: true });143renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));144renderer.setSize(window.innerWidth, window.innerHeight);145renderer.outputColorSpace = THREE.SRGBColorSpace;146app.appendChild(renderer.domElement);147 148const sunDir = new THREE.Vector3(0.55, 0.45, -0.35).normalize();149 150const skyUniforms = {151 top: { value: new THREE.Color(0x2a6fb0) },152 bottom: { value: new THREE.Color(0xddeef6) },153 sunDir: { value: sunDir.clone() },154 sunColor: { value: new THREE.Color(0xfff4d6) },155};156const sky = new THREE.Mesh(157 new THREE.SphereGeometry(300, 32, 16),158 new THREE.ShaderMaterial({159 side: THREE.BackSide,160 depthWrite: false,161 uniforms: skyUniforms,162 vertexShader: `163 varying vec3 vWorldPos;164 void main() {165 vec4 w = modelMatrix * vec4(position, 1.0);166 vWorldPos = w.xyz;167 gl_Position = projectionMatrix * viewMatrix * w;168 }169 `,170 fragmentShader: `171 varying vec3 vWorldPos;172 uniform vec3 top;173 uniform vec3 bottom;174 uniform vec3 sunDir;175 uniform vec3 sunColor;176 void main() {177 vec3 dir = normalize(vWorldPos);178 float h = clamp(dir.y * 0.5 + 0.5, 0.0, 1.0);179 vec3 col = mix(bottom, top, smoothstep(0.0, 1.0, h));180 float sd = max(dot(dir, normalize(sunDir)), 0.0);181 col += sunColor * pow(sd, 90.0) * 1.2;182 col += sunColor * pow(sd, 8.0) * 0.12;183 gl_FragColor = vec4(col, 1.0);184 }185 `,186 })187);188scene.add(sky);189 190const oceanUniforms = {191 uTime: { value: 0 },192 uWaveHeight: { value: state.waveHeight },193 uSpeed: { value: state.speed },194 uChoppiness: { value: state.choppiness },195 uWindAngle: { value: state.windAngle },196 uMaxAmp: { value: 2.53 },197 uSunDirection: { value: sunDir.clone() },198 uCameraPos: { value: new THREE.Vector3() },199 uDeepColor: { value: new THREE.Color(0x0a3d62) },200 uShallowColor: { value: new THREE.Color(0x2f93c4) },201 uSkyColor: { value: new THREE.Color(0xbfe3f0) },202 uHorizonColor: { value: horizonColor.clone() },203};204 205const oceanMat = new THREE.ShaderMaterial({206 uniforms: oceanUniforms,207 vertexShader: `208 uniform float uTime;209 uniform float uWaveHeight;210 uniform float uSpeed;211 uniform float uChoppiness;212 uniform float uWindAngle;213 uniform float uMaxAmp;214 varying vec3 vWorldPos;215 varying vec3 vNormal;216 varying float vElevation;217 218 vec2 rotateDir(vec2 d, float ang) {219 float ca = cos(ang), sa = sin(ang);220 return vec2(d.x * ca - d.y * sa, d.x * sa + d.y * ca);221 }222 223 void addWave(inout float h, inout vec2 g, vec2 p, float t, vec2 baseDir, float wavelength, float amplitude, float speedMul) {224 vec2 d = rotateDir(normalize(baseDir), uWindAngle);225 float k = 6.28318530718 / wavelength * uChoppiness;226 float phase = k * dot(d, p) + t * speedMul;227 h += amplitude * sin(phase);228 g += amplitude * k * d * cos(phase);229 }230 231 void main() {232 vec3 pos = position;233 float t = uTime * uSpeed;234 vec2 p = pos.xz;235 float h = 0.0;236 vec2 g = vec2(0.0);237 addWave(h, g, p, t, vec2(1.0, 0.6), 22.0, 1.0, 1.0);238 addWave(h, g, p, t, vec2(1.0, -0.5), 14.0, 0.65, 1.15);239 addWave(h, g, p, t, vec2(0.5, 1.0), 8.5, 0.4, 1.3);240 addWave(h, g, p, t, vec2(-0.6, 0.8), 5.0, 0.25, 1.5);241 addWave(h, g, p, t, vec2(0.9, 0.2), 3.0, 0.15, 1.8);242 addWave(h, g, p, t, vec2(-0.3, -0.9), 1.7, 0.08, 2.2);243 244 float disp = h * uWaveHeight;245 pos.y += disp;246 247 vec3 nLocal = vec3(-g.x * uWaveHeight, 1.0, -g.y * uWaveHeight);248 nLocal.y = max(nLocal.y, 0.25);249 nLocal = normalize(nLocal);250 251 vec4 worldPos = modelMatrix * vec4(pos, 1.0);252 vWorldPos = worldPos.xyz;253 vNormal = normalize(mat3(modelMatrix) * nLocal);254 vElevation = h / uMaxAmp;255 gl_Position = projectionMatrix * viewMatrix * worldPos;256 }257 `,258 fragmentShader: `259 uniform vec3 uSunDirection;260 uniform vec3 uCameraPos;261 uniform vec3 uDeepColor;262 uniform vec3 uShallowColor;263 uniform vec3 uSkyColor;264 uniform vec3 uHorizonColor;265 varying vec3 vWorldPos;266 varying vec3 vNormal;267 varying float vElevation;268 269 void main() {270 vec3 N = normalize(vNormal);271 vec3 V = normalize(uCameraPos - vWorldPos);272 vec3 L = normalize(uSunDirection);273 274 float depthMix = clamp(vElevation * 0.5 + 0.5, 0.0, 1.0);275 vec3 waterColor = mix(uDeepColor, uShallowColor, depthMix);276 277 float fresnel = pow(1.0 - max(dot(N, V), 0.0), 3.0);278 fresnel = clamp(fresnel, 0.0, 1.0);279 280 float diff = max(dot(N, L), 0.0);281 vec3 color = waterColor * (0.35 + 0.65 * diff);282 283 vec3 skyRefl = uSkyColor;284 color = mix(color, skyRefl, fresnel * 0.85);285 286 vec3 H = normalize(L + V);287 float spec = pow(max(dot(N, H), 0.0), 90.0);288 color += vec3(1.0, 0.96, 0.82) * spec * 1.6;289 290 float foam = smoothstep(0.72, 1.0, vElevation);291 color = mix(color, vec3(0.96, 0.98, 1.0), foam * 0.55);292 293 float dist = length(uCameraPos - vWorldPos);294 float fogF = smoothstep(45.0, 130.0, dist);295 color = mix(color, uHorizonColor, fogF);296 297 gl_FragColor = vec4(color, 1.0);298 }299 `,300});301 302const oceanGeo = new THREE.PlaneGeometry(260, 260, 240, 240);303oceanGeo.rotateX(-Math.PI / 2);304const ocean = new THREE.Mesh(oceanGeo, oceanMat);305scene.add(ocean);306 307const metaEl = buildUI();308 309const clock = new THREE.Clock();310let elapsed = 0;311let fpsEMA = 60;312let lastFpsUpdate = 0;313 314function updateCamera() {315 if (!state.paused && state.autoOrbit) {316 state.theta += 0.0014;317 }318 const r = state.radius;319 const phi = Math.min(Math.max(state.phi, 0.04), 1.35);320 camera.position.set(321 r * Math.cos(phi) * Math.sin(state.theta),322 r * Math.sin(phi) + 1.5,323 r * Math.cos(phi) * Math.cos(state.theta)324 );325 camera.lookAt(0, 1.5, 0);326 oceanUniforms.uCameraPos.value.copy(camera.position);327}328 329function animate() {330 requestAnimationFrame(animate);331 const dt = clock.getDelta();332 if (!state.paused) {333 elapsed += dt;334 oceanUniforms.uTime.value = elapsed;335 }336 updateCamera();337 renderer.render(scene, camera);338 339 const now = performance.now();340 if (dt > 0) fpsEMA = fpsEMA * 0.9 + (1 / dt) * 0.1;341 if (now - lastFpsUpdate > 250) {342 lastFpsUpdate = now;343 if (metaEl) {344 metaEl.textContent = `model run: ${MODEL_NAME} · ${Math.round(fpsEMA)} fps`;345 }346 }347}348animate();349 350window.addEventListener('resize', () => {351 camera.aspect = window.innerWidth / window.innerHeight;352 camera.updateProjectionMatrix();353 renderer.setSize(window.innerWidth, window.innerHeight);354});355 356const dom = renderer.domElement;357let dragging = false;358let lastX = 0;359let lastY = 0;360let pinchDist = 0;361 362dom.addEventListener('pointerdown', (e) => {363 dragging = true;364 lastX = e.clientX;365 lastY = e.clientY;366 state.autoOrbit = false;367 dom.setPointerCapture(e.pointerId);368});369dom.addEventListener('pointermove', (e) => {370 if (!dragging) return;371 const dx = e.clientX - lastX;372 const dy = e.clientY - lastY;373 lastX = e.clientX;374 lastY = e.clientY;375 state.theta -= dx * 0.005;376 state.phi += dy * 0.005;377 state.phi = Math.min(Math.max(state.phi, 0.04), 1.35);378});379dom.addEventListener('pointerup', (e) => {380 dragging = false;381 try { dom.releasePointerCapture(e.pointerId); } catch (_) {}382});383dom.addEventListener('pointercancel', () => { dragging = false; });384 385dom.addEventListener('wheel', (e) => {386 e.preventDefault();387 state.radius = Math.min(Math.max(state.radius + e.deltaY * 0.02, 12), 60);388 state.autoOrbit = false;389}, { passive: false });390 391dom.addEventListener('touchmove', (e) => {392 if (e.touches.length === 2) {393 e.preventDefault();394 const dx = e.touches[0].clientX - e.touches[1].clientX;395 const dy = e.touches[0].clientY - e.touches[1].clientY;396 const dist = Math.hypot(dx, dy);397 if (pinchDist > 0) {398 state.radius = Math.min(Math.max(state.radius - (dist - pinchDist) * 0.05, 12), 60);399 }400 pinchDist = dist;401 state.autoOrbit = false;402 }403}, { passive: false });404dom.addEventListener('touchend', () => { pinchDist = 0; });405
CSS
src/styles.css
1* {2 box-sizing: border-box;3}4 5html,6body,7#app {8 width: 100%;9 height: 100%;10 margin: 0;11}12 13body {14 font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;15 overflow: hidden;16 background: #0a3d62;17 color: #e8f4fb;18 -webkit-font-smoothing: antialiased;19}20 21#app {22 position: relative;23 overflow: hidden;24}25 26#app canvas {27 position: absolute;28 inset: 0;29 display: block;30 touch-action: none;31}32 33.panel {34 position: absolute;35 top: 16px;36 left: 16px;37 width: 280px;38 max-width: calc(100vw - 32px);39 padding: 14px 16px 16px;40 border-radius: 14px;41 background: rgba(10, 28, 44, 0.55);42 border: 1px solid rgba(160, 210, 240, 0.18);43 box-shadow: 0 10px 40px rgba(0, 0, 0, 0.35);44 backdrop-filter: blur(14px) saturate(120%);45 -webkit-backdrop-filter: blur(14px) saturate(120%);46 z-index: 10;47 user-select: none;48 display: flex;49 flex-direction: column;50 gap: 12px;51}52 53.panel.collapsed .panel-body {54 display: none;55}56 57.panel-header {58 display: flex;59 align-items: center;60 justify-content: space-between;61 gap: 8px;62}63 64.identity {65 display: flex;66 flex-direction: column;67 line-height: 1.25;68}69 70.identity .title {71 font-size: 14px;72 font-weight: 600;73 letter-spacing: 0.2px;74}75 76.identity .meta {77 font-size: 11px;78 color: #9fc7e0;79 font-variant-numeric: tabular-nums;80}81 82.collapse-btn {83 border: 1px solid rgba(160, 210, 240, 0.25);84 background: rgba(160, 210, 240, 0.08);85 color: #e8f4fb;86 width: 28px;87 height: 28px;88 border-radius: 8px;89 cursor: pointer;90 font-size: 15px;91 line-height: 1;92 display: flex;93 align-items: center;94 justify-content: center;95 transition: background 0.15s ease;96}97 98.collapse-btn:hover {99 background: rgba(160, 210, 240, 0.18);100}101 102.panel-body {103 display: flex;104 flex-direction: column;105 gap: 12px;106}107 108.row {109 display: flex;110 flex-direction: column;111 gap: 6px;112}113 114.row label {115 font-size: 11px;116 text-transform: uppercase;117 letter-spacing: 0.6px;118 color: #9fc7e0;119 display: flex;120 justify-content: space-between;121}122 123.row label .val {124 color: #e8f4fb;125 font-variant-numeric: tabular-nums;126 text-transform: none;127 letter-spacing: 0;128}129 130input[type="range"] {131 -webkit-appearance: none;132 appearance: none;133 width: 100%;134 height: 4px;135 border-radius: 4px;136 background: rgba(160, 210, 240, 0.2);137 outline: none;138 cursor: pointer;139}140 141input[type="range"]::-webkit-slider-thumb {142 -webkit-appearance: none;143 appearance: none;144 width: 16px;145 height: 16px;146 border-radius: 50%;147 background: #6fc1ee;148 border: 2px solid #0a3d62;149 box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);150}151 152input[type="range"]::-moz-range-thumb {153 width: 16px;154 height: 16px;155 border-radius: 50%;156 background: #6fc1ee;157 border: 2px solid #0a3d62;158 box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);159}160 161.btn-group {162 display: flex;163 gap: 8px;164}165 166.btn {167 flex: 1;168 padding: 8px 10px;169 border-radius: 9px;170 border: 1px solid rgba(160, 210, 240, 0.22);171 background: rgba(160, 210, 240, 0.08);172 color: #e8f4fb;173 font-size: 12px;174 font-weight: 500;175 cursor: pointer;176 transition: background 0.15s ease, border-color 0.15s ease;177}178 179.btn:hover {180 background: rgba(160, 210, 240, 0.16);181}182 183.btn.active {184 background: #2a8fbd;185 border-color: #6fc1ee;186 color: #fff;187}188 189.btn.primary {190 background: #2a8fbd;191 border-color: #6fc1ee;192 color: #fff;193}194 195.btn.primary.paused {196 background: #c66a3d;197 border-color: #e8a070;198}199 200.hint {201 font-size: 11px;202 color: #8fb4cf;203 line-height: 1.4;204}205 206.hint kbd {207 background: rgba(160, 210, 240, 0.15);208 border: 1px solid rgba(160, 210, 240, 0.25);209 border-radius: 4px;210 padding: 0 4px;211 font-family: inherit;212 font-size: 10px;213}214 215@media (max-width: 600px) {216 .panel {217 top: auto;218 bottom: 12px;219 left: 12px;220 right: 12px;221 width: auto;222 max-width: none;223 padding: 12px 14px 14px;224 }225}226
Package
package.json
1{2 "name": "ocean-ui-model-run",3 "version": "0.1.0",4 "private": true,5 "type": "module",6 "scripts": {7 "dev": "vite --host 127.0.0.1",8 "build": "vite build",9 "preview": "vite preview --host 127.0.0.1"10 },11 "dependencies": {12 "three": "^0.171.0",13 "vite": "^6.0.7"14 },15 "devDependencies": {}16}17