@buley/hexgrid-3d 3.2.3 → 3.3.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 (55) hide show
  1. package/dist/algorithms/AdvancedStatistics.d.ts +2 -2
  2. package/dist/algorithms/AdvancedStatistics.d.ts.map +1 -1
  3. package/dist/algorithms/AdvancedStatistics.js +2 -2
  4. package/dist/algorithms/BayesianStatistics.d.ts +6 -0
  5. package/dist/algorithms/BayesianStatistics.d.ts.map +1 -1
  6. package/dist/algorithms/BayesianStatistics.js +54 -0
  7. package/dist/algorithms/FlowField.d.ts +11 -0
  8. package/dist/algorithms/FlowField.d.ts.map +1 -1
  9. package/dist/algorithms/FlowField.js +13 -0
  10. package/dist/algorithms/FluidSimulation.d.ts +21 -3
  11. package/dist/algorithms/FluidSimulation.d.ts.map +1 -1
  12. package/dist/algorithms/FluidSimulation.js +40 -0
  13. package/dist/algorithms/GraphAlgorithms.d.ts +1 -1
  14. package/dist/algorithms/GraphAlgorithms.d.ts.map +1 -1
  15. package/dist/algorithms/GraphAlgorithms.js +87 -15
  16. package/dist/algorithms/OutlierDetection.d.ts +4 -0
  17. package/dist/algorithms/OutlierDetection.d.ts.map +1 -1
  18. package/dist/algorithms/OutlierDetection.js +15 -3
  19. package/dist/algorithms/ParticleSystem.d.ts +46 -3
  20. package/dist/algorithms/ParticleSystem.d.ts.map +1 -1
  21. package/dist/algorithms/ParticleSystem.js +118 -15
  22. package/dist/components/HexGrid.d.ts +32 -2
  23. package/dist/components/HexGrid.d.ts.map +1 -1
  24. package/dist/components/HexGrid.js +5456 -25
  25. package/dist/components/debug/PoolStatsOverlay.d.ts +6 -0
  26. package/dist/components/debug/PoolStatsOverlay.d.ts.map +1 -0
  27. package/dist/components/debug/PoolStatsOverlay.js +18 -0
  28. package/dist/components/index.d.ts +3 -2
  29. package/dist/components/index.d.ts.map +1 -1
  30. package/dist/components/index.js +1 -1
  31. package/dist/lib/html-utils.d.ts +2 -0
  32. package/dist/lib/html-utils.d.ts.map +1 -0
  33. package/dist/lib/html-utils.js +7 -0
  34. package/dist/lib/logger.d.ts +20 -0
  35. package/dist/lib/logger.d.ts.map +1 -0
  36. package/dist/lib/logger.js +9 -0
  37. package/dist/lib/narration.d.ts +5 -0
  38. package/dist/lib/narration.d.ts.map +1 -1
  39. package/dist/lib/narration.js +19 -0
  40. package/dist/lib/stats-tracker.d.ts +2 -0
  41. package/dist/lib/stats-tracker.d.ts.map +1 -1
  42. package/dist/lib/stats-tracker.js +13 -0
  43. package/dist/lib/theme-colors.d.ts +9 -0
  44. package/dist/lib/theme-colors.d.ts.map +1 -1
  45. package/dist/lib/theme-colors.js +18 -1
  46. package/dist/math/Matrix4.d.ts +179 -2
  47. package/dist/math/Matrix4.d.ts.map +1 -1
  48. package/dist/math/Matrix4.js +528 -8
  49. package/dist/math/Quaternion.d.ts +69 -0
  50. package/dist/math/Quaternion.d.ts.map +1 -1
  51. package/dist/math/Quaternion.js +439 -0
  52. package/dist/math/SpatialIndex.d.ts +32 -13
  53. package/dist/math/SpatialIndex.d.ts.map +1 -1
  54. package/dist/math/SpatialIndex.js +239 -33
  55. package/package.json +4 -2
@@ -1,36 +1,176 @@
1
1
  export class KDTree {
2
- constructor(points, data) {
3
- this.points = points;
4
- this.data = data;
2
+ constructor(dimensions) {
3
+ this.root = null;
4
+ this.dimensions = dimensions;
5
5
  }
6
- static build(points, data, _dimensions) {
7
- return new KDTree(points, data);
6
+ static build(points, data) {
7
+ if (points.length !== data.length) {
8
+ throw new Error('points and data arrays must have the same length');
9
+ }
10
+ if (points.length === 0) {
11
+ const tree = new KDTree(0);
12
+ return tree;
13
+ }
14
+ const dimensions = points[0].length;
15
+ const tree = new KDTree(dimensions);
16
+ const indices = points.map((_, i) => i);
17
+ tree.root = tree.buildRecursive(points, data, indices, 0);
18
+ return tree;
19
+ }
20
+ static fromVector3(vectors, data) {
21
+ const points = vectors.map((v) => [v.x, v.y, v.z]);
22
+ return KDTree.build(points, data);
23
+ }
24
+ static fromVector2(vectors, data) {
25
+ const points = vectors.map((v) => [v.x, v.y]);
26
+ return KDTree.build(points, data);
27
+ }
28
+ buildRecursive(points, data, indices, depth) {
29
+ if (indices.length === 0)
30
+ return null;
31
+ const dim = depth % this.dimensions;
32
+ indices.sort((a, b) => points[a][dim] - points[b][dim]);
33
+ const mid = Math.floor(indices.length / 2);
34
+ const midIndex = indices[mid];
35
+ return {
36
+ point: points[midIndex],
37
+ data: data[midIndex],
38
+ splitDimension: dim,
39
+ left: this.buildRecursive(points, data, indices.slice(0, mid), depth + 1),
40
+ right: this.buildRecursive(points, data, indices.slice(mid + 1), depth + 1),
41
+ };
42
+ }
43
+ insert(point, data) {
44
+ if (this.dimensions === 0) {
45
+ this.dimensions = point.length;
46
+ }
47
+ this.root = this.insertRecursive(this.root, point, data, 0);
48
+ }
49
+ insertRecursive(node, point, data, depth) {
50
+ if (node === null) {
51
+ return {
52
+ point,
53
+ data,
54
+ left: null,
55
+ right: null,
56
+ splitDimension: depth % this.dimensions,
57
+ };
58
+ }
59
+ const dim = node.splitDimension;
60
+ if (point[dim] < node.point[dim]) {
61
+ node.left = this.insertRecursive(node.left, point, data, depth + 1);
62
+ }
63
+ else {
64
+ node.right = this.insertRecursive(node.right, point, data, depth + 1);
65
+ }
66
+ return node;
67
+ }
68
+ nearestNeighbor(target) {
69
+ if (this.root === null)
70
+ return null;
71
+ const best = { node: null, distance: Infinity };
72
+ this.nearestRecursive(this.root, target, best);
73
+ if (best.node === null)
74
+ return null;
75
+ return { data: best.node.data, distance: best.distance };
76
+ }
77
+ nearestRecursive(node, target, best) {
78
+ if (node === null)
79
+ return;
80
+ const dist = KDTree.distance(node.point, target);
81
+ if (dist < best.distance) {
82
+ best.distance = dist;
83
+ best.node = node;
84
+ }
85
+ const dim = node.splitDimension;
86
+ const diff = target[dim] - node.point[dim];
87
+ const first = diff < 0 ? node.left : node.right;
88
+ const second = diff < 0 ? node.right : node.left;
89
+ this.nearestRecursive(first, target, best);
90
+ if (Math.abs(diff) < best.distance) {
91
+ this.nearestRecursive(second, target, best);
92
+ }
8
93
  }
9
94
  kNearest(target, k) {
10
- const results = this.points.map((point, index) => ({
11
- data: this.data[index],
12
- distance: KDTree.distance(point, target),
13
- }));
14
- return results.sort((a, b) => a.distance - b.distance).slice(0, k);
95
+ if (k <= 0)
96
+ return [];
97
+ const results = [];
98
+ this.collectAll(this.root, target, results);
99
+ results.sort((a, b) => a.distance - b.distance);
100
+ return results.slice(0, k);
15
101
  }
16
102
  rangeQuery(target, radius) {
17
- return this.points
18
- .map((point, index) => ({
19
- data: this.data[index],
20
- distance: KDTree.distance(point, target),
21
- }))
22
- .filter((result) => result.distance <= radius);
103
+ const results = [];
104
+ this.rangeRecursive(this.root, target, radius, results);
105
+ return results;
106
+ }
107
+ rangeRecursive(node, target, radius, results) {
108
+ if (node === null)
109
+ return;
110
+ const dist = KDTree.distance(node.point, target);
111
+ if (dist <= radius) {
112
+ results.push({ data: node.data, distance: dist });
113
+ }
114
+ const dim = node.splitDimension;
115
+ const diff = target[dim] - node.point[dim];
116
+ const first = diff < 0 ? node.left : node.right;
117
+ const second = diff < 0 ? node.right : node.left;
118
+ this.rangeRecursive(first, target, radius, results);
119
+ if (Math.abs(diff) <= radius) {
120
+ this.rangeRecursive(second, target, radius, results);
121
+ }
122
+ }
123
+ boxQuery(min, max) {
124
+ const results = [];
125
+ this.boxRecursive(this.root, min, max, results);
126
+ return results;
127
+ }
128
+ boxRecursive(node, min, max, results) {
129
+ if (node === null)
130
+ return;
131
+ let inside = true;
132
+ for (let i = 0; i < node.point.length; i++) {
133
+ if (node.point[i] < min[i] || node.point[i] > max[i]) {
134
+ inside = false;
135
+ break;
136
+ }
137
+ }
138
+ if (inside) {
139
+ const dist = KDTree.distance(node.point, min);
140
+ results.push({ data: node.data, distance: dist });
141
+ }
142
+ const dim = node.splitDimension;
143
+ if (min[dim] <= node.point[dim]) {
144
+ this.boxRecursive(node.left, min, max, results);
145
+ }
146
+ if (max[dim] >= node.point[dim]) {
147
+ this.boxRecursive(node.right, min, max, results);
148
+ }
149
+ }
150
+ collectAll(node, target, results) {
151
+ if (node === null)
152
+ return;
153
+ results.push({
154
+ data: node.data,
155
+ distance: KDTree.distance(node.point, target),
156
+ });
157
+ this.collectAll(node.left, target, results);
158
+ this.collectAll(node.right, target, results);
23
159
  }
24
160
  static distance(a, b) {
25
- const dx = a[0] - b[0];
26
- const dy = a[1] - b[1];
27
- return Math.sqrt(dx * dx + dy * dy);
161
+ let sum = 0;
162
+ for (let i = 0; i < a.length; i++) {
163
+ const d = a[i] - b[i];
164
+ sum += d * d;
165
+ }
166
+ return Math.sqrt(sum);
28
167
  }
29
168
  }
30
169
  export class SpatialHashGrid {
31
- constructor(cellSize, _dimensions) {
170
+ constructor(cellSize, dimensions = 3) {
32
171
  this.grid = new Map();
33
172
  this.cellSize = cellSize;
173
+ this.dimensions = dimensions;
34
174
  }
35
175
  insert(position, data) {
36
176
  const key = this.keyFor(position);
@@ -38,6 +178,28 @@ export class SpatialHashGrid {
38
178
  bucket.push({ data, position });
39
179
  this.grid.set(key, bucket);
40
180
  }
181
+ insertAll(entries) {
182
+ for (const entry of entries) {
183
+ this.insert(entry.position, entry.data);
184
+ }
185
+ }
186
+ remove(position, data) {
187
+ const key = this.keyFor(position);
188
+ const bucket = this.grid.get(key);
189
+ if (!bucket)
190
+ return false;
191
+ const index = bucket.findIndex((entry) => entry.data === data && this.positionsEqual(entry.position, position));
192
+ if (index === -1)
193
+ return false;
194
+ bucket.splice(index, 1);
195
+ if (bucket.length === 0) {
196
+ this.grid.delete(key);
197
+ }
198
+ return true;
199
+ }
200
+ clear() {
201
+ this.grid.clear();
202
+ }
41
203
  query(position, radius) {
42
204
  const cellsToCheck = this.nearbyKeys(position, radius);
43
205
  const results = [];
@@ -46,30 +208,74 @@ export class SpatialHashGrid {
46
208
  if (!bucket)
47
209
  continue;
48
210
  for (const entry of bucket) {
49
- const distance = Math.hypot(entry.position[0] - position[0], entry.position[1] - position[1]);
50
- if (distance <= radius) {
211
+ const dist = this.distance(entry.position, position);
212
+ if (dist <= radius) {
51
213
  results.push(entry);
52
214
  }
53
215
  }
54
216
  }
55
217
  return results;
56
218
  }
219
+ nearest(position, searchRadius) {
220
+ const candidates = this.query(position, searchRadius);
221
+ if (candidates.length === 0)
222
+ return null;
223
+ let bestEntry = null;
224
+ let bestDist = Infinity;
225
+ for (const entry of candidates) {
226
+ const dist = this.distance(entry.position, position);
227
+ if (dist < bestDist) {
228
+ bestDist = dist;
229
+ bestEntry = entry;
230
+ }
231
+ }
232
+ return bestEntry;
233
+ }
234
+ positionsEqual(a, b) {
235
+ if (a.length !== b.length)
236
+ return false;
237
+ for (let i = 0; i < a.length; i++) {
238
+ if (a[i] !== b[i])
239
+ return false;
240
+ }
241
+ return true;
242
+ }
243
+ distance(a, b) {
244
+ let sum = 0;
245
+ for (let i = 0; i < a.length; i++) {
246
+ const d = a[i] - b[i];
247
+ sum += d * d;
248
+ }
249
+ return Math.sqrt(sum);
250
+ }
57
251
  keyFor(position) {
58
- const x = Math.floor(position[0] / this.cellSize);
59
- const y = Math.floor(position[1] / this.cellSize);
60
- return `${x},${y}`;
252
+ const parts = [];
253
+ for (let i = 0; i < this.dimensions; i++) {
254
+ parts.push(Math.floor((position[i] ?? 0) / this.cellSize));
255
+ }
256
+ return parts.join(',');
61
257
  }
62
258
  nearbyKeys(position, radius) {
63
- const minX = Math.floor((position[0] - radius) / this.cellSize);
64
- const maxX = Math.floor((position[0] + radius) / this.cellSize);
65
- const minY = Math.floor((position[1] - radius) / this.cellSize);
66
- const maxY = Math.floor((position[1] + radius) / this.cellSize);
259
+ const mins = [];
260
+ const maxs = [];
261
+ for (let i = 0; i < this.dimensions; i++) {
262
+ const val = position[i] ?? 0;
263
+ mins.push(Math.floor((val - radius) / this.cellSize));
264
+ maxs.push(Math.floor((val + radius) / this.cellSize));
265
+ }
67
266
  const keys = [];
68
- for (let x = minX; x <= maxX; x++) {
69
- for (let y = minY; y <= maxY; y++) {
70
- keys.push(`${x},${y}`);
267
+ const coords = new Array(this.dimensions).fill(0);
268
+ const generate = (dim) => {
269
+ if (dim === this.dimensions) {
270
+ keys.push(coords.join(','));
271
+ return;
71
272
  }
72
- }
273
+ for (let v = mins[dim]; v <= maxs[dim]; v++) {
274
+ coords[dim] = v;
275
+ generate(dim + 1);
276
+ }
277
+ };
278
+ generate(0);
73
279
  return keys;
74
280
  }
75
281
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buley/hexgrid-3d",
3
- "version": "3.2.3",
3
+ "version": "3.3.0",
4
4
  "description": "3D hexagonal grid visualization component for React",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -67,7 +67,8 @@
67
67
  "peerDependencies": {
68
68
  "next": "^14.0.0",
69
69
  "react": "^18.0.0",
70
- "react-dom": "^18.0.0"
70
+ "react-dom": "^18.0.0",
71
+ "three": "^0.183.1"
71
72
  },
72
73
  "devDependencies": {
73
74
  "@happy-dom/global-registrator": "^20.3.3",
@@ -77,6 +78,7 @@
77
78
  "@testing-library/user-event": "^14.6.1",
78
79
  "@types/jest": "^29.5.14",
79
80
  "@types/react": "^18.3.27",
81
+ "@types/three": "^0.183.1",
80
82
  "@types/react-dom": "^18.3.7",
81
83
  "happy-dom": "^20.3.3",
82
84
  "jest": "^29.7.0",