@buley/hexgrid-3d 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/build_log.txt +500 -0
  2. package/build_src_log.txt +8 -0
  3. package/examples/basic-usage.tsx +19 -19
  4. package/package.json +1 -1
  5. package/public/hexgrid-worker.js +2350 -1638
  6. package/site/.eslintrc.json +3 -0
  7. package/site/DEPLOYMENT.md +196 -0
  8. package/site/INDEX.md +127 -0
  9. package/site/QUICK_START.md +86 -0
  10. package/site/README.md +85 -0
  11. package/site/SITE_SUMMARY.md +180 -0
  12. package/site/next.config.js +12 -0
  13. package/site/package.json +26 -0
  14. package/site/src/app/docs/page.tsx +148 -0
  15. package/site/src/app/examples/page.tsx +133 -0
  16. package/site/src/app/globals.css +160 -0
  17. package/site/src/app/layout.tsx +29 -0
  18. package/site/src/app/page.tsx +163 -0
  19. package/site/tsconfig.json +29 -0
  20. package/site/vercel.json +6 -0
  21. package/src/Snapshot.ts +790 -585
  22. package/src/adapters/DashAdapter.ts +57 -0
  23. package/src/adapters.ts +16 -18
  24. package/src/algorithms/AdvancedStatistics.ts +58 -24
  25. package/src/algorithms/BayesianStatistics.ts +43 -12
  26. package/src/algorithms/FlowField.ts +30 -6
  27. package/src/algorithms/FlowField3D.ts +573 -0
  28. package/src/algorithms/FluidSimulation.ts +19 -3
  29. package/src/algorithms/FluidSimulation3D.ts +664 -0
  30. package/src/algorithms/GraphAlgorithms.ts +19 -12
  31. package/src/algorithms/OutlierDetection.ts +72 -38
  32. package/src/algorithms/ParticleSystem.ts +12 -2
  33. package/src/algorithms/ParticleSystem3D.ts +567 -0
  34. package/src/algorithms/index.ts +14 -8
  35. package/src/compat.ts +10 -10
  36. package/src/components/HexGrid.tsx +10 -23
  37. package/src/components/NarrationOverlay.tsx +140 -52
  38. package/src/components/index.ts +2 -1
  39. package/src/features.ts +31 -31
  40. package/src/index.ts +11 -11
  41. package/src/lib/narration.ts +17 -0
  42. package/src/lib/stats-tracker.ts +25 -0
  43. package/src/lib/theme-colors.ts +12 -0
  44. package/src/math/HexCoordinates.ts +849 -4
  45. package/src/math/Matrix4.ts +2 -12
  46. package/src/math/Vector3.ts +49 -1
  47. package/src/math/index.ts +6 -6
  48. package/src/note-adapter.ts +50 -42
  49. package/src/ontology-adapter.ts +30 -23
  50. package/src/stores/uiStore.ts +34 -34
  51. package/src/types/shared-utils.d.ts +10 -0
  52. package/src/types.ts +110 -98
  53. package/src/utils/image-utils.ts +9 -6
  54. package/src/wasm/HexGridWasmWrapper.ts +436 -388
  55. package/src/wasm/index.ts +2 -2
  56. package/src/workers/hexgrid-math.ts +40 -35
  57. package/src/workers/hexgrid-worker.worker.ts +1992 -1018
  58. package/tsconfig.json +21 -14
