fuzzyworld

nordic-skating / local-littlejs / files / src / render / sky-littlejs.ts

Branch local-littlejs · default branch · all branches

sky-littlejs.ts

9.0 KB · history · blame

1/**
2 * LittleJS implementation of the sky system
3 * Converts the Three.js shader-based sky to LittleJS equivalent
4 */
5
6import type { Look } from './sky.js'
7import { skyOf } from './sky.js'
8import * as THREE from 'three'
9import * as LITTLEJS from 'littlejsengine'
10
11/**
12 * LittleJS implementation of the sky system using a custom shader
13 */
14export class SkyLittleJS {
15 private uniforms: Record<string, any>
16 private planeFrom = 0
17 private planeSpeed = 0
18 private shader: LITTLEJS.Shader | null = null
19
20 constructor() {
21 // Setup uniforms with initial values
22 this.uniforms = {
23 uTop: [0, 0, 0],
24 uHorizon: [0, 0, 0],
25 uStars: 0,
26 uMilkyWay: 0,
27 uAurora: 0,
28 uTime: 0,
29 uMeteors: 0,
30 uComet: [0, 0, 0, 0],
31 uCometTail: [0, 1, 0],
32 uPlane: [0, 0, 0, 0],
33 uPlaneAt: 0,
34 uTrail: [0, 0, 0],
35 uOvercast: 0
36 }
37
38 // Create shader for sky rendering
39 this.createShader()
40 }
41
42 /**
43 * Create the custom shader for sky rendering
44 */
45 private createShader(): void {
46 const fragmentCode = /* glsl */ `
47 uniform vec3 uTop;
48 uniform vec3 uHorizon;
49 uniform float uStars;
50 uniform float uMilkyWay;
51 uniform float uAurora;
52 uniform float uTime;
53 uniform float uMeteors;
54 uniform vec4 uComet;
55 uniform vec3 uCometTail;
56 uniform vec4 uPlane;
57 uniform float uPlaneAt;
58 uniform vec3 uTrail;
59 uniform float uOvercast;
60
61 float hash(vec3 p) {
62 p = fract(p * 0.3183099 + 0.1);
63 p *= 17.0;
64 return fract(p.x * p.y * p.z * (p.x + p.y + p.z));
65 }
66 float noise(vec3 x) {
67 vec3 i = floor(x);
68 vec3 f = fract(x);
69 f = f * f * (3.0 - 2.0 * f);
70 return mix(
71 mix(mix(hash(i), hash(i + vec3(1, 0, 0)), f.x), mix(hash(i + vec3(0, 1, 0)), hash(i + vec3(1, 1, 0)), f.x), f.y),
72 mix(mix(hash(i + vec3(0, 0, 1)), hash(i + vec3(1, 0, 1)), f.x), mix(hash(i + vec3(0, 1, 1)), hash(i + vec3(1, 1, 1)), f.x), f.y),
73 f.z);
74 }
75 float fbm(vec3 p) {
76 float sum = 0.0;
77 float gain = 0.5;
78 for (int i = 0; i < 4; i++) {
79 sum += gain * noise(p);
80 p *= 2.03;
81 gain *= 0.5;
82 }
83 return sum;
84 }
85
86 float stars(vec3 d, float cells, float share, float size) {
87 vec3 p = d * cells;
88 vec3 cell = floor(p);
89 float h = hash(cell);
90 vec3 off = vec3(hash(cell + 7.0), hash(cell + 13.0), hash(cell + 29.0)) - 0.5;
91 float twinkle = 0.8 + 0.2 * sin(uTime * (1.5 + 3.0 * h) + h * 40.0);
92 return step(1.0 - share, h) * smoothstep(size, 0.0, length(fract(p) - 0.5 - off * 0.6)) * (0.3 + 0.7 * fract(h * 91.7)) * twinkle;
93 }
94
95 float meteor(vec3 d, float which) {
96 float turn = uTime / 6.0 + which * 0.37;
97 float n = floor(turn);
98 float t = fract(turn) * 6.0;
99 if (hash(vec3(n, which, 7.0)) > 0.2 || t > 0.9) return 0.0;
100 float round = hash(vec3(n, which, 1.0)) * 6.2832;
101 float high = 0.35 + 0.9 * hash(vec3(n, which, 2.0));
102 vec3 from = vec3(cos(high) * sin(round), sin(high), cos(high) * cos(round));
103 vec3 any = normalize(vec3(hash(vec3(n, which, 3.0)) - 0.5, -0.6, hash(vec3(n, which, 4.0)) - 0.5));
104 vec3 way = normalize(any - from * dot(any, from));
105 float gone = t / 0.9;
106 vec3 head = normalize(from + way * 0.35 * gone);
107 vec3 tail = normalize(from + way * 0.35 * max(0.0, gone - 0.35));
108 vec3 line = head - tail;
109 float along = clamp(dot(d - tail, line) / dot(line, line), 0.0, 1.0);
110 return smoothstep(0.004, 0.0, length(d - (tail + line * along))) * along * sin(3.1416 * gone);
111 }
112
113 void mainImage(out vec4 c, vec2 uv) {
114 // In LittleJS, we need to convert UV coordinates to a direction vector
115 // This is a simplified approach - in a real implementation, we'd use the 3D rendering API
116 vec3 d = normalize(vec3(uv * 2.0 - 1.0, 1.0));
117 float up = d.y;
118 vec3 colour = mix(uHorizon, uTop, pow(clamp(up, 0.0, 1.0), 0.45));
119 float clear = smoothstep(0.02, 0.3, up);
120
121 float band = exp(-pow(dot(d, normalize(vec3(0.75, 0.35, 0.56))), 2.0) / 0.022);
122 float cloud = fbm(d * 9.0) * (0.6 + 0.8 * fbm(d * 2.5 + 11.0));
123 colour += uMilkyWay * clear * band * cloud * cloud * 1.1 * vec3(0.32, 0.36, 0.5);
124 float share = 0.09 + 0.3 * band * uMilkyWay;
125 colour += uStars * clear * vec3(0.95, 0.97, 1.0)
126 * (stars(d, 60.0, share, 0.11) + 0.6 * stars(d, 110.0, share * 1.3, 0.14));
127
128 if (uAurora > 0.0 && up > 0.02) {
129 vec2 p = d.xz / (up + 0.15) * 1.2;
130 float warp = fbm(vec3(p * 0.5, uTime * 0.03));
131 float curtain = pow(1.0 - abs(sin(p.y * 1.1 + p.x * 0.25 + warp * 5.0)), 7.0);
132 float rays = 0.45 + 0.55 * noise(vec3(p.x * 7.0 + warp * 3.0, p.y * 0.7, uTime * 0.25));
133 float where = smoothstep(0.03, 0.16, up) * smoothstep(0.85, 0.3, up);
134 vec3 hue = mix(vec3(0.15, 1.0, 0.45), vec3(0.55, 0.3, 1.0), smoothstep(0.25, 0.75, up));
135 colour += uAurora * where * (curtain * rays * 1.1 * hue + 0.025 * vec3(0.1, 0.8, 0.4));
136 }
137
138 if (uMeteors > 0.0) colour += uMeteors * clear * (meteor(d, 0.0) + meteor(d, 1.0)) * vec3(1.0, 0.97, 0.9);
139 if (uComet.w > 0.0) {
140 vec3 off = d - uComet.xyz;
141 float along = dot(off, uCometTail);
142 float across = length(off - uCometTail * along);
143 float width = 0.008 + 0.2 * max(along, 0.0);
144 float tail = step(0.0, along) * exp(-across * across / (width * width)) * exp(-along / 0.16);
145 colour += uComet.w * clear * (1.3 * exp(-dot(off, off) / 0.00005) + 0.5 * tail) * vec3(0.75, 0.86, 1.0);
146 }
147
148 if (uPlane.w > 0.0) {
149 vec3 level = normalize(cross(uPlane.xyz, vec3(0.0, 1.0, 0.0)));
150 vec3 over = cross(level, uPlane.xyz);
151 float behind = uPlaneAt - atan(dot(d, over), dot(d, level));
152 float age = behind / 1.1;
153 if (behind > 0.0 && age < 1.0) {
154 float width = 0.0015 + 0.012 * age;
155 float trail = smoothstep(width, width * 0.3, abs(dot(d, uPlane.xyz))) * (1.0 - age) * smoothstep(0.0, 0.02, behind);
156 colour = mix(colour, uTrail, uPlane.w * clear * trail * (0.55 + 0.25 * noise(d * 90.0)));
157 }
158 colour += uPlane.w * clear * smoothstep(0.0035, 0.0, length(d - (level * cos(uPlaneAt) + over * sin(uPlaneAt))));
159 }
160
161 colour = mix(colour, uHorizon, uOvercast);
162 c.rgb = colour;
163 c.a = 1.0;
164 }
165 `
166
167 try {
168 this.shader = new LITTLEJS.Shader(fragmentCode)
169 } catch (error) {
170 console.error('Failed to create shader:', error)
171 // Fallback to a simple color if shader fails
172 this.shader = null
173 }
174 }
175
176 /**
177 * Show the sky with the given look and configuration
178 */
179 show(look: Look, seed: number, overcast: number): void {
180 const things = skyOf(seed)
181
182 // Convert colors from Three.js format to LittleJS (RGB values 0-1)
183 this.uniforms.uTop = [new THREE.Color(look.top).r, new THREE.Color(look.top).g, new THREE.Color(look.top).b]
184 this.uniforms.uHorizon = [new THREE.Color(look.horizon).r, new THREE.Color(look.horizon).g, new THREE.Color(look.horizon).b]
185 this.uniforms.uStars = look.stars
186 this.uniforms.uMilkyWay = look.milkyWay
187 this.uniforms.uAurora = look.aurora
188 this.uniforms.uOvercast = overcast
189 this.uniforms.uMeteors = look.meteors ? 1 : 0
190
191 // Set comet properties (as arrays for GLSL uniforms)
192 this.uniforms.uComet = [...things.comet, look.meteors && things.hasComet ? 1 : 0]
193 this.uniforms.uCometTail = things.cometTail
194
195 // Set plane properties
196 this.uniforms.uPlane = [...things.planePath, look.planes && things.hasPlane ? 1 : 0]
197 this.uniforms.uTrail = [new THREE.Color(look.trail).r, new THREE.Color(look.trail).g, new THREE.Color(look.trail).b]
198
199 this.planeFrom = things.planeFrom
200 this.planeSpeed = things.planeSpeed
201 }
202
203 /**
204 * Update the sky animation based on time
205 */
206 update(seconds: number): void {
207 this.uniforms.uTime = seconds
208 // Over the far horizon, a pause, and then another comes up over the near one.
209 this.uniforms.uPlaneAt = ((this.planeFrom + seconds * this.planeSpeed) % (Math.PI + 2.6)) - 0.1
210 }
211
212 /**
213 * Render the sky using LittleJS's shader API
214 */
215 render(): void {
216 if (!LITTLEJS.render3D || !this.shader) return
217
218 // Use the shader for rendering
219 const oldShader = LITTLEJS.render3D.shader
220 LITTLEJS.render3D.shader = this.shader
221
222 try {
223 // Draw a fullscreen quad representing the sky
224 LITTLEJS.render3D.drawQuad(
225 new LITTLEJS.Vector3(0, 0, -1),
226 new LITTLEJS.Vector3(1000, 0, -1),
227 new LITTLEJS.Vector3(1000, 1000, -1),
228 new LITTLEJS.Vector3(0, 1000, -1)
229 )
230 } finally {
231 // Restore the previous shader
232 LITTLEJS.render3D.shader = oldShader
233 }
234 }
235
236 /**
237 * Clean up resources
238 */
239 dispose(): void {
240 if (this.shader) {
241 this.shader.dispose()
242 this.shader = null
243 }
244 }
245}