andorxotnot commited on
Commit
d555bfd
·
verified ·
1 Parent(s): 1cce8b1

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +721 -19
index.html CHANGED
@@ -1,19 +1,721 @@
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>Complex Stable Life 3D</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;500;700&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --primary: #00d2d3; /* Cyan/Teal for a sci-fi biological look */
11
+ --glass-bg: rgba(10, 15, 20, 0.8);
12
+ --glass-border: rgba(255, 255, 255, 0.08);
13
+ --text: #f1f5f9;
14
+ }
15
+
16
+ * {
17
+ margin: 0;
18
+ padding: 0;
19
+ box-sizing: border-box;
20
+ user-select: none;
21
+ }
22
+
23
+ body {
24
+ overflow: hidden;
25
+ background-color: #050505;
26
+ font-family: 'Inter', sans-serif;
27
+ color: var(--text);
28
+ }
29
+
30
+ /* Canvas */
31
+ #canvas-container {
32
+ position: fixed;
33
+ top: 0;
34
+ left: 0;
35
+ width: 100%;
36
+ height: 100%;
37
+ z-index: 1;
38
+ background: radial-gradient(circle at center, #111 0%, #000 100%);
39
+ }
40
+
41
+ /* Header Link */
42
+ .brand-link {
43
+ position: fixed;
44
+ top: 20px;
45
+ left: 50%;
46
+ transform: translateX(-50%);
47
+ z-index: 100;
48
+ color: var(--text);
49
+ text-decoration: none;
50
+ font-weight: 600;
51
+ font-size: 0.85rem;
52
+ background: var(--glass-bg);
53
+ padding: 8px 20px;
54
+ border-radius: 30px;
55
+ border: 1px solid var(--glass-border);
56
+ backdrop-filter: blur(12px);
57
+ transition: all 0.3s ease;
58
+ letter-spacing: 0.5px;
59
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
60
+ }
61
+
62
+ .brand-link:hover {
63
+ background: rgba(255, 255, 255, 0.1);
64
+ border-color: rgba(255, 255, 255, 0.3);
65
+ box-shadow: 0 0 15px var(--primary);
66
+ }
67
+
68
+ /* UI Overlay */
69
+ .ui-panel {
70
+ position: fixed;
71
+ top: 20px;
72
+ right: 20px;
73
+ width: 340px;
74
+ background: var(--glass-bg);
75
+ backdrop-filter: blur(16px);
76
+ -webkit-backdrop-filter: blur(16px);
77
+ border: 1px solid var(--glass-border);
78
+ border-radius: 16px;
79
+ padding: 24px;
80
+ z-index: 10;
81
+ display: flex;
82
+ flex-direction: column;
83
+ gap: 16px;
84
+ box-shadow: 0 20px 40px rgba(0,0,0,0.4);
85
+ max-height: 90vh;
86
+ overflow-y: auto;
87
+ transition: transform 0.3s ease;
88
+ }
89
+
90
+ .panel-header h1 {
91
+ font-size: 1.2rem;
92
+ font-weight: 700;
93
+ background: linear-gradient(135deg, #fff, var(--primary));
94
+ -webkit-background-clip: text;
95
+ -webkit-text-fill-color: transparent;
96
+ margin-bottom: 4px;
97
+ }
98
+
99
+ .panel-header p {
100
+ font-size: 0.75rem;
101
+ color: #94a3b8;
102
+ }
103
+
104
+ .control-group {
105
+ display: flex;
106
+ flex-direction: column;
107
+ gap: 8px;
108
+ }
109
+
110
+ label {
111
+ font-size: 0.75rem;
112
+ color: #cbd5e1;
113
+ display: flex;
114
+ justify-content: space-between;
115
+ font-weight: 500;
116
+ text-transform: uppercase;
117
+ letter-spacing: 0.5px;
118
+ }
119
+
120
+ .value-tag {
121
+ color: var(--primary);
122
+ font-family: monospace;
123
+ font-size: 0.85rem;
124
+ }
125
+
126
+ input[type="range"] {
127
+ -webkit-appearance: none;
128
+ width: 100%;
129
+ height: 4px;
130
+ background: rgba(255,255,255,0.1);
131
+ border-radius: 2px;
132
+ outline: none;
133
+ }
134
+
135
+ input[type="range"]::-webkit-slider-thumb {
136
+ -webkit-appearance: none;
137
+ width: 14px;
138
+ height: 14px;
139
+ border-radius: 50%;
140
+ background: var(--primary);
141
+ cursor: pointer;
142
+ transition: transform 0.2s;
143
+ box-shadow: 0 0 10px rgba(0, 210, 211, 0.5);
144
+ }
145
+
146
+ input[type="range"]::-webkit-slider-thumb:hover {
147
+ transform: scale(1.2);
148
+ }
149
+
150
+ .btn-grid {
151
+ display: grid;
152
+ grid-template-columns: 1fr 1fr;
153
+ gap: 8px;
154
+ }
155
+
156
+ button {
157
+ background: rgba(255,255,255,0.03);
158
+ border: 1px solid var(--glass-border);
159
+ color: #e2e8f0;
160
+ padding: 10px;
161
+ border-radius: 8px;
162
+ cursor: pointer;
163
+ font-weight: 600;
164
+ font-size: 0.8rem;
165
+ transition: all 0.2s ease;
166
+ font-family: 'Inter', sans-serif;
167
+ }
168
+
169
+ button:hover {
170
+ background: rgba(255,255,255,0.08);
171
+ border-color: rgba(255,255,255,0.2);
172
+ }
173
+
174
+ button.primary {
175
+ background: var(--primary);
176
+ color: #000;
177
+ border: none;
178
+ box-shadow: 0 4px 12px rgba(0, 210, 211, 0.3);
179
+ }
180
+
181
+ button.primary:hover {
182
+ background: #48dbfb;
183
+ transform: translateY(-1px);
184
+ }
185
+
186
+ .checkbox-group {
187
+ display: flex;
188
+ align-items: center;
189
+ gap: 10px;
190
+ font-size: 0.75rem;
191
+ color: #94a3b8;
192
+ cursor: pointer;
193
+ padding: 5px 0;
194
+ }
195
+
196
+ .checkbox-group input {
197
+ accent-color: var(--primary);
198
+ width: 14px;
199
+ height: 14px;
200
+ }
201
+
202
+ .stats {
203
+ font-size: 0.7rem;
204
+ color: #576574;
205
+ text-align: center;
206
+ padding-top: 10px;
207
+ border-top: 1px solid var(--glass-border);
208
+ font-family: monospace;
209
+ }
210
+
211
+ /* Loading overlay */
212
+ #loading {
213
+ position: fixed;
214
+ top: 0; left: 0; width: 100%; height: 100%;
215
+ background: #000;
216
+ z-index: 200;
217
+ display: flex;
218
+ flex-direction: column;
219
+ justify-content: center;
220
+ align-items: center;
221
+ color: var(--primary);
222
+ transition: opacity 0.8s ease-out;
223
+ pointer-events: none;
224
+ font-weight: 300;
225
+ letter-spacing: 2px;
226
+ }
227
+
228
+ @media (max-width: 600px) {
229
+ .ui-panel {
230
+ width: calc(100% - 30px);
231
+ right: 15px;
232
+ bottom: 20px;
233
+ top: auto;
234
+ max-height: 55vh;
235
+ }
236
+ .brand-link {
237
+ top: 10px;
238
+ padding: 6px 16px;
239
+ }
240
+ }
241
+ </style>
242
+
243
+ <script type="importmap">
244
+ {
245
+ "imports": {
246
+ "three": "https://unpkg.com/three@0.160.0/build/three.module.js",
247
+ "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
248
+ }
249
+ }
250
+ </script>
251
+ </head>
252
+ <body>
253
+
254
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="brand-link">Built with anycoder</a>
255
+
256
+ <div id="loading">
257
+ <div>GENERATING COMPLEXITY</div>
258
+ </div>
259
+
260
+ <div id="canvas-container"></div>
261
+
262
+ <div class="ui-panel" id="uiPanel">
263
+ <div class="panel-header">
264
+ <h1>Complex Life Engine</h1>
265
+ <p>Large Scale Structure Simulation</p>
266
+ </div>
267
+
268
+ <div class="control-group">
269
+ <label>Particles <span id="val-count" class="value-tag">1400</span></label>
270
+ <input type="range" id="inp-count" min="500" max="2500" step="100" value="1400">
271
+ </div>
272
+
273
+ <div class="control-group">
274
+ <label>Interaction Radius <span id="val-radius" class="value-tag">80</span></label>
275
+ <input type="range" id="inp-radius" min="30" max="150" value="80">
276
+ </div>
277
+
278
+ <div class="control-group">
279
+ <label>Force Strength <span id="val-force" class="value-tag">0.6</span></label>
280
+ <input type="range" id="inp-force" min="0.1" max="3.0" step="0.1" value="0.6">
281
+ </div>
282
+
283
+ <div class="control-group">
284
+ <label>Friction (Stability) <span id="val-friction" class="value-tag">0.88</span></label>
285
+ <input type="range" id="inp-friction" min="0.50" max="0.98" step="0.01" value="0.88">
286
+ </div>
287
+
288
+ <label class="checkbox-group">
289
+ <input type="checkbox" id="inp-symmetric" checked>
290
+ Symmetric Rules (Physics Law)
291
+ </label>
292
+
293
+ <button id="btn-randomize" class="primary">🎲 Evolve New Rules</button>
294
+
295
+ <div class="btn-grid">
296
+ <button id="btn-complex">🧬 Complex</button>
297
+ <button id="btn-membranes">🕸️ Membranes</button>
298
+ </div>
299
+ <div class="btn-grid">
300
+ <button id="btn-snake">🐍 Snake</button>
301
+ <button id="btn-cells">🦠 Cells</button>
302
+ </div>
303
+
304
+ <div class="stats" id="stats">
305
+ Initializing...
306
+ </div>
307
+ </div>
308
+
309
+ <script type="module">
310
+ import * as THREE from 'three';
311
+ import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
312
+ import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
313
+ import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
314
+ import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
315
+
316
+ // --- Configuration ---
317
+ const CONFIG = {
318
+ particleCount: 1400,
319
+ types: 6,
320
+ radius: 80, // High radius for larger structures
321
+ forceFactor: 0.6, // Lower force to balance high radius
322
+ friction: 0.88, // High friction to stabilize large structures
323
+ worldSize: 300, // Larger world
324
+ symmetric: true,
325
+ maxSpeed: 2.5,
326
+ repulsionRadius: 0.3 // Distance where particles strongly push apart
327
+ };
328
+
329
+ // --- State ---
330
+ let particles = [];
331
+ let rules = [];
332
+ let typeColors = [];
333
+
334
+ // --- Three.js Setup ---
335
+ const container = document.getElementById('canvas-container');
336
+ const scene = new THREE.Scene();
337
+ scene.fog = new THREE.FogExp2(0x000000, 0.0015);
338
+
339
+ const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1500);
340
+ camera.position.set(0, 200, 400);
341
+
342
+ const renderer = new THREE.WebGLRenderer({ antialias: false, powerPreference: "high-performance" });
343
+ renderer.setSize(window.innerWidth, window.innerHeight);
344
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
345
+ container.appendChild(renderer.domElement);
346
+
347
+ // Post-processing
348
+ const renderScene = new RenderPass(scene, camera);
349
+ const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
350
+ bloomPass.threshold = 0.1;
351
+ bloomPass.strength = 0.8; // Stronger bloom for sci-fi effect
352
+ bloomPass.radius = 0.5;
353
+
354
+ const composer = new EffectComposer(renderer);
355
+ composer.addPass(renderScene);
356
+ composer.addPass(bloomPass);
357
+
358
+ const controls = new OrbitControls(camera, renderer.domElement);
359
+ controls.enableDamping = true;
360
+ controls.autoRotate = true;
361
+ controls.autoRotateSpeed = 0.3;
362
+
363
+ // Lights
364
+ const ambientLight = new THREE.AmbientLight(0x222222);
365
+ scene.add(ambientLight);
366
+
367
+ const lights = [];
368
+ const lightColors = [0x00d2d3, 0xff9ff3, 0xfeca57];
369
+ for(let i=0; i<3; i++) {
370
+ const l = new THREE.PointLight(lightColors[i], 1, 500);
371
+ l.position.set(
372
+ (Math.random()-0.5)*300,
373
+ (Math.random()-0.5)*300,
374
+ (Math.random()-0.5)*300
375
+ );
376
+ scene.add(l);
377
+ lights.push(l);
378
+ }
379
+
380
+ // --- Particle System ---
381
+ const geometry = new THREE.SphereGeometry(1, 8, 8); // Low poly is fine with bloom
382
+ const material = new THREE.MeshBasicMaterial({ color: 0xffffff }); // Basic material is faster
383
+
384
+ let instancedMesh;
385
+ const dummy = new THREE.Object3D();
386
+ const _color = new THREE.Color();
387
+
388
+ // --- Logic ---
389
+
390
+ function initSystem() {
391
+ if (instancedMesh) {
392
+ scene.remove(instancedMesh);
393
+ instancedMesh.dispose();
394
+ }
395
+
396
+ // Distinct color palette
397
+ typeColors = [
398
+ new THREE.Color(0x00d2d3), // Cyan
399
+ new THREE.Color(0xff6b6b), // Red
400
+ new THREE.Color(0xfeca57), // Yellow
401
+ new THREE.Color(0x5f27cd), // Purple
402
+ new THREE.Color(0x54a0ff), // Blue
403
+ new THREE.Color(0x1dd1a1) // Green
404
+ ];
405
+
406
+ particles = new Float32Array(CONFIG.particleCount * 8);
407
+
408
+ for (let i = 0; i < CONFIG.particleCount; i++) {
409
+ const i8 = i * 8;
410
+ particles[i8] = (Math.random() - 0.5) * CONFIG.worldSize;
411
+ particles[i8+1] = (Math.random() - 0.5) * CONFIG.worldSize;
412
+ particles[i8+2] = (Math.random() - 0.5) * CONFIG.worldSize;
413
+ particles[i8+3] = 0; // vx
414
+ particles[i8+4] = 0; // vy
415
+ particles[i8+5] = 0; // vz
416
+ particles[i8+6] = Math.floor(Math.random() * CONFIG.types); // type
417
+ particles[i8+7] = 1.0; // Scale placeholder
418
+ }
419
+
420
+ instancedMesh = new THREE.InstancedMesh(geometry, material, CONFIG.particleCount);
421
+ instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
422
+ scene.add(instancedMesh);
423
+
424
+ randomizeRules();
425
+
426
+ setTimeout(() => {
427
+ document.getElementById('loading').style.opacity = 0;
428
+ }, 500);
429
+ }
430
+
431
+ function randomizeRules(mode = 'random') {
432
+ rules = [];
433
+ for (let i = 0; i < CONFIG.types; i++) {
434
+ rules[i] = [];
435
+ for (let j = 0; j < CONFIG.types; j++) {
436
+ if (mode === 'discrete') {
437
+ // Discrete steps create sharper structures
438
+ const steps = [-1.0, -0.5, 0.0, 0.2, 0.5, 0.8];
439
+ rules[i][j] = steps[Math.floor(Math.random() * steps.length)];
440
+ } else {
441
+ rules[i][j] = Math.random() * 2 - 1;
442
+ }
443
+ }
444
+ }
445
+
446
+ if (CONFIG.symmetric) {
447
+ for (let i = 0; i < CONFIG.types; i++) {
448
+ for (let j = i + 1; j < CONFIG.types; j++) {
449
+ rules[j][i] = rules[i][j];
450
+ }
451
+ // Self-interaction usually needs to be slightly repulsive or weakly attractive for volume
452
+ rules[i][i] = Math.max(-1.0, Math.min(0.5, rules[i][i]));
453
+ }
454
+ }
455
+ }
456
+
457
+ function setPreset(name) {
458
+ // Base settings for stability
459
+ CONFIG.symmetric = true;
460
+ document.getElementById('inp-symmetric').checked = true;
461
+
462
+ if (name === 'complex') {
463
+ // Large, slow-moving complex structures
464
+ CONFIG.radius = 90;
465
+ CONFIG.friction = 0.88;
466
+ CONFIG.forceFactor = 0.5;
467
+ CONFIG.maxSpeed = 2.0;
468
+ CONFIG.repulsionRadius = 0.3;
469
+ randomizeRules('discrete');
470
+ } else if (name === 'membranes') {
471
+ // Sheet-like structures
472
+ CONFIG.radius = 70;
473
+ CONFIG.friction = 0.82;
474
+ CONFIG.forceFactor = 0.8;
475
+ CONFIG.maxSpeed = 3.0;
476
+ CONFIG.repulsionRadius = 0.25;
477
+ // Generate specific membrane rules
478
+ randomizeRules();
479
+ // Hack: enforce some strong chains
480
+ for(let i=0; i<CONFIG.types; i++) {
481
+ rules[i][i] = 0.6; // Like self
482
+ rules[i][(i+1)%CONFIG.types] = 0.2;
483
+ }
484
+ } else if (name === 'snake') {
485
+ CONFIG.radius = 60;
486
+ CONFIG.friction = 0.85;
487
+ CONFIG.forceFactor = 1.2;
488
+ randomizeRules();
489
+ } else if (name === 'cells') {
490
+ // High repulsion short range, high attraction long range
491
+ CONFIG.radius = 100;
492
+ CONFIG.friction = 0.90; // Very stable
493
+ CONFIG.forceFactor = 0.4;
494
+ randomizeRules('discrete');
495
+ }
496
+ updateUiValues();
497
+ }
498
+
499
+ // --- Physics Loop ---
500
+
501
+ function updatePhysics() {
502
+ const count = CONFIG.particleCount;
503
+ const rMax = CONFIG.radius;
504
+ const rMaxSq = rMax * rMax;
505
+ const forceFactor = CONFIG.forceFactor;
506
+ const friction = CONFIG.friction;
507
+ const worldSize = CONFIG.worldSize;
508
+ const halfWorld = worldSize / 2;
509
+ const maxSpeed = CONFIG.maxSpeed;
510
+ const repulsionRad = CONFIG.repulsionRadius; // 0.0 to 1.0 of rMax
511
+
512
+ for (let i = 0; i < count; i++) {
513
+ const i8 = i * 8;
514
+ let fx = 0, fy = 0, fz = 0;
515
+ const typeI = particles[i8+6];
516
+
517
+ const px = particles[i8];
518
+ const py = particles[i8+1];
519
+ const pz = particles[i8+2];
520
+
521
+ for (let j = 0; j < count; j++) {
522
+ if (i === j) continue;
523
+
524
+ const j8 = j * 8;
525
+ let dx = particles[j8] - px;
526
+ let dy = particles[j8+1] - py;
527
+ let dz = particles[j8+2] - pz;
528
+
529
+ // Toroidal Wrap
530
+ if (dx > halfWorld) dx -= worldSize;
531
+ else if (dx < -halfWorld) dx += worldSize;
532
+ if (dy > halfWorld) dy -= worldSize;
533
+ else if (dy < -halfWorld) dy += worldSize;
534
+ if (dz > halfWorld) dz -= worldSize;
535
+ else if (dz < -halfWorld) dz += worldSize;
536
+
537
+ const distSq = dx*dx + dy*dy + dz*dz;
538
+
539
+ if (distSq > 0.01 && distSq < rMaxSq) {
540
+ const dist = Math.sqrt(distSq);
541
+ const q = dist / rMax;
542
+ const typeJ = particles[j8+6];
543
+
544
+ let f = 0;
545
+
546
+ // STABILITY LOGIC:
547
+ // 1. Strong Repulsion Zone (The Core)
548
+ if (q < repulsionRad) {
549
+ // Normalize q to 0..1 within the repulsion zone
550
+ const subQ = q / repulsionRad;
551
+ // Force scales up massively as we get closer to 0
552
+ f = (subQ - 1.0) * 2.0;
553
+ } else {
554
+ // 2. Interaction Zone
555
+ // Normalize q to 0..1 within interaction zone
556
+ const subQ = (q - repulsionRad) / (1.0 - repulsionRad);
557
+
558
+ // Peak curve: 0 at edges, 1 in middle
559
+ const strength = rules[typeI][typeJ];
560
+
561
+ // Smooth transition curve
562
+ f = strength * (1.0 - Math.abs(2.0 * subQ - 1.0));
563
+
564
+ // Alternative: Linear falloff
565
+ // f = strength * (1.0 - subQ);
566
+ }
567
+
568
+ // Apply force
569
+ const fScaled = (f * forceFactor) / dist;
570
+ fx += dx * fScaled;
571
+ fy += dy * fScaled;
572
+ fz += dz * fScaled;
573
+ }
574
+ }
575
+
576
+ // Integration
577
+ particles[i8+3] = (particles[i8+3] + fx) * friction;
578
+ particles[i8+4] = (particles[i8+4] + fy) * friction;
579
+ particles[i8+5] = (particles[i8+5] + fz) * friction;
580
+
581
+ // Velocity Cap
582
+ const vx = particles[i8+3];
583
+ const vy = particles[i8+4];
584
+ const vz = particles[i8+5];
585
+ const speedSq = vx*vx + vy*vy + vz*vz;
586
+
587
+ if (speedSq > maxSpeed * maxSpeed) {
588
+ const scale = maxSpeed / Math.sqrt(speedSq);
589
+ particles[i8+3] *= scale;
590
+ particles[i8+4] *= scale;
591
+ particles[i8+5] *= scale;
592
+ }
593
+
594
+ // Position Update
595
+ particles[i8] += particles[i8+3];
596
+ particles[i8+1] += particles[i8+4];
597
+ particles[i8+2] += particles[i8+5];
598
+
599
+ // Boundary Wrap
600
+ if (particles[i8] <= -halfWorld) particles[i8] += worldSize;
601
+ if (particles[i8] >= halfWorld) particles[i8] -= worldSize;
602
+ if (particles[i8+1] <= -halfWorld) particles[i8+1] += worldSize;
603
+ if (particles[i8+1] >= halfWorld) particles[i8+1] -= worldSize;
604
+ if (particles[i8+2] <= -halfWorld) particles[i8+2] += worldSize;
605
+ if (particles[i8+2] >= halfWorld) particles[i8+2] -= worldSize;
606
+ }
607
+ }
608
+
609
+ function updateVisuals() {
610
+ const count = CONFIG.particleCount;
611
+ const rMax = CONFIG.radius;
612
+
613
+ for (let i = 0; i < count; i++) {
614
+ const i8 = i * 8;
615
+
616
+ dummy.position.set(
617
+ particles[i8],
618
+ particles[i8+1],
619
+ particles[i8+2]
620
+ );
621
+
622
+ // Dynamic sizing based on neighbors/density could be cool,
623
+ // but for now let's scale based on interaction radius to keep it looking "structural"
624
+ // Smaller particles look better for large structures
625
+ const scale = rMax / 35.0;
626
+ dummy.scale.set(scale, scale, scale);
627
+ dummy.updateMatrix();
628
+
629
+ instancedMesh.setMatrixAt(i, dummy.matrix);
630
+
631
+ const type = particles[i8+6];
632
+ _color.copy(typeColors[type]);
633
+
634
+ instancedMesh.setColorAt(i, _color);
635
+ }
636
+ instancedMesh.instanceMatrix.needsUpdate = true;
637
+ if(instancedMesh.instanceColor) instancedMesh.instanceColor.needsUpdate = true;
638
+ }
639
+
640
+ // --- Loop ---
641
+ const statsEl = document.getElementById('stats');
642
+ let frames = 0;
643
+ let lastTime = 0;
644
+
645
+ function animate(time) {
646
+ requestAnimationFrame(animate);
647
+
648
+ frames++;
649
+ if (time - lastTime > 1000) {
650
+ statsEl.innerText = `FPS: ${frames} | Radius: ${CONFIG.radius} | Friction: ${CONFIG.friction}`;
651
+ frames = 0;
652
+ lastTime = time;
653
+ }
654
+
655
+ updatePhysics();
656
+ updateVisuals();
657
+ controls.update();
658
+ composer.render();
659
+ }
660
+
661
+ // --- UI Handlers ---
662
+
663
+ document.getElementById('inp-count').addEventListener('change', (e) => {
664
+ CONFIG.particleCount = parseInt(e.target.value);
665
+ document.getElementById('val-count').innerText = CONFIG.particleCount;
666
+ initSystem();
667
+ });
668
+
669
+ document.getElementById('inp-radius').addEventListener('input', (e) => {
670
+ CONFIG.radius = parseInt(e.target.value);
671
+ document.getElementById('val-radius').innerText = CONFIG.radius;
672
+ });
673
+
674
+ document.getElementById('inp-force').addEventListener('input', (e) => {
675
+ CONFIG.forceFactor = parseFloat(e.target.value);
676
+ document.getElementById('val-force').innerText = CONFIG.forceFactor;
677
+ });
678
+
679
+ document.getElementById('inp-friction').addEventListener('input', (e) => {
680
+ CONFIG.friction = parseFloat(e.target.value);
681
+ document.getElementById('val-friction').innerText = CONFIG.friction;
682
+ });
683
+
684
+ document.getElementById('inp-symmetric').addEventListener('change', (e) => {
685
+ CONFIG.symmetric = e.target.checked;
686
+ });
687
+
688
+ document.getElementById('btn-randomize').addEventListener('click', () => {
689
+ randomizeRules('discrete');
690
+ });
691
+
692
+ document.getElementById('btn-complex').addEventListener('click', () => setPreset('complex'));
693
+ document.getElementById('btn-membranes').addEventListener('click', () => setPreset('membranes'));
694
+ document.getElementById('btn-snake').addEventListener('click', () => setPreset('snake'));
695
+ document.getElementById('btn-cells').addEventListener('click', () => setPreset('cells'));
696
+
697
+ function updateUiValues() {
698
+ document.getElementById('inp-friction').value = CONFIG.friction;
699
+ document.getElementById('val-friction').innerText = CONFIG.friction;
700
+ document.getElementById('inp-force').value = CONFIG.forceFactor;
701
+ document.getElementById('val-force').innerText = CONFIG.forceFactor;
702
+ document.getElementById('inp-radius').value = CONFIG.radius;
703
+ document.getElementById('val-radius').innerText = CONFIG.radius;
704
+ }
705
+
706
+ window.addEventListener('resize', () => {
707
+ camera.aspect = window.innerWidth / window.innerHeight;
708
+ camera.updateProjectionMatrix();
709
+ renderer.setSize(window.innerWidth, window.innerHeight);
710
+ composer.setSize(window.innerWidth, window.innerHeight);
711
+ });
712
+
713
+ // Initial Boot
714
+ initSystem();
715
+ // Set initial complex preset
716
+ setPreset('complex');
717
+ animate(0);
718
+
719
+ </script>
720
+ </body>
721
+ </html>