@@ -0,0 +1,664 @@
1
+ /**
2
+ * 3D Stable Fluids Implementation
3
+ *
4
+ * Based on Jos Stam's "Stable Fluids" paper (SIGGRAPH 1999)
5
+ * Extended to 3D for the Pensieve particle visualization.
6
+ *
7
+ * Key operations:
8
+ * 1. Add sources (density, velocity)
9
+ * 2. Diffuse (spread quantities)
10
+ * 3. Advect (move quantities along velocity field)
11
+ * 4. Project (make velocity field divergence-free)
12
+ */
13
+
14
+ import { Vector3 } from '../math/Vector3';
15
+
16
+ export interface FluidConfig3D {
17
+ /** Grid width in cells */
18
+ width: number;
19
+ /** Grid height in cells */
20
+ height: number;
21
+ /** Grid depth in cells */
22
+ depth: number;
23
+ /** Viscosity coefficient (0 = inviscid, higher = more viscous) */
24
+ viscosity: number;
25
+ /** Diffusion rate for density (0 = no diffusion) */
26
+ diffusion: number;
27
+ /** Number of iterations for linear solver (higher = more accurate but slower) */
28
+ iterations?: number;
29
+ }
30
+
31
+ /**
32
+ * 3D Stable Fluids solver for realistic fluid dynamics
33
+ */
34
+ export class StableFluids3D {
35
+ private width: number;
36
+ private height: number;
37
+ private depth: number;
38
+ private size: number;
39
+ private viscosity: number;
40
+ private diffusion: number;
41
+ private iterations: number;
42
+
43
+ // Current state
44
+ private density: Float32Array;
45
+ private velocityX: Float32Array;
46
+ private velocityY: Float32Array;
47
+ private velocityZ: Float32Array;
48
+
49
+ // Previous state (for advection)
50
+ private density0: Float32Array;
51
+ private velocityX0: Float32Array;
52
+ private velocityY0: Float32Array;
53
+ private velocityZ0: Float32Array;
54
+
55
+ constructor(config: FluidConfig3D) {
56
+ this.width = Math.max(1, Math.round(config.width));
57
+ this.height = Math.max(1, Math.round(config.height));
58
+ this.depth = Math.max(1, Math.round(config.depth));
59
+ this.size = this.width * this.height * this.depth;
60
+ this.viscosity = config.viscosity;
61
+ this.diffusion = config.diffusion;
62
+ this.iterations = config.iterations ?? 4;
63
+
64
+ // Allocate arrays
65
+ this.density = new Float32Array(this.size);
66
+ this.velocityX = new Float32Array(this.size);
67
+ this.velocityY = new Float32Array(this.size);
68
+ this.velocityZ = new Float32Array(this.size);
69
+
70
+ this.density0 = new Float32Array(this.size);
71
+ this.velocityX0 = new Float32Array(this.size);
72
+ this.velocityY0 = new Float32Array(this.size);
73
+ this.velocityZ0 = new Float32Array(this.size);
74
+ }
75
+
76
+ /**
77
+ * Add density at a point with radius falloff
78
+ */
79
+ addDensity(
80
+ x: number,
81
+ y: number,
82
+ z: number,
83
+ amount: number,
84
+ radius: number
85
+ ): void {
86
+ const r2 = radius * radius;
87
+ const ix0 = Math.max(0, Math.floor(x - radius));
88
+ const ix1 = Math.min(this.width - 1, Math.ceil(x + radius));
89
+ const iy0 = Math.max(0, Math.floor(y - radius));
90
+ const iy1 = Math.min(this.height - 1, Math.ceil(y + radius));
91
+ const iz0 = Math.max(0, Math.floor(z - radius));
92
+ const iz1 = Math.min(this.depth - 1, Math.ceil(z + radius));
93
+
94
+ for (let iz = iz0; iz <= iz1; iz++) {
95
+ for (let iy = iy0; iy <= iy1; iy++) {
96
+ for (let ix = ix0; ix <= ix1; ix++) {
97
+ const dx = ix - x;
98
+ const dy = iy - y;
99
+ const dz = iz - z;
100
+ const dist2 = dx * dx + dy * dy + dz * dz;
101
+ if (dist2 < r2) {
102
+ const falloff = 1 - dist2 / r2;
103
+ const idx = this.indexFor(ix, iy, iz);
104
+ this.density[idx] += amount * falloff;
105
+ }
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Add force at a point with radius falloff
113
+ */
114
+ addForce(pos: Vector3, force: Vector3, radius: number): void {
115
+ const r2 = radius * radius;
116
+ const ix0 = Math.max(0, Math.floor(pos.x - radius));
117
+ const ix1 = Math.min(this.width - 1, Math.ceil(pos.x + radius));
118
+ const iy0 = Math.max(0, Math.floor(pos.y - radius));
119
+ const iy1 = Math.min(this.height - 1, Math.ceil(pos.y + radius));
120
+ const iz0 = Math.max(0, Math.floor(pos.z - radius));
121
+ const iz1 = Math.min(this.depth - 1, Math.ceil(pos.z + radius));
122
+
123
+ for (let iz = iz0; iz <= iz1; iz++) {
124
+ for (let iy = iy0; iy <= iy1; iy++) {
125
+ for (let ix = ix0; ix <= ix1; ix++) {
126
+ const dx = ix - pos.x;
127
+ const dy = iy - pos.y;
128
+ const dz = iz - pos.z;
129
+ const dist2 = dx * dx + dy * dy + dz * dz;
130
+ if (dist2 < r2) {
131
+ const falloff = 1 - dist2 / r2;
132
+ const idx = this.indexFor(ix, iy, iz);
133
+ this.velocityX[idx] += force.x * falloff;
134
+ this.velocityY[idx] += force.y * falloff;
135
+ this.velocityZ[idx] += force.z * falloff;
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Step the simulation forward by dt seconds
144
+ */
145
+ step(dt: number): void {
146
+ // Velocity step
147
+ this.velocityStep(dt);
148
+
149
+ // Density step
150
+ this.densityStep(dt);
151
+ }
152
+
153
+ /**
154
+ * Get velocity at a point (with trilinear interpolation)
155
+ */
156
+ getVelocityAt(pos: Vector3): Vector3 {
157
+ return new Vector3(
158
+ this.sampleField(this.velocityX, pos.x, pos.y, pos.z),
159
+ this.sampleField(this.velocityY, pos.x, pos.y, pos.z),
160
+ this.sampleField(this.velocityZ, pos.x, pos.y, pos.z)
161
+ );
162
+ }
163
+
164
+ /**
165
+ * Get density at a point (with trilinear interpolation)
166
+ */
167
+ getDensityAt(pos: Vector3): number {
168
+ return this.sampleField(this.density, pos.x, pos.y, pos.z);
169
+ }
170
+
171
+ /**
172
+ * Get the raw velocity fields for direct access
173
+ */
174
+ getVelocityFields(): { x: Float32Array; y: Float32Array; z: Float32Array } {
175
+ return { x: this.velocityX, y: this.velocityY, z: this.velocityZ };
176
+ }
177
+
178
+ /**
179
+ * Get the raw density field
180
+ */
181
+ getDensityField(): Float32Array {
182
+ return this.density;
183
+ }
184
+
185
+ /**
186
+ * Get grid dimensions
187
+ */
188
+ getDimensions(): { width: number; height: number; depth: number } {
189
+ return { width: this.width, height: this.height, depth: this.depth };
190
+ }
191
+
192
+ /**
193
+ * Clear all fields
194
+ */
195
+ clear(): void {
196
+ this.density.fill(0);
197
+ this.velocityX.fill(0);
198
+ this.velocityY.fill(0);
199
+ this.velocityZ.fill(0);
200
+ }
201
+
202
+ // =========================================================================
203
+ // PRIVATE METHODS
204
+ // =========================================================================
205
+
206
+ private velocityStep(dt: number): void {
207
+ // Add sources
208
+ this.addSource(this.velocityX, this.velocityX0, dt);
209
+ this.addSource(this.velocityY, this.velocityY0, dt);
210
+ this.addSource(this.velocityZ, this.velocityZ0, dt);
211
+
212
+ // Diffuse
213
+ this.swap(this.velocityX0, this.velocityX);
214
+ this.swap(this.velocityY0, this.velocityY);
215
+ this.swap(this.velocityZ0, this.velocityZ);
216
+
217
+ this.diffuse(1, this.velocityX, this.velocityX0, this.viscosity, dt);
218
+ this.diffuse(2, this.velocityY, this.velocityY0, this.viscosity, dt);
219
+ this.diffuse(3, this.velocityZ, this.velocityZ0, this.viscosity, dt);
220
+
221
+ // Project to make divergence-free
222
+ this.project(
223
+ this.velocityX,
224
+ this.velocityY,
225
+ this.velocityZ,
226
+ this.velocityX0,
227
+ this.velocityY0
228
+ );
229
+
230
+ // Advect
231
+ this.swap(this.velocityX0, this.velocityX);
232
+ this.swap(this.velocityY0, this.velocityY);
233
+ this.swap(this.velocityZ0, this.velocityZ);
234
+
235
+ this.advect(
236
+ 1,
237
+ this.velocityX,
238
+ this.velocityX0,
239
+ this.velocityX0,
240
+ this.velocityY0,
241
+ this.velocityZ0,
242
+ dt
243
+ );
244
+ this.advect(
245
+ 2,
246
+ this.velocityY,
247
+ this.velocityY0,
248
+ this.velocityX0,
249
+ this.velocityY0,
250
+ this.velocityZ0,
251
+ dt
252
+ );
253
+ this.advect(
254
+ 3,
255
+ this.velocityZ,
256
+ this.velocityZ0,
257
+ this.velocityX0,
258
+ this.velocityY0,
259
+ this.velocityZ0,
260
+ dt
261
+ );
262
+
263
+ // Project again
264
+ this.project(
265
+ this.velocityX,
266
+ this.velocityY,
267
+ this.velocityZ,
268
+ this.velocityX0,
269
+ this.velocityY0
270
+ );
271
+ }
272
+
273
+ private densityStep(dt: number): void {
274
+ // Add sources
275
+ this.addSource(this.density, this.density0, dt);
276
+
277
+ // Diffuse
278
+ this.swap(this.density0, this.density);
279
+ this.diffuse(0, this.density, this.density0, this.diffusion, dt);
280
+
281
+ // Advect
282
+ this.swap(this.density0, this.density);
283
+ this.advect(
284
+ 0,
285
+ this.density,
286
+ this.density0,
287
+ this.velocityX,
288
+ this.velocityY,
289
+ this.velocityZ,
290
+ dt
291
+ );
292
+ }
293
+
294
+ private addSource(
295
+ target: Float32Array,
296
+ source: Float32Array,
297
+ dt: number
298
+ ): void {
299
+ for (let i = 0; i < this.size; i++) {
300
+ target[i] += dt * source[i];
301
+ }
302
+ source.fill(0);
303
+ }
304
+
305
+ private diffuse(
306
+ b: number,
307
+ x: Float32Array,
308
+ x0: Float32Array,
309
+ diff: number,
310
+ dt: number
311
+ ): void {
312
+ const a = dt * diff * this.width * this.height * this.depth;
313
+ this.linearSolve(b, x, x0, a, 1 + 6 * a);
314
+ }
315
+
316
+ private advect(
317
+ b: number,
318
+ d: Float32Array,
319
+ d0: Float32Array,
320
+ u: Float32Array,
321
+ v: Float32Array,
322
+ w: Float32Array,
323
+ dt: number
324
+ ): void {
325
+ const dtx = dt * (this.width - 2);
326
+ const dty = dt * (this.height - 2);
327
+ const dtz = dt * (this.depth - 2);
328
+
329
+ for (let k = 1; k < this.depth - 1; k++) {
330
+ for (let j = 1; j < this.height - 1; j++) {
331
+ for (let i = 1; i < this.width - 1; i++) {
332
+ const idx = this.indexFor(i, j, k);
333
+
334
+ // Trace back
335
+ let x = i - dtx * u[idx];
336
+ let y = j - dty * v[idx];
337
+ let z = k - dtz * w[idx];
338
+
339
+ // Clamp to grid
340
+ x = Math.max(0.5, Math.min(this.width - 1.5, x));
341
+ y = Math.max(0.5, Math.min(this.height - 1.5, y));
342
+ z = Math.max(0.5, Math.min(this.depth - 1.5, z));
343
+
344
+ // Trilinear interpolation
345
+ d[idx] = this.sampleField(d0, x, y, z);
346
+ }
347
+ }
348
+ }
349
+
350
+ this.setBoundary(b, d);
351
+ }
352
+
353
+ private project(
354
+ u: Float32Array,
355
+ v: Float32Array,
356
+ w: Float32Array,
357
+ p: Float32Array,
358
+ div: Float32Array
359
+ ): void {
360
+ const h = 1.0 / Math.max(this.width, this.height, this.depth);
361
+
362
+ // Calculate divergence
363
+ for (let k = 1; k < this.depth - 1; k++) {
364
+ for (let j = 1; j < this.height - 1; j++) {
365
+ for (let i = 1; i < this.width - 1; i++) {
366
+ const idx = this.indexFor(i, j, k);
367
+ div[idx] =
368
+ -0.5 *
369
+ h *
370
+ (u[this.indexFor(i + 1, j, k)] -
371
+ u[this.indexFor(i - 1, j, k)] +
372
+ v[this.indexFor(i, j + 1, k)] -
373
+ v[this.indexFor(i, j - 1, k)] +
374
+ w[this.indexFor(i, j, k + 1)] -
375
+ w[this.indexFor(i, j, k - 1)]);
376
+ p[idx] = 0;
377
+ }
378
+ }
379
+ }
380
+
381
+ this.setBoundary(0, div);
382
+ this.setBoundary(0, p);
383
+
384
+ // Solve for pressure
385
+ this.linearSolve(0, p, div, 1, 6);
386
+
387
+ // Subtract pressure gradient from velocity
388
+ for (let k = 1; k < this.depth - 1; k++) {
389
+ for (let j = 1; j < this.height - 1; j++) {
390
+ for (let i = 1; i < this.width - 1; i++) {
391
+ const idx = this.indexFor(i, j, k);
392
+ u[idx] -=
393
+ (0.5 *
394
+ (p[this.indexFor(i + 1, j, k)] - p[this.indexFor(i - 1, j, k)])) /
395
+ h;
396
+ v[idx] -=
397
+ (0.5 *
398
+ (p[this.indexFor(i, j + 1, k)] - p[this.indexFor(i, j - 1, k)])) /
399
+ h;
400
+ w[idx] -=
401
+ (0.5 *
402
+ (p[this.indexFor(i, j, k + 1)] - p[this.indexFor(i, j, k - 1)])) /
403
+ h;
404
+ }
405
+ }
406
+ }
407
+
408
+ this.setBoundary(1, u);
409
+ this.setBoundary(2, v);
410
+ this.setBoundary(3, w);
411
+ }
412
+
413
+ private linearSolve(
414
+ b: number,
415
+ x: Float32Array,
416
+ x0: Float32Array,
417
+ a: number,
418
+ c: number
419
+ ): void {
420
+ const cRecip = 1.0 / c;
421
+
422
+ for (let iter = 0; iter < this.iterations; iter++) {
423
+ for (let k = 1; k < this.depth - 1; k++) {
424
+ for (let j = 1; j < this.height - 1; j++) {
425
+ for (let i = 1; i < this.width - 1; i++) {
426
+ const idx = this.indexFor(i, j, k);
427
+ x[idx] =
428
+ (x0[idx] +
429
+ a *
430
+ (x[this.indexFor(i + 1, j, k)] +
431
+ x[this.indexFor(i - 1, j, k)] +
432
+ x[this.indexFor(i, j + 1, k)] +
433
+ x[this.indexFor(i, j - 1, k)] +
434
+ x[this.indexFor(i, j, k + 1)] +
435
+ x[this.indexFor(i, j, k - 1)])) *
436
+ cRecip;
437
+ }
438
+ }
439
+ }
440
+ this.setBoundary(b, x);
441
+ }
442
+ }
443
+
444
+ private setBoundary(b: number, x: Float32Array): void {
445
+ // Set boundary conditions (reflective for velocity, continuous for density)
446
+ for (let k = 1; k < this.depth - 1; k++) {
447
+ for (let j = 1; j < this.height - 1; j++) {
448
+ x[this.indexFor(0, j, k)] =
449
+ b === 1 ? -x[this.indexFor(1, j, k)] : x[this.indexFor(1, j, k)];
450
+ x[this.indexFor(this.width - 1, j, k)] =
451
+ b === 1
452
+ ? -x[this.indexFor(this.width - 2, j, k)]
453
+ : x[this.indexFor(this.width - 2, j, k)];
454
+ }
455
+ }
456
+
457
+ for (let k = 1; k < this.depth - 1; k++) {
458
+ for (let i = 1; i < this.width - 1; i++) {
459
+ x[this.indexFor(i, 0, k)] =
460
+ b === 2 ? -x[this.indexFor(i, 1, k)] : x[this.indexFor(i, 1, k)];
461
+ x[this.indexFor(i, this.height - 1, k)] =
462
+ b === 2
463
+ ? -x[this.indexFor(i, this.height - 2, k)]
464
+ : x[this.indexFor(i, this.height - 2, k)];
465
+ }
466
+ }
467
+
468
+ for (let j = 1; j < this.height - 1; j++) {
469
+ for (let i = 1; i < this.width - 1; i++) {
470
+ x[this.indexFor(i, j, 0)] =
471
+ b === 3 ? -x[this.indexFor(i, j, 1)] : x[this.indexFor(i, j, 1)];
472
+ x[this.indexFor(i, j, this.depth - 1)] =
473
+ b === 3
474
+ ? -x[this.indexFor(i, j, this.depth - 2)]
475
+ : x[this.indexFor(i, j, this.depth - 2)];
476
+ }
477
+ }
478
+
479
+ // Corner cases - average of neighbors
480
+ x[this.indexFor(0, 0, 0)] =
481
+ 0.33 *
482
+ (x[this.indexFor(1, 0, 0)] +
483
+ x[this.indexFor(0, 1, 0)] +
484
+ x[this.indexFor(0, 0, 1)]);
485
+ x[this.indexFor(0, this.height - 1, 0)] =
486
+ 0.33 *
487
+ (x[this.indexFor(1, this.height - 1, 0)] +
488
+ x[this.indexFor(0, this.height - 2, 0)] +
489
+ x[this.indexFor(0, this.height - 1, 1)]);
490
+ x[this.indexFor(0, 0, this.depth - 1)] =
491
+ 0.33 *
492
+ (x[this.indexFor(1, 0, this.depth - 1)] +
493
+ x[this.indexFor(0, 1, this.depth - 1)] +
494
+ x[this.indexFor(0, 0, this.depth - 2)]);
495
+ x[this.indexFor(0, this.height - 1, this.depth - 1)] =
496
+ 0.33 *
497
+ (x[this.indexFor(1, this.height - 1, this.depth - 1)] +
498
+ x[this.indexFor(0, this.height - 2, this.depth - 1)] +
499
+ x[this.indexFor(0, this.height - 1, this.depth - 2)]);
500
+ x[this.indexFor(this.width - 1, 0, 0)] =
501
+ 0.33 *
502
+ (x[this.indexFor(this.width - 2, 0, 0)] +
503
+ x[this.indexFor(this.width - 1, 1, 0)] +
504
+ x[this.indexFor(this.width - 1, 0, 1)]);
505
+ x[this.indexFor(this.width - 1, this.height - 1, 0)] =
506
+ 0.33 *
507
+ (x[this.indexFor(this.width - 2, this.height - 1, 0)] +
508
+ x[this.indexFor(this.width - 1, this.height - 2, 0)] +
509
+ x[this.indexFor(this.width - 1, this.height - 1, 1)]);
510
+ x[this.indexFor(this.width - 1, 0, this.depth - 1)] =
511
+ 0.33 *
512
+ (x[this.indexFor(this.width - 2, 0, this.depth - 1)] +
513
+ x[this.indexFor(this.width - 1, 1, this.depth - 1)] +
514
+ x[this.indexFor(this.width - 1, 0, this.depth - 2)]);
515
+ x[this.indexFor(this.width - 1, this.height - 1, this.depth - 1)] =
516
+ 0.33 *
517
+ (x[this.indexFor(this.width - 2, this.height - 1, this.depth - 1)] +
518
+ x[this.indexFor(this.width - 1, this.height - 2, this.depth - 1)] +
519
+ x[this.indexFor(this.width - 1, this.height - 1, this.depth - 2)]);
520
+ }
521
+
522
+ private sampleField(
523
+ field: Float32Array,
524
+ x: number,
525
+ y: number,
526
+ z: number
527
+ ): number {
528
+ // Trilinear interpolation
529
+ const i0 = Math.floor(x);
530
+ const i1 = i0 + 1;
531
+ const j0 = Math.floor(y);
532
+ const j1 = j0 + 1;
533
+ const k0 = Math.floor(z);
534
+ const k1 = k0 + 1;
535
+
536
+ const sx = x - i0;
537
+ const sy = y - j0;
538
+ const sz = z - k0;
539
+
540
+ const c000 = field[this.indexForClamped(i0, j0, k0)];
541
+ const c001 = field[this.indexForClamped(i0, j0, k1)];
542
+ const c010 = field[this.indexForClamped(i0, j1, k0)];
543
+ const c011 = field[this.indexForClamped(i0, j1, k1)];
544
+ const c100 = field[this.indexForClamped(i1, j0, k0)];
545
+ const c101 = field[this.indexForClamped(i1, j0, k1)];
546
+ const c110 = field[this.indexForClamped(i1, j1, k0)];
547
+ const c111 = field[this.indexForClamped(i1, j1, k1)];
548
+
549
+ // Interpolate along x
550
+ const c00 = c000 * (1 - sx) + c100 * sx;
551
+ const c01 = c001 * (1 - sx) + c101 * sx;
552
+ const c10 = c010 * (1 - sx) + c110 * sx;
553
+ const c11 = c011 * (1 - sx) + c111 * sx;
554
+
555
+ // Interpolate along y
556
+ const c0 = c00 * (1 - sy) + c10 * sy;
557
+ const c1 = c01 * (1 - sy) + c11 * sy;
558
+
559
+ // Interpolate along z
560
+ return c0 * (1 - sz) + c1 * sz;
561
+ }
562
+
563
+ private indexFor(x: number, y: number, z: number): number {
564
+ return z * this.width * this.height + y * this.width + x;
565
+ }
566
+
567
+ private indexForClamped(x: number, y: number, z: number): number {
568
+ const ix = Math.min(this.width - 1, Math.max(0, x));
569
+ const iy = Math.min(this.height - 1, Math.max(0, y));
570
+ const iz = Math.min(this.depth - 1, Math.max(0, z));
571
+ return this.indexFor(ix, iy, iz);
572
+ }
573
+
574
+ private swap(a: Float32Array, b: Float32Array): void {
575
+ // Swap contents efficiently
576
+ const temp = new Float32Array(a);
577
+ a.set(b);
578
+ b.set(temp);
579
+ }
580
+ }
581
+
582
+ /**
583
+ * Pensieve-specific fluid simulator with aesthetic presets
584
+ */
585
+ export class PensieveFluidSimulator {
586
+ private fluid: StableFluids3D;
587
+ private time: number = 0;
588
+
589
+ constructor(size: number = 64) {
590
+ this.fluid = new StableFluids3D({
591
+ width: size,
592
+ height: size,
593
+ depth: size,
594
+ viscosity: 0.0001,
595
+ diffusion: 0.00001,
596
+ iterations: 4,
597
+ });
598
+ }
599
+
600
+ /**
601
+ * Add a "memory splash" - when a particle enters the pensieve
602
+ */
603
+ addMemorySplash(position: Vector3, intensity: number): void {
604
+ this.fluid.addDensity(
605
+ position.x,
606
+ position.y,
607
+ position.z,
608
+ intensity * 10,
609
+ 5
610
+ );
611
+ // Add a slight upward swirl
612
+ this.fluid.addForce(position, new Vector3(0, intensity * 2, 0), 3);
613
+ }
614
+
615
+ /**
616
+ * Add ambient swirling motion
617
+ */
618
+ addSwirl(dt: number): void {
619
+ this.time += dt;
620
+ const dims = this.fluid.getDimensions();
621
+ const center = new Vector3(dims.width / 2, dims.height / 2, dims.depth / 2);
622
+
623
+ // Rotating force around Y axis
624
+ const angle = this.time * 0.5;
625
+ const radius = dims.width * 0.3;
626
+ const pos = new Vector3(
627
+ center.x + Math.cos(angle) * radius,
628
+ center.y,
629
+ center.z + Math.sin(angle) * radius
630
+ );
631
+ const tangent = new Vector3(-Math.sin(angle), 0, Math.cos(angle));
632
+ this.fluid.addForce(pos, tangent, radius * 0.5);
633
+ }
634
+
635
+ /**
636
+ * Step the simulation
637
+ */
638
+ step(dt: number): void {
639
+ this.addSwirl(dt);
640
+ this.fluid.step(dt);
641
+ }
642
+
643
+ /**
644
+ * Get velocity at a point for particle advection
645
+ */
646
+ getVelocityAt(pos: Vector3): Vector3 {
647
+ return this.fluid.getVelocityAt(pos);
648
+ }
649
+
650
+ /**
651
+ * Get the underlying fluid for direct access
652
+ */
653
+ getFluid(): StableFluids3D {
654
+ return this.fluid;
655
+ }
656
+
657
+ /**
658
+ * Clear the simulation
659
+ */
660
+ clear(): void {
661
+ this.fluid.clear();
662
+ this.time = 0;
663
+ }
664
+ }
@@ -41,16 +41,17 @@ export function kMeansClustering(points: number[][], k: number): Cluster[] {
41
41
  return sum / cluster.members.length;
42
42
  });
43
43
  cluster.centroid = centroid;
44
- cluster.cohesion = cluster.members.reduce((sum, idx) => {
45
- const point = points[idx];
46
- const distance = Math.sqrt(
47
- centroid.reduce((acc, value, dim) => {
48
- const diff = value - (point?.[dim] ?? 0);
49
- return acc + diff * diff;
50
- }, 0)
51
- );
52
- return sum + distance;
53
- }, 0) / cluster.members.length;
44
+ cluster.cohesion =
45
+ cluster.members.reduce((sum, idx) => {
46
+ const point = points[idx];
47
+ const distance = Math.sqrt(
48
+ centroid.reduce((acc, value, dim) => {
49
+ const diff = value - (point?.[dim] ?? 0);
50
+ return acc + diff * diff;
51
+ }, 0)
52
+ );
53
+ return sum + distance;
54
+ }, 0) / cluster.members.length;
54
55
  }
55
56
 
56
57
  return clusters;
@@ -118,12 +119,18 @@ export function analyzeTerritorBoundaries(
118
119
  return { frontLength, hotspots };
119
120
  }
120
121
 
121
- export function kMeansClustering2D(points: Array<[number, number]>, k: number): Cluster[] {
122
+ export function kMeansClustering2D(
123
+ points: Array<[number, number]>,
124
+ k: number
125
+ ): Cluster[] {
122
126
  const asPoints = points.map((p) => [p[0], p[1]]);
123
127
  return kMeansClustering(asPoints, k);
124
128
  }
125
129
 
126
- export function findConnectedComponents(graph: { nodes: number[]; edges: Map<number, number[]> }): number[][] {
130
+ export function findConnectedComponents(graph: {
131
+ nodes: number[];
132
+ edges: Map<number, number[]>;
133
+ }): number[][] {
127
134
  const visited = new Set<number>();
128
135
  const components: number[][] = [];
129
136