@buley/hexgrid-3d 1.0.0

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 (46) hide show
  1. package/.eslintrc.json +28 -0
  2. package/LICENSE +39 -0
  3. package/README.md +291 -0
  4. package/examples/basic-usage.tsx +52 -0
  5. package/package.json +65 -0
  6. package/public/hexgrid-worker.js +1763 -0
  7. package/rust/Cargo.toml +41 -0
  8. package/rust/src/lib.rs +740 -0
  9. package/rust/src/math.rs +574 -0
  10. package/rust/src/spatial.rs +245 -0
  11. package/rust/src/statistics.rs +496 -0
  12. package/src/HexGridEnhanced.ts +16 -0
  13. package/src/Snapshot.ts +1402 -0
  14. package/src/adapters.ts +65 -0
  15. package/src/algorithms/AdvancedStatistics.ts +328 -0
  16. package/src/algorithms/BayesianStatistics.ts +317 -0
  17. package/src/algorithms/FlowField.ts +126 -0
  18. package/src/algorithms/FluidSimulation.ts +99 -0
  19. package/src/algorithms/GraphAlgorithms.ts +184 -0
  20. package/src/algorithms/OutlierDetection.ts +391 -0
  21. package/src/algorithms/ParticleSystem.ts +85 -0
  22. package/src/algorithms/index.ts +13 -0
  23. package/src/compat.ts +96 -0
  24. package/src/components/HexGrid.tsx +31 -0
  25. package/src/components/NarrationOverlay.tsx +221 -0
  26. package/src/components/index.ts +2 -0
  27. package/src/features.ts +125 -0
  28. package/src/index.ts +30 -0
  29. package/src/math/HexCoordinates.ts +15 -0
  30. package/src/math/Matrix4.ts +35 -0
  31. package/src/math/Quaternion.ts +37 -0
  32. package/src/math/SpatialIndex.ts +114 -0
  33. package/src/math/Vector3.ts +69 -0
  34. package/src/math/index.ts +11 -0
  35. package/src/note-adapter.ts +124 -0
  36. package/src/ontology-adapter.ts +77 -0
  37. package/src/stores/index.ts +1 -0
  38. package/src/stores/uiStore.ts +85 -0
  39. package/src/types/index.ts +3 -0
  40. package/src/types.ts +152 -0
  41. package/src/utils/image-utils.ts +25 -0
  42. package/src/wasm/HexGridWasmWrapper.ts +753 -0
  43. package/src/wasm/index.ts +7 -0
  44. package/src/workers/hexgrid-math.ts +177 -0
  45. package/src/workers/hexgrid-worker.worker.ts +1807 -0
  46. package/tsconfig.json +18 -0
@@ -0,0 +1,126 @@
1
+ import { Vector2 } from '../math/Vector3';
2
+
3
+ export interface FlowFieldConfig {
4
+ width: number;
5
+ height: number;
6
+ resolution: number;
7
+ }
8
+
9
+ export interface Streamline {
10
+ points: Vector2[];
11
+ }
12
+
13
+ interface Source {
14
+ x: number;
15
+ y: number;
16
+ vx: number;
17
+ vy: number;
18
+ strength: number;
19
+ }
20
+
21
+ export class FlowField2D {
22
+ private config: FlowFieldConfig;
23
+ private sources: Source[] = [];
24
+
25
+ constructor(config: FlowFieldConfig) {
26
+ this.config = config;
27
+ }
28
+
29
+ clear(): void {
30
+ this.sources = [];
31
+ }
32
+
33
+ addSource(x: number, y: number, vx: number, vy: number, strength: number): void {
34
+ this.sources.push({ x, y, vx, vy, strength });
35
+ }
36
+
37
+ update(_deltaTime: number): void {
38
+ // No-op for stub
39
+ }
40
+
41
+ sample(x: number, y: number): Vector2 {
42
+ if (this.sources.length === 0) {
43
+ return new Vector2(0, 0);
44
+ }
45
+ let vx = 0;
46
+ let vy = 0;
47
+ for (const source of this.sources) {
48
+ const dx = x - source.x;
49
+ const dy = y - source.y;
50
+ const distance = Math.hypot(dx, dy) || 1;
51
+ const influence = source.strength / distance;
52
+ vx += source.vx * influence;
53
+ vy += source.vy * influence;
54
+ }
55
+ return new Vector2(vx, vy);
56
+ }
57
+
58
+ sampleFull(x: number, y: number): { velocity: Vector2; divergence: number; curl: number } {
59
+ const velocity = this.sample(x, y);
60
+ return {
61
+ velocity,
62
+ divergence: velocity.x * 0.01,
63
+ curl: velocity.y * 0.01,
64
+ };
65
+ }
66
+
67
+ traceStreamline(x: number, y: number, options: { maxLength: number; stepSize: number; maxSteps: number }): Streamline {
68
+ const points: Vector2[] = [new Vector2(x, y)];
69
+ let current = new Vector2(x, y);
70
+ for (let i = 0; i < options.maxSteps; i++) {
71
+ const velocity = this.sample(current.x, current.y);
72
+ const next = new Vector2(
73
+ current.x + velocity.x * options.stepSize,
74
+ current.y + velocity.y * options.stepSize
75
+ );
76
+ points.push(next);
77
+ current = next;
78
+ if (points.length * options.stepSize >= options.maxLength) {
79
+ break;
80
+ }
81
+ }
82
+ return { points };
83
+ }
84
+ }
85
+
86
+ export class InfectionFlowAnalyzer {
87
+ private width: number;
88
+ private height: number;
89
+
90
+ constructor(width: number, height: number) {
91
+ this.width = width;
92
+ this.height = height;
93
+ }
94
+
95
+ getFlowData(
96
+ _infections: Map<number, unknown>,
97
+ _positions: Array<{ x: number; y: number }>
98
+ ): Array<{ centroid: Vector2; velocity: Vector2 }> {
99
+ return [];
100
+ }
101
+ }
102
+
103
+ export class HeatMap {
104
+ private width: number;
105
+ private height: number;
106
+ private resolution: number;
107
+ private data: Float32Array;
108
+
109
+ constructor(config: { width: number; height: number; resolution: number; kernelRadius: number }) {
110
+ this.width = Math.max(1, Math.round(config.width / config.resolution));
111
+ this.height = Math.max(1, Math.round(config.height / config.resolution));
112
+ this.resolution = config.resolution;
113
+ this.data = new Float32Array(this.width * this.height);
114
+ }
115
+
116
+ addPoint(x: number, y: number, value: number): void {
117
+ const gridX = Math.min(this.width - 1, Math.max(0, Math.floor(x / this.resolution)));
118
+ const gridY = Math.min(this.height - 1, Math.max(0, Math.floor(y / this.resolution)));
119
+ const index = gridY * this.width + gridX;
120
+ this.data[index] += value;
121
+ }
122
+
123
+ getData(): Float32Array {
124
+ return this.data;
125
+ }
126
+ }
@@ -0,0 +1,99 @@
1
+ export interface FluidConfig {
2
+ width: number;
3
+ height: number;
4
+ viscosity: number;
5
+ diffusion: number;
6
+ }
7
+
8
+ export class StableFluids {
9
+ private width: number;
10
+ private height: number;
11
+ private density: Float32Array;
12
+ private velocityX: Float32Array;
13
+ private velocityY: Float32Array;
14
+
15
+ constructor(config: FluidConfig) {
16
+ this.width = Math.max(1, Math.round(config.width));
17
+ this.height = Math.max(1, Math.round(config.height));
18
+ const size = this.width * this.height;
19
+ this.density = new Float32Array(size);
20
+ this.velocityX = new Float32Array(size);
21
+ this.velocityY = new Float32Array(size);
22
+ }
23
+
24
+ addDensity(x: number, y: number, amount: number, radius: number): void {
25
+ const index = this.indexFor(x, y);
26
+ this.density[index] += amount;
27
+ }
28
+
29
+ addForce(x: number, y: number, vx: number, vy: number, _radius: number): void {
30
+ const index = this.indexFor(x, y);
31
+ this.velocityX[index] += vx;
32
+ this.velocityY[index] += vy;
33
+ }
34
+
35
+ step(_deltaTime: number): void {
36
+ // Simple damping
37
+ for (let i = 0; i < this.velocityX.length; i++) {
38
+ this.velocityX[i] *= 0.98;
39
+ this.velocityY[i] *= 0.98;
40
+ }
41
+ }
42
+
43
+ getDensity(x: number, y: number): number {
44
+ return this.density[this.indexFor(x, y)] || 0;
45
+ }
46
+
47
+ getVelocity(x: number, y: number): { x: number; y: number } {
48
+ const index = this.indexFor(x, y);
49
+ return { x: this.velocityX[index] || 0, y: this.velocityY[index] || 0 };
50
+ }
51
+
52
+ getDensityField(): Float32Array {
53
+ return this.density;
54
+ }
55
+
56
+ getVelocityField(): { x: Float32Array; y: Float32Array } {
57
+ return { x: this.velocityX, y: this.velocityY };
58
+ }
59
+
60
+ clear(): void {
61
+ this.density.fill(0);
62
+ this.velocityX.fill(0);
63
+ this.velocityY.fill(0);
64
+ }
65
+
66
+ private indexFor(x: number, y: number): number {
67
+ const ix = Math.min(this.width - 1, Math.max(0, Math.floor(x)));
68
+ const iy = Math.min(this.height - 1, Math.max(0, Math.floor(y)));
69
+ return iy * this.width + ix;
70
+ }
71
+ }
72
+
73
+ export class LatticeBoltzmann {
74
+ constructor(_width: number, _height: number) {}
75
+ }
76
+
77
+ export class InfectionFluidSimulator {
78
+ private fluid: StableFluids;
79
+
80
+ constructor(width: number, height: number) {
81
+ this.fluid = new StableFluids({ width, height, viscosity: 0, diffusion: 0 });
82
+ }
83
+
84
+ registerTerritory(_ownerId: number, x: number, y: number, intensity: number): void {
85
+ this.fluid.addDensity(x, y, intensity, 1);
86
+ }
87
+
88
+ update(deltaTime: number): void {
89
+ this.fluid.step(deltaTime);
90
+ }
91
+
92
+ getFluid(): StableFluids {
93
+ return this.fluid;
94
+ }
95
+
96
+ clear(): void {
97
+ this.fluid.clear();
98
+ }
99
+ }
@@ -0,0 +1,184 @@
1
+ export interface Cluster {
2
+ centroid: number[];
3
+ members: number[];
4
+ cohesion: number;
5
+ separation: number;
6
+ }
7
+
8
+ export interface VoronoiCell {
9
+ siteIndex: number;
10
+ site: [number, number];
11
+ vertices: Array<[number, number]>;
12
+ neighbors: number[];
13
+ }
14
+
15
+ export interface VoronoiDiagram {
16
+ cells: VoronoiCell[];
17
+ vertices: Array<[number, number]>;
18
+ edges: Array<[[number, number], [number, number]]>;
19
+ }
20
+
21
+ export function kMeansClustering(points: number[][], k: number): Cluster[] {
22
+ if (points.length === 0 || k <= 0) return [];
23
+ const clusters: Cluster[] = Array.from({ length: k }, () => ({
24
+ centroid: new Array(points[0]?.length || 0).fill(0),
25
+ members: [],
26
+ cohesion: 0,
27
+ separation: 0,
28
+ }));
29
+
30
+ points.forEach((_point, index) => {
31
+ clusters[index % k].members.push(index);
32
+ });
33
+
34
+ for (const cluster of clusters) {
35
+ if (cluster.members.length === 0) continue;
36
+ const centroid = cluster.centroid.map((_, dim) => {
37
+ const sum = cluster.members.reduce(
38
+ (acc, memberIdx) => acc + (points[memberIdx]?.[dim] ?? 0),
39
+ 0
40
+ );
41
+ return sum / cluster.members.length;
42
+ });
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;
54
+ }
55
+
56
+ return clusters;
57
+ }
58
+
59
+ export function dbscan(
60
+ points: number[][],
61
+ _eps: number,
62
+ _minPoints: number
63
+ ): Cluster[] {
64
+ if (points.length === 0) return [];
65
+ return kMeansClustering(points, Math.min(1, points.length));
66
+ }
67
+
68
+ export function computeVoronoi(
69
+ sites: Array<[number, number]>,
70
+ bounds: { minX: number; maxX: number; minY: number; maxY: number }
71
+ ): VoronoiDiagram {
72
+ const vertices: Array<[number, number]> = [
73
+ [bounds.minX, bounds.minY],
74
+ [bounds.maxX, bounds.minY],
75
+ [bounds.maxX, bounds.maxY],
76
+ [bounds.minX, bounds.maxY],
77
+ ];
78
+
79
+ const cells: VoronoiCell[] = sites.map((site, index) => ({
80
+ siteIndex: index,
81
+ site,
82
+ vertices,
83
+ neighbors: sites.map((_s, i) => i).filter((i) => i !== index),
84
+ }));
85
+
86
+ const edges: Array<[[number, number], [number, number]]> = [];
87
+ for (let i = 0; i < vertices.length; i++) {
88
+ const start = vertices[i];
89
+ const end = vertices[(i + 1) % vertices.length];
90
+ edges.push([start, end]);
91
+ }
92
+
93
+ return { cells, vertices, edges };
94
+ }
95
+
96
+ export function analyzeTerritorBoundaries(
97
+ infections: Map<number, { photoId: string }>,
98
+ neighbors: number[][]
99
+ ): {
100
+ frontLength: Map<string, number>;
101
+ hotspots: number[];
102
+ } {
103
+ const frontLength = new Map<string, number>();
104
+ const hotspots: number[] = [];
105
+
106
+ infections.forEach((value, idx) => {
107
+ const neighborList = neighbors[idx] ?? [];
108
+ const different = neighborList.filter(
109
+ (neighborIdx) => infections.get(neighborIdx)?.photoId !== value.photoId
110
+ );
111
+ if (different.length > 0) {
112
+ hotspots.push(idx);
113
+ }
114
+ const current = frontLength.get(value.photoId) ?? 0;
115
+ frontLength.set(value.photoId, current + different.length);
116
+ });
117
+
118
+ return { frontLength, hotspots };
119
+ }
120
+
121
+ export function kMeansClustering2D(points: Array<[number, number]>, k: number): Cluster[] {
122
+ const asPoints = points.map((p) => [p[0], p[1]]);
123
+ return kMeansClustering(asPoints, k);
124
+ }
125
+
126
+ export function findConnectedComponents(graph: { nodes: number[]; edges: Map<number, number[]> }): number[][] {
127
+ const visited = new Set<number>();
128
+ const components: number[][] = [];
129
+
130
+ for (const node of graph.nodes) {
131
+ if (visited.has(node)) continue;
132
+ const stack = [node];
133
+ const component: number[] = [];
134
+
135
+ while (stack.length > 0) {
136
+ const current = stack.pop();
137
+ if (current === undefined || visited.has(current)) continue;
138
+ visited.add(current);
139
+ component.push(current);
140
+ const neighbors = graph.edges.get(current) ?? [];
141
+ for (const neighbor of neighbors) {
142
+ if (!visited.has(neighbor)) {
143
+ stack.push(neighbor);
144
+ }
145
+ }
146
+ }
147
+
148
+ components.push(component);
149
+ }
150
+
151
+ return components;
152
+ }
153
+
154
+ export function louvainCommunities(graph: {
155
+ nodes: number[];
156
+ edges: Map<number, number[]>;
157
+ weights?: Map<string, number>;
158
+ }): Array<{ members: number[]; modularity: number }> {
159
+ const components = findConnectedComponents(graph);
160
+ return components.map((members) => ({
161
+ members,
162
+ modularity: members.length / Math.max(1, graph.nodes.length),
163
+ }));
164
+ }
165
+
166
+ export function computeVoronoiGraph(sites: Array<[number, number]>) {
167
+ return computeVoronoi(sites, {
168
+ minX: Math.min(...sites.map((s) => s[0])),
169
+ maxX: Math.max(...sites.map((s) => s[0])),
170
+ minY: Math.min(...sites.map((s) => s[1])),
171
+ maxY: Math.max(...sites.map((s) => s[1])),
172
+ });
173
+ }
174
+
175
+ export function kMeansCluster(points: number[][], k: number): Cluster[] {
176
+ return kMeansClustering(points, k);
177
+ }
178
+
179
+ export function kMeansClusteringWithLabels(
180
+ points: number[][],
181
+ k: number
182
+ ): Cluster[] {
183
+ return kMeansClustering(points, k);
184
+ }