@woosh/meep-engine 2.87.3 → 2.87.5
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/build/meep.cjs +4 -4
- package/build/meep.min.js +1 -1
- package/build/meep.module.js +4 -4
- package/package.json +1 -1
- package/src/core/binary/align_4.d.ts +7 -0
- package/src/core/binary/align_4.d.ts.map +1 -0
- package/src/core/binary/align_4.js +14 -0
- package/src/core/geom/2d/aabb/aabb2_intersects_ray.d.ts +14 -0
- package/src/core/geom/2d/aabb/aabb2_intersects_ray.d.ts.map +1 -0
- package/src/core/geom/2d/aabb/aabb2_intersects_ray.js +50 -0
- package/src/core/geom/2d/aabb/aabb2_intersects_ray.spec.d.ts +2 -0
- package/src/core/geom/2d/aabb/aabb2_intersects_ray.spec.d.ts.map +1 -0
- package/src/core/geom/2d/aabb/aabb2_intersects_ray.spec.js +28 -0
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.d.ts +52 -0
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.d.ts.map +1 -0
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.js +317 -0
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.spec.d.ts +2 -0
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.spec.d.ts.map +1 -0
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.spec.js +158 -0
- package/src/core/geom/2d/hash-grid/shg_query_raycast.d.ts +14 -0
- package/src/core/geom/2d/hash-grid/shg_query_raycast.d.ts.map +1 -0
- package/src/core/geom/2d/hash-grid/shg_query_raycast.js +21 -0
- package/src/core/geom/2d/lt-grid/LooseTightGrid.d.ts +55 -0
- package/src/core/geom/2d/lt-grid/LooseTightGrid.d.ts.map +1 -0
- package/src/core/geom/2d/lt-grid/LooseTightGrid.js +221 -0
- package/src/core/geom/2d/quad-tree/QuadTreeNode.js +3 -3
- package/src/core/geom/2d/quad-tree/qt_query_data_raycast.d.ts.map +1 -1
- package/src/core/geom/2d/quad-tree/qt_query_data_raycast.js +19 -6
- package/src/core/geom/2d/quad-tree-binary/QuadTree.d.ts +94 -0
- package/src/core/geom/2d/quad-tree-binary/QuadTree.d.ts.map +1 -0
- package/src/core/geom/2d/quad-tree-binary/QuadTree.js +715 -0
- package/src/core/geom/2d/quad-tree-binary/QuadTree.spec.d.ts +2 -0
- package/src/core/geom/2d/quad-tree-binary/QuadTree.spec.d.ts.map +1 -0
- package/src/core/geom/2d/quad-tree-binary/QuadTree.spec.js +53 -0
- package/src/core/geom/3d/morton/de_interleave_2_bits.spec.d.ts +2 -0
- package/src/core/geom/3d/morton/de_interleave_2_bits.spec.d.ts.map +1 -0
- package/src/core/geom/3d/morton/de_interleave_2_bits.spec.js +21 -0
- package/src/core/geom/3d/morton/de_interleave_bits_by_2.d.ts +7 -0
- package/src/core/geom/3d/morton/de_interleave_bits_by_2.d.ts.map +1 -0
- package/src/core/geom/3d/morton/de_interleave_bits_by_2.js +15 -0
- package/src/core/geom/3d/morton/split_by_2.d.ts +1 -1
- package/src/core/geom/3d/morton/split_by_2.js +1 -1
- package/src/core/geom/3d/morton/split_by_3.d.ts +1 -1
- package/src/core/geom/3d/morton/split_by_3.js +1 -1
- package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts +7 -0
- package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts.map +1 -1
- package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +14 -14
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
import { assert } from "../../../assert.js";
|
|
2
|
+
import { UINT32_MAX } from "../../../binary/UINT32_MAX.js";
|
|
3
|
+
import { max2 } from "../../../math/max2.js";
|
|
4
|
+
import { min2 } from "../../../math/min2.js";
|
|
5
|
+
import { BinaryElementPool } from "../../3d/topology/struct/binary/BinaryElementPool.js";
|
|
6
|
+
|
|
7
|
+
export const QT_NULL_POINTER = UINT32_MAX;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Numeric data supplied by the user
|
|
11
|
+
* @type {number}
|
|
12
|
+
*/
|
|
13
|
+
const COLUMN_ELEMENT_USER_DATA = 0;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Singly-linked list, nest element
|
|
17
|
+
* can be NULL_POINTER to represent end of the list
|
|
18
|
+
* @type {number}
|
|
19
|
+
*/
|
|
20
|
+
const COLUMN_ELEMENT_NEXT = 1;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Tree node that contains this element
|
|
24
|
+
* @type {number}
|
|
25
|
+
*/
|
|
26
|
+
const COLUMN_ELEMENT_PARENT_NODE = 2;
|
|
27
|
+
|
|
28
|
+
const COLUMN_ELEMENT_X0 = 3;
|
|
29
|
+
const COLUMN_ELEMENT_Y0 = 4;
|
|
30
|
+
const COLUMN_ELEMENT_X1 = 5;
|
|
31
|
+
const COLUMN_ELEMENT_Y1 = 6;
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
const COLUMN_TREE_NODE_FIRST_ELEMENT_NODE_POINTER = 0;
|
|
35
|
+
/**
|
|
36
|
+
* Number of elements stored in the node's element list
|
|
37
|
+
* @type {number}
|
|
38
|
+
*/
|
|
39
|
+
const COLUMN_TREE_NODE_ELEMENT_COUNT = 1
|
|
40
|
+
const COLUMN_TREE_NODE_PARENT = 2;
|
|
41
|
+
const COLUMN_TREE_NODE_CHILDREN_POINTER_TL = 3;
|
|
42
|
+
const COLUMN_TREE_NODE_CHILDREN_POINTER_TR = 4;
|
|
43
|
+
const COLUMN_TREE_NODE_CHILDREN_POINTER_BL = 5;
|
|
44
|
+
const COLUMN_TREE_NODE_CHILDREN_POINTER_BR = 6;
|
|
45
|
+
|
|
46
|
+
const THRESHOLD_SPLIT = 16;
|
|
47
|
+
const THRESHOLD_MERGE = 8;
|
|
48
|
+
|
|
49
|
+
const temp_array_element = new Uint32Array(4096);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
*
|
|
53
|
+
* NOTE: THIS CODE IS UNFINISHED, IT IS ONLY A SKETCH
|
|
54
|
+
* TODO finish implementation
|
|
55
|
+
*/
|
|
56
|
+
export class QuadTree {
|
|
57
|
+
|
|
58
|
+
#node_pool = new BinaryElementPool(28);
|
|
59
|
+
#element_pool = new BinaryElementPool(28);
|
|
60
|
+
|
|
61
|
+
#root = QT_NULL_POINTER
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* AABB of the entire tree
|
|
65
|
+
* @type {Float32Array}
|
|
66
|
+
*/
|
|
67
|
+
#dimensions = new Float32Array([0, 0, 0, 0]);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parameters allow us to set initial bounds to prevent early resizing
|
|
71
|
+
* @param {number} x0
|
|
72
|
+
* @param {number} y0
|
|
73
|
+
* @param {number} x1
|
|
74
|
+
* @param {number} y1
|
|
75
|
+
*/
|
|
76
|
+
constructor(x0 = 0, y0 = 0, x1 = 0, y1 = 0) {
|
|
77
|
+
this.#setDimensions(x0, y0, x1, y1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get root() {
|
|
81
|
+
return this.#root;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* This method is unsafe as it does not re-build the tree
|
|
86
|
+
* Make sure to rebuild the tree as necessary after calling this
|
|
87
|
+
* @param {number} x0
|
|
88
|
+
* @param {number} y0
|
|
89
|
+
* @param {number} x1
|
|
90
|
+
* @param {number} y1
|
|
91
|
+
*/
|
|
92
|
+
#setDimensions(x0, y0, x1, y1) {
|
|
93
|
+
|
|
94
|
+
assert.isFiniteNumber(x0, 'x0');
|
|
95
|
+
assert.notNaN(x0, 'x0');
|
|
96
|
+
|
|
97
|
+
assert.isFiniteNumber(y0, 'y0');
|
|
98
|
+
assert.notNaN(y0, 'y0');
|
|
99
|
+
|
|
100
|
+
assert.isFiniteNumber(x1, 'x1');
|
|
101
|
+
assert.notNaN(x1, 'x1');
|
|
102
|
+
|
|
103
|
+
assert.isFiniteNumber(y1, 'y1');
|
|
104
|
+
assert.notNaN(y1, 'y1');
|
|
105
|
+
|
|
106
|
+
const dimensions = this.#dimensions;
|
|
107
|
+
|
|
108
|
+
dimensions[0] = x0;
|
|
109
|
+
dimensions[1] = y0;
|
|
110
|
+
dimensions[2] = x1;
|
|
111
|
+
dimensions[3] = y1;
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Resize dimensions of the tree to tightly fit all inserted elements
|
|
117
|
+
*/
|
|
118
|
+
shrink() {
|
|
119
|
+
if (this.#root === QT_NULL_POINTER) {
|
|
120
|
+
// tree is empty
|
|
121
|
+
this.#setDimensions(0, 0, 0, 0);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const bounds = [0, 0, 0, 0];
|
|
125
|
+
|
|
126
|
+
this.compute_tight_bounds(bounds);
|
|
127
|
+
|
|
128
|
+
this.#setDimensions(...bounds);
|
|
129
|
+
|
|
130
|
+
this.rebuild();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
*
|
|
135
|
+
* @param {number[]|Float32Array} output
|
|
136
|
+
*/
|
|
137
|
+
compute_tight_bounds(output) {
|
|
138
|
+
const pool = this.#element_pool;
|
|
139
|
+
const data_pool_size = pool.size;
|
|
140
|
+
const float32 = pool.data_float32;
|
|
141
|
+
|
|
142
|
+
let bounds_x0 = Infinity;
|
|
143
|
+
let bounds_y0 = Infinity;
|
|
144
|
+
let bounds_x1 = -Infinity;
|
|
145
|
+
let bounds_y1 = -Infinity;
|
|
146
|
+
|
|
147
|
+
for (let id = 0; id < data_pool_size; id++) {
|
|
148
|
+
if (pool.is_allocated(id)) {
|
|
149
|
+
|
|
150
|
+
const word = pool.element_word(id);
|
|
151
|
+
|
|
152
|
+
const x0 = float32[word + COLUMN_ELEMENT_X0];
|
|
153
|
+
const y0 = float32[word + COLUMN_ELEMENT_Y0];
|
|
154
|
+
|
|
155
|
+
const x1 = float32[word + COLUMN_ELEMENT_X1];
|
|
156
|
+
const y1 = float32[word + COLUMN_ELEMENT_Y1];
|
|
157
|
+
|
|
158
|
+
bounds_x0 = min2(x0, bounds_x0);
|
|
159
|
+
bounds_y0 = min2(y0, bounds_y0);
|
|
160
|
+
|
|
161
|
+
bounds_x1 = max2(x1, bounds_x1);
|
|
162
|
+
bounds_y1 = max2(y1, bounds_y1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
output[0] = bounds_x0;
|
|
167
|
+
output[1] = bounds_y0;
|
|
168
|
+
output[2] = bounds_x1;
|
|
169
|
+
output[3] = bounds_y1;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Rebuild tree but keep all the data
|
|
174
|
+
*/
|
|
175
|
+
rebuild() {
|
|
176
|
+
// drop existing structure
|
|
177
|
+
this.#root = QT_NULL_POINTER;
|
|
178
|
+
|
|
179
|
+
this.#node_pool.clear();
|
|
180
|
+
|
|
181
|
+
// re-insert data elements
|
|
182
|
+
const pool = this.#element_pool;
|
|
183
|
+
const data_pool_size = pool.size;
|
|
184
|
+
for (let i = 0; i < data_pool_size; i++) {
|
|
185
|
+
if (pool.is_allocated(i)) {
|
|
186
|
+
this.element_insert(i);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
*
|
|
193
|
+
* @returns {number} ID of data in the tree
|
|
194
|
+
*/
|
|
195
|
+
element_allocate() {
|
|
196
|
+
return this.#element_pool.allocate();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
*
|
|
201
|
+
* @param {number} element
|
|
202
|
+
* @param {number} user_data
|
|
203
|
+
*/
|
|
204
|
+
element_set_user_data(element, user_data) {
|
|
205
|
+
assert.isNonNegativeInteger(user_data, 'user_data');
|
|
206
|
+
|
|
207
|
+
const pool = this.#element_pool;
|
|
208
|
+
|
|
209
|
+
const word = pool.element_word(element);
|
|
210
|
+
|
|
211
|
+
pool.data_uint32[word + COLUMN_ELEMENT_USER_DATA] = user_data;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
*
|
|
216
|
+
* @param {number} element
|
|
217
|
+
* @return {number}
|
|
218
|
+
*/
|
|
219
|
+
element_get_user_data(element) {
|
|
220
|
+
const pool = this.#element_pool;
|
|
221
|
+
const word = pool.element_word(element);
|
|
222
|
+
|
|
223
|
+
return pool.data_uint32[word + COLUMN_ELEMENT_USER_DATA];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
*
|
|
228
|
+
* @param {number} element
|
|
229
|
+
* @param {number} x0
|
|
230
|
+
* @param {number} y0
|
|
231
|
+
* @param {number} x1
|
|
232
|
+
* @param {number} y1
|
|
233
|
+
*/
|
|
234
|
+
element_set_bounds_primitive(
|
|
235
|
+
element,
|
|
236
|
+
x0, y0,
|
|
237
|
+
x1, y1
|
|
238
|
+
) {
|
|
239
|
+
|
|
240
|
+
assert.isFiniteNumber(x0, 'x0');
|
|
241
|
+
assert.notNaN(x0, 'x0');
|
|
242
|
+
|
|
243
|
+
assert.isFiniteNumber(y0, 'y0');
|
|
244
|
+
assert.notNaN(y0, 'y0');
|
|
245
|
+
|
|
246
|
+
assert.isFiniteNumber(x1, 'x1');
|
|
247
|
+
assert.notNaN(x1, 'x1');
|
|
248
|
+
|
|
249
|
+
assert.isFiniteNumber(y1, 'y1');
|
|
250
|
+
assert.notNaN(y1, 'y1');
|
|
251
|
+
|
|
252
|
+
const pool = this.#element_pool;
|
|
253
|
+
|
|
254
|
+
const word = pool.element_word(element);
|
|
255
|
+
|
|
256
|
+
const float32 = pool.data_float32;
|
|
257
|
+
|
|
258
|
+
float32[word + COLUMN_ELEMENT_X0] = x0;
|
|
259
|
+
float32[word + COLUMN_ELEMENT_Y0] = y0;
|
|
260
|
+
float32[word + COLUMN_ELEMENT_X1] = x1;
|
|
261
|
+
float32[word + COLUMN_ELEMENT_Y1] = y1;
|
|
262
|
+
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
*
|
|
267
|
+
* @param {number} element
|
|
268
|
+
* @return {number} next element in the node
|
|
269
|
+
*/
|
|
270
|
+
element_get_next(element) {
|
|
271
|
+
const pool = this.#element_pool;
|
|
272
|
+
const word = pool.element_word(element);
|
|
273
|
+
|
|
274
|
+
return pool.data_uint32[word + COLUMN_ELEMENT_NEXT];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
*
|
|
279
|
+
* @param {number} x0
|
|
280
|
+
* @param {number} y0
|
|
281
|
+
* @param {number} x1
|
|
282
|
+
* @param {number} y1
|
|
283
|
+
* @return {number}
|
|
284
|
+
*/
|
|
285
|
+
#find_parent_for_box(x0, y0, x1, y1) {
|
|
286
|
+
|
|
287
|
+
if (this.#root === QT_NULL_POINTER) {
|
|
288
|
+
// special case
|
|
289
|
+
return QT_NULL_POINTER;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let node = this.#root;
|
|
293
|
+
|
|
294
|
+
let bounds_x0 = this.#dimensions[0];
|
|
295
|
+
let bounds_y0 = this.#dimensions[1];
|
|
296
|
+
let bounds_x1 = this.#dimensions[2];
|
|
297
|
+
let bounds_y1 = this.#dimensions[3];
|
|
298
|
+
|
|
299
|
+
const node_pool = this.#node_pool;
|
|
300
|
+
const node_uint32 = node_pool.data_uint32;
|
|
301
|
+
|
|
302
|
+
for (; ;) {
|
|
303
|
+
const node_address = node_pool.element_word(node);
|
|
304
|
+
|
|
305
|
+
const bounds_mid_x = (bounds_x0 + bounds_x1) * 0.5;
|
|
306
|
+
const bounds_mid_y = (bounds_y0 + bounds_y1) * 0.5;
|
|
307
|
+
|
|
308
|
+
if (y1 < bounds_mid_y) {
|
|
309
|
+
//top
|
|
310
|
+
bounds_y1 = bounds_mid_y;
|
|
311
|
+
if (x1 < bounds_mid_x) {
|
|
312
|
+
//left
|
|
313
|
+
const child_tl = node_uint32[node_address + COLUMN_TREE_NODE_CHILDREN_POINTER_TL];
|
|
314
|
+
if (child_tl === QT_NULL_POINTER) {
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
node = child_tl;
|
|
318
|
+
bounds_x1 = bounds_mid_x;
|
|
319
|
+
} else if (x0 >= bounds_mid_x) {
|
|
320
|
+
//right
|
|
321
|
+
const child_tr = node_uint32[node_address + COLUMN_TREE_NODE_CHILDREN_POINTER_TR];
|
|
322
|
+
if (child_tr === QT_NULL_POINTER) {
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
node = child_tr;
|
|
326
|
+
bounds_x0 = bounds_mid_x;
|
|
327
|
+
} else {
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
} else if (y0 >= bounds_mid_y) {
|
|
331
|
+
//bottom
|
|
332
|
+
bounds_y0 = bounds_mid_y;
|
|
333
|
+
if (x1 < bounds_mid_x) {
|
|
334
|
+
//left
|
|
335
|
+
const child_bl = node_uint32[node_address + COLUMN_TREE_NODE_CHILDREN_POINTER_BL];
|
|
336
|
+
if (child_bl === QT_NULL_POINTER) {
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
node = child_bl;
|
|
340
|
+
bounds_x1 = bounds_mid_x;
|
|
341
|
+
} else if (x0 >= bounds_mid_x) {
|
|
342
|
+
//right
|
|
343
|
+
const child_br = node_uint32[node_address + COLUMN_TREE_NODE_CHILDREN_POINTER_BR];
|
|
344
|
+
if (child_br === QT_NULL_POINTER) {
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
node = child_br;
|
|
348
|
+
bounds_x0 = bounds_mid_x;
|
|
349
|
+
} else {
|
|
350
|
+
break;
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
// violates child bounds
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return node;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
*
|
|
363
|
+
* @param {number} x0
|
|
364
|
+
* @param {number} y0
|
|
365
|
+
* @param {number} x1
|
|
366
|
+
* @param {number} y1
|
|
367
|
+
*/
|
|
368
|
+
ensure_bounds(x0, y0, x1, y1) {
|
|
369
|
+
|
|
370
|
+
const dimensions = this.#dimensions;
|
|
371
|
+
|
|
372
|
+
const bounds_x0 = dimensions[0];
|
|
373
|
+
const bounds_y0 = dimensions[1];
|
|
374
|
+
const bounds_x1 = dimensions[2];
|
|
375
|
+
const bounds_y1 = dimensions[3];
|
|
376
|
+
|
|
377
|
+
if (
|
|
378
|
+
x0 >= bounds_x0
|
|
379
|
+
&& y0 >= bounds_y0
|
|
380
|
+
&& x1 < bounds_x1
|
|
381
|
+
&& y1 < bounds_y1
|
|
382
|
+
) {
|
|
383
|
+
// bounds are satisfied
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// dimensions violated
|
|
388
|
+
this.#setDimensions(
|
|
389
|
+
min2(x0, bounds_x0),
|
|
390
|
+
min2(y0, bounds_y0),
|
|
391
|
+
max2(x1, bounds_x1),
|
|
392
|
+
max2(y1, bounds_y1),
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Assumes element is allocated and is not present in the tree yet
|
|
399
|
+
* @param {number} element
|
|
400
|
+
*/
|
|
401
|
+
element_insert(element) {
|
|
402
|
+
const element_pool = this.#element_pool;
|
|
403
|
+
const element_word = element_pool.element_word(element);
|
|
404
|
+
|
|
405
|
+
// clear out element pointers
|
|
406
|
+
element_pool.data_uint32[element_word + COLUMN_ELEMENT_NEXT] = QT_NULL_POINTER;
|
|
407
|
+
|
|
408
|
+
const x0 = element_pool.data_float32[element_word + COLUMN_ELEMENT_X0];
|
|
409
|
+
const y0 = element_pool.data_float32[element_word + COLUMN_ELEMENT_Y0];
|
|
410
|
+
const x1 = element_pool.data_float32[element_word + COLUMN_ELEMENT_X1];
|
|
411
|
+
const y1 = element_pool.data_float32[element_word + COLUMN_ELEMENT_Y1];
|
|
412
|
+
|
|
413
|
+
this.ensure_bounds(x0, y0, x1, y1);
|
|
414
|
+
|
|
415
|
+
let parent_node = this.#find_parent_for_box(x0, y0, x1, y1);
|
|
416
|
+
|
|
417
|
+
if (parent_node === QT_NULL_POINTER) {
|
|
418
|
+
this.#root = this.#node_allocate();
|
|
419
|
+
|
|
420
|
+
parent_node = this.#root;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
this.#insert_element_into(parent_node, element);
|
|
425
|
+
|
|
426
|
+
this.#node_balance(parent_node);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
#node_allocate() {
|
|
430
|
+
const pool = this.#node_pool;
|
|
431
|
+
|
|
432
|
+
const node = pool.allocate();
|
|
433
|
+
|
|
434
|
+
const address = pool.element_word(node);
|
|
435
|
+
|
|
436
|
+
const uint32 = pool.data_uint32;
|
|
437
|
+
|
|
438
|
+
// initialize pointers
|
|
439
|
+
uint32[address + COLUMN_TREE_NODE_PARENT] = QT_NULL_POINTER;
|
|
440
|
+
uint32[address + COLUMN_TREE_NODE_FIRST_ELEMENT_NODE_POINTER] = QT_NULL_POINTER;
|
|
441
|
+
uint32[address + COLUMN_TREE_NODE_CHILDREN_POINTER_TL] = QT_NULL_POINTER;
|
|
442
|
+
uint32[address + COLUMN_TREE_NODE_CHILDREN_POINTER_TR] = QT_NULL_POINTER;
|
|
443
|
+
uint32[address + COLUMN_TREE_NODE_CHILDREN_POINTER_BL] = QT_NULL_POINTER;
|
|
444
|
+
uint32[address + COLUMN_TREE_NODE_CHILDREN_POINTER_BR] = QT_NULL_POINTER;
|
|
445
|
+
|
|
446
|
+
return node;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* This operation may create new nodes
|
|
451
|
+
* @param {number} node
|
|
452
|
+
*/
|
|
453
|
+
#node_balance(node) {
|
|
454
|
+
// count owned elements
|
|
455
|
+
const address = this.#node_pool.element_word(node);
|
|
456
|
+
|
|
457
|
+
const element_count = this.#node_pool.data_uint32[address + COLUMN_TREE_NODE_ELEMENT_COUNT];
|
|
458
|
+
|
|
459
|
+
if (element_count > THRESHOLD_SPLIT) {
|
|
460
|
+
|
|
461
|
+
this.#node_push_data_down(node);
|
|
462
|
+
} else if (element_count < THRESHOLD_MERGE) {
|
|
463
|
+
this.#node_pull_data();
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
*
|
|
469
|
+
* @param {number} node
|
|
470
|
+
* @param {number[]|Uint32Array} destination
|
|
471
|
+
* @param {number} destination_offset
|
|
472
|
+
* @return {number} number of elements read
|
|
473
|
+
*/
|
|
474
|
+
node_read_elements(node, destination, destination_offset) {
|
|
475
|
+
const node_address = this.#node_pool.element_word(node);
|
|
476
|
+
|
|
477
|
+
let element = this.#node_pool.data_uint32[node_address + COLUMN_TREE_NODE_FIRST_ELEMENT_NODE_POINTER];
|
|
478
|
+
|
|
479
|
+
let count = 0;
|
|
480
|
+
|
|
481
|
+
while (element !== QT_NULL_POINTER) {
|
|
482
|
+
destination[destination_offset + count] = element;
|
|
483
|
+
|
|
484
|
+
count++;
|
|
485
|
+
|
|
486
|
+
const element_address = this.#element_pool.element_word(element);
|
|
487
|
+
element = this.#element_pool.data_uint32[element_address + COLUMN_ELEMENT_NEXT];
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return count;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Take elements owned by the element and attempt to push them as deep as possible
|
|
495
|
+
* This operation may create new nodes
|
|
496
|
+
* @param {number} node
|
|
497
|
+
*/
|
|
498
|
+
#node_push_data_down(node) {
|
|
499
|
+
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
#node_pull_data() {
|
|
503
|
+
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
*
|
|
508
|
+
* @param {number} node
|
|
509
|
+
* @param {number} element
|
|
510
|
+
*/
|
|
511
|
+
#insert_element_into(node, element) {
|
|
512
|
+
|
|
513
|
+
const node_pool = this.#node_pool;
|
|
514
|
+
const node_address = node_pool.element_word(node);
|
|
515
|
+
|
|
516
|
+
// increment node count
|
|
517
|
+
node_pool[node_address + COLUMN_TREE_NODE_ELEMENT_COUNT]++;
|
|
518
|
+
|
|
519
|
+
let _element = node_pool.data_uint32[node_address + COLUMN_TREE_NODE_FIRST_ELEMENT_NODE_POINTER];
|
|
520
|
+
|
|
521
|
+
const element_pool = this.#element_pool;
|
|
522
|
+
|
|
523
|
+
if (_element === QT_NULL_POINTER) {
|
|
524
|
+
// first element
|
|
525
|
+
node_pool.data_uint32[node_address + COLUMN_TREE_NODE_FIRST_ELEMENT_NODE_POINTER] = element;
|
|
526
|
+
} else {
|
|
527
|
+
// scan to the last element
|
|
528
|
+
let _link = _element;
|
|
529
|
+
|
|
530
|
+
for (; ;) {
|
|
531
|
+
const address = element_pool.element_word(_link);
|
|
532
|
+
const next = element_pool.data_uint32[address + COLUMN_ELEMENT_NEXT];
|
|
533
|
+
if (next === QT_NULL_POINTER) {
|
|
534
|
+
// end of the list is found
|
|
535
|
+
element_pool.data_uint32[address + COLUMN_ELEMENT_NEXT] = element;
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
_link = next;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
// patch links on the element
|
|
545
|
+
const element_address = element_pool.element_word(element);
|
|
546
|
+
element_pool.data_uint32[element_address + COLUMN_ELEMENT_PARENT_NODE] = node;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
*
|
|
551
|
+
* @param {number} datum_id ID of data in tree
|
|
552
|
+
*/
|
|
553
|
+
element_remove(datum_id) {
|
|
554
|
+
|
|
555
|
+
const element_pool = this.#element_pool;
|
|
556
|
+
const element_word = element_pool.element_word(datum_id);
|
|
557
|
+
|
|
558
|
+
const tree_node_id = element_pool.data_uint32[element_word + COLUMN_ELEMENT_PARENT_NODE];
|
|
559
|
+
|
|
560
|
+
this.#tree_node_remove_element(tree_node_id, datum_id);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
*
|
|
565
|
+
* @param {number} node
|
|
566
|
+
* @return {boolean}
|
|
567
|
+
*/
|
|
568
|
+
#is_node_empty(node) {
|
|
569
|
+
const pool = this.#node_pool;
|
|
570
|
+
const word = pool.element_word(node);
|
|
571
|
+
|
|
572
|
+
const uint32 = pool.data_uint32;
|
|
573
|
+
|
|
574
|
+
const first_element_node = uint32[word + COLUMN_TREE_NODE_FIRST_ELEMENT_NODE_POINTER]
|
|
575
|
+
|
|
576
|
+
if (first_element_node !== QT_NULL_POINTER) {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const has_allocated_children = uint32[word + COLUMN_TREE_NODE_CHILDREN_POINTER_TL] !== QT_NULL_POINTER
|
|
581
|
+
|| uint32[word + COLUMN_TREE_NODE_CHILDREN_POINTER_TR] !== QT_NULL_POINTER
|
|
582
|
+
|| uint32[word + COLUMN_TREE_NODE_CHILDREN_POINTER_BL] !== QT_NULL_POINTER
|
|
583
|
+
|| uint32[word + COLUMN_TREE_NODE_CHILDREN_POINTER_BR] !== QT_NULL_POINTER;
|
|
584
|
+
|
|
585
|
+
return !has_allocated_children;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Assumes the node is empty
|
|
590
|
+
* Does not perform any checks
|
|
591
|
+
* Only updates parent node
|
|
592
|
+
* @param {number} node
|
|
593
|
+
* @returns {number} parent of this node
|
|
594
|
+
*/
|
|
595
|
+
#remove_node(node) {
|
|
596
|
+
|
|
597
|
+
const pool = this.#node_pool;
|
|
598
|
+
const word = pool.element_word(node);
|
|
599
|
+
|
|
600
|
+
const uint32 = pool.data_uint32;
|
|
601
|
+
|
|
602
|
+
// let's remove reference from the parent to this node
|
|
603
|
+
const parent = uint32[word + COLUMN_TREE_NODE_PARENT];
|
|
604
|
+
|
|
605
|
+
if (parent !== QT_NULL_POINTER) {
|
|
606
|
+
// not root
|
|
607
|
+
const parent_word = pool.element_word(parent);
|
|
608
|
+
|
|
609
|
+
if (uint32[parent_word + COLUMN_TREE_NODE_CHILDREN_POINTER_TL] === node) {
|
|
610
|
+
uint32[parent_word + COLUMN_TREE_NODE_CHILDREN_POINTER_TL] = QT_NULL_POINTER;
|
|
611
|
+
} else if (uint32[parent_word + COLUMN_TREE_NODE_CHILDREN_POINTER_TR] === node) {
|
|
612
|
+
uint32[parent_word + COLUMN_TREE_NODE_CHILDREN_POINTER_TR] = QT_NULL_POINTER;
|
|
613
|
+
} else if (uint32[parent_word + COLUMN_TREE_NODE_CHILDREN_POINTER_BL] === node) {
|
|
614
|
+
uint32[parent_word + COLUMN_TREE_NODE_CHILDREN_POINTER_BL] = QT_NULL_POINTER;
|
|
615
|
+
} else if (uint32[parent_word + COLUMN_TREE_NODE_CHILDREN_POINTER_BR] === node) {
|
|
616
|
+
uint32[parent_word + COLUMN_TREE_NODE_CHILDREN_POINTER_BR] = QT_NULL_POINTER;
|
|
617
|
+
} else {
|
|
618
|
+
throw new Error(`specified 'parent' node(${parent}) does not point to this node(${node})`);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
} else {
|
|
622
|
+
// we are root
|
|
623
|
+
this.#root = QT_NULL_POINTER;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// de-allocate self
|
|
627
|
+
pool.release(node);
|
|
628
|
+
|
|
629
|
+
return parent;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Remove node if it's empty, propagates up the parent chain if possible
|
|
634
|
+
* @param {number} node
|
|
635
|
+
* @returns {boolean} true if collapsed
|
|
636
|
+
*/
|
|
637
|
+
#try_remove_node(node) {
|
|
638
|
+
|
|
639
|
+
let n = node;
|
|
640
|
+
|
|
641
|
+
while (n !== QT_NULL_POINTER && this.#is_node_empty(n)) {
|
|
642
|
+
// at this point we store no data and have no children, node is useless and can be reclaimed
|
|
643
|
+
n = this.#remove_node(n);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return true;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* NOTE: This method does NOT do de-allocation
|
|
651
|
+
* @param {number} node ID of tree node
|
|
652
|
+
* @param {number} element ID of inserted element
|
|
653
|
+
* @returns {boolean} if element was found and cut
|
|
654
|
+
*/
|
|
655
|
+
#tree_node_remove_element(
|
|
656
|
+
node,
|
|
657
|
+
element
|
|
658
|
+
) {
|
|
659
|
+
|
|
660
|
+
assert.isNonNegativeInteger(node, 'tree_node_id');
|
|
661
|
+
assert.isNonNegativeInteger(element, 'element_id');
|
|
662
|
+
|
|
663
|
+
const node_pool = this.#node_pool;
|
|
664
|
+
|
|
665
|
+
const node_address = node_pool.element_word(node);
|
|
666
|
+
|
|
667
|
+
let element_node_pointer = node_pool.data_uint32[node_address + COLUMN_TREE_NODE_FIRST_ELEMENT_NODE_POINTER];
|
|
668
|
+
let previous_node_pointer = QT_NULL_POINTER;
|
|
669
|
+
|
|
670
|
+
const element_pool = this.#element_pool;
|
|
671
|
+
|
|
672
|
+
while (element_node_pointer !== QT_NULL_POINTER) {
|
|
673
|
+
const element_node_word = element_pool.element_word(element_node_pointer);
|
|
674
|
+
|
|
675
|
+
const next_element_pointer = element_pool.data_uint32[element_node_word + COLUMN_ELEMENT_NEXT];
|
|
676
|
+
|
|
677
|
+
if (element_node_pointer === element) {
|
|
678
|
+
// found the right element
|
|
679
|
+
|
|
680
|
+
node_pool.data_uint32[node_address + COLUMN_TREE_NODE_ELEMENT_COUNT]--;
|
|
681
|
+
|
|
682
|
+
if (previous_node_pointer === QT_NULL_POINTER) {
|
|
683
|
+
// this was the first element in the list
|
|
684
|
+
node_pool.data_uint32[node_address + COLUMN_TREE_NODE_FIRST_ELEMENT_NODE_POINTER] = next_element_pointer;
|
|
685
|
+
// this might be the last element in the list, try to clean-up node if possible
|
|
686
|
+
this.#try_remove_node(node);
|
|
687
|
+
} else {
|
|
688
|
+
// patch previous element to point to the next element
|
|
689
|
+
const previous_node_word = element_pool.element_word(previous_node_pointer);
|
|
690
|
+
|
|
691
|
+
element_pool.data_uint32[previous_node_word + COLUMN_ELEMENT_NEXT] = next_element_pointer;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
previous_node_pointer = element_node_pointer;
|
|
699
|
+
|
|
700
|
+
element_node_pointer = next_element_pointer;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// element not found
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Remove all data
|
|
709
|
+
*/
|
|
710
|
+
release_all() {
|
|
711
|
+
this.#root = QT_NULL_POINTER;
|
|
712
|
+
this.#element_pool.clear();
|
|
713
|
+
this.#node_pool.clear();
|
|
714
|
+
}
|
|
715
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QuadTree.spec.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/2d/quad-tree-binary/QuadTree.spec.js"],"names":[],"mappings":""}
|