@buley/hexgrid-3d 3.2.4 → 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.
- package/dist/algorithms/AdvancedStatistics.d.ts +2 -2
- package/dist/algorithms/AdvancedStatistics.d.ts.map +1 -1
- package/dist/algorithms/AdvancedStatistics.js +2 -2
- package/dist/algorithms/BayesianStatistics.d.ts +6 -0
- package/dist/algorithms/BayesianStatistics.d.ts.map +1 -1
- package/dist/algorithms/BayesianStatistics.js +54 -0
- package/dist/algorithms/FlowField.d.ts +11 -0
- package/dist/algorithms/FlowField.d.ts.map +1 -1
- package/dist/algorithms/FlowField.js +13 -0
- package/dist/algorithms/FluidSimulation.d.ts +21 -3
- package/dist/algorithms/FluidSimulation.d.ts.map +1 -1
- package/dist/algorithms/FluidSimulation.js +40 -0
- package/dist/algorithms/GraphAlgorithms.d.ts +1 -1
- package/dist/algorithms/GraphAlgorithms.d.ts.map +1 -1
- package/dist/algorithms/GraphAlgorithms.js +87 -15
- package/dist/algorithms/OutlierDetection.d.ts +4 -0
- package/dist/algorithms/OutlierDetection.d.ts.map +1 -1
- package/dist/algorithms/OutlierDetection.js +15 -3
- package/dist/algorithms/ParticleSystem.d.ts +46 -3
- package/dist/algorithms/ParticleSystem.d.ts.map +1 -1
- package/dist/algorithms/ParticleSystem.js +118 -15
- package/dist/components/HexGrid.d.ts +32 -2
- package/dist/components/HexGrid.d.ts.map +1 -1
- package/dist/components/HexGrid.js +5456 -25
- package/dist/components/debug/PoolStatsOverlay.d.ts +6 -0
- package/dist/components/debug/PoolStatsOverlay.d.ts.map +1 -0
- package/dist/components/debug/PoolStatsOverlay.js +18 -0
- package/dist/components/index.d.ts +3 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/lib/html-utils.d.ts +2 -0
- package/dist/lib/html-utils.d.ts.map +1 -0
- package/dist/lib/html-utils.js +7 -0
- package/dist/lib/logger.d.ts +20 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +9 -0
- package/dist/lib/narration.d.ts +5 -0
- package/dist/lib/narration.d.ts.map +1 -1
- package/dist/lib/narration.js +19 -0
- package/dist/lib/stats-tracker.d.ts +2 -0
- package/dist/lib/stats-tracker.d.ts.map +1 -1
- package/dist/lib/stats-tracker.js +13 -0
- package/dist/lib/theme-colors.d.ts +9 -0
- package/dist/lib/theme-colors.d.ts.map +1 -1
- package/dist/lib/theme-colors.js +18 -1
- package/dist/math/Matrix4.d.ts +179 -2
- package/dist/math/Matrix4.d.ts.map +1 -1
- package/dist/math/Matrix4.js +528 -8
- package/dist/math/Quaternion.d.ts +69 -0
- package/dist/math/Quaternion.d.ts.map +1 -1
- package/dist/math/Quaternion.js +439 -0
- package/dist/math/SpatialIndex.d.ts +32 -13
- package/dist/math/SpatialIndex.d.ts.map +1 -1
- package/dist/math/SpatialIndex.js +239 -33
- package/package.json +4 -2
|
@@ -1,36 +1,176 @@
|
|
|
1
1
|
export class KDTree {
|
|
2
|
-
constructor(
|
|
3
|
-
this.
|
|
4
|
-
this.
|
|
2
|
+
constructor(dimensions) {
|
|
3
|
+
this.root = null;
|
|
4
|
+
this.dimensions = dimensions;
|
|
5
5
|
}
|
|
6
|
-
static build(points, data
|
|
7
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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,
|
|
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
|
|
50
|
-
if (
|
|
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
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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.
|
|
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",
|