import * as THREE from 'three'; import type { SceneResourceBag } from '@/scene/resources'; import type { MutableChapterPalette } from './palette'; /** Thin, spatially separated air masses. No screen-sized render targets. */ export function createHorizonAir(resources: SceneResourceBag) { const group = new THREE.Group(); group.name = 'horizon-air-masses'; const uniforms = { uTime: { value: 0 }, uWarmth: { value: 0 }, uDawn: { value: 0 }, uColor: { value: new THREE.Color(0xc5646b) }, }; const material = resources.own( 'material', new THREE.ShaderMaterial({ uniforms, vertexShader: ` varying vec2 vUv; void main(){vUv=uv;gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);} `, fragmentShader: ` uniform float uTime; uniform float uWarmth; uniform float uDawn; uniform vec3 uColor; varying vec2 vUv; void main(){ float wave=sin(vUv.x*12.0+uTime*0.018)*0.055+sin(vUv.x*31.0-uTime*0.026)*0.018; float ribbon=exp(-pow((vUv.y-0.45-wave)*5.6,2.0)); float thin=exp(-pow((vUv.y-0.51-wave)*37.0,2.0)); float horizontal=pow(max(0.0,sin(vUv.x*3.14159)),2.0); float colorWave=0.5+0.5*sin(vUv.x*7.0+uTime*0.035); vec3 color=mix(uColor,vec3(0.27,0.045,0.75),colorWave*0.55); color=mix(color,vec3(0.05,0.52,0.75),smoothstep(0.54,0.82,vUv.y)*0.3); gl_FragColor=vec4(color,(ribbon*0.16+thin*0.032)*horizontal*(0.7+uWarmth*0.3)*(1.0+uDawn)); #include #include } `, transparent: true, depthWrite: false, depthTest: true, blending: THREE.AdditiveBlending, }), ); const geometry = resources.own('geometry', new THREE.PlaneGeometry(1450, 95)); for (let i = 0; i < 3; i++) { const mesh = new THREE.Mesh(geometry, material); mesh.name = `stratified-air-${i + 1}`; mesh.position.set((i - 1) * 85, 15 + i * 11, -500 - i * 70); mesh.scale.set(1 - i * 0.12, 1 - i * 0.18, 1); group.add(mesh); } return { group, /** dawn: END OF SHIFT, the air masses catch first light (gain up to 2x). */ update(palette: Readonly, time: number, dawn = 0) { uniforms.uTime.value = time; uniforms.uDawn.value = Number.isFinite(dawn) ? Math.min(1, Math.max(0, dawn)) : 0; uniforms.uWarmth.value = palette.warmth; uniforms.uColor.value.copy(palette.sun); }, }; }