idgmatrix commited on
Commit
cd6e336
·
verified ·
1 Parent(s): 6ba16bc

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +712 -19
index.html CHANGED
@@ -1,19 +1,712 @@
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
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Black Hole with Accretion Disk - WebGPU Simulator</title>
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
17
+ background: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #0a0a0a 100%);
18
+ min-height: 100vh;
19
+ color: #fff;
20
+ overflow-x: hidden;
21
+ }
22
+
23
+ header {
24
+ padding: 1rem 2rem;
25
+ display: flex;
26
+ justify-content: space-between;
27
+ align-items: center;
28
+ background: rgba(0, 0, 0, 0.5);
29
+ backdrop-filter: blur(10px);
30
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
31
+ }
32
+
33
+ .logo {
34
+ font-size: 1.5rem;
35
+ font-weight: bold;
36
+ background: linear-gradient(90deg, #ff6b35, #f7c59f);
37
+ -webkit-background-clip: text;
38
+ -webkit-text-fill-color: transparent;
39
+ background-clip: text;
40
+ }
41
+
42
+ .built-with {
43
+ font-size: 0.9rem;
44
+ color: #888;
45
+ }
46
+
47
+ .built-with a {
48
+ color: #ff6b35;
49
+ text-decoration: none;
50
+ transition: color 0.3s ease;
51
+ }
52
+
53
+ .built-with a:hover {
54
+ color: #f7c59f;
55
+ }
56
+
57
+ main {
58
+ display: flex;
59
+ flex-direction: column;
60
+ align-items: center;
61
+ padding: 2rem;
62
+ gap: 2rem;
63
+ }
64
+
65
+ .canvas-container {
66
+ position: relative;
67
+ width: 100%;
68
+ max-width: 900px;
69
+ aspect-ratio: 16/9;
70
+ border-radius: 16px;
71
+ overflow: hidden;
72
+ box-shadow: 0 0 60px rgba(255, 107, 53, 0.3),
73
+ 0 0 100px rgba(255, 107, 53, 0.1);
74
+ }
75
+
76
+ canvas {
77
+ width: 100%;
78
+ height: 100%;
79
+ display: block;
80
+ }
81
+
82
+ .controls {
83
+ display: grid;
84
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
85
+ gap: 1.5rem;
86
+ width: 100%;
87
+ max-width: 900px;
88
+ padding: 1.5rem;
89
+ background: rgba(255, 255, 255, 0.05);
90
+ border-radius: 16px;
91
+ backdrop-filter: blur(10px);
92
+ border: 1px solid rgba(255, 255, 255, 0.1);
93
+ }
94
+
95
+ .control-group {
96
+ display: flex;
97
+ flex-direction: column;
98
+ gap: 0.5rem;
99
+ }
100
+
101
+ .control-group label {
102
+ font-size: 0.9rem;
103
+ color: #ccc;
104
+ display: flex;
105
+ justify-content: space-between;
106
+ align-items: center;
107
+ }
108
+
109
+ .control-group label span {
110
+ color: #ff6b35;
111
+ font-weight: bold;
112
+ }
113
+
114
+ input[type="range"] {
115
+ -webkit-appearance: none;
116
+ appearance: none;
117
+ width: 100%;
118
+ height: 8px;
119
+ border-radius: 4px;
120
+ background: linear-gradient(90deg, #333, #555);
121
+ outline: none;
122
+ cursor: pointer;
123
+ }
124
+
125
+ input[type="range"]::-webkit-slider-thumb {
126
+ -webkit-appearance: none;
127
+ appearance: none;
128
+ width: 20px;
129
+ height: 20px;
130
+ border-radius: 50%;
131
+ background: linear-gradient(135deg, #ff6b35, #f7c59f);
132
+ cursor: pointer;
133
+ box-shadow: 0 0 10px rgba(255, 107, 53, 0.5);
134
+ transition: transform 0.2s ease;
135
+ }
136
+
137
+ input[type="range"]::-webkit-slider-thumb:hover {
138
+ transform: scale(1.2);
139
+ }
140
+
141
+ input[type="range"]::-moz-range-thumb {
142
+ width: 20px;
143
+ height: 20px;
144
+ border-radius: 50%;
145
+ background: linear-gradient(135deg, #ff6b35, #f7c59f);
146
+ cursor: pointer;
147
+ border: none;
148
+ box-shadow: 0 0 10px rgba(255, 107, 53, 0.5);
149
+ }
150
+
151
+ .info-panel {
152
+ width: 100%;
153
+ max-width: 900px;
154
+ padding: 1.5rem;
155
+ background: rgba(255, 255, 255, 0.03);
156
+ border-radius: 16px;
157
+ border: 1px solid rgba(255, 255, 255, 0.1);
158
+ }
159
+
160
+ .info-panel h2 {
161
+ font-size: 1.2rem;
162
+ margin-bottom: 1rem;
163
+ color: #ff6b35;
164
+ }
165
+
166
+ .info-panel p {
167
+ font-size: 0.9rem;
168
+ line-height: 1.6;
169
+ color: #aaa;
170
+ }
171
+
172
+ .error-message {
173
+ padding: 2rem;
174
+ background: rgba(255, 0, 0, 0.1);
175
+ border: 1px solid rgba(255, 0, 0, 0.3);
176
+ border-radius: 16px;
177
+ text-align: center;
178
+ max-width: 600px;
179
+ }
180
+
181
+ .error-message h2 {
182
+ color: #ff4444;
183
+ margin-bottom: 1rem;
184
+ }
185
+
186
+ .error-message p {
187
+ color: #ccc;
188
+ line-height: 1.6;
189
+ }
190
+
191
+ .stats {
192
+ display: flex;
193
+ gap: 2rem;
194
+ flex-wrap: wrap;
195
+ margin-top: 1rem;
196
+ padding-top: 1rem;
197
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
198
+ }
199
+
200
+ .stat {
201
+ display: flex;
202
+ flex-direction: column;
203
+ gap: 0.25rem;
204
+ }
205
+
206
+ .stat-label {
207
+ font-size: 0.8rem;
208
+ color: #888;
209
+ }
210
+
211
+ .stat-value {
212
+ font-size: 1.1rem;
213
+ color: #ff6b35;
214
+ font-weight: bold;
215
+ }
216
+
217
+ @media (max-width: 768px) {
218
+ header {
219
+ flex-direction: column;
220
+ gap: 0.5rem;
221
+ text-align: center;
222
+ }
223
+
224
+ main {
225
+ padding: 1rem;
226
+ }
227
+
228
+ .controls {
229
+ grid-template-columns: 1fr;
230
+ }
231
+
232
+ .stats {
233
+ justify-content: center;
234
+ }
235
+ }
236
+ </style>
237
+ </head>
238
+
239
+ <body>
240
+ <header>
241
+ <div class="logo">🌌 Black Hole Simulator</div>
242
+ <div class="built-with">Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder"
243
+ target="_blank">anycoder</a></div>
244
+ </header>
245
+
246
+ <main>
247
+ <div class="canvas-container">
248
+ <canvas id="webgpu-canvas"></canvas>
249
+ </div>
250
+
251
+ <div class="controls">
252
+ <div class="control-group">
253
+ <label>Black Hole Mass <span id="mass-value">1.0</span></label>
254
+ <input type="range" id="mass" min="0.5" max="3.0" step="0.1" value="1.0">
255
+ </div>
256
+ <div class="control-group">
257
+ <label>Disk Inner Radius <span id="inner-radius-value">3.0</span></label>
258
+ <input type="range" id="inner-radius" min="2.0" max="5.0" step="0.1" value="3.0">
259
+ </div>
260
+ <div class="control-group">
261
+ <label>Disk Outer Radius <span id="outer-radius-value">12.0</span></label>
262
+ <input type="range" id="outer-radius" min="8.0" max="20.0" step="0.5" value="12.0">
263
+ </div>
264
+ <div class="control-group">
265
+ <label>Disk Temperature <span id="temperature-value">1.0</span></label>
266
+ <input type="range" id="temperature" min="0.3" max="2.0" step="0.1" value="1.0">
267
+ </div>
268
+ <div class="control-group">
269
+ <label>Camera Distance <span id="camera-dist-value">20.0</span></label>
270
+ <input type="range" id="camera-dist" min="10.0" max="40.0" step="1.0" value="20.0">
271
+ </div>
272
+ <div class="control-group">
273
+ <label>View Angle <span id="view-angle-value">75</span>°</label>
274
+ <input type="range" id="view-angle" min="10" max="90" step="1" value="75">
275
+ </div>
276
+ </div>
277
+
278
+ <div class="info-panel">
279
+ <h2>About This Simulation</h2>
280
+ <p>
281
+ This is a real-time ray-traced simulation of a Schwarzschild black hole with an accretion disk,
282
+ powered by WebGPU. The simulation uses general relativistic ray tracing to accurately depict
283
+ gravitational lensing, photon sphere effects, and the characteristic appearance of matter
284
+ spiraling into the event horizon. The accretion disk glows due to friction-heated matter,
285
+ with colors ranging from red (cooler outer regions) to white-hot (near the inner edge).
286
+ </p>
287
+ <div class="stats">
288
+ <div class="stat">
289
+ <span class="stat-label">Schwarzschild Radius</span>
290
+ <span class="stat-value" id="schwarzschild-radius">2.0 Rs</span>
291
+ </div>
292
+ <div class="stat">
293
+ <span class="stat-label">Photon Sphere</span>
294
+ <span class="stat-value" id="photon-sphere">3.0 Rs</span>
295
+ </div>
296
+ <div class="stat">
297
+ <span class="stat-label">ISCO</span>
298
+ <span class="stat-value" id="isco">6.0 Rs</span>
299
+ </div>
300
+ <div class="stat">
301
+ <span class="stat-label">FPS</span>
302
+ <span class="stat-value" id="fps">0</span>
303
+ </div>
304
+ </div>
305
+ </div>
306
+ </main>
307
+
308
+ <script>
309
+ const shaderCode = `
310
+ struct Uniforms {
311
+ resolution: vec2f,
312
+ time: f32,
313
+ mass: f32,
314
+ innerRadius: f32,
315
+ outerRadius: f32,
316
+ temperature: f32,
317
+ cameraDist: f32,
318
+ viewAngle: f32,
319
+ padding: f32,
320
+ }
321
+
322
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
323
+
324
+ struct VertexOutput {
325
+ @builtin(position) position: vec4f,
326
+ @location(0) uv: vec2f,
327
+ }
328
+
329
+ @vertex
330
+ fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
331
+ var positions = array<vec2f, 6>(
332
+ vec2f(-1.0, -1.0),
333
+ vec2f(1.0, -1.0),
334
+ vec2f(-1.0, 1.0),
335
+ vec2f(-1.0, 1.0),
336
+ vec2f(1.0, -1.0),
337
+ vec2f(1.0, 1.0)
338
+ );
339
+
340
+ var output: VertexOutput;
341
+ output.position = vec4f(positions[vertexIndex], 0.0, 1.0);
342
+ output.uv = positions[vertexIndex] * 0.5 + 0.5;
343
+ return output;
344
+ }
345
+
346
+ const PI: f32 = 3.14159265359;
347
+ const MAX_STEPS: i32 = 300;
348
+ const STEP_SIZE: f32 = 0.15;
349
+
350
+ fn rotateX(p: vec3f, angle: f32) -> vec3f {
351
+ let c = cos(angle);
352
+ let s = sin(angle);
353
+ return vec3f(p.x, c * p.y - s * p.z, s * p.y + c * p.z);
354
+ }
355
+
356
+ fn rotateY(p: vec3f, angle: f32) -> vec3f {
357
+ let c = cos(angle);
358
+ let s = sin(angle);
359
+ return vec3f(c * p.x + s * p.z, p.y, -s * p.x + c * p.z);
360
+ }
361
+
362
+ fn hash(p: vec2f) -> f32 {
363
+ let h = dot(p, vec2f(127.1, 311.7));
364
+ return fract(sin(h) * 43758.5453123);
365
+ }
366
+
367
+ fn noise(p: vec2f) -> f32 {
368
+ let i = floor(p);
369
+ let f = fract(p);
370
+ let u = f * f * (3.0 - 2.0 * f);
371
+ return mix(
372
+ mix(hash(i + vec2f(0.0, 0.0)), hash(i + vec2f(1.0, 0.0)), u.x),
373
+ mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u.x),
374
+ u.y
375
+ );
376
+ }
377
+
378
+ fn fbm(p: vec2f) -> f32 {
379
+ var value: f32 = 0.0;
380
+ var amplitude: f32 = 0.5;
381
+ var frequency: f32 = 1.0;
382
+ var pp = p;
383
+
384
+ for (var i: i32 = 0; i < 5; i++) {
385
+ value += amplitude * noise(pp * frequency);
386
+ amplitude *= 0.5;
387
+ frequency *= 2.0;
388
+ }
389
+ return value;
390
+ }
391
+
392
+ fn blackbodyColor(temp: f32) -> vec3f {
393
+ let t = temp * 6500.0;
394
+ var color: vec3f;
395
+
396
+ if (t < 6600.0) {
397
+ color.r = 1.0;
398
+ color.g = saturate(0.39 * log(t / 100.0) - 0.634);
399
+ if (t < 2000.0) {
400
+ color.b = 0.0;
401
+ } else {
402
+ color.b = saturate(0.543 * log(t / 100.0 - 10.0) - 1.185);
403
+ }
404
+ } else {
405
+ color.r = saturate(1.29 * pow(t / 100.0 - 60.0, -0.1332));
406
+ color.g = saturate(1.13 * pow(t / 100.0 - 60.0, -0.0755));
407
+ color.b = 1.0;
408
+ }
409
+
410
+ return color;
411
+ }
412
+
413
+ fn diskColor(r: f32, phi: f32, time: f32) -> vec3f {
414
+ let innerR = uniforms.innerRadius * uniforms.mass;
415
+ let outerR = uniforms.outerRadius * uniforms.mass;
416
+
417
+ let normalizedR = (r - innerR) / (outerR - innerR);
418
+ let temp = uniforms.temperature * pow(1.0 - normalizedR, 0.75) * (0.8 + 0.4 * fbm(vec2f(phi * 3.0, r * 0.5 + time * 0.1)));
419
+
420
+ var color = blackbodyColor(temp);
421
+
422
+ let spiral = sin(phi * 4.0 - r * 0.5 + time * 2.0) * 0.5 + 0.5;
423
+ let turbulence = fbm(vec2f(phi * 5.0 + time * 0.5, r * 2.0)) * 0.3;
424
+
425
+ color *= (0.7 + 0.3 * spiral + turbulence);
426
+
427
+ let falloff = smoothstep(innerR, innerR + 0.5, r) * smoothstep(outerR, outerR - 2.0, r);
428
+ color *= falloff;
429
+
430
+ let glow = exp(-normalizedR * 2.0) * 2.0;
431
+ color += vec3f(1.0, 0.5, 0.2) * glow * 0.3;
432
+
433
+ return color;
434
+ }
435
+
436
+ fn rayMarchBlackHole(ro: vec3f, rd: vec3f) -> vec3f {
437
+ let rs = 2.0 * uniforms.mass;
438
+ let photonSphere = 1.5 * rs;
439
+ let innerR = uniforms.innerRadius * uniforms.mass;
440
+ let outerR = uniforms.outerRadius * uniforms.mass;
441
+
442
+ var pos = ro;
443
+ var dir = rd;
444
+ var color = vec3f(0.0);
445
+ var accumulated = vec3f(0.0);
446
+ var transmittance: f32 = 1.0;
447
+
448
+ for (var i: i32 = 0; i < MAX_STEPS; i++) {
449
+ let r = length(pos);
450
+
451
+ if (r < rs * 1.01) {
452
+ return accumulated;
453
+ }
454
+
455
+ if (r > 100.0) {
456
+ let starField = pow(hash(dir.xy * 1000.0), 20.0) * 0.8;
457
+ let nebula = fbm(dir.xy * 3.0 + dir.z) * 0.1;
458
+ accumulated += transmittance * (vec3f(starField) + vec3f(0.1, 0.05, 0.15) * nebula);
459
+ break;
460
+ }
461
+
462
+ let diskY = pos.y;
463
+ let diskR = length(pos.xz);
464
+
465
+ if (abs(diskY) < 0.3 && diskR > innerR && diskR < outerR) {
466
+ let phi = atan2(pos.z, pos.x) + PI;
467
+ let diskCol = diskColor(diskR, phi, uniforms.time);
468
+
469
+ let density = (1.0 - abs(diskY) / 0.3) * 0.5;
470
+ accumulated += transmittance * diskCol * density;
471
+ transmittance *= (1.0 - density * 0.3);
472
+
473
+ if (transmittance < 0.01) {
474
+ break;
475
+ }
476
+ }
477
+
478
+ let schwarzschildFactor = 1.0 - rs / r;
479
+ let bendStrength = rs / (r * r) * 1.5;
480
+
481
+ let toCenter = -normalize(pos);
482
+ let perpComponent = dir - toCenter * dot(dir, toCenter);
483
+ dir = normalize(dir + toCenter * bendStrength * STEP_SIZE);
484
+
485
+ let adaptiveStep = STEP_SIZE * (1.0 + r * 0.05);
486
+ pos += dir * adaptiveStep;
487
+ }
488
+
489
+ return accumulated;
490
+ }
491
+
492
+ @fragment
493
+ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
494
+ let uv = (input.uv * 2.0 - 1.0) * vec2f(uniforms.resolution.x / uniforms.resolution.y, 1.0);
495
+
496
+ let viewAngleRad = uniforms.viewAngle * PI / 180.0;
497
+ let camDist = uniforms.cameraDist * uniforms.mass;
498
+
499
+ var camPos = vec3f(0.0, 0.0, camDist);
500
+ camPos = rotateX(camPos, PI / 2.0 - viewAngleRad);
501
+
502
+ let fov: f32 = 1.5;
503
+ var rayDir = normalize(vec3f(uv.x * fov, uv.y * fov, -1.0));
504
+ rayDir = rotateX(rayDir, PI / 2.0 - viewAngleRad);
505
+
506
+ var color = rayMarchBlackHole(camPos, rayDir);
507
+
508
+ color = pow(color, vec3f(0.85));
509
+ color = color / (color + vec3f(1.0));
510
+
511
+ let vignette = 1.0 - length(input.uv - 0.5) * 0.5;
512
+ color *= vignette;
513
+
514
+ color = pow(color, vec3f(1.0 / 2.2));
515
+
516
+ return vec4f(color, 1.0);
517
+ }
518
+ `;
519
+
520
+ async function initWebGPU() {
521
+ const canvas = document.getElementById('webgpu-canvas');
522
+
523
+ if (!navigator.gpu) {
524
+ showError();
525
+ return null;
526
+ }
527
+
528
+ const adapter = await navigator.gpu.requestAdapter();
529
+ if (!adapter) {
530
+ showError();
531
+ return null;
532
+ }
533
+
534
+ const device = await adapter.requestDevice();
535
+ const context = canvas.getContext('webgpu');
536
+
537
+ const format = navigator.gpu.getPreferredCanvasFormat();
538
+ context.configure({
539
+ device: device,
540
+ format: format,
541
+ alphaMode: 'premultiplied',
542
+ });
543
+
544
+ return { device, context, format, canvas };
545
+ }
546
+
547
+ function showError() {
548
+ const container = document.querySelector('.canvas-container');
549
+ container.innerHTML = `
550
+ <div class="error-message">
551
+ <h2>WebGPU Not Supported</h2>
552
+ <p>Your browser doesn't support WebGPU. Please try using Chrome 113+ or Edge 113+ with WebGPU enabled.</p>
553
+ </div>
554
+ `;
555
+ }
556
+
557
+ async function main() {
558
+ const gpu = await initWebGPU();
559
+ if (!gpu) return;
560
+
561
+ const { device, context, format, canvas } = gpu;
562
+
563
+ const shaderModule = device.createShaderModule({
564
+ code: shaderCode
565
+ });
566
+
567
+ const uniformBuffer = device.createBuffer({
568
+ size: 48,
569
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
570
+ });
571
+
572
+ const bindGroupLayout = device.createBindGroupLayout({
573
+ entries: [{
574
+ binding: 0,
575
+ visibility: GPUShaderStage.FRAGMENT,
576
+ buffer: { type: 'uniform' }
577
+ }]
578
+ });
579
+
580
+ const bindGroup = device.createBindGroup({
581
+ layout: bindGroupLayout,
582
+ entries: [{
583
+ binding: 0,
584
+ resource: { buffer: uniformBuffer }
585
+ }]
586
+ });
587
+
588
+ const pipelineLayout = device.createPipelineLayout({
589
+ bindGroupLayouts: [bindGroupLayout]
590
+ });
591
+
592
+ const pipeline = device.createRenderPipeline({
593
+ layout: pipelineLayout,
594
+ vertex: {
595
+ module: shaderModule,
596
+ entryPoint: 'vertexMain',
597
+ },
598
+ fragment: {
599
+ module: shaderModule,
600
+ entryPoint: 'fragmentMain',
601
+ targets: [{ format: format }],
602
+ },
603
+ primitive: {
604
+ topology: 'triangle-list',
605
+ },
606
+ });
607
+
608
+ const params = {
609
+ mass: 1.0,
610
+ innerRadius: 3.0,
611
+ outerRadius: 12.0,
612
+ temperature: 1.0,
613
+ cameraDist: 20.0,
614
+ viewAngle: 75.0,
615
+ };
616
+
617
+ function setupControls() {
618
+ const controls = ['mass', 'inner-radius', 'outer-radius', 'temperature', 'camera-dist', 'view-angle'];
619
+ const paramNames = ['mass', 'innerRadius', 'outerRadius', 'temperature', 'cameraDist', 'viewAngle'];
620
+
621
+ controls.forEach((id, index) => {
622
+ const input = document.getElementById(id);
623
+ const valueSpan = document.getElementById(`${id}-value`);
624
+
625
+ input.addEventListener('input', (e) => {
626
+ const value = parseFloat(e.target.value);
627
+ params[paramNames[index]] = value;
628
+ valueSpan.textContent = value.toFixed(1);
629
+ updateStats();
630
+ });
631
+ });
632
+ }
633
+
634
+ function updateStats() {
635
+ const rs = 2.0 * params.mass;
636
+ document.getElementById('schwarzschild-radius').textContent = `${rs.toFixed(1)} Rs`;
637
+ document.getElementById('photon-sphere').textContent = `${(1.5 * rs).toFixed(1)} Rs`;
638
+ document.getElementById('isco').textContent = `${(3.0 * rs).toFixed(1)} Rs`;
639
+ }
640
+
641
+ setupControls();
642
+ updateStats();
643
+
644
+ let lastTime = performance.now();
645
+ let frameCount = 0;
646
+ let fps = 0;
647
+
648
+ function resizeCanvas() {
649
+ const container = canvas.parentElement;
650
+ const rect = container.getBoundingClientRect();
651
+ const dpr = Math.min(window.devicePixelRatio, 2);
652
+ canvas.width = rect.width * dpr;
653
+ canvas.height = rect.height * dpr;
654
+ }
655
+
656
+ window.addEventListener('resize', resizeCanvas);
657
+ resizeCanvas();
658
+
659
+ function render(time) {
660
+ frameCount++;
661
+ const now = performance.now();
662
+ if (now - lastTime >= 1000) {
663
+ fps = frameCount;
664
+ frameCount = 0;
665
+ lastTime = now;
666
+ document.getElementById('fps').textContent = fps;
667
+ }
668
+
669
+ resizeCanvas();
670
+
671
+ const uniformData = new Float32Array([
672
+ canvas.width, canvas.height,
673
+ time * 0.001,
674
+ params.mass,
675
+ params.innerRadius,
676
+ params.outerRadius,
677
+ params.temperature,
678
+ params.cameraDist,
679
+ params.viewAngle,
680
+ 0.0,
681
+ ]);
682
+ device.queue.writeBuffer(uniformBuffer, 0, uniformData);
683
+
684
+ const commandEncoder = device.createCommandEncoder();
685
+ const textureView = context.getCurrentTexture().createView();
686
+
687
+ const renderPass = commandEncoder.beginRenderPass({
688
+ colorAttachments: [{
689
+ view: textureView,
690
+ clearValue: { r: 0, g: 0, b: 0, a: 1 },
691
+ loadOp: 'clear',
692
+ storeOp: 'store',
693
+ }]
694
+ });
695
+
696
+ renderPass.setPipeline(pipeline);
697
+ renderPass.setBindGroup(0, bindGroup);
698
+ renderPass.draw(6, 1, 0, 0);
699
+ renderPass.end();
700
+
701
+ device.queue.submit([commandEncoder.finish()]);
702
+ requestAnimationFrame(render);
703
+ }
704
+
705
+ requestAnimationFrame(render);
706
+ }
707
+
708
+ main();
709
+ </script>
710
+ </body>
711
+
712
+ </html>