@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.
Files changed (47) hide show
  1. package/build/meep.cjs +4 -4
  2. package/build/meep.min.js +1 -1
  3. package/build/meep.module.js +4 -4
  4. package/package.json +1 -1
  5. package/src/core/binary/align_4.d.ts +7 -0
  6. package/src/core/binary/align_4.d.ts.map +1 -0
  7. package/src/core/binary/align_4.js +14 -0
  8. package/src/core/geom/2d/aabb/aabb2_intersects_ray.d.ts +14 -0
  9. package/src/core/geom/2d/aabb/aabb2_intersects_ray.d.ts.map +1 -0
  10. package/src/core/geom/2d/aabb/aabb2_intersects_ray.js +50 -0
  11. package/src/core/geom/2d/aabb/aabb2_intersects_ray.spec.d.ts +2 -0
  12. package/src/core/geom/2d/aabb/aabb2_intersects_ray.spec.d.ts.map +1 -0
  13. package/src/core/geom/2d/aabb/aabb2_intersects_ray.spec.js +28 -0
  14. package/src/core/geom/2d/hash-grid/SpatialHashGrid.d.ts +52 -0
  15. package/src/core/geom/2d/hash-grid/SpatialHashGrid.d.ts.map +1 -0
  16. package/src/core/geom/2d/hash-grid/SpatialHashGrid.js +317 -0
  17. package/src/core/geom/2d/hash-grid/SpatialHashGrid.spec.d.ts +2 -0
  18. package/src/core/geom/2d/hash-grid/SpatialHashGrid.spec.d.ts.map +1 -0
  19. package/src/core/geom/2d/hash-grid/SpatialHashGrid.spec.js +158 -0
  20. package/src/core/geom/2d/hash-grid/shg_query_raycast.d.ts +14 -0
  21. package/src/core/geom/2d/hash-grid/shg_query_raycast.d.ts.map +1 -0
  22. package/src/core/geom/2d/hash-grid/shg_query_raycast.js +21 -0
  23. package/src/core/geom/2d/lt-grid/LooseTightGrid.d.ts +55 -0
  24. package/src/core/geom/2d/lt-grid/LooseTightGrid.d.ts.map +1 -0
  25. package/src/core/geom/2d/lt-grid/LooseTightGrid.js +221 -0
  26. package/src/core/geom/2d/quad-tree/QuadTreeNode.js +3 -3
  27. package/src/core/geom/2d/quad-tree/qt_query_data_raycast.d.ts.map +1 -1
  28. package/src/core/geom/2d/quad-tree/qt_query_data_raycast.js +19 -6
  29. package/src/core/geom/2d/quad-tree-binary/QuadTree.d.ts +94 -0
  30. package/src/core/geom/2d/quad-tree-binary/QuadTree.d.ts.map +1 -0
  31. package/src/core/geom/2d/quad-tree-binary/QuadTree.js +715 -0
  32. package/src/core/geom/2d/quad-tree-binary/QuadTree.spec.d.ts +2 -0
  33. package/src/core/geom/2d/quad-tree-binary/QuadTree.spec.d.ts.map +1 -0
  34. package/src/core/geom/2d/quad-tree-binary/QuadTree.spec.js +53 -0
  35. package/src/core/geom/3d/morton/de_interleave_2_bits.spec.d.ts +2 -0
  36. package/src/core/geom/3d/morton/de_interleave_2_bits.spec.d.ts.map +1 -0
  37. package/src/core/geom/3d/morton/de_interleave_2_bits.spec.js +21 -0
  38. package/src/core/geom/3d/morton/de_interleave_bits_by_2.d.ts +7 -0
  39. package/src/core/geom/3d/morton/de_interleave_bits_by_2.d.ts.map +1 -0
  40. package/src/core/geom/3d/morton/de_interleave_bits_by_2.js +15 -0
  41. package/src/core/geom/3d/morton/split_by_2.d.ts +1 -1
  42. package/src/core/geom/3d/morton/split_by_2.js +1 -1
  43. package/src/core/geom/3d/morton/split_by_3.d.ts +1 -1
  44. package/src/core/geom/3d/morton/split_by_3.js +1 -1
  45. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts +7 -0
  46. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.d.ts.map +1 -1
  47. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=QuadTree.spec.d.ts.map
@@ -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":""}