@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,567 @@
1
+ /**
2
+ * 3D Particle System
3
+ *
4
+ * GPU-friendly particle physics for React Three Fiber InstancedMesh rendering.
5
+ * Supports forces, attractors, fluid coupling, and biometric visualization.
6
+ */
7
+
8
+ import { Vector3 } from '../math/Vector3';
9
+ import type { StableFluids3D } from './FluidSimulation3D';
10
+
11
+ /**
12
+ * A single 3D particle
13
+ */
14
+ export interface Particle3D {
15
+ /** Unique identifier */
16
+ id: string;
17
+ /** 3D position */
18
+ position: Vector3;
19
+ /** 3D velocity */
20
+ velocity: Vector3;
21
+ /** 3D acceleration (accumulated forces) */
22
+ acceleration: Vector3;
23
+ /** RGB color (0-1 range) */
24
+ color: [number, number, number];
25
+ /** Base size */
26
+ size: number;
27
+ /** Current life remaining (seconds) */
28
+ life: number;
29
+ /** Maximum life (seconds) */
30
+ maxLife: number;
31
+ /** Mass for physics calculations */
32
+ mass: number;
33
+ /** Custom data attached to particle */
34
+ userData?: Record<string, unknown>;
35
+
36
+ // Biometric visualization
37
+ /** Heart rate in BPM - affects pulse animation */
38
+ heartRate?: number;
39
+ /** HRV in ms - affects stability/jitter */
40
+ hrvValue?: number;
41
+ /** Current pulse scale (computed each frame) */
42
+ pulseScale?: number;
43
+ }
44
+
45
+ /**
46
+ * Options for emitting particles
47
+ */
48
+ export interface EmitOptions {
49
+ /** Number of particles to emit */
50
+ count?: number;
51
+ /** RGB color (0-1 range) */
52
+ color?: [number, number, number];
53
+ /** Initial velocity */
54
+ velocity?: Vector3;
55
+ /** Velocity spread (random variation) */
56
+ velocitySpread?: Vector3;
57
+ /** Initial size */
58
+ size?: number;
59
+ /** Size spread (random variation) */
60
+ sizeSpread?: number;
61
+ /** Particle lifetime in seconds */
62
+ life?: number;
63
+ /** Life spread (random variation) */
64
+ lifeSpread?: number;
65
+ /** Particle mass */
66
+ mass?: number;
67
+ /** Custom user data */
68
+ userData?: Record<string, unknown>;
69
+ /** Heart rate for biometric visualization */
70
+ heartRate?: number;
71
+ /** HRV for biometric visualization */
72
+ hrvValue?: number;
73
+ }
74
+
75
+ /**
76
+ * Configuration for the particle system
77
+ */
78
+ export interface ParticleSystem3DConfig {
79
+ /** Maximum number of particles */
80
+ maxParticles: number;
81
+ /** Global gravity vector */
82
+ gravity?: Vector3;
83
+ /** Global drag coefficient (0-1, where 1 = no drag) */
84
+ drag?: number;
85
+ /** Bounds for particle containment (spherical) */
86
+ boundsSphereRadius?: number;
87
+ /** Bounds center */
88
+ boundsCenter?: Vector3;
89
+ /** Bounce factor when hitting bounds (0 = absorb, 1 = perfect bounce) */
90
+ bounceFactor?: number;
91
+ /** Zero-copy shared buffers */
92
+ sharedBuffers?: {
93
+ positions: Float32Array;
94
+ colors: Float32Array;
95
+ scales: Float32Array;
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Instance data for GPU rendering
101
+ */
102
+ export interface InstanceData {
103
+ /** Flat array of positions [x,y,z, x,y,z, ...] */
104
+ positions: Float32Array;
105
+ /** Flat array of colors [r,g,b, r,g,b, ...] */
106
+ colors: Float32Array;
107
+ /** Flat array of scales [s, s, s, ...] */
108
+ scales: Float32Array;
109
+ /** Number of active particles */
110
+ count: number;
111
+ /** Particle IDs in order */
112
+ ids: string[];
113
+ }
114
+
115
+ /**
116
+ * 3D Particle System with GPU-friendly output
117
+ */
118
+ export class ParticleSystem3D {
119
+ private particles: Map<string, Particle3D> = new Map();
120
+ private particleOrder: string[] = [];
121
+ private maxParticles: number;
122
+ private gravity: Vector3;
123
+ private drag: number;
124
+ private boundsSphereRadius: number;
125
+ private boundsCenter: Vector3;
126
+ private bounceFactor: number;
127
+ private time: number = 0;
128
+ private idCounter: number = 0;
129
+
130
+ // Pre-allocated buffers for GPU data
131
+ private positionBuffer: Float32Array;
132
+ private colorBuffer: Float32Array;
133
+ private scaleBuffer: Float32Array;
134
+
135
+ constructor(config: ParticleSystem3DConfig) {
136
+ this.maxParticles = config.maxParticles;
137
+ this.gravity = config.gravity ?? new Vector3(0, 0, 0);
138
+ this.drag = config.drag ?? 0.99;
139
+ this.boundsSphereRadius = config.boundsSphereRadius ?? Infinity;
140
+ this.boundsCenter = config.boundsCenter ?? new Vector3(0, 0, 0);
141
+ this.bounceFactor = config.bounceFactor ?? 0.5;
142
+
143
+ // Pre-allocate buffers (unless provided via config for zero-copy)
144
+ if (config.sharedBuffers) {
145
+ this.positionBuffer = config.sharedBuffers.positions;
146
+ this.colorBuffer = config.sharedBuffers.colors;
147
+ this.scaleBuffer = config.sharedBuffers.scales;
148
+ } else {
149
+ this.positionBuffer = new Float32Array(this.maxParticles * 3);
150
+ this.colorBuffer = new Float32Array(this.maxParticles * 3);
151
+ this.scaleBuffer = new Float32Array(this.maxParticles);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Zero-Copy Binding: Inject shared buffers from WASM/Dash
157
+ */
158
+ setSharedBuffers(buffers: { positions: Float32Array, colors: Float32Array, scales: Float32Array }) {
159
+ this.positionBuffer = buffers.positions;
160
+ this.colorBuffer = buffers.colors;
161
+ this.scaleBuffer = buffers.scales;
162
+ }
163
+
164
+ /**
165
+ * Emit particles at a position
166
+ */
167
+ emit(position: Vector3, options: EmitOptions = {}): string[] {
168
+ const count = options.count ?? 1;
169
+ const emittedIds: string[] = [];
170
+
171
+ for (let i = 0; i < count; i++) {
172
+ if (this.particles.size >= this.maxParticles) {
173
+ // Remove oldest particle
174
+ const oldest = this.particleOrder.shift();
175
+ if (oldest) {
176
+ this.particles.delete(oldest);
177
+ }
178
+ }
179
+
180
+ const id = `p_${this.idCounter++}`;
181
+ const spread = options.velocitySpread ?? new Vector3(0, 0, 0);
182
+ const sizeSpread = options.sizeSpread ?? 0;
183
+ const lifeSpread = options.lifeSpread ?? 0;
184
+
185
+ const particle: Particle3D = {
186
+ id,
187
+ position: new Vector3(position.x, position.y, position.z),
188
+ velocity: new Vector3(
189
+ (options.velocity?.x ?? 0) + (Math.random() - 0.5) * 2 * spread.x,
190
+ (options.velocity?.y ?? 0) + (Math.random() - 0.5) * 2 * spread.y,
191
+ (options.velocity?.z ?? 0) + (Math.random() - 0.5) * 2 * spread.z
192
+ ),
193
+ acceleration: new Vector3(0, 0, 0),
194
+ color: options.color ?? [1, 1, 1],
195
+ size: (options.size ?? 1) + (Math.random() - 0.5) * 2 * sizeSpread,
196
+ life: (options.life ?? 5) + (Math.random() - 0.5) * 2 * lifeSpread,
197
+ maxLife: options.life ?? 5,
198
+ mass: options.mass ?? 1,
199
+ userData: options.userData,
200
+ heartRate: options.heartRate,
201
+ hrvValue: options.hrvValue,
202
+ pulseScale: 1,
203
+ };
204
+
205
+ this.particles.set(id, particle);
206
+ this.particleOrder.push(id);
207
+ emittedIds.push(id);
208
+ }
209
+
210
+ return emittedIds;
211
+ }
212
+
213
+ /**
214
+ * Add or update a persistent particle (for memory particles that don't expire)
215
+ */
216
+ setParticle(
217
+ id: string,
218
+ particle: Omit<Particle3D, 'acceleration' | 'pulseScale'>
219
+ ): void {
220
+ const existing = this.particles.get(id);
221
+ if (existing) {
222
+ // Update existing
223
+ Object.assign(existing, particle);
224
+ existing.acceleration = new Vector3(0, 0, 0);
225
+ } else {
226
+ // Add new
227
+ if (this.particles.size >= this.maxParticles) {
228
+ // Remove oldest non-persistent particle
229
+ for (let i = 0; i < this.particleOrder.length; i++) {
230
+ const oldId = this.particleOrder[i];
231
+ const oldParticle = this.particles.get(oldId);
232
+ if (oldParticle && oldParticle.life !== Infinity) {
233
+ this.particles.delete(oldId);
234
+ this.particleOrder.splice(i, 1);
235
+ break;
236
+ }
237
+ }
238
+ }
239
+
240
+ const newParticle: Particle3D = {
241
+ ...particle,
242
+ acceleration: new Vector3(0, 0, 0),
243
+ pulseScale: 1,
244
+ };
245
+ this.particles.set(id, newParticle);
246
+ this.particleOrder.push(id);
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Remove a particle by ID
252
+ */
253
+ removeParticle(id: string): void {
254
+ this.particles.delete(id);
255
+ const idx = this.particleOrder.indexOf(id);
256
+ if (idx >= 0) {
257
+ this.particleOrder.splice(idx, 1);
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Get a particle by ID
263
+ */
264
+ getParticle(id: string): Particle3D | undefined {
265
+ return this.particles.get(id);
266
+ }
267
+
268
+ /**
269
+ * Apply a force field to all particles
270
+ */
271
+ applyForceField(field: (pos: Vector3) => Vector3): void {
272
+ Array.from(this.particles.values()).forEach((particle) => {
273
+ const force = field(particle.position);
274
+ particle.acceleration.x += force.x / particle.mass;
275
+ particle.acceleration.y += force.y / particle.mass;
276
+ particle.acceleration.z += force.z / particle.mass;
277
+ });
278
+ }
279
+
280
+ /**
281
+ * Apply an attractor force
282
+ */
283
+ applyAttractor(
284
+ center: Vector3,
285
+ strength: number,
286
+ falloffRadius: number
287
+ ): void {
288
+ Array.from(this.particles.values()).forEach((particle) => {
289
+ const dx = center.x - particle.position.x;
290
+ const dy = center.y - particle.position.y;
291
+ const dz = center.z - particle.position.z;
292
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
293
+
294
+ if (dist > 0.001) {
295
+ const falloff = Math.max(0, 1 - dist / falloffRadius);
296
+ const force = (strength * falloff * falloff) / (dist * dist);
297
+ const invDist = 1 / dist;
298
+ particle.acceleration.x += (dx * invDist * force) / particle.mass;
299
+ particle.acceleration.y += (dy * invDist * force) / particle.mass;
300
+ particle.acceleration.z += (dz * invDist * force) / particle.mass;
301
+ }
302
+ });
303
+ }
304
+
305
+ /**
306
+ * Apply velocity from a fluid simulation
307
+ */
308
+ applyFluidVelocity(fluid: StableFluids3D, strength: number = 1): void {
309
+ Array.from(this.particles.values()).forEach((particle) => {
310
+ const fluidVel = fluid.getVelocityAt(particle.position);
311
+ particle.velocity.x += fluidVel.x * strength;
312
+ particle.velocity.y += fluidVel.y * strength;
313
+ particle.velocity.z += fluidVel.z * strength;
314
+ });
315
+ }
316
+
317
+ /**
318
+ * Update all particles
319
+ */
320
+ update(dt: number): void {
321
+ this.time += dt;
322
+ const deadParticles: string[] = [];
323
+
324
+ Array.from(this.particles.values()).forEach((particle) => {
325
+ // Apply gravity
326
+ particle.acceleration.x += this.gravity.x;
327
+ particle.acceleration.y += this.gravity.y;
328
+ particle.acceleration.z += this.gravity.z;
329
+
330
+ // Integrate velocity
331
+ particle.velocity.x += particle.acceleration.x * dt;
332
+ particle.velocity.y += particle.acceleration.y * dt;
333
+ particle.velocity.z += particle.acceleration.z * dt;
334
+
335
+ // Apply drag
336
+ particle.velocity.x *= this.drag;
337
+ particle.velocity.y *= this.drag;
338
+ particle.velocity.z *= this.drag;
339
+
340
+ // Integrate position
341
+ particle.position.x += particle.velocity.x * dt;
342
+ particle.position.y += particle.velocity.y * dt;
343
+ particle.position.z += particle.velocity.z * dt;
344
+
345
+ // Reset acceleration
346
+ particle.acceleration.x = 0;
347
+ particle.acceleration.y = 0;
348
+ particle.acceleration.z = 0;
349
+
350
+ // Sphere bounds collision
351
+ if (this.boundsSphereRadius < Infinity) {
352
+ const dx = particle.position.x - this.boundsCenter.x;
353
+ const dy = particle.position.y - this.boundsCenter.y;
354
+ const dz = particle.position.z - this.boundsCenter.z;
355
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
356
+
357
+ if (dist > this.boundsSphereRadius) {
358
+ // Push back inside
359
+ const invDist = 1 / dist;
360
+ const nx = dx * invDist;
361
+ const ny = dy * invDist;
362
+ const nz = dz * invDist;
363
+
364
+ particle.position.x =
365
+ this.boundsCenter.x + nx * this.boundsSphereRadius * 0.99;
366
+ particle.position.y =
367
+ this.boundsCenter.y + ny * this.boundsSphereRadius * 0.99;
368
+ particle.position.z =
369
+ this.boundsCenter.z + nz * this.boundsSphereRadius * 0.99;
370
+
371
+ // Reflect velocity
372
+ const dot =
373
+ particle.velocity.x * nx +
374
+ particle.velocity.y * ny +
375
+ particle.velocity.z * nz;
376
+ particle.velocity.x =
377
+ (particle.velocity.x - 2 * dot * nx) * this.bounceFactor;
378
+ particle.velocity.y =
379
+ (particle.velocity.y - 2 * dot * ny) * this.bounceFactor;
380
+ particle.velocity.z =
381
+ (particle.velocity.z - 2 * dot * nz) * this.bounceFactor;
382
+ }
383
+ }
384
+
385
+ // Update biometric pulse
386
+ if (particle.heartRate) {
387
+ const pulseFreq = particle.heartRate / 60; // Hz
388
+ const pulse = Math.sin(this.time * pulseFreq * Math.PI * 2);
389
+ particle.pulseScale = 1 + pulse * 0.15; // 15% size variation
390
+ }
391
+
392
+ // Decrease life (skip for Infinity life)
393
+ if (particle.life !== Infinity) {
394
+ particle.life -= dt;
395
+ if (particle.life <= 0) {
396
+ deadParticles.push(particle.id);
397
+ }
398
+ }
399
+ });
400
+
401
+ // Remove dead particles
402
+ deadParticles.forEach((id) => {
403
+ this.particles.delete(id);
404
+ const idx = this.particleOrder.indexOf(id);
405
+ if (idx >= 0) {
406
+ this.particleOrder.splice(idx, 1);
407
+ }
408
+ });
409
+ }
410
+
411
+ /**
412
+ * Get instance data for React Three Fiber InstancedMesh
413
+ */
414
+ getInstanceData(): InstanceData {
415
+ const count = this.particles.size;
416
+ const ids: string[] = [];
417
+
418
+ let i = 0;
419
+ const entries = Array.from(this.particles.entries());
420
+ for (let j = 0; j < entries.length && i < this.maxParticles; j++) {
421
+ const [id, particle] = entries[j];
422
+
423
+ // Position
424
+ this.positionBuffer[i * 3] = particle.position.x;
425
+ this.positionBuffer[i * 3 + 1] = particle.position.y;
426
+ this.positionBuffer[i * 3 + 2] = particle.position.z;
427
+
428
+ // Color with life-based alpha (encoded in color for now)
429
+ const lifeFactor =
430
+ particle.life === Infinity
431
+ ? 1
432
+ : Math.min(1, particle.life / particle.maxLife);
433
+ this.colorBuffer[i * 3] = particle.color[0] * lifeFactor;
434
+ this.colorBuffer[i * 3 + 1] = particle.color[1] * lifeFactor;
435
+ this.colorBuffer[i * 3 + 2] = particle.color[2] * lifeFactor;
436
+
437
+ // Scale with pulse
438
+ this.scaleBuffer[i] = particle.size * (particle.pulseScale ?? 1);
439
+
440
+ ids.push(id);
441
+ i++;
442
+ }
443
+
444
+ return {
445
+ positions: this.positionBuffer.subarray(0, count * 3),
446
+ colors: this.colorBuffer.subarray(0, count * 3),
447
+ scales: this.scaleBuffer.subarray(0, count),
448
+ count,
449
+ ids,
450
+ };
451
+ }
452
+
453
+ /**
454
+ * Get all particles
455
+ */
456
+ getParticles(): Particle3D[] {
457
+ return Array.from(this.particles.values());
458
+ }
459
+
460
+ /**
461
+ * Get particle count
462
+ */
463
+ getCount(): number {
464
+ return this.particles.size;
465
+ }
466
+
467
+ /**
468
+ * Clear all particles
469
+ */
470
+ clear(): void {
471
+ this.particles.clear();
472
+ this.particleOrder = [];
473
+ }
474
+
475
+ /**
476
+ * Find particle nearest to a point
477
+ */
478
+ findNearest(
479
+ point: Vector3,
480
+ maxDistance: number = Infinity
481
+ ): Particle3D | null {
482
+ let nearest: Particle3D | null = null;
483
+ let nearestDist = maxDistance;
484
+
485
+ Array.from(this.particles.values()).forEach((particle) => {
486
+ const dist = particle.position.distanceTo(point);
487
+ if (dist < nearestDist) {
488
+ nearest = particle;
489
+ nearestDist = dist;
490
+ }
491
+ });
492
+
493
+ return nearest;
494
+ }
495
+
496
+ /**
497
+ * Find all particles within a radius
498
+ */
499
+ findWithinRadius(center: Vector3, radius: number): Particle3D[] {
500
+ const results: Particle3D[] = [];
501
+ const r2 = radius * radius;
502
+
503
+ Array.from(this.particles.values()).forEach((particle) => {
504
+ const dx = particle.position.x - center.x;
505
+ const dy = particle.position.y - center.y;
506
+ const dz = particle.position.z - center.z;
507
+ if (dx * dx + dy * dy + dz * dz <= r2) {
508
+ results.push(particle);
509
+ }
510
+ });
511
+
512
+ return results;
513
+ }
514
+ }
515
+
516
+ /**
517
+ * Pensieve-specific particle system with memory visualization presets
518
+ */
519
+ export class PensieveParticleSystem extends ParticleSystem3D {
520
+ constructor(maxParticles: number = 10000) {
521
+ super({
522
+ maxParticles,
523
+ gravity: new Vector3(0, -0.1, 0), // Gentle downward drift
524
+ drag: 0.995,
525
+ boundsSphereRadius: 50,
526
+ boundsCenter: new Vector3(0, 0, 0),
527
+ bounceFactor: 0.3,
528
+ });
529
+ }
530
+
531
+ /**
532
+ * Add a memory particle with reflection data
533
+ */
534
+ addMemoryParticle(
535
+ id: string,
536
+ position: Vector3,
537
+ color: [number, number, number],
538
+ options: {
539
+ intensity?: number;
540
+ heartRate?: number;
541
+ hrvValue?: number;
542
+ ageFactor?: number;
543
+ userData?: Record<string, unknown>;
544
+ } = {}
545
+ ): void {
546
+ const size = 0.5 + (options.intensity ?? 0.5) * 0.5;
547
+ const ageFactor = options.ageFactor ?? 1;
548
+
549
+ this.setParticle(id, {
550
+ id,
551
+ position,
552
+ velocity: new Vector3(
553
+ (Math.random() - 0.5) * 0.5,
554
+ (Math.random() - 0.5) * 0.5,
555
+ (Math.random() - 0.5) * 0.5
556
+ ),
557
+ color: [color[0] * ageFactor, color[1] * ageFactor, color[2] * ageFactor],
558
+ size,
559
+ life: Infinity, // Memory particles don't expire
560
+ maxLife: Infinity,
561
+ mass: 1,
562
+ heartRate: options.heartRate,
563
+ hrvValue: options.hrvValue,
564
+ userData: options.userData,
565
+ });
566
+ }
567
+ }
@@ -1,13 +1,19 @@
1
1
  /**
2
2
  * Algorithms Module Exports
3
- *
3
+ *
4
4
  * @module algorithms
5
5
  */
6
6
 
7
- export * from './GraphAlgorithms'
8
- export * from './FlowField'
9
- export * from './ParticleSystem'
10
- export * from './FluidSimulation'
11
- export * from './AdvancedStatistics'
12
- export * from './BayesianStatistics'
13
- export * from './OutlierDetection'
7
+ // 2D algorithms (existing)
8
+ export * from './GraphAlgorithms';
9
+ export * from './FlowField';
10
+ export * from './ParticleSystem';
11
+ export * from './FluidSimulation';
12
+ export * from './AdvancedStatistics';
13
+ export * from './BayesianStatistics';
14
+ export * from './OutlierDetection';
15
+
16
+ // 3D algorithms (new - for Pensieve)
17
+ export * from './FluidSimulation3D';
18
+ export * from './ParticleSystem3D';
19
+ export * from './FlowField3D';
package/src/compat.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Backward compatibility layer for Photo and GridItem
3
- *
3
+ *
4
4
  * Provides conversion utilities to ensure existing Photo-based code
5
5
  * continues to work while new code can use GridItem.
6
6
  */
7
7
 
8
- import type { Photo, GridItem } from './types'
8
+ import type { Photo, GridItem } from './types';
9
9
 
10
10
  /**
11
11
  * Convert Photo to GridItem for backward compatibility
@@ -37,7 +37,7 @@ export function photoToGridItem(photo: Photo): GridItem<Photo> {
37
37
  sourceUrl: photo.sourceUrl,
38
38
  createdAt: photo.createdAt,
39
39
  velocity: photo.velocity,
40
- }
40
+ };
41
41
  }
42
42
 
43
43
  /**
@@ -46,9 +46,9 @@ export function photoToGridItem(photo: Photo): GridItem<Photo> {
46
46
  export function gridItemToPhoto(item: GridItem<Photo>): Photo | null {
47
47
  // If item contains original Photo data, return it
48
48
  if (item.type === 'photo' && item.data) {
49
- return item.data
49
+ return item.data;
50
50
  }
51
-
51
+
52
52
  // Fallback: construct Photo from GridItem fields
53
53
  if (item.imageUrl || item.url) {
54
54
  return {
@@ -73,17 +73,17 @@ export function gridItemToPhoto(item: GridItem<Photo>): Photo | null {
73
73
  comments: item.comments,
74
74
  dominantColor: item.dominantColor,
75
75
  velocity: item.velocity,
76
- }
76
+ };
77
77
  }
78
-
79
- return null
78
+
79
+ return null;
80
80
  }
81
81
 
82
82
  /**
83
83
  * Convert an array of Photos to GridItems
84
84
  */
85
85
  export function photosToGridItems(photos: Photo[]): GridItem<Photo>[] {
86
- return photos.map(photoToGridItem)
86
+ return photos.map(photoToGridItem);
87
87
  }
88
88
 
89
89
  /**
@@ -92,5 +92,5 @@ export function photosToGridItems(photos: Photo[]): GridItem<Photo>[] {
92
92
  export function gridItemsToPhotos(items: GridItem<Photo>[]): Photo[] {
93
93
  return items
94
94
  .map(gridItemToPhoto)
95
- .filter((photo): photo is Photo => photo !== null)
95
+ .filter((photo): photo is Photo => photo !== null);
96
96
  }
@@ -1,30 +1,17 @@
1
- import type { CSSProperties } from 'react';
1
+ import type { CSSProperties, RefObject } from 'react';
2
2
  import React from 'react';
3
+ import type {
4
+ Photo as PhotoType,
5
+ HexGridProps as HexGridPropsType,
6
+ HexGridFeatureFlags,
7
+ } from '../types';
3
8
 
4
- export interface Photo {
5
- id: string;
6
- title: string;
7
- alt: string;
8
- imageUrl: string;
9
- thumbnailUrl: string;
10
- category: string;
11
- description: string;
12
- source: string;
13
- createdAt: string;
14
- velocity: number;
15
- sourceUrl: string;
16
- likes: number;
17
- age_in_hours: number;
18
- }
9
+ export type Photo = PhotoType;
19
10
 
20
- export interface HexGridProps {
21
- photos?: Photo[];
22
- className?: string;
23
- style?: CSSProperties;
24
- onPhotoClick?: (photo: Photo) => void;
25
- }
11
+ // Re-export the proper HexGridProps from types.ts
12
+ export type { HexGridProps } from '../types';
26
13
 
27
- export function HexGrid(_props: HexGridProps): JSX.Element | null {
14
+ export function HexGrid(_props: HexGridPropsType): React.JSX.Element | null {
28
15
  return null;
29
16
  }
30
17