@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,573 @@
1
+ /**
2
+ * 3D Flow Field
3
+ *
4
+ * A 3D vector field supporting vortices, attractors, and streamline tracing.
5
+ * Used for creating swirling "fluid soup" motion in the Pensieve.
6
+ */
7
+
8
+ import { Vector3 } from '../math/Vector3';
9
+
10
+ /**
11
+ * Configuration for the flow field
12
+ */
13
+ export interface FlowField3DConfig {
14
+ /** Field width in cells */
15
+ width: number;
16
+ /** Field height in cells */
17
+ height: number;
18
+ /** Field depth in cells */
19
+ depth: number;
20
+ /** Cell size (world units per cell) */
21
+ resolution: number;
22
+ }
23
+
24
+ /**
25
+ * A vortex source in the flow field
26
+ */
27
+ export interface Vortex3D {
28
+ /** Center position */
29
+ center: Vector3;
30
+ /** Axis of rotation (normalized) */
31
+ axis: Vector3;
32
+ /** Rotational strength (positive = counterclockwise when looking along axis) */
33
+ strength: number;
34
+ /** Influence radius */
35
+ radius: number;
36
+ /** Falloff type */
37
+ falloff: 'linear' | 'quadratic' | 'gaussian';
38
+ }
39
+
40
+ /**
41
+ * An attractor/repeller in the flow field
42
+ */
43
+ export interface Attractor3D {
44
+ /** Center position */
45
+ center: Vector3;
46
+ /** Strength (positive = attract, negative = repel) */
47
+ strength: number;
48
+ /** Influence radius */
49
+ radius: number;
50
+ /** Falloff type */
51
+ falloff: 'linear' | 'quadratic' | 'inverse-square';
52
+ }
53
+
54
+ /**
55
+ * A directional source in the flow field
56
+ */
57
+ export interface DirectionalSource3D {
58
+ /** Position */
59
+ position: Vector3;
60
+ /** Direction vector (will be normalized) */
61
+ direction: Vector3;
62
+ /** Strength */
63
+ strength: number;
64
+ /** Influence radius */
65
+ radius: number;
66
+ }
67
+
68
+ /**
69
+ * Options for streamline tracing
70
+ */
71
+ export interface StreamlineOptions {
72
+ /** Maximum number of steps */
73
+ maxSteps?: number;
74
+ /** Step size (world units) */
75
+ stepSize?: number;
76
+ /** Minimum velocity magnitude to continue tracing */
77
+ minVelocity?: number;
78
+ /** Whether to trace backwards as well */
79
+ bidirectional?: boolean;
80
+ }
81
+
82
+ /**
83
+ * Full field sample including velocity, divergence, and curl
84
+ */
85
+ export interface FieldSample {
86
+ /** Velocity at the sampled point */
87
+ velocity: Vector3;
88
+ /** Divergence (scalar) - measure of "outward flow" */
89
+ divergence: number;
90
+ /** Curl (vector) - measure of rotation */
91
+ curl: Vector3;
92
+ }
93
+
94
+ /**
95
+ * 3D Flow Field with vortices, attractors, and sources
96
+ */
97
+ export class FlowField3D {
98
+ protected width: number;
99
+ protected height: number;
100
+ protected depth: number;
101
+ protected resolution: number;
102
+ protected size: number;
103
+
104
+ // Velocity field storage
105
+ protected fieldX: Float32Array;
106
+ protected fieldY: Float32Array;
107
+ protected fieldZ: Float32Array;
108
+
109
+ // Sources
110
+ protected vortices: Vortex3D[] = [];
111
+ protected attractors: Attractor3D[] = [];
112
+ protected sources: DirectionalSource3D[] = [];
113
+
114
+ // Noise for turbulence
115
+ protected noiseTime: number = 0;
116
+
117
+ constructor(config: FlowField3DConfig) {
118
+ this.width = Math.max(1, Math.round(config.width));
119
+ this.height = Math.max(1, Math.round(config.height));
120
+ this.depth = Math.max(1, Math.round(config.depth));
121
+ this.resolution = config.resolution;
122
+ this.size = this.width * this.height * this.depth;
123
+
124
+ this.fieldX = new Float32Array(this.size);
125
+ this.fieldY = new Float32Array(this.size);
126
+ this.fieldZ = new Float32Array(this.size);
127
+ }
128
+
129
+ /**
130
+ * Add a vortex to the field
131
+ */
132
+ addVortex(
133
+ center: Vector3,
134
+ axis: Vector3,
135
+ strength: number,
136
+ radius: number,
137
+ falloff: Vortex3D['falloff'] = 'quadratic'
138
+ ): void {
139
+ // Normalize axis
140
+ const len = Math.sqrt(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z);
141
+ const normalizedAxis = new Vector3(
142
+ axis.x / len,
143
+ axis.y / len,
144
+ axis.z / len
145
+ );
146
+
147
+ this.vortices.push({
148
+ center,
149
+ axis: normalizedAxis,
150
+ strength,
151
+ radius,
152
+ falloff,
153
+ });
154
+ }
155
+
156
+ /**
157
+ * Add an attractor (positive strength) or repeller (negative strength)
158
+ */
159
+ addAttractor(
160
+ center: Vector3,
161
+ strength: number,
162
+ radius: number = 10,
163
+ falloff: Attractor3D['falloff'] = 'inverse-square'
164
+ ): void {
165
+ this.attractors.push({
166
+ center,
167
+ strength,
168
+ radius,
169
+ falloff,
170
+ });
171
+ }
172
+
173
+ /**
174
+ * Add a directional source
175
+ */
176
+ addSource(
177
+ position: Vector3,
178
+ velocity: Vector3,
179
+ strength: number,
180
+ radius: number = 5
181
+ ): void {
182
+ // Normalize direction
183
+ const len = Math.sqrt(
184
+ velocity.x * velocity.x +
185
+ velocity.y * velocity.y +
186
+ velocity.z * velocity.z
187
+ );
188
+ const direction =
189
+ len > 0
190
+ ? new Vector3(velocity.x / len, velocity.y / len, velocity.z / len)
191
+ : new Vector3(0, 1, 0);
192
+
193
+ this.sources.push({
194
+ position,
195
+ direction,
196
+ strength,
197
+ radius,
198
+ });
199
+ }
200
+
201
+ /**
202
+ * Clear all sources
203
+ */
204
+ clearSources(): void {
205
+ this.vortices = [];
206
+ this.attractors = [];
207
+ this.sources = [];
208
+ }
209
+
210
+ /**
211
+ * Sample the velocity at a world position
212
+ */
213
+ sample(position: Vector3): Vector3 {
214
+ let vx = 0;
215
+ let vy = 0;
216
+ let vz = 0;
217
+
218
+ // Contribution from vortices
219
+ for (const vortex of this.vortices) {
220
+ const contribution = this.sampleVortex(position, vortex);
221
+ vx += contribution.x;
222
+ vy += contribution.y;
223
+ vz += contribution.z;
224
+ }
225
+
226
+ // Contribution from attractors
227
+ for (const attractor of this.attractors) {
228
+ const contribution = this.sampleAttractor(position, attractor);
229
+ vx += contribution.x;
230
+ vy += contribution.y;
231
+ vz += contribution.z;
232
+ }
233
+
234
+ // Contribution from directional sources
235
+ for (const source of this.sources) {
236
+ const contribution = this.sampleSource(position, source);
237
+ vx += contribution.x;
238
+ vy += contribution.y;
239
+ vz += contribution.z;
240
+ }
241
+
242
+ return new Vector3(vx, vy, vz);
243
+ }
244
+
245
+ /**
246
+ * Sample the full field including curl and divergence
247
+ */
248
+ sampleFull(position: Vector3): FieldSample {
249
+ const velocity = this.sample(position);
250
+
251
+ // Compute curl numerically
252
+ const h = this.resolution * 0.5;
253
+ const vxp = this.sample(
254
+ new Vector3(position.x + h, position.y, position.z)
255
+ );
256
+ const vxm = this.sample(
257
+ new Vector3(position.x - h, position.y, position.z)
258
+ );
259
+ const vyp = this.sample(
260
+ new Vector3(position.x, position.y + h, position.z)
261
+ );
262
+ const vym = this.sample(
263
+ new Vector3(position.x, position.y - h, position.z)
264
+ );
265
+ const vzp = this.sample(
266
+ new Vector3(position.x, position.y, position.z + h)
267
+ );
268
+ const vzm = this.sample(
269
+ new Vector3(position.x, position.y, position.z - h)
270
+ );
271
+
272
+ const curl = new Vector3(
273
+ (vyp.z - vym.z - vzp.y + vzm.y) / (2 * h),
274
+ (vzp.x - vzm.x - vxp.z + vxm.z) / (2 * h),
275
+ (vxp.y - vxm.y - vyp.x + vym.x) / (2 * h)
276
+ );
277
+
278
+ const divergence =
279
+ (vxp.x - vxm.x + vyp.y - vym.y + vzp.z - vzm.z) / (2 * h);
280
+
281
+ return { velocity, curl, divergence };
282
+ }
283
+
284
+ /**
285
+ * Trace a streamline from a starting point
286
+ */
287
+ traceStreamline(start: Vector3, options: StreamlineOptions = {}): Vector3[] {
288
+ const maxSteps = options.maxSteps ?? 100;
289
+ const stepSize = options.stepSize ?? this.resolution;
290
+ const minVelocity = options.minVelocity ?? 0.001;
291
+ const bidirectional = options.bidirectional ?? false;
292
+
293
+ const points: Vector3[] = [start];
294
+
295
+ // Trace forward
296
+ let pos = new Vector3(start.x, start.y, start.z);
297
+ for (let i = 0; i < maxSteps; i++) {
298
+ const vel = this.sample(pos);
299
+ const speed = Math.sqrt(vel.x * vel.x + vel.y * vel.y + vel.z * vel.z);
300
+
301
+ if (speed < minVelocity) break;
302
+
303
+ // Normalize and step
304
+ pos = new Vector3(
305
+ pos.x + (vel.x / speed) * stepSize,
306
+ pos.y + (vel.y / speed) * stepSize,
307
+ pos.z + (vel.z / speed) * stepSize
308
+ );
309
+ points.push(pos);
310
+ }
311
+
312
+ // Trace backward
313
+ if (bidirectional) {
314
+ pos = new Vector3(start.x, start.y, start.z);
315
+ const backwardPoints: Vector3[] = [];
316
+
317
+ for (let i = 0; i < maxSteps; i++) {
318
+ const vel = this.sample(pos);
319
+ const speed = Math.sqrt(vel.x * vel.x + vel.y * vel.y + vel.z * vel.z);
320
+
321
+ if (speed < minVelocity) break;
322
+
323
+ // Normalize and step backward
324
+ pos = new Vector3(
325
+ pos.x - (vel.x / speed) * stepSize,
326
+ pos.y - (vel.y / speed) * stepSize,
327
+ pos.z - (vel.z / speed) * stepSize
328
+ );
329
+ backwardPoints.unshift(pos);
330
+ }
331
+
332
+ return [...backwardPoints, ...points];
333
+ }
334
+
335
+ return points;
336
+ }
337
+
338
+ /**
339
+ * Update the flow field (for time-varying fields)
340
+ */
341
+ update(dt: number): void {
342
+ this.noiseTime += dt;
343
+ }
344
+
345
+ /**
346
+ * Get field dimensions
347
+ */
348
+ getDimensions(): {
349
+ width: number;
350
+ height: number;
351
+ depth: number;
352
+ resolution: number;
353
+ } {
354
+ return {
355
+ width: this.width,
356
+ height: this.height,
357
+ depth: this.depth,
358
+ resolution: this.resolution,
359
+ };
360
+ }
361
+
362
+ // =========================================================================
363
+ // PRIVATE METHODS
364
+ // =========================================================================
365
+
366
+ private sampleVortex(pos: Vector3, vortex: Vortex3D): Vector3 {
367
+ // Vector from vortex center to position
368
+ const dx = pos.x - vortex.center.x;
369
+ const dy = pos.y - vortex.center.y;
370
+ const dz = pos.z - vortex.center.z;
371
+
372
+ // Project onto plane perpendicular to axis
373
+ // p_proj = p - (p . axis) * axis
374
+ const dot = dx * vortex.axis.x + dy * vortex.axis.y + dz * vortex.axis.z;
375
+ const px = dx - dot * vortex.axis.x;
376
+ const py = dy - dot * vortex.axis.y;
377
+ const pz = dz - dot * vortex.axis.z;
378
+
379
+ // Distance in plane
380
+ const planeDist = Math.sqrt(px * px + py * py + pz * pz);
381
+
382
+ if (planeDist < 0.001) {
383
+ return new Vector3(0, 0, 0);
384
+ }
385
+
386
+ // Calculate falloff
387
+ let falloff: number;
388
+ const normalizedDist = planeDist / vortex.radius;
389
+
390
+ if (normalizedDist > 1) {
391
+ falloff = 0;
392
+ } else {
393
+ switch (vortex.falloff) {
394
+ case 'linear':
395
+ falloff = 1 - normalizedDist;
396
+ break;
397
+ case 'quadratic':
398
+ falloff = (1 - normalizedDist) * (1 - normalizedDist);
399
+ break;
400
+ case 'gaussian':
401
+ falloff = Math.exp(-normalizedDist * normalizedDist * 3);
402
+ break;
403
+ default:
404
+ falloff = 1 - normalizedDist;
405
+ }
406
+ }
407
+
408
+ // Cross product: axis x (pos - center) gives tangent direction
409
+ // This creates circular motion around the axis
410
+ const tx = vortex.axis.y * pz - vortex.axis.z * py;
411
+ const ty = vortex.axis.z * px - vortex.axis.x * pz;
412
+ const tz = vortex.axis.x * py - vortex.axis.y * px;
413
+
414
+ // Normalize and apply strength
415
+ const tLen = Math.sqrt(tx * tx + ty * ty + tz * tz);
416
+ if (tLen < 0.001) {
417
+ return new Vector3(0, 0, 0);
418
+ }
419
+
420
+ const strength = vortex.strength * falloff;
421
+ return new Vector3(
422
+ (tx / tLen) * strength,
423
+ (ty / tLen) * strength,
424
+ (tz / tLen) * strength
425
+ );
426
+ }
427
+
428
+ private sampleAttractor(pos: Vector3, attractor: Attractor3D): Vector3 {
429
+ const dx = attractor.center.x - pos.x;
430
+ const dy = attractor.center.y - pos.y;
431
+ const dz = attractor.center.z - pos.z;
432
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
433
+
434
+ if (dist < 0.001 || dist > attractor.radius) {
435
+ return new Vector3(0, 0, 0);
436
+ }
437
+
438
+ // Calculate falloff
439
+ let strength: number;
440
+ const normalizedDist = dist / attractor.radius;
441
+
442
+ switch (attractor.falloff) {
443
+ case 'linear':
444
+ strength = attractor.strength * (1 - normalizedDist);
445
+ break;
446
+ case 'quadratic':
447
+ strength =
448
+ attractor.strength * (1 - normalizedDist) * (1 - normalizedDist);
449
+ break;
450
+ case 'inverse-square':
451
+ // Clamped inverse square to avoid singularity
452
+ strength = attractor.strength / Math.max(1, dist * dist);
453
+ break;
454
+ default:
455
+ strength = attractor.strength * (1 - normalizedDist);
456
+ }
457
+
458
+ // Direction towards/away from attractor
459
+ const invDist = 1 / dist;
460
+ return new Vector3(
461
+ dx * invDist * strength,
462
+ dy * invDist * strength,
463
+ dz * invDist * strength
464
+ );
465
+ }
466
+
467
+ private sampleSource(pos: Vector3, source: DirectionalSource3D): Vector3 {
468
+ const dx = pos.x - source.position.x;
469
+ const dy = pos.y - source.position.y;
470
+ const dz = pos.z - source.position.z;
471
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
472
+
473
+ if (dist > source.radius) {
474
+ return new Vector3(0, 0, 0);
475
+ }
476
+
477
+ const falloff = 1 - dist / source.radius;
478
+ const strength = source.strength * falloff;
479
+
480
+ return new Vector3(
481
+ source.direction.x * strength,
482
+ source.direction.y * strength,
483
+ source.direction.z * strength
484
+ );
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Pensieve-specific flow field with aesthetic presets
490
+ */
491
+ export class PensieveFlowField extends FlowField3D {
492
+ constructor(size: number = 100) {
493
+ super({
494
+ width: size,
495
+ height: size,
496
+ depth: size,
497
+ resolution: 1,
498
+ });
499
+
500
+ // Add default swirling vortices for the pensieve "soup" effect
501
+ this.addDefaultVortices();
502
+ }
503
+
504
+ /**
505
+ * Add default vortices for pensieve aesthetic
506
+ */
507
+ private addDefaultVortices(): void {
508
+ const dims = this.getDimensions();
509
+ const center = dims.width / 2;
510
+
511
+ // Central upward spiral
512
+ this.addVortex(
513
+ new Vector3(center, center * 0.3, center),
514
+ new Vector3(0, 1, 0),
515
+ 2,
516
+ center * 0.8,
517
+ 'gaussian'
518
+ );
519
+
520
+ // Secondary horizontal vortices
521
+ this.addVortex(
522
+ new Vector3(center * 0.7, center, center),
523
+ new Vector3(1, 0.3, 0),
524
+ 1,
525
+ center * 0.4,
526
+ 'quadratic'
527
+ );
528
+
529
+ this.addVortex(
530
+ new Vector3(center * 1.3, center, center),
531
+ new Vector3(-1, 0.3, 0),
532
+ 1,
533
+ center * 0.4,
534
+ 'quadratic'
535
+ );
536
+
537
+ // Gentle central attractor to keep particles from flying away
538
+ this.addAttractor(
539
+ new Vector3(center, center, center),
540
+ 0.5,
541
+ center * 1.5,
542
+ 'linear'
543
+ );
544
+ }
545
+
546
+ /**
547
+ * Add mouse interaction force
548
+ */
549
+ addMouseForce(
550
+ position: Vector3,
551
+ direction: Vector3,
552
+ strength: number = 5
553
+ ): void {
554
+ // Temporarily add a directional source
555
+ this.addSource(position, direction, strength, 10);
556
+ }
557
+
558
+ /**
559
+ * Clear mouse forces (call after interaction ends)
560
+ */
561
+ clearMouseForces(): void {
562
+ // Remove all directional sources but keep vortices and attractors
563
+ const vorticesCopy = [...this.vortices];
564
+ const attractorsCopy = [...this.attractors];
565
+ this.clearSources();
566
+ for (const v of vorticesCopy) {
567
+ this.addVortex(v.center, v.axis, v.strength, v.radius, v.falloff);
568
+ }
569
+ for (const a of attractorsCopy) {
570
+ this.addAttractor(a.center, a.strength, a.radius, a.falloff);
571
+ }
572
+ }
573
+ }
@@ -26,7 +26,13 @@ export class StableFluids {
26
26
  this.density[index] += amount;
27
27
  }
28
28
 
29
- addForce(x: number, y: number, vx: number, vy: number, _radius: number): void {
29
+ addForce(
30
+ x: number,
31
+ y: number,
32
+ vx: number,
33
+ vy: number,
34
+ _radius: number
35
+ ): void {
30
36
  const index = this.indexFor(x, y);
31
37
  this.velocityX[index] += vx;
32
38
  this.velocityY[index] += vy;
@@ -78,10 +84,20 @@ export class InfectionFluidSimulator {
78
84
  private fluid: StableFluids;
79
85
 
80
86
  constructor(width: number, height: number) {
81
- this.fluid = new StableFluids({ width, height, viscosity: 0, diffusion: 0 });
87
+ this.fluid = new StableFluids({
88
+ width,
89
+ height,
90
+ viscosity: 0,
91
+ diffusion: 0,
92
+ });
82
93
  }
83
94
 
84
- registerTerritory(_ownerId: number, x: number, y: number, intensity: number): void {
95
+ registerTerritory(
96
+ _ownerId: number,
97
+ x: number,
98
+ y: number,
99
+ intensity: number
100
+ ): void {
85
101
  this.fluid.addDensity(x, y, intensity, 1);
86
102
  }
87
103