jbilcke-hf HF Staff commited on
Commit
afcc7c7
·
verified ·
1 Parent(s): 763ad51

Upload 2 files

Browse files
Files changed (3) hide show
  1. .gitattributes +1 -0
  2. index.html +727 -18
  3. rock-sprite.png +3 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ rock-sprite.png filter=lfs diff=lfs merge=lfs -text
index.html CHANGED
@@ -1,19 +1,728 @@
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.0" />
6
+ <title>Cloud Chamber Simulator</title>
7
+ <style>
8
+ body { margin: 0; overflow: hidden; background: #000; color: white; font-family: sans-serif; }
9
+ canvas { display: block; }
10
+ #rock-sprite {
11
+ position: absolute;
12
+ width: 80px;
13
+ height: 80px;
14
+ left: 50%;
15
+ top: 50%;
16
+ transform: translate(-50%, -50%);
17
+ cursor: grab;
18
+ z-index: 10;
19
+ user-select: none;
20
+ }
21
+ #rock-sprite:active {
22
+ cursor: grabbing;
23
+ }
24
+ #gui {
25
+ position: absolute;
26
+ top: 10px;
27
+ right: 10px;
28
+ background: rgba(0, 0, 0, 0.7);
29
+ padding: 10px;
30
+ border-radius: 8px;
31
+ font-size: 14px;
32
+ }
33
+ #performance {
34
+ position: absolute;
35
+ top: 10px;
36
+ left: 10px;
37
+ background: rgba(0, 0, 0, 0.7);
38
+ padding: 10px;
39
+ border-radius: 8px;
40
+ font-size: 14px;
41
+ font-family: monospace;
42
+ color: #0f0;
43
+ min-width: 120px;
44
+ }
45
+ #gui label {
46
+ display: block;
47
+ margin-top: 10px;
48
+ }
49
+ #gui input, #gui select {
50
+ width: 100%;
51
+ margin-top: 4px;
52
+ }
53
+ .slider-container {
54
+ display: flex;
55
+ align-items: center;
56
+ gap: 8px;
57
+ margin-top: 4px;
58
+ }
59
+ .slider-value {
60
+ font-size: 12px;
61
+ color: #ccc;
62
+ white-space: nowrap;
63
+ }
64
+ .slider-input {
65
+ flex: 1;
66
+ }
67
+ </style>
68
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/gl-matrix-min.js"></script>
69
+ </head>
70
+ <body>
71
+ <canvas id="glcanvas"></canvas>
72
+ <img id="rock-sprite" src="rock-sprite.png" alt="Radiation Source">
73
+ <div id="performance">
74
+ <div>FPS: <span id="fps-counter">0</span></div>
75
+ <div>Particles: <span id="particle-counter">0</span></div>
76
+ </div>
77
+ <div id="gui">
78
+ <label>Background Cosmic Intensity
79
+ <div class="slider-container">
80
+ <span class="slider-value">0</span>
81
+ <input type="range" id="intensity" class="slider-input" min="0" max="100" step="0.1" value="0">
82
+ <span class="slider-value">100</span>
83
+ </div>
84
+ <div class="slider-value">Current: <span id="intensity-value">0</span></div>
85
+ </label>
86
+ <label>Radiation Source Intensity
87
+ <div class="slider-container">
88
+ <span class="slider-value">0</span>
89
+ <input type="range" id="sourceIntensity" class="slider-input" min="0" max="10" step="0.1" value="2">
90
+ <span class="slider-value">10</span>
91
+ </div>
92
+ <div class="slider-value">Current: <span id="sourceIntensity-value">2</span></div>
93
+ </label>
94
+ <label>Particle Size
95
+ <div class="slider-container">
96
+ <span class="slider-value">0.05</span>
97
+ <input type="range" id="size" class="slider-input" min="0.05" max="2.0" step="0.05" value="0.6">
98
+ <span class="slider-value">2.0</span>
99
+ </div>
100
+ <div class="slider-value">Current: <span id="size-value">0.6</span></div>
101
+ </label>
102
+ <label>Heat Flow
103
+ <div class="slider-container">
104
+ <span class="slider-value">0</span>
105
+ <input type="range" id="heatFlow" class="slider-input" min="0" max="0.5" step="0.01" value="0.04">
106
+ <span class="slider-value">0.5</span>
107
+ </div>
108
+ <div class="slider-value">Current: <span id="heatFlow-value">0.04</span></div>
109
+ </label>
110
+ <label>Trail Lifetime
111
+ <div class="slider-container">
112
+ <span class="slider-value">0.01</span>
113
+ <input type="range" id="trailLife" class="slider-input" min="0.01" max="1.0" step="0.01" value="0.03">
114
+ <span class="slider-value">1.0</span>
115
+ </div>
116
+ <div class="slider-value">Current: <span id="trailLife-value">0.03</span></div>
117
+ </label>
118
+ <label>Central Source Material
119
+ <select id="radiationType">
120
+ <option value="Uranium-238">Uranium-238 (Alpha)</option>
121
+ <option value="Cesium-137">Cesium-137 (Beta/Gamma)</option>
122
+ <option value="Cobalt-60">Cobalt-60 (Gamma)</option>
123
+ <option value="Radium-226">Radium-226 (Alpha)</option>
124
+ <option value="Strontium-90">Strontium-90 (Beta)</option>
125
+ <option value="Americium-241">Americium-241 (Alpha)</option>
126
+ </select>
127
+ </label>
128
+ <label>Cosmic Particle Type
129
+ <select id="cosmicType">
130
+ <option value="Muons">Muons</option>
131
+ <option value="Protons">Protons</option>
132
+ <option value="Electrons">Electrons</option>
133
+ <option value="Pions">Pions</option>
134
+ <option value="Mixed">Mixed Cosmic Ray</option>
135
+ </select>
136
+ </label>
137
+ <label>Lift Force
138
+ <div class="slider-container">
139
+ <span class="slider-value">0</span>
140
+ <input type="range" id="liftForce" class="slider-input" min="0" max="0.1" step="0.005" value="0.05">
141
+ <span class="slider-value">0.1</span>
142
+ </div>
143
+ <div class="slider-value">Current: <span id="liftForce-value">0.05</span></div>
144
+ </label>
145
+ <label>Friction Variability
146
+ <div class="slider-container">
147
+ <span class="slider-value">0</span>
148
+ <input type="range" id="frictionVariance" class="slider-input" min="0" max="1.0" step="0.005" value="0.4">
149
+ <span class="slider-value">1.0</span>
150
+ </div>
151
+ <div class="slider-value">Current: <span id="frictionVariance-value">0.4</span></div>
152
+ </label>
153
+ <label>Particle Curvature
154
+ <div class="slider-container">
155
+ <span class="slider-value">0</span>
156
+ <input type="range" id="curvatureMultiplier" class="slider-input" min="0" max="5.0" step="0.1" value="1.0">
157
+ <span class="slider-value">5.0</span>
158
+ </div>
159
+ <div class="slider-value">Current: <span id="curvatureMultiplier-value">1.0</span></div>
160
+ </label>
161
+ <label>Trail Length Scale
162
+ <div class="slider-container">
163
+ <span class="slider-value">1.0</span>
164
+ <input type="range" id="trailLengthScale" class="slider-input" min="1.0" max="10.0" step="0.1" value="2.0">
165
+ <span class="slider-value">10.0</span>
166
+ </div>
167
+ <div class="slider-value">Current: <span id="trailLengthScale-value">2.0</span></div>
168
+ </label>
169
+ </div>
170
+ <script>
171
+ const params = {
172
+ intensity: 0,
173
+ sourceIntensity: 2,
174
+ size: 0.6,
175
+ heatFlow: 0.04,
176
+ trailLife: 0.03,
177
+ trailLengthScale: 2.0,
178
+ radiationType: 'Cesium-137',
179
+ cosmicType: 'Muons',
180
+ liftForce: 0.05,
181
+ frictionVariance: 0.4,
182
+ curvatureMultiplier: 1.0
183
+ };
184
+
185
+ for (const id in params) {
186
+ const el = document.getElementById(id);
187
+ el.addEventListener('input', () => {
188
+ params[id] = el.type === 'range' ? parseFloat(el.value) : el.value;
189
+ if (el.type === 'range') {
190
+ const valueSpan = document.getElementById(id + '-value');
191
+ if (valueSpan) valueSpan.textContent = el.value;
192
+ }
193
+ });
194
+ }
195
+
196
+ // Sprite dragging functionality
197
+ const rockSprite = document.getElementById('rock-sprite');
198
+ let isDragging = false;
199
+ let dragOffset = { x: 0, y: 0 };
200
+ let spritePosition = { x: 0, y: 0 }; // Position in world coordinates
201
+
202
+ function screenToWorld(screenX, screenY) {
203
+ const canvas = document.getElementById("glcanvas");
204
+ const rect = canvas.getBoundingClientRect();
205
+
206
+ // Convert to normalized device coordinates (-1 to 1)
207
+ const ndcX = ((screenX - rect.left) / rect.width) * 2 - 1;
208
+ const ndcY = -(((screenY - rect.top) / rect.height) * 2 - 1);
209
+
210
+ // Calculate world space dimensions at z=0 plane
211
+ // Camera is at z=-50, FOV = PI/3, distance to z=0 = 50
212
+ const fov = Math.PI / 3;
213
+ const distance = 50;
214
+ const halfHeight = Math.tan(fov / 2) * distance;
215
+ const halfWidth = halfHeight * (canvas.width / canvas.height);
216
+
217
+ // Convert NDC to world coordinates
218
+ const worldX = ndcX * halfWidth;
219
+ const worldY = ndcY * halfHeight;
220
+
221
+ return { x: worldX, y: worldY };
222
+ }
223
+
224
+ function worldToScreen(worldX, worldY) {
225
+ const canvas = document.getElementById("glcanvas");
226
+ const rect = canvas.getBoundingClientRect();
227
+
228
+ // Calculate world space dimensions at z=0 plane
229
+ const fov = Math.PI / 3;
230
+ const distance = 50;
231
+ const halfHeight = Math.tan(fov / 2) * distance;
232
+ const halfWidth = halfHeight * (canvas.width / canvas.height);
233
+
234
+ // Convert world coordinates to NDC
235
+ const ndcX = worldX / halfWidth;
236
+ const ndcY = worldY / halfHeight;
237
+
238
+ // Convert NDC to screen coordinates
239
+ const screenX = (ndcX + 1) * 0.5 * rect.width + rect.left;
240
+ const screenY = (-ndcY + 1) * 0.5 * rect.height + rect.top;
241
+
242
+ return { x: screenX, y: screenY };
243
+ }
244
+
245
+ rockSprite.addEventListener('mousedown', (e) => {
246
+ isDragging = true;
247
+ const rect = rockSprite.getBoundingClientRect();
248
+ dragOffset.x = e.clientX - rect.left - rect.width / 2;
249
+ dragOffset.y = e.clientY - rect.top - rect.height / 2;
250
+ e.preventDefault();
251
+ });
252
+
253
+ document.addEventListener('mousemove', (e) => {
254
+ if (isDragging) {
255
+ const newX = e.clientX - dragOffset.x;
256
+ const newY = e.clientY - dragOffset.y;
257
+
258
+ rockSprite.style.left = newX + 'px';
259
+ rockSprite.style.top = newY + 'px';
260
+ rockSprite.style.transform = 'translate(-50%, -50%)';
261
+
262
+ // Update world position
263
+ const worldPos = screenToWorld(newX, newY); // newX, newY is already the center due to CSS transform
264
+ spritePosition.x = worldPos.x;
265
+ spritePosition.y = worldPos.y;
266
+ }
267
+ });
268
+
269
+ document.addEventListener('mouseup', () => {
270
+ isDragging = false;
271
+ });
272
+
273
+ const canvas = document.getElementById("glcanvas");
274
+ const gl = canvas.getContext("webgl");
275
+
276
+ if (!gl) {
277
+ alert("WebGL not supported");
278
+ }
279
+
280
+ // Create matrices early so they can be used in resize function
281
+ const projection = glMatrix.mat4.create();
282
+ const modelView = glMatrix.mat4.create();
283
+
284
+ function resize() {
285
+ canvas.width = window.innerWidth;
286
+ canvas.height = window.innerHeight;
287
+ gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
288
+
289
+ // Update projection matrix with new aspect ratio
290
+ glMatrix.mat4.perspective(projection, Math.PI / 3, canvas.width / canvas.height, 0.1, 100);
291
+
292
+ // Update sprite screen position to maintain world coordinates
293
+ if (spritePosition.x !== 0 || spritePosition.y !== 0) {
294
+ const screenPos = worldToScreen(spritePosition.x, spritePosition.y);
295
+ rockSprite.style.left = screenPos.x + 'px';
296
+ rockSprite.style.top = screenPos.y + 'px';
297
+ }
298
+ }
299
+ window.addEventListener('resize', resize);
300
+ resize();
301
+
302
+ const vsSource = `
303
+ attribute vec3 aPosition;
304
+ attribute float aLife;
305
+ attribute vec3 aColor;
306
+ attribute float aDensity;
307
+ uniform float uPointSize;
308
+ uniform mat4 uProjection;
309
+ uniform mat4 uModelView;
310
+ varying float vLife;
311
+ varying vec3 vColor;
312
+ varying float vDensity;
313
+ void main() {
314
+ gl_Position = uProjection * uModelView * vec4(aPosition, 1.0);
315
+ gl_PointSize = uPointSize * (0.5 + vDensity);
316
+ vLife = aLife;
317
+ vColor = aColor;
318
+ vDensity = aDensity;
319
+ }
320
+ `;
321
+
322
+ const fsSource = `
323
+ precision mediump float;
324
+ varying float vLife;
325
+ varying vec3 vColor;
326
+ varying float vDensity;
327
+ void main() {
328
+ vec2 coord = gl_PointCoord - vec2(0.5);
329
+ float distance = length(coord);
330
+
331
+ float alpha = vLife * vDensity * (1.0 - smoothstep(0.0, 0.5, distance));
332
+ alpha *= exp(-distance * (2.0 + vDensity));
333
+
334
+ gl_FragColor = vec4(vColor, alpha);
335
+ }
336
+ `;
337
+
338
+ function compileShader(source, type) {
339
+ const shader = gl.createShader(type);
340
+ gl.shaderSource(shader, source);
341
+ gl.compileShader(shader);
342
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
343
+ console.error("Shader compile error:", gl.getShaderInfoLog(shader));
344
+ gl.deleteShader(shader);
345
+ return null;
346
+ }
347
+ return shader;
348
+ }
349
+
350
+ const vertexShader = compileShader(vsSource, gl.VERTEX_SHADER);
351
+ const fragmentShader = compileShader(fsSource, gl.FRAGMENT_SHADER);
352
+ const program = gl.createProgram();
353
+ gl.attachShader(program, vertexShader);
354
+ gl.attachShader(program, fragmentShader);
355
+ gl.linkProgram(program);
356
+ gl.useProgram(program);
357
+
358
+ gl.enable(gl.BLEND);
359
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
360
+
361
+ const aPosition = gl.getAttribLocation(program, "aPosition");
362
+ const aLife = gl.getAttribLocation(program, "aLife");
363
+ const aColor = gl.getAttribLocation(program, "aColor");
364
+ const aDensity = gl.getAttribLocation(program, "aDensity");
365
+ const uPointSize = gl.getUniformLocation(program, "uPointSize");
366
+ const uProjection = gl.getUniformLocation(program, "uProjection");
367
+ const uModelView = gl.getUniformLocation(program, "uModelView");
368
+
369
+ // Fixed particle pool system
370
+ const PARTICLE_POOL_SIZE = 40000;
371
+ const particles = [];
372
+ let activeParticleCount = 0;
373
+
374
+ // Initialize particle pool
375
+ for (let i = 0; i < PARTICLE_POOL_SIZE; i++) {
376
+ particles.push({
377
+ position: [0, 0, 0],
378
+ age: 0,
379
+ totalLife: 0,
380
+ friction: 1.0,
381
+ color: [1.0, 1.0, 1.0],
382
+ source: 'cosmic',
383
+ density: 0.5,
384
+ particleType: 'mixed',
385
+ active: false
386
+ });
387
+ }
388
+
389
+ // Performance tracking
390
+ let frameCount = 0;
391
+ let lastTime = performance.now();
392
+ let fps = 0;
393
+ const fpsCounter = document.getElementById('fps-counter');
394
+ const particleCounter = document.getElementById('particle-counter');
395
+
396
+ // Particle recycling
397
+ function getInactiveParticle() {
398
+ for (let i = 0; i < PARTICLE_POOL_SIZE; i++) {
399
+ if (!particles[i].active) {
400
+ return particles[i];
401
+ }
402
+ }
403
+ return null; // Pool is full
404
+ }
405
+
406
+ function activateParticle(particle, position, totalLife, friction, color, source, density, particleType) {
407
+ particle.position[0] = position[0];
408
+ particle.position[1] = position[1];
409
+ particle.position[2] = position[2];
410
+ particle.age = 0;
411
+ particle.totalLife = totalLife;
412
+ particle.friction = friction;
413
+ particle.color = color;
414
+ particle.source = source;
415
+ particle.density = density;
416
+ particle.particleType = particleType;
417
+ particle.active = true;
418
+ activeParticleCount++;
419
+ }
420
+
421
+ function deactivateParticle(particle) {
422
+ if (particle.active) {
423
+ particle.active = false;
424
+ activeParticleCount--;
425
+ }
426
+ }
427
+
428
+ function getRadiationProperties(material) {
429
+ const properties = {
430
+ 'Uranium-238': {
431
+ length: [15, 25], spacing: 0.08, color: [1.0, 0.8, 0.6],
432
+ density: 0.9, curvature: 0.02, scattering: 0.1, type: 'alpha'
433
+ },
434
+ 'Cesium-137': {
435
+ length: [20, 40], spacing: 0.06, color: [0.8, 1.0, 0.8],
436
+ density: 0.6, curvature: 0.05, scattering: 0.3, type: 'beta-gamma'
437
+ },
438
+ 'Cobalt-60': {
439
+ length: [25, 50], spacing: 0.04, color: [0.6, 0.8, 1.0],
440
+ density: 0.3, curvature: 0.001, scattering: 0.05, type: 'gamma'
441
+ },
442
+ 'Radium-226': {
443
+ length: [12, 20], spacing: 0.1, color: [1.0, 0.6, 0.8],
444
+ density: 0.95, curvature: 0.015, scattering: 0.08, type: 'alpha'
445
+ },
446
+ 'Strontium-90': {
447
+ length: [30, 45], spacing: 0.05, color: [1.0, 1.0, 0.6],
448
+ density: 0.5, curvature: 0.08, scattering: 0.4, type: 'beta'
449
+ },
450
+ 'Americium-241': {
451
+ length: [10, 18], spacing: 0.12, color: [0.8, 0.6, 1.0],
452
+ density: 0.85, curvature: 0.025, scattering: 0.12, type: 'alpha'
453
+ }
454
+ };
455
+ return properties[material] || properties['Uranium-238'];
456
+ }
457
+
458
+ function getCosmicProperties(type) {
459
+ const properties = {
460
+ 'Muons': {
461
+ length: [200, 300], spacing: 0.03, penetration: 0.9,
462
+ density: 0.2, curvature: 0.001, scattering: 0.02, type: 'muon'
463
+ },
464
+ 'Protons': {
465
+ length: [150, 250], spacing: 0.05, penetration: 0.7,
466
+ density: 0.6, curvature: 0.03, scattering: 0.15, type: 'proton'
467
+ },
468
+ 'Electrons': {
469
+ length: [100, 180], spacing: 0.08, penetration: 0.3,
470
+ density: 0.15, curvature: 0.12, scattering: 0.6, type: 'electron'
471
+ },
472
+ 'Pions': {
473
+ length: [180, 280], spacing: 0.06, penetration: 0.6,
474
+ density: 0.4, curvature: 0.04, scattering: 0.25, type: 'pion'
475
+ },
476
+ 'Mixed': {
477
+ length: [150, 300], spacing: 0.04, penetration: 0.8,
478
+ density: 0.3, curvature: 0.06, scattering: 0.3, type: 'mixed'
479
+ }
480
+ };
481
+ return properties[type] || properties['Muons'];
482
+ }
483
+
484
+ function spawnCentralSourceTrail() {
485
+ const props = getRadiationProperties(params.radiationType);
486
+ const baseLength = props.length[0] + Math.floor(Math.random() * (props.length[1] - props.length[0]));
487
+ const length = Math.floor(baseLength * 5 * params.trailLengthScale * props.density);
488
+
489
+ let dir = [Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5];
490
+ const mag = Math.sqrt(dir[0]**2 + dir[1]**2 + dir[2]**2);
491
+ dir[0] /= mag; dir[1] /= mag; dir[2] /= mag;
492
+
493
+ let currentPos = [spritePosition.x, spritePosition.y, 0];
494
+ let currentDir = [...dir];
495
+
496
+ for (let i = 0; i < length; i++) {
497
+ if (Math.random() < props.scattering && i > 5) {
498
+ const scatterAngle = (Math.random() - 0.5) * props.scattering * 2;
499
+ const perpDir = [
500
+ Math.random() - 0.5,
501
+ Math.random() - 0.5,
502
+ Math.random() - 0.5
503
+ ];
504
+ currentDir[0] += perpDir[0] * scatterAngle;
505
+ currentDir[1] += perpDir[1] * scatterAngle;
506
+ currentDir[2] += perpDir[2] * scatterAngle;
507
+
508
+ const newMag = Math.sqrt(currentDir[0]**2 + currentDir[1]**2 + currentDir[2]**2);
509
+ currentDir[0] /= newMag; currentDir[1] /= newMag; currentDir[2] /= newMag;
510
+ }
511
+
512
+ const curveFactor = props.curvature * params.curvatureMultiplier * Math.sin(i * 0.1) * (1 + Math.random() * 0.5);
513
+ currentDir[0] += curveFactor * (Math.random() - 0.5);
514
+ currentDir[1] += curveFactor * (Math.random() - 0.5);
515
+ currentDir[2] += curveFactor * (Math.random() - 0.5);
516
+
517
+ const dirMag = Math.sqrt(currentDir[0]**2 + currentDir[1]**2 + currentDir[2]**2);
518
+ currentDir[0] /= dirMag; currentDir[1] /= dirMag; currentDir[2] /= dirMag;
519
+
520
+ currentPos[0] += currentDir[0] * props.spacing;
521
+ currentPos[1] += currentDir[1] * props.spacing;
522
+ currentPos[2] += currentDir[2] * props.spacing;
523
+
524
+ const particle = getInactiveParticle();
525
+ if (particle) {
526
+ activateParticle(
527
+ particle,
528
+ [...currentPos],
529
+ params.trailLife + 1.0,
530
+ 1.0 - Math.random() * params.frictionVariance,
531
+ props.color,
532
+ 'central',
533
+ props.density,
534
+ props.type
535
+ );
536
+ }
537
+ }
538
+ }
539
+
540
+ function spawnCosmicTrail() {
541
+ const props = getCosmicProperties(params.cosmicType);
542
+ const baseLength = props.length[0] + Math.floor(Math.random() * (props.length[1] - props.length[0]));
543
+ const length = Math.floor(baseLength * 3 * params.trailLengthScale * props.density);
544
+
545
+ // Generate completely random direction for cosmic particles
546
+ const angle1 = Math.random() * Math.PI * 2;
547
+ const angle2 = Math.random() * Math.PI;
548
+ let currentDir = [
549
+ Math.sin(angle2) * Math.cos(angle1),
550
+ Math.sin(angle2) * Math.sin(angle1),
551
+ Math.cos(angle2)
552
+ ];
553
+
554
+ // Start from a random position on the edge of the simulation space
555
+ const startDistance = 60 + Math.random() * 20;
556
+ const startAngle1 = Math.random() * Math.PI * 2;
557
+ const startAngle2 = Math.random() * Math.PI;
558
+ let currentPos = [
559
+ Math.sin(startAngle2) * Math.cos(startAngle1) * startDistance,
560
+ Math.sin(startAngle2) * Math.sin(startAngle1) * startDistance,
561
+ Math.cos(startAngle2) * startDistance
562
+ ];
563
+
564
+ for (let i = 0; i < length; i++) {
565
+ if (Math.random() < props.scattering && i > 10) {
566
+ const scatterAngle = (Math.random() - 0.5) * props.scattering;
567
+ const perpDir = [
568
+ Math.random() - 0.5,
569
+ Math.random() - 0.5,
570
+ Math.random() - 0.5
571
+ ];
572
+ currentDir[0] += perpDir[0] * scatterAngle;
573
+ currentDir[1] += perpDir[1] * scatterAngle;
574
+ currentDir[2] += perpDir[2] * scatterAngle;
575
+
576
+ const newMag = Math.sqrt(currentDir[0]**2 + currentDir[1]**2 + currentDir[2]**2);
577
+ currentDir[0] /= newMag; currentDir[1] /= newMag; currentDir[2] /= newMag;
578
+ }
579
+
580
+ if (props.type === 'electron' || props.type === 'mixed') {
581
+ const tangleFactor = props.curvature * params.curvatureMultiplier * (1 + Math.random());
582
+ currentDir[0] += tangleFactor * (Math.random() - 0.5);
583
+ currentDir[1] += tangleFactor * (Math.random() - 0.5);
584
+ currentDir[2] += tangleFactor * (Math.random() - 0.5);
585
+
586
+ const dirMag = Math.sqrt(currentDir[0]**2 + currentDir[1]**2 + currentDir[2]**2);
587
+ currentDir[0] /= dirMag; currentDir[1] /= dirMag; currentDir[2] /= dirMag;
588
+ }
589
+
590
+ currentPos[0] += currentDir[0] * props.spacing;
591
+ currentPos[1] += currentDir[1] * props.spacing;
592
+ currentPos[2] += currentDir[2] * props.spacing;
593
+
594
+ const particle = getInactiveParticle();
595
+ if (particle) {
596
+ activateParticle(
597
+ particle,
598
+ [...currentPos],
599
+ (params.trailLife + 1.0) * props.penetration,
600
+ 1.0 - Math.random() * params.frictionVariance * 0.5,
601
+ [1.0, 1.0, 1.0],
602
+ 'cosmic',
603
+ props.density,
604
+ props.type
605
+ );
606
+ }
607
+ }
608
+ }
609
+
610
+ // Set up model view matrix (projection is handled in resize function)
611
+ glMatrix.mat4.translate(modelView, modelView, [0, 0, -50]);
612
+
613
+ const positionBuffer = gl.createBuffer();
614
+ const lifeBuffer = gl.createBuffer();
615
+ const colorBuffer = gl.createBuffer();
616
+ const densityBuffer = gl.createBuffer();
617
+
618
+ function render() {
619
+ // FPS calculation
620
+ frameCount++;
621
+ const currentTime = performance.now();
622
+ if (currentTime - lastTime >= 1000) {
623
+ fps = Math.round((frameCount * 1000) / (currentTime - lastTime));
624
+ frameCount = 0;
625
+ lastTime = currentTime;
626
+ fpsCounter.textContent = fps;
627
+ }
628
+
629
+ // Update particle count
630
+ particleCounter.textContent = activeParticleCount;
631
+
632
+ gl.clearColor(0, 0, 0, 1);
633
+ gl.clear(gl.COLOR_BUFFER_BIT);
634
+
635
+ if (Math.random() < 0.1 * params.intensity) {
636
+ spawnCosmicTrail();
637
+ }
638
+
639
+ if (Math.random() < 0.1 * params.sourceIntensity) {
640
+ spawnCentralSourceTrail();
641
+ }
642
+
643
+ // Update and deactivate particles
644
+ for (let i = 0; i < PARTICLE_POOL_SIZE; i++) {
645
+ const p = particles[i];
646
+ if (!p.active) continue;
647
+
648
+ p.age += 0.01 * p.friction;
649
+
650
+ // Apply physics if particle is still in visible lifetime
651
+ if (p.age <= params.trailLife + 1.0) {
652
+ const f = params.heatFlow;
653
+ p.position[0] += (Math.random() - 0.5) * f * p.friction;
654
+ p.position[1] += (Math.random() - 0.5) * f * p.friction + params.liftForce * p.friction;
655
+ p.position[2] += (Math.random() - 0.5) * f * p.friction;
656
+ }
657
+
658
+ // Deactivate particles that have exceeded their lifetime or are out of bounds
659
+ if (p.age > p.totalLife ||
660
+ Math.abs(p.position[0]) > 100 ||
661
+ Math.abs(p.position[1]) > 100 ||
662
+ Math.abs(p.position[2]) > 100) {
663
+ deactivateParticle(p);
664
+ }
665
+ }
666
+
667
+ // Build arrays only for active particles
668
+ const posArray = new Float32Array(activeParticleCount * 3);
669
+ const lifeArray = new Float32Array(activeParticleCount);
670
+ const colorArray = new Float32Array(activeParticleCount * 3);
671
+ const densityArray = new Float32Array(activeParticleCount);
672
+
673
+ let activeIndex = 0;
674
+ for (let i = 0; i < PARTICLE_POOL_SIZE; i++) {
675
+ const p = particles[i];
676
+ if (!p.active) continue;
677
+
678
+ posArray.set(p.position, activeIndex * 3);
679
+ let alpha = 1.0;
680
+ const fadeStart = params.trailLife * 0.7;
681
+ if (p.age < fadeStart) {
682
+ alpha = 1.0;
683
+ } else if (p.age < p.totalLife) {
684
+ const fadeProgress = (p.age - fadeStart) / (p.totalLife - fadeStart);
685
+ alpha = 1.0 - Math.pow(fadeProgress, 0.8);
686
+ } else {
687
+ alpha = 0.0;
688
+ }
689
+ lifeArray[activeIndex] = Math.max(0.0, alpha);
690
+ colorArray.set(p.color || [1.0, 1.0, 1.0], activeIndex * 3);
691
+ densityArray[activeIndex] = p.density || 0.5;
692
+ activeIndex++;
693
+ }
694
+
695
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
696
+ gl.bufferData(gl.ARRAY_BUFFER, posArray, gl.DYNAMIC_DRAW);
697
+ gl.enableVertexAttribArray(aPosition);
698
+ gl.vertexAttribPointer(aPosition, 3, gl.FLOAT, false, 0, 0);
699
+
700
+ gl.bindBuffer(gl.ARRAY_BUFFER, lifeBuffer);
701
+ gl.bufferData(gl.ARRAY_BUFFER, lifeArray, gl.DYNAMIC_DRAW);
702
+ gl.enableVertexAttribArray(aLife);
703
+ gl.vertexAttribPointer(aLife, 1, gl.FLOAT, false, 0, 0);
704
+
705
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
706
+ gl.bufferData(gl.ARRAY_BUFFER, colorArray, gl.DYNAMIC_DRAW);
707
+ gl.enableVertexAttribArray(aColor);
708
+ gl.vertexAttribPointer(aColor, 3, gl.FLOAT, false, 0, 0);
709
+
710
+ gl.bindBuffer(gl.ARRAY_BUFFER, densityBuffer);
711
+ gl.bufferData(gl.ARRAY_BUFFER, densityArray, gl.DYNAMIC_DRAW);
712
+ gl.enableVertexAttribArray(aDensity);
713
+ gl.vertexAttribPointer(aDensity, 1, gl.FLOAT, false, 0, 0);
714
+
715
+ gl.uniformMatrix4fv(uProjection, false, projection);
716
+ gl.uniformMatrix4fv(uModelView, false, modelView);
717
+ gl.uniform1f(uPointSize, 10.0 * params.size);
718
+
719
+ gl.drawArrays(gl.POINTS, 0, activeParticleCount);
720
+
721
+ requestAnimationFrame(render);
722
+ }
723
+
724
+ requestAnimationFrame(render);
725
+ </script>
726
+ </body>
727
  </html>
728
+
rock-sprite.png ADDED

Git LFS Details

  • SHA256: 867c86dba490694c9c8091cd3be5c16ce0f59a2e1af8d49a05522ce9212db381
  • Pointer size: 131 Bytes
  • Size of remote file: 201 kB