terminallylazy commited on
Commit
45bc605
·
verified ·
1 Parent(s): 2a91a6b

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +766 -19
index.html CHANGED
@@ -1,19 +1,766 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,viewport-fit=cover" />
6
+ <title>Space-X Debris Explosion Shader</title>
7
+ <style>
8
+ :root {
9
+ color-scheme: dark;
10
+ }
11
+ html, body {
12
+ margin: 0;
13
+ height: 100%;
14
+ background: #000;
15
+ overflow: hidden;
16
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji";
17
+ }
18
+ #wrap {
19
+ position: relative;
20
+ width: 100vw;
21
+ height: 100vh;
22
+ background: radial-gradient(1200px 800px at 50% 60%, #04060a 0%, #020307 55%, #010205 75%, #000 100%);
23
+ }
24
+ canvas {
25
+ position: absolute;
26
+ inset: 0;
27
+ width: 100%;
28
+ height: 100%;
29
+ display: block;
30
+ }
31
+ #overlay {
32
+ position: absolute;
33
+ top: 12px;
34
+ left: 12px;
35
+ right: 12px;
36
+ display: flex;
37
+ justify-content: space-between;
38
+ align-items: center;
39
+ gap: 12px;
40
+ z-index: 10;
41
+ pointer-events: none;
42
+ }
43
+ #title {
44
+ font-size: clamp(12px, 2vw, 16px);
45
+ letter-spacing: .08em;
46
+ text-transform: uppercase;
47
+ opacity: .8;
48
+ color: #a9b7d1;
49
+ pointer-events: auto;
50
+ }
51
+ #title a {
52
+ color: #9ad4ff;
53
+ text-decoration: none;
54
+ border-bottom: 1px dashed rgba(154,212,255,.35);
55
+ }
56
+ #controls {
57
+ pointer-events: auto;
58
+ display: flex;
59
+ gap: 8px;
60
+ }
61
+ button {
62
+ background: rgba(255,255,255,.06);
63
+ color: #d9e3ff;
64
+ border: 1px solid rgba(255,255,255,.12);
65
+ border-radius: 10px;
66
+ padding: 8px 12px;
67
+ font-size: 12px;
68
+ cursor: pointer;
69
+ backdrop-filter: blur(6px);
70
+ transition: all .2s ease;
71
+ }
72
+ button:hover {
73
+ background: rgba(255,255,255,.1);
74
+ transform: translateY(-1px);
75
+ }
76
+ #hint {
77
+ position: absolute;
78
+ bottom: 12px;
79
+ right: 12px;
80
+ color: #b9c6e6;
81
+ opacity: .65;
82
+ font-size: 12px;
83
+ padding: 6px 10px;
84
+ border: 1px solid rgba(255,255,255,.08);
85
+ border-radius: 8px;
86
+ background: rgba(0,0,0,.25);
87
+ backdrop-filter: blur(6px);
88
+ pointer-events: none;
89
+ }
90
+ </style>
91
+ </head>
92
+ <body>
93
+ <div id="wrap">
94
+ <canvas id="gl"></canvas>
95
+ <div id="overlay">
96
+ <div id="title">
97
+ Debris After a Space-X Rocket Explosion
98
+ &nbsp;|&nbsp; Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" rel="noopener noreferrer">anycoder</a>
99
+ </div>
100
+ <div id="controls">
101
+ <button id="btn-explode" title="Spawn a new explosion (E)">Explode</button>
102
+ <button id="btn-toggle" title="Pause/Resume (Space)">Pause</button>
103
+ <button id="btn-reset" title="Reset particles (R)">Reset</button>
104
+ </div>
105
+ </div>
106
+ <div id="hint">Click or press E to explode • Space to pause • R to reset</div>
107
+ </div>
108
+
109
+ <script>
110
+ (function(){
111
+ const canvas = document.getElementById('gl');
112
+ const gl = canvas.getContext('webgl', { antialias: false, alpha: false, preserveDrawingBuffer: false });
113
+ if (!gl) {
114
+ alert('WebGL not supported on this device/browser.');
115
+ return;
116
+ }
117
+
118
+ // Globals
119
+ const DPR = Math.min(2, window.devicePixelRatio || 1);
120
+ let W = 0, H = 0;
121
+ let time = 0, lastT = 0, paused = false;
122
+
123
+ // Explosion and physics
124
+ const G = 0.10; // "gravity" in world units per second^2 (scaled)
125
+ const DRAG = 0.995; // velocity damping per frame
126
+ const MAX_PARTICLES = 14000;
127
+ const MIN_PARTICLES = 4000;
128
+ let PARTICLE_COUNT = MAX_PARTICLES;
129
+
130
+ // Explosion origin in world coordinates (will be recentered on resize)
131
+ let origin = { x: 0, y: 0 };
132
+
133
+ // Particles data
134
+ let pLife, pPos, pVel, pSize, pSeed;
135
+
136
+ // Stars
137
+ let STAR_COUNT = 600;
138
+ let starPos, starPhase, starSize;
139
+
140
+ // Shockwave
141
+ let shockActive = false;
142
+ let shockStart = 0;
143
+ let shockDuration = 6.0;
144
+ let shockMaxRadius = 0.0;
145
+ let shockThickness = 0.06;
146
+
147
+ // Trail FBO
148
+ let trailFbo = null;
149
+ let trailTex = null;
150
+
151
+ // Utility
152
+ const rand = (a=0,b=1)=>a+Math.random()*(b-a);
153
+ const clamp = (x, a, b)=>Math.max(a, Math.min(b, x));
154
+ const mix = (a,b,t)=>a*(1-t)+b*t;
155
+
156
+ // Shaders
157
+ const particleVS = `
158
+ attribute vec2 a_pos;
159
+ attribute vec2 a_vel;
160
+ attribute float a_size;
161
+ attribute float a_seed;
162
+
163
+ uniform vec2 u_origin;
164
+ uniform vec2 u_scale;
165
+ uniform float u_aspect;
166
+ uniform float u_time;
167
+
168
+ varying float v_speed;
169
+ varying float v_seed;
170
+ varying float v_size;
171
+
172
+ void main() {
173
+ vec2 dir = normalize(a_vel + vec2(1e-6));
174
+ float speed = length(a_vel);
175
+ v_speed = speed;
176
+ v_seed = a_seed;
177
+ v_size = a_size;
178
+
179
+ vec2 world = a_pos;
180
+ vec2 clip = (world - u_origin) * u_scale;
181
+ gl_Position = vec4(clip, 0.0, 1.0);
182
+ gl_PointSize = a_size;
183
+ }
184
+ `;
185
+
186
+ const particleFS = `
187
+ precision mediump float;
188
+
189
+ uniform float u_time;
190
+
191
+ varying float v_speed;
192
+ varying float v_seed;
193
+ varying float v_size;
194
+
195
+ // 2D rotation
196
+ mat2 rot(float a){ float c=cos(a), s=sin(a); return mat2(c,-s,s,c); }
197
+
198
+ float hash21(vec2 p){
199
+ p = fract(p*vec2(123.34, 345.45));
200
+ p += dot(p, p+34.345);
201
+ return fract(p.x*p.y);
202
+ }
203
+
204
+ void main() {
205
+ // Point sprite coord
206
+ vec2 uv = gl_PointCoord*2.0 - 1.0;
207
+
208
+ // Flicker rotation for anisotropy
209
+ float ang = v_seed*6.2831 + u_time*2.0;
210
+ uv = rot(ang) * uv;
211
+
212
+ // Elliptical falloff: elongated along x
213
+ float e = 0.42;
214
+ float d = dot(vec2(uv.x/e, uv.y), vec2(uv.x/e, uv.y));
215
+ float alpha = smoothstep(1.0, 0.0, d);
216
+
217
+ // Spark core
218
+ float core = smoothstep(0.20, 0.0, d);
219
+ alpha = max(alpha, core*0.75);
220
+
221
+ // Color by speed: white->yellow->orange->red
222
+ float t = clamp(v_speed*0.6, 0.0, 1.0);
223
+ vec3 col = mix(vec3(1.0, 0.95, 0.90), vec3(1.0, 0.75, 0.25), smoothstep(0.0, 0.5, t));
224
+ col = mix(col, vec3(1.0, 0.45, 0.10), smoothstep(0.5, 1.0, t));
225
+ col = mix(col, vec3(0.9, 0.1, 0.05), smoothstep(0.9, 1.6, t));
226
+
227
+ // Slight color jitter
228
+ float n = hash21(vec2(v_seed*19.31, v_seed*91.7));
229
+ col *= mix(0.9, 1.1, n);
230
+
231
+ // Flicker
232
+ float flick = 0.85 + 0.25*sin(u_time*40.0 + v_seed*123.4);
233
+ alpha *= flick;
234
+
235
+ gl_FragColor = vec4(col, alpha);
236
+ // Premultiplied-like glow via additive blend
237
+ }
238
+ `;
239
+
240
+ const compositeVS = `
241
+ attribute vec2 a_pos;
242
+ varying vec2 v_uv;
243
+ void main(){
244
+ v_uv = a_pos*0.5 + 0.5;
245
+ gl_Position = vec4(a_pos, 0.0, 1.0);
246
+ }
247
+ `;
248
+
249
+ const compositeFS = `
250
+ precision mediump float;
251
+ varying vec2 v_uv;
252
+
253
+ uniform sampler2D u_prevTrail;
254
+ uniform sampler2D u_curr;
255
+ uniform float u_decay;
256
+
257
+ uniform vec2 u_resolution;
258
+ uniform float u_time;
259
+
260
+ uniform int u_starCount;
261
+ uniform vec2 u_starPos[1200];
262
+ uniform float u_starPhase[1200];
263
+ uniform float u_starSize[1200];
264
+
265
+ // Starfield function
266
+ float hash(float x){ return fract(sin(x)*43758.5453123); }
267
+ vec3 starfield(vec2 uv, float aspect){
268
+ // Tile to reduce cost
269
+ vec2 grid = uv*vec2(aspect, 1.0)*400.0;
270
+ vec2 id = floor(grid);
271
+ vec2 gv = fract(grid) - 0.5;
272
+
273
+ float n = hash(id.x + id.y*57.0);
274
+ vec3 col = vec3(0.0);
275
+
276
+ // Sparse stars
277
+ if (n > 0.997) {
278
+ float tw = 0.6 + 0.4*sin(u_time*2.0 + n*123.0);
279
+ float size = mix(0.5, 1.5, hash(n*13.7));
280
+ float d = length(gv);
281
+ float s = smoothstep(size, 0.0, d);
282
+ col += vec3(1.0, 1.0, 1.0) * s * tw;
283
+ }
284
+ return col;
285
+ }
286
+
287
+ // Shockwave ring
288
+ float ring(vec2 uv, vec2 center, float radius, float thickness){
289
+ float d = length(uv - center);
290
+ float inner = smoothstep(radius - thickness, radius, d);
291
+ float outer = 1.0 - smoothstep(radius, radius + thickness, d);
292
+ float ringv = inner * outer;
293
+ // fade ends
294
+ return ringv;
295
+ }
296
+
297
+ // Background nebula (cheap multi-noise)
298
+ float noise(vec2 p){
299
+ vec2 i = floor(p);
300
+ vec2 f = fract(p);
301
+ float a = hash(i.x + i.y*57.0);
302
+ float b = hash(i.x+1.0 + i.y*57.0);
303
+ float c = hash(i.x + (i.y+1.0)*57.0);
304
+ float d = hash(i.x+1.0 + (i.y+1.0)*57.0);
305
+ vec2 u = f*f*(3.0-2.0*f);
306
+ return mix(mix(a,b,u.x), mix(c,d,u.x), u.y);
307
+ }
308
+ vec3 nebula(vec2 uv, float t){
309
+ uv *= 2.5;
310
+ float n = 0.0;
311
+ n += noise(uv + vec2(t*0.03, t*0.01));
312
+ n += 0.5*noise(uv*2.0 - vec2(t*0.02, -t*0.015));
313
+ n += 0.25*noise(uv*4.0 + vec2(t*0.01, t*0.02));
314
+ n = pow(n, 1.2);
315
+ vec3 c = mix(vec3(0.05, 0.07, 0.10), vec3(0.10, 0.05, 0.15), n);
316
+ c += vec3(0.03, 0.02, 0.06)*n*n;
317
+ return c;
318
+ }
319
+
320
+ void main(){
321
+ // UV to NDC space (0..1)
322
+ vec2 uv = v_uv;
323
+ vec2 ndc = uv*2.0 - 1.0;
324
+
325
+ // Background base
326
+ vec3 col = nebula(ndc, u_time);
327
+
328
+ // Stars (procedural sparse)
329
+ float aspect = u_resolution.x / u_resolution.y;
330
+ vec3 stars = starfield(uv, aspect);
331
+ col += stars * 0.7;
332
+
333
+ // Shockwave ring
334
+ if (u_time < ${shockDuration.toFixed(1)}) {
335
+ float radius = (u_time - shockStart) * 0.9; // world units to NDC
336
+ // convert center
337
+ vec2 c = vec2(0.0); // origin in NDC
338
+ float ringv = ring(ndc, c, radius, ${shockThickness.toFixed(2)});
339
+ vec3 ringCol = mix(vec3(0.5,0.8,1.0), vec3(1.0,0.9,0.6), smoothstep(0.0, 1.0, radius));
340
+ col += ringCol * ringv * 1.2;
341
+ }
342
+
343
+ // Trails: fade previous, add current
344
+ vec3 prev = texture2D(u_prevTrail, uv).rgb;
345
+ vec3 curr = texture2D(u_curr, uv).rgb;
346
+
347
+ vec3 combined = prev * u_decay + curr;
348
+
349
+ // Vignette
350
+ float vign = smoothstep(1.3, 0.2, length(ndc));
351
+ col *= vign;
352
+
353
+ // Mix trails over background
354
+ col += combined;
355
+
356
+ // Tone map and gamma
357
+ col = col / (1.0 + col); // simple Reinhard
358
+ col = pow(col, vec3(1.0/2.2));
359
+
360
+ gl_FragColor = vec4(col, 1.0);
361
+ }
362
+ `;
363
+
364
+ // GL helpers
365
+ function createShader(type, src){
366
+ const sh = gl.createShader(type);
367
+ gl.shaderSource(sh, src);
368
+ gl.compileShader(sh);
369
+ if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
370
+ console.error(gl.getShaderInfoLog(sh));
371
+ throw new Error('Shader compile error');
372
+ }
373
+ return sh;
374
+ }
375
+ function createProgram(vsSrc, fsSrc){
376
+ const vs = createShader(gl.VERTEX_SHADER, vsSrc);
377
+ const fs = createShader(gl.FRAGMENT_SHADER, fsSrc);
378
+ const pr = gl.createProgram();
379
+ gl.attachShader(pr, vs);
380
+ gl.attachShader(pr, fs);
381
+ gl.linkProgram(pr);
382
+ if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) {
383
+ console.error(gl.getProgramInfoLog(pr));
384
+ throw new Error('Program link error');
385
+ }
386
+ return pr;
387
+ }
388
+
389
+ // Programs
390
+ const particleProg = createProgram(particleVS, particleFS);
391
+ const particleLoc = {
392
+ a_pos: gl.getAttribLocation(particleProg, 'a_pos'),
393
+ a_vel: gl.getAttribLocation(particleProg, 'a_vel'),
394
+ a_size: gl.getAttribLocation(particleProg, 'a_size'),
395
+ a_seed: gl.getAttribLocation(particleProg, 'a_seed'),
396
+ u_origin: gl.getUniformLocation(particleProg, 'u_origin'),
397
+ u_scale: gl.getUniformLocation(particleProg, 'u_scale'),
398
+ u_aspect: gl.getUniformLocation(particleProg, 'u_aspect'),
399
+ u_time: gl.getUniformLocation(particleProg, 'u_time'),
400
+ };
401
+
402
+ const compositeProg = createProgram(compositeVS, compositeFS);
403
+ const compositeLoc = {
404
+ a_pos: gl.getAttribLocation(compositeProg, 'a_pos'),
405
+ u_prevTrail: gl.getUniformLocation(compositeProg, 'u_prevTrail'),
406
+ u_curr: gl.getUniformLocation(compositeProg, 'u_curr'),
407
+ u_decay: gl.getUniformLocation(compositeProg, 'u_decay'),
408
+ u_resolution: gl.getUniformLocation(compositeProg, 'u_resolution'),
409
+ u_time: gl.getUniformLocation(compositeProg, 'u_time'),
410
+ u_starCount: gl.getUniformLocation(compositeProg, 'u_starCount'),
411
+ u_starPos: gl.getUniformLocation(compositeProg, 'u_starPos'),
412
+ u_starPhase: gl.getUniformLocation(compositeProg, 'u_starPhase'),
413
+ u_starSize: gl.getUniformLocation(compositeProg, 'u_starSize'),
414
+ };
415
+
416
+ // Buffers
417
+ const fsQuad = gl.createBuffer();
418
+ gl.bindBuffer(gl.ARRAY_BUFFER, fsQuad);
419
+ // Full-screen triangle
420
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
421
+ -1, -1,
422
+ 3, -1,
423
+ -1, 3
424
+ ]), gl.STATIC_DRAW);
425
+
426
+ // Particle buffers
427
+ const bufPos = gl.createBuffer();
428
+ const bufVel = gl.createBuffer();
429
+ const bufSize = gl.createBuffer();
430
+ const bufSeed = gl.createBuffer();
431
+
432
+ // Trail target
433
+ function createTrailTarget(w, h){
434
+ const tex = gl.createTexture();
435
+ gl.bindTexture(gl.TEXTURE_2D, tex);
436
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
437
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
438
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
439
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
440
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
441
+
442
+ const fbo = gl.createFramebuffer();
443
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
444
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
445
+
446
+ const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
447
+ if (status !== gl.FRAMEBUFFER_COMPLETE) {
448
+ console.error('FBO incomplete', status.toString(16));
449
+ }
450
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
451
+
452
+ return { fbo, tex };
453
+ }
454
+
455
+ function resize(){
456
+ const cssW = canvas.clientWidth || window.innerWidth;
457
+ const cssH = canvas.clientHeight || window.innerHeight;
458
+ const w = Math.floor(cssW * DPR);
459
+ const h = Math.floor(cssH * DPR);
460
+ if (w === W && h === H) return;
461
+
462
+ W = w; H = h;
463
+ canvas.width = W;
464
+ canvas.height = H;
465
+ gl.viewport(0, 0, W, H);
466
+
467
+ // Recreate trail target
468
+ if (trailTex) {
469
+ gl.deleteTexture(trailTex);
470
+ gl.deleteFramebuffer(trailFbo);
471
+ }
472
+ const t = createTrailTarget(W, H);
473
+ trailFbo = t.fbo;
474
+ trailTex = t.tex;
475
+
476
+ // Center explosion
477
+ origin.x = 0;
478
+ origin.y = 0;
479
+
480
+ // Recompute scale (world -> NDC)
481
+ worldScale.x = 1.0 / (WORLD_RADIUS);
482
+ worldScale.y = 1.0 / (WORLD_RADIUS);
483
+ }
484
+
485
+ // World settings
486
+ const WORLD_RADIUS = 2.2; // world half-extent in NDC
487
+ const worldScale = { x: 1/WORLD_RADIUS, y: 1/WORLD_RADIUS };
488
+
489
+ // Particles init
490
+ function allocParticles(count){
491
+ PARTICLE_COUNT = clamp(count|0, MIN_PARTICLES, MAX_PARTICLES);
492
+ pLife = new Float32Array(PARTICLE_COUNT);
493
+ pPos = new Float32Array(PARTICLE_COUNT * 2);
494
+ pVel = new Float32Array(PARTICLE_COUNT * 2);
495
+ pSize = new Float32Array(PARTICLE_COUNT);
496
+ pSeed = new Float32Array(PARTICLE_COUNT);
497
+ }
498
+
499
+ function randomInCircle(radius){
500
+ const t = Math.random()*Math.PI*2;
501
+ const r = Math.sqrt(Math.random())*radius;
502
+ return { x: Math.cos(t)*r, y: Math.sin(t)*r };
503
+ }
504
+
505
+ function hslToRgb(h, s, l) {
506
+ let r, g, b;
507
+ if (s === 0) {
508
+ r = g = b = l; // achromatic
509
+ } else {
510
+ const hue2rgb = function hue2rgb(p, q, t){
511
+ if (t < 0) t += 1;
512
+ if (t > 1) t -= 1;
513
+ if (t < 1/6) return p + (q - p) * 6 * t;
514
+ if (t < 1/2) return q;
515
+ if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
516
+ return p;
517
+ };
518
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
519
+ const p = 2 * l - q;
520
+ r = hue2rgb(p, q, h + 1/3);
521
+ g = hue2rgb(p, q, h);
522
+ b = hue2rgb(p, q, h - 1/3);
523
+ }
524
+ return [r, g, b];
525
+ }
526
+
527
+ function resetParticles(count){
528
+ allocParticles(count);
529
+ for (let i = 0; i < PARTICLE_COUNT; i++) {
530
+ pSeed[i] = Math.random();
531
+ const r = randomInCircle(0.15 + Math.random()*0.25);
532
+ pPos[i*2+0] = origin.x + r.x;
533
+ pPos[i*2+1] = origin.y + r.y;
534
+
535
+ // Velocity: radial burst
536
+ const angle = Math.atan2(r.y, r.x);
537
+ const speed = 0.7 + Math.random()*2.6; // world units per second
538
+ pVel[i*2+0] = Math.cos(angle) * speed;
539
+ pVel[i*2+1] = Math.sin(angle) * speed;
540
+
541
+ // Size in pixels (scaled with DPR)
542
+ pSize[i] = (1.0 + Math.random()*3.5) * DPR * 1.2;
543
+
544
+ // Life in seconds
545
+ pLife[i] = 1.5 + Math.random()*5.0;
546
+ }
547
+
548
+ // Upload static buffers
549
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufSeed);
550
+ gl.bufferData(gl.ARRAY_BUFFER, pSeed, gl.STATIC_DRAW);
551
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufSize);
552
+ gl.bufferData(gl.ARRAY_BUFFER, pSize, gl.DYNAMIC_DRAW);
553
+
554
+ // Initial positions
555
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufPos);
556
+ gl.bufferData(gl.ARRAY_BUFFER, pPos, gl.DYNAMIC_DRAW);
557
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufVel);
558
+ gl.bufferData(gl.ARRAY_BUFFER, pVel, gl.DYNAMIC_DRAW);
559
+ }
560
+
561
+ function respawn(i){
562
+ pSeed[i] = Math.random();
563
+ const r = randomInCircle(0.05 + Math.random()*0.18);
564
+ pPos[i*2+0] = origin.x + r.x;
565
+ pPos[i*2+1] = origin.y + r.y;
566
+
567
+ const angle = Math.atan2(r.y, r.x);
568
+ const speed = 0.9 + Math.random()*2.8;
569
+ pVel[i*2+0] = Math.cos(angle) * speed;
570
+ pVel[i*2+1] = Math.sin(angle) * speed;
571
+
572
+ pSize[i] = (1.0 + Math.random()*3.5) * DPR * 1.2;
573
+ pLife[i] = 1.8 + Math.random()*6.0;
574
+ }
575
+
576
+ function initStars(count){
577
+ STAR_COUNT = Math.min(1200, count|0);
578
+ starPos = new Float32Array(STAR_COUNT*2);
579
+ starPhase = new Float32Array(STAR_COUNT);
580
+ starSize = new Float32Array(STAR_COUNT);
581
+ for (let i = 0; i < STAR_COUNT; i++){
582
+ // Distribute roughly uniformly in NDC
583
+ starPos[i*2+0] = rand(-1, 1);
584
+ starPos[i*2+1] = rand(-1, 1);
585
+ starPhase[i] = rand(0, Math.PI*2);
586
+ starSize[i] = rand(0.5, 1.5);
587
+ }
588
+ }
589
+
590
+ function explode(){
591
+ // Shockwave start
592
+ shockActive = true;
593
+ shockStart = time;
594
+ // Reset velocities with a powerful burst and add some turbulence
595
+ for (let i = 0; i < PARTICLE_COUNT; i++){
596
+ const angle = Math.random()*Math.PI*2;
597
+ const speed = 1.2 + Math.random()*3.2; // stronger impulse
598
+ pVel[i*2+0] = Math.cos(angle) * speed;
599
+ pVel[i*2+1] = Math.sin(angle) * speed;
600
+ // Add slight size jitter for flair
601
+ pSize[i] = (1.2 + Math.random()*4.2) * DPR * 1.2;
602
+ pLife[i] = 2.0 + Math.random()*6.0;
603
+ }
604
+ }
605
+
606
+ // Init
607
+ resetParticles(MAX_PARTICLES);
608
+ initStars(STAR_COUNT);
609
+
610
+ // GL State
611
+ gl.disable(gl.DEPTH_TEST);
612
+ gl.enable(gl.BLEND);
613
+ gl.blendFunc(gl.ONE, gl.ONE); // additive
614
+ gl.clearColor(0,0,0,1);
615
+
616
+ // Controls
617
+ const btnExplode = document.getElementById('btn-explode');
618
+ const btnToggle = document.getElementById('btn-toggle');
619
+ const btnReset = document.getElementById('btn-reset');
620
+ btnExplode.addEventListener('click', explode);
621
+ btnToggle.addEventListener('click', ()=>{
622
+ paused = !paused;
623
+ btnToggle.textContent = paused ? 'Resume' : 'Pause';
624
+ });
625
+ btnReset.addEventListener('click', ()=>resetParticles(PARTICLE_COUNT));
626
+
627
+ window.addEventListener('keydown', (e)=>{
628
+ if (e.code === 'Space') { paused = !paused; btnToggle.textContent = paused ? 'Resume' : 'Pause'; }
629
+ if (e.key.toLowerCase() === 'e') explode();
630
+ if (e.key.toLowerCase() === 'r') resetParticles(PARTICLE_COUNT);
631
+ });
632
+ window.addEventListener('resize', resize);
633
+ window.addEventListener('pointerdown', explode);
634
+ resize();
635
+
636
+ // Animation
637
+ function update(dt){
638
+ // Update particles on CPU
639
+ const decay = Math.pow(DRAG, dt*60);
640
+ for (let i = 0; i < PARTICLE_COUNT; i++){
641
+ // Life
642
+ pLife[i] -= dt;
643
+ if (pLife[i] <= 0) {
644
+ respawn(i);
645
+ continue;
646
+ }
647
+ // Velocity
648
+ pVel[i*2+0] *= decay;
649
+ pVel[i*2+1] = pVel[i*2+1]*decay - G*dt;
650
+
651
+ // Position
652
+ pPos[i*2+0] += pVel[i*2+0] * dt;
653
+ pPos[i*2+1] += pVel[i*2+1] * dt;
654
+
655
+ // If out of bounds (with margin), respawn
656
+ const x = pPos[i*2+0], y = pPos[i*2+1];
657
+ if (Math.abs(x) > WORLD_RADIUS*1.8 || Math.abs(y) > WORLD_RADIUS*1.8) {
658
+ respawn(i);
659
+ }
660
+ }
661
+ }
662
+
663
+ function drawParticlesToFBO(){
664
+ // Render current particles into trailTex FBO
665
+ gl.bindFramebuffer(gl.FRAMEBUFFER, trailFbo);
666
+ gl.viewport(0, 0, W, H);
667
+ gl.clearColor(0,0,0,0);
668
+ gl.clear(gl.COLOR_BUFFER_BIT);
669
+
670
+ gl.useProgram(particleProg);
671
+
672
+ // Attributes
673
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufPos);
674
+ gl.enableVertexAttribArray(particleLoc.a_pos);
675
+ gl.vertexAttribPointer(particleLoc.a_pos, 2, gl.FLOAT, false, 0, 0);
676
+
677
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufVel);
678
+ gl.enableVertexAttribArray(particleLoc.a_vel);
679
+ gl.vertexAttribPointer(particleLoc.a_vel, 2, gl.FLOAT, false, 0, 0);
680
+
681
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufSize);
682
+ gl.enableVertexAttribArray(particleLoc.a_size);
683
+ gl.vertexAttribPointer(particleLoc.a_size, 1, gl.FLOAT, false, 0, 0);
684
+
685
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufSeed);
686
+ gl.enableVertexAttribArray(particleLoc.a_seed);
687
+ gl.vertexAttribPointer(particleLoc.a_seed, 1, gl.FLOAT, false, 0, 0);
688
+
689
+ // Uniforms
690
+ gl.uniform2f(particleLoc.u_origin, origin.x, origin.y);
691
+ gl.uniform2f(particleLoc.u_scale, worldScale.x, worldScale.y);
692
+ gl.uniform1f(particleLoc.u_aspect, W / H);
693
+ gl.uniform1f(particleLoc.u_time, time);
694
+
695
+ // Update dynamic buffers
696
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufPos);
697
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, pPos);
698
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufVel);
699
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, pVel);
700
+
701
+ gl.drawArrays(gl.POINTS, 0, PARTICLE_COUNT);
702
+
703
+ // Cleanup
704
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
705
+ gl.useProgram(null);
706
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
707
+ }
708
+
709
+ function compositeToScreen(){
710
+ gl.useProgram(compositeProg);
711
+
712
+ gl.bindBuffer(gl.ARRAY_BUFFER, fsQuad);
713
+ gl.enableVertexAttribArray(compositeLoc.a_pos);
714
+ gl.vertexAttribPointer(compositeLoc.a_pos, 2, gl.FLOAT, false, 0, 0);
715
+
716
+ gl.activeTexture(gl.TEXTURE0);
717
+ gl.bindTexture(gl.TEXTURE_2D, trailTex);
718
+ gl.uniform1i(compositeLoc.u_prevTrail, 0);
719
+
720
+ gl.activeTexture(gl.TEXTURE1);
721
+ gl.bindTexture(gl.TEXTURE_2D, trailTex); // same target this frame
722
+ gl.uniform1i(compositeLoc.u_curr, 1);
723
+
724
+ gl.uniform1f(compositeLoc.u_decay, 0.965); // trail persistence
725
+ gl.uniform2f(compositeLoc.u_resolution, W, H);
726
+ gl.uniform1f(compositeLoc.u_time, time);
727
+
728
+ gl.uniform1i(compositeLoc.u_starCount, STAR_COUNT);
729
+ gl.uniform2fv(compositeLoc.u_starPos, starPos);
730
+ gl.uniform1fv(compositeLoc.u_starPhase, starPhase);
731
+ gl.uniform1fv(compositeLoc.u_starSize, starSize);
732
+
733
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
734
+
735
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
736
+ gl.useProgram(null);
737
+ }
738
+
739
+ function frame(t){
740
+ const now = t * 0.001;
741
+ const dt = Math.min(0.033, now - lastT || 0.016);
742
+ lastT = now;
743
+ if (!paused) {
744
+ time += dt;
745
+ update(dt);
746
+ }
747
+
748
+ // Draw particles to trail buffer
749
+ drawParticlesToFBO();
750
+
751
+ // Composite to screen
752
+ gl.viewport(0, 0, W, H);
753
+ gl.clearColor(0,0,0,1);
754
+ gl.clear(gl.COLOR_BUFFER_BIT);
755
+ compositeToScreen();
756
+
757
+ requestAnimationFrame(frame);
758
+ }
759
+ requestAnimationFrame(frame);
760
+
761
+ // Auto explode after a moment for dramatic entrance
762
+ setTimeout(()=>explode(), 900);
763
+ })();
764
+ </script>
765
+ </body>
766
+ </html>