@woosh/meep-engine 2.87.2 → 2.87.4
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 +53 -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 +2 -2
- 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 +22 -9
- 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,158 @@
|
|
|
1
|
+
import { randomFloatBetween } from "../../../math/random/randomFloatBetween.js";
|
|
2
|
+
import { seededRandom } from "../../../math/random/seededRandom.js";
|
|
3
|
+
import { SpatialHashGrid } from "./SpatialHashGrid.js";
|
|
4
|
+
|
|
5
|
+
test("constructor does not throw", () => {
|
|
6
|
+
new SpatialHashGrid()
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test("new grid should be empty", () => {
|
|
10
|
+
const grid = new SpatialHashGrid(1, 1);
|
|
11
|
+
|
|
12
|
+
expect(grid.is_cell_empty(0, 0)).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("insert a small tile that fits into a single grid cell", () => {
|
|
16
|
+
const grid = new SpatialHashGrid(2, 2, 4);
|
|
17
|
+
|
|
18
|
+
const el = grid.element_allocate();
|
|
19
|
+
|
|
20
|
+
grid.element_set_user_data(el, 7);
|
|
21
|
+
grid.element_set_bounds_primitive(el, 0.5, 0.5, 1, 1);
|
|
22
|
+
|
|
23
|
+
grid.element_insert(el);
|
|
24
|
+
|
|
25
|
+
expect(grid.is_cell_empty(0, 0)).toBe(false);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("insert an element that spans multiple grid cells", () => {
|
|
29
|
+
|
|
30
|
+
const grid = new SpatialHashGrid(2, 3, 4);
|
|
31
|
+
|
|
32
|
+
const el = grid.element_allocate();
|
|
33
|
+
|
|
34
|
+
grid.element_set_user_data(el, 7);
|
|
35
|
+
grid.element_set_bounds_primitive(el, 0.5, 0.5, 6, 6);
|
|
36
|
+
|
|
37
|
+
grid.element_insert(el);
|
|
38
|
+
|
|
39
|
+
expect(grid.is_cell_empty(0, 0)).toBe(false);
|
|
40
|
+
expect(grid.is_cell_empty(0, 1)).toBe(false);
|
|
41
|
+
expect(grid.is_cell_empty(1, 0)).toBe(false);
|
|
42
|
+
expect(grid.is_cell_empty(1, 1)).toBe(false);
|
|
43
|
+
expect(grid.is_cell_empty(0, 2)).toBe(true);
|
|
44
|
+
expect(grid.is_cell_empty(1, 2)).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("remove an element that spans multiple grid cells", () => {
|
|
48
|
+
|
|
49
|
+
const grid = new SpatialHashGrid(2, 3, 4);
|
|
50
|
+
|
|
51
|
+
const el = grid.element_allocate();
|
|
52
|
+
|
|
53
|
+
grid.element_set_user_data(el, 7);
|
|
54
|
+
grid.element_set_bounds_primitive(el, 0.5, 0.5, 6, 6);
|
|
55
|
+
|
|
56
|
+
grid.element_insert(el);
|
|
57
|
+
grid.element_remove(el);
|
|
58
|
+
|
|
59
|
+
expect(grid.is_cell_empty(0, 0)).toBe(true);
|
|
60
|
+
expect(grid.is_cell_empty(0, 1)).toBe(true);
|
|
61
|
+
expect(grid.is_cell_empty(1, 0)).toBe(true);
|
|
62
|
+
expect(grid.is_cell_empty(1, 1)).toBe(true);
|
|
63
|
+
expect(grid.is_cell_empty(0, 2)).toBe(true);
|
|
64
|
+
expect(grid.is_cell_empty(1, 2)).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("remove element from grid", () => {
|
|
68
|
+
|
|
69
|
+
const grid = new SpatialHashGrid(2, 2, 4);
|
|
70
|
+
|
|
71
|
+
const el = grid.element_allocate();
|
|
72
|
+
|
|
73
|
+
grid.element_set_user_data(el, 7);
|
|
74
|
+
grid.element_set_bounds_primitive(el, 0.5, 0.5, 1, 1);
|
|
75
|
+
|
|
76
|
+
grid.element_insert(el);
|
|
77
|
+
grid.element_remove(el);
|
|
78
|
+
|
|
79
|
+
expect(grid.is_cell_empty(0, 0)).toBe(true);
|
|
80
|
+
expect(grid.is_cell_empty(0, 1)).toBe(true);
|
|
81
|
+
expect(grid.is_cell_empty(1, 0)).toBe(true);
|
|
82
|
+
expect(grid.is_cell_empty(1, 1)).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("add 3 element and remove in same order, expect grid to be empty", () => {
|
|
86
|
+
|
|
87
|
+
const grid = new SpatialHashGrid(2, 2, 4);
|
|
88
|
+
|
|
89
|
+
const a = grid.element_allocate();
|
|
90
|
+
|
|
91
|
+
grid.element_set_user_data(a, 3);
|
|
92
|
+
grid.element_set_bounds_primitive(a, 0.5, 0.5, 1, 1);
|
|
93
|
+
|
|
94
|
+
const b = grid.element_allocate();
|
|
95
|
+
|
|
96
|
+
grid.element_set_user_data(b, 7);
|
|
97
|
+
grid.element_set_bounds_primitive(b, 0.5, 0.5, 1, 1);
|
|
98
|
+
|
|
99
|
+
const c = grid.element_allocate();
|
|
100
|
+
|
|
101
|
+
grid.element_set_user_data(c, 11);
|
|
102
|
+
grid.element_set_bounds_primitive(c, 0.5, 0.5, 1, 1);
|
|
103
|
+
|
|
104
|
+
grid.element_insert(a);
|
|
105
|
+
grid.element_insert(b);
|
|
106
|
+
grid.element_insert(c);
|
|
107
|
+
|
|
108
|
+
grid.element_remove(a);
|
|
109
|
+
grid.element_remove(b);
|
|
110
|
+
grid.element_remove(c);
|
|
111
|
+
|
|
112
|
+
expect(grid.is_cell_empty(0, 0)).toBe(true);
|
|
113
|
+
expect(grid.is_cell_empty(0, 1)).toBe(true);
|
|
114
|
+
expect(grid.is_cell_empty(1, 0)).toBe(true);
|
|
115
|
+
expect(grid.is_cell_empty(1, 1)).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test.skip("performance, insertion 1m random", () => {
|
|
119
|
+
const grid = new SpatialHashGrid(100, 100, 1);
|
|
120
|
+
|
|
121
|
+
const random = seededRandom(42);
|
|
122
|
+
|
|
123
|
+
const N = 1000000;
|
|
124
|
+
|
|
125
|
+
const elements = new Uint32Array(N);
|
|
126
|
+
|
|
127
|
+
for (let i = 0; i < N; i++) {
|
|
128
|
+
|
|
129
|
+
const position_x = randomFloatBetween(random, 0, 98);
|
|
130
|
+
const position_y = randomFloatBetween(random, 0, 98);
|
|
131
|
+
|
|
132
|
+
const size_x = randomFloatBetween(random, 0.1, 2);
|
|
133
|
+
const size_y = randomFloatBetween(random, 0.1, 2);
|
|
134
|
+
|
|
135
|
+
const element = grid.element_allocate();
|
|
136
|
+
|
|
137
|
+
grid.element_set_user_data(element, i);
|
|
138
|
+
grid.element_set_bounds_primitive(element,
|
|
139
|
+
position_x,
|
|
140
|
+
position_y,
|
|
141
|
+
position_x + size_x,
|
|
142
|
+
position_y + size_y
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
elements[i] = element;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// perform insertion
|
|
149
|
+
const t0 = performance.now();
|
|
150
|
+
|
|
151
|
+
for (let i = 0; i < N; i++) {
|
|
152
|
+
grid.element_insert(elements[i]);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const duration = performance.now() - t0;
|
|
156
|
+
|
|
157
|
+
console.log(`Duration ${duration}ms, ${(N / (duration * 1e-3)).toFixed(2)} records per second`);
|
|
158
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param {SpatialHashGrid} grid
|
|
4
|
+
* @param {number} origin_x
|
|
5
|
+
* @param {number} origin_y
|
|
6
|
+
* @param {number} direction_x
|
|
7
|
+
* @param {number} direction_y
|
|
8
|
+
* @param {number} max_distance
|
|
9
|
+
* @param {function} predicate
|
|
10
|
+
* @param {*} [predicateContext]
|
|
11
|
+
* @returns {number} element ID or -1 if not found
|
|
12
|
+
*/
|
|
13
|
+
export function shg_query_raycast(grid: SpatialHashGrid, origin_x: number, origin_y: number, direction_x: number, direction_y: number, max_distance: number, predicate: Function, predicateContext?: any): number;
|
|
14
|
+
//# sourceMappingURL=shg_query_raycast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shg_query_raycast.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/2d/hash-grid/shg_query_raycast.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,mEATW,MAAM,YACN,MAAM,eACN,MAAM,eACN,MAAM,gBACN,MAAM,gDAGJ,MAAM,CAUlB"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param {SpatialHashGrid} grid
|
|
4
|
+
* @param {number} origin_x
|
|
5
|
+
* @param {number} origin_y
|
|
6
|
+
* @param {number} direction_x
|
|
7
|
+
* @param {number} direction_y
|
|
8
|
+
* @param {number} max_distance
|
|
9
|
+
* @param {function} predicate
|
|
10
|
+
* @param {*} [predicateContext]
|
|
11
|
+
* @returns {number} element ID or -1 if not found
|
|
12
|
+
*/
|
|
13
|
+
export function shg_query_raycast(
|
|
14
|
+
grid,
|
|
15
|
+
origin_x, origin_y,
|
|
16
|
+
direction_x, direction_y,
|
|
17
|
+
max_distance,
|
|
18
|
+
predicate, predicateContext
|
|
19
|
+
) {
|
|
20
|
+
throw new Error('NIY');
|
|
21
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* NOTE: THIS CODE IS UNFINISHED, IT IS ONLY A SKETCH
|
|
4
|
+
* TODO finish implementation
|
|
5
|
+
*
|
|
6
|
+
* Inspired by Makyen's post on stackoverflow: https://stackoverflow.com/a/48384354
|
|
7
|
+
* Core idea is to have dynamically-sized bounding volumes that contain our AABBs, and then have a grid-aligned index that stores pointers to these volumes
|
|
8
|
+
* Makyren called it "Loose/Tight Double-Grid", where "loose" refers to dynamic bounding volumes and tight refers to the grid
|
|
9
|
+
*
|
|
10
|
+
* Invariants:
|
|
11
|
+
* - An element inserted into the structure will belong to exactly one "loose" cell
|
|
12
|
+
*
|
|
13
|
+
*/
|
|
14
|
+
export class LooseTightGrid {
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* @param {number} v
|
|
18
|
+
*/
|
|
19
|
+
set grid_scale(arg: number);
|
|
20
|
+
/**
|
|
21
|
+
*
|
|
22
|
+
* @returns {number}
|
|
23
|
+
*/
|
|
24
|
+
get grid_scale(): number;
|
|
25
|
+
/**
|
|
26
|
+
*
|
|
27
|
+
* @param {number} size_x
|
|
28
|
+
* @param {number} size_y
|
|
29
|
+
*/
|
|
30
|
+
resize(size_x: number, size_y: number): void;
|
|
31
|
+
/**
|
|
32
|
+
*
|
|
33
|
+
* @param {number} x
|
|
34
|
+
* @param {number} y
|
|
35
|
+
* @returns {number}
|
|
36
|
+
*/
|
|
37
|
+
cell_position_to_index(x: number, y: number): number;
|
|
38
|
+
/**
|
|
39
|
+
*
|
|
40
|
+
* @param {number[]} out
|
|
41
|
+
* @param {number} out_offset
|
|
42
|
+
* @param {number} index
|
|
43
|
+
*/
|
|
44
|
+
cell_index_to_position(out: number[], out_offset: number, index: number): void;
|
|
45
|
+
allocate_datum(): void;
|
|
46
|
+
insert(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Rebuild structure from ground-up
|
|
49
|
+
* This is an expensive operation
|
|
50
|
+
* Generally you'll never want to call it explicitly, it is used internally when underlying structure changes
|
|
51
|
+
*/
|
|
52
|
+
rebuild(): void;
|
|
53
|
+
#private;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=LooseTightGrid.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LooseTightGrid.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/2d/lt-grid/LooseTightGrid.js"],"names":[],"mappings":"AAiBA;;;;;;;;;;;;GAYG;AACH;IAcI;;;OAGG;IACH,4BAYC;IAED;;;OAGG;IACH,yBAEC;IAED;;;;OAIG;IACH,eAHW,MAAM,UACN,MAAM,QAehB;IAED;;;;;OAKG;IACH,0BAJW,MAAM,KACN,MAAM,GACJ,MAAM,CAUlB;IAED;;;;;OAKG;IACH,4BAJW,MAAM,EAAE,cACR,MAAM,SACN,MAAM,QAOhB;IAED,uBAEC;IAED,eAEC;IAwDD;;;;OAIG;IACH,gBAgCC;;CAEJ"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { assert } from "../../../assert.js";
|
|
2
|
+
import { UINT32_MAX } from "../../../binary/UINT32_MAX.js";
|
|
3
|
+
import { SCRATCH_UINT32_TRAVERSAL_STACK } from "../../../collection/SCRATCH_UINT32_TRAVERSAL_STACK.js";
|
|
4
|
+
import { de_interleave_2_bits } from "../../3d/morton/de_interleave_bits_by_2.js";
|
|
5
|
+
import { split_by_2 } from "../../3d/morton/split_by_2.js";
|
|
6
|
+
import { BinaryElementPool } from "../../3d/topology/struct/binary/BinaryElementPool.js";
|
|
7
|
+
|
|
8
|
+
const NULL_POINTER = UINT32_MAX;
|
|
9
|
+
|
|
10
|
+
const COLUMN_PARENT = 0;
|
|
11
|
+
const COLUMN_CHILD_1 = 1;
|
|
12
|
+
const COLUMN_CHILD_2 = 2;
|
|
13
|
+
const COLUMN_HEIGHT = 3;
|
|
14
|
+
const COLUMN_AABB_OFFSET = 4;
|
|
15
|
+
|
|
16
|
+
const COLUMN_USER_DATA = COLUMN_CHILD_2;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
*
|
|
20
|
+
* NOTE: THIS CODE IS UNFINISHED, IT IS ONLY A SKETCH
|
|
21
|
+
* TODO finish implementation
|
|
22
|
+
*
|
|
23
|
+
* Inspired by Makyen's post on stackoverflow: https://stackoverflow.com/a/48384354
|
|
24
|
+
* Core idea is to have dynamically-sized bounding volumes that contain our AABBs, and then have a grid-aligned index that stores pointers to these volumes
|
|
25
|
+
* Makyren called it "Loose/Tight Double-Grid", where "loose" refers to dynamic bounding volumes and tight refers to the grid
|
|
26
|
+
*
|
|
27
|
+
* Invariants:
|
|
28
|
+
* - An element inserted into the structure will belong to exactly one "loose" cell
|
|
29
|
+
*
|
|
30
|
+
*/
|
|
31
|
+
export class LooseTightGrid {
|
|
32
|
+
|
|
33
|
+
#resolution_x = 0
|
|
34
|
+
#resolution_y = 0
|
|
35
|
+
|
|
36
|
+
// amount of space covered by a single grid cell
|
|
37
|
+
#grid_scale = 1
|
|
38
|
+
|
|
39
|
+
#node_pool = new BinaryElementPool(10 * 4)
|
|
40
|
+
#grid_data = new Uint32Array(0);
|
|
41
|
+
|
|
42
|
+
constructor() {
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* @param {number} v
|
|
48
|
+
*/
|
|
49
|
+
set grid_scale(v) {
|
|
50
|
+
assert.isNumber(v, 'v');
|
|
51
|
+
assert.greaterThan(v, 0, 'v');
|
|
52
|
+
|
|
53
|
+
if (this.#grid_scale === v) {
|
|
54
|
+
// no change
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
this.#grid_scale = v;
|
|
59
|
+
|
|
60
|
+
this.rebuild();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
*
|
|
65
|
+
* @returns {number}
|
|
66
|
+
*/
|
|
67
|
+
get grid_scale() {
|
|
68
|
+
return this.#grid_scale;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* @param {number} size_x
|
|
74
|
+
* @param {number} size_y
|
|
75
|
+
*/
|
|
76
|
+
resize(size_x, size_y) {
|
|
77
|
+
assert.isNonNegativeInteger(size_x, 'size_x');
|
|
78
|
+
assert.isNonNegativeInteger(size_y, 'size_y');
|
|
79
|
+
|
|
80
|
+
if (this.#resolution_x === size_x && this.#resolution_y === size_y) {
|
|
81
|
+
// no change
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
this.#resolution_x = size_x;
|
|
86
|
+
this.#resolution_y = size_y;
|
|
87
|
+
|
|
88
|
+
this.rebuild();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
*
|
|
93
|
+
* @param {number} x
|
|
94
|
+
* @param {number} y
|
|
95
|
+
* @returns {number}
|
|
96
|
+
*/
|
|
97
|
+
cell_position_to_index(x, y) {
|
|
98
|
+
assert.isNonNegativeInteger(x, 'x');
|
|
99
|
+
assert.isNonNegativeInteger(y, 'y');
|
|
100
|
+
|
|
101
|
+
const _x = split_by_2(x);
|
|
102
|
+
const _y = split_by_2(y);
|
|
103
|
+
|
|
104
|
+
return _x | (_y << 1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
*
|
|
109
|
+
* @param {number[]} out
|
|
110
|
+
* @param {number} out_offset
|
|
111
|
+
* @param {number} index
|
|
112
|
+
*/
|
|
113
|
+
cell_index_to_position(out, out_offset, index) {
|
|
114
|
+
|
|
115
|
+
out[out_offset] = de_interleave_2_bits(index)
|
|
116
|
+
out[out_offset + 1] = de_interleave_2_bits(index >> 1)
|
|
117
|
+
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
allocate_datum() {
|
|
121
|
+
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
insert() {
|
|
125
|
+
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
*
|
|
130
|
+
* @param {number} node
|
|
131
|
+
*/
|
|
132
|
+
#node_is_leaf(node) {
|
|
133
|
+
const address = this.#node_pool.element_word(node);
|
|
134
|
+
|
|
135
|
+
return this.#node_pool.data_uint32[address + COLUMN_CHILD_1] === NULL_POINTER;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Collect leaves and de-allocate all intermediate nodes
|
|
140
|
+
* @param {number[]} output
|
|
141
|
+
* @param {number} output_offset
|
|
142
|
+
* @param {number} node
|
|
143
|
+
*/
|
|
144
|
+
#erase_tree_structure(output, output_offset, node) {
|
|
145
|
+
const pool = this.#node_pool;
|
|
146
|
+
|
|
147
|
+
const stack = SCRATCH_UINT32_TRAVERSAL_STACK;
|
|
148
|
+
const top = SCRATCH_UINT32_TRAVERSAL_STACK.pointer;
|
|
149
|
+
|
|
150
|
+
stack[stack.pointer++] = node;
|
|
151
|
+
|
|
152
|
+
let result_count = 0;
|
|
153
|
+
|
|
154
|
+
while (stack.pointer > top) {
|
|
155
|
+
|
|
156
|
+
stack.pointer--;
|
|
157
|
+
|
|
158
|
+
const n = stack[stack.pointer];
|
|
159
|
+
|
|
160
|
+
const node_address = pool.element_word(n);
|
|
161
|
+
|
|
162
|
+
const child_1 = pool.data_uint32[node_address + COLUMN_CHILD_1];
|
|
163
|
+
const child_2 = pool.data_uint32[node_address + COLUMN_CHILD_2];
|
|
164
|
+
|
|
165
|
+
if (child_1 === NULL_POINTER) {
|
|
166
|
+
// leaf node
|
|
167
|
+
output[output_offset + result_count] = n;
|
|
168
|
+
result_count++;
|
|
169
|
+
} else {
|
|
170
|
+
// binary node
|
|
171
|
+
stack[stack.pointer++] = child_2;
|
|
172
|
+
stack[stack.pointer++] = child_1;
|
|
173
|
+
|
|
174
|
+
// de-allocate node
|
|
175
|
+
pool.release(n);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return result_count;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Rebuild structure from ground-up
|
|
184
|
+
* This is an expensive operation
|
|
185
|
+
* Generally you'll never want to call it explicitly, it is used internally when underlying structure changes
|
|
186
|
+
*/
|
|
187
|
+
rebuild() {
|
|
188
|
+
//TODO implement
|
|
189
|
+
|
|
190
|
+
let leaf_count = 0;
|
|
191
|
+
const leaf_nodes = [];
|
|
192
|
+
// traverse previous grid
|
|
193
|
+
for (let i = 0; i < this.#grid_data.length; i++) {
|
|
194
|
+
const node = this.#grid_data[i];
|
|
195
|
+
|
|
196
|
+
if (node === NULL_POINTER) {
|
|
197
|
+
// empty grid cell
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
leaf_count += this.#erase_tree_structure(leaf_nodes, leaf_count, node);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
const resolution_y = this.#resolution_y;
|
|
206
|
+
const resolution_x = this.#resolution_x;
|
|
207
|
+
|
|
208
|
+
const grid_cell_count = resolution_x * resolution_y;
|
|
209
|
+
|
|
210
|
+
if (grid_cell_count !== this.#grid_data.length) {
|
|
211
|
+
this.#grid_data = new Uint32Array(grid_cell_count);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// drop all roots
|
|
215
|
+
this.#grid_data.fill(NULL_POINTER);
|
|
216
|
+
|
|
217
|
+
// re-insert all leaves
|
|
218
|
+
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
}
|
|
@@ -174,8 +174,8 @@ export class QuadTreeNode extends AABB2 {
|
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
const xm = (x0 + x1)
|
|
178
|
-
const ym = (y0 + y1)
|
|
177
|
+
const xm = (x0 + x1) *0.5;
|
|
178
|
+
const ym = (y0 + y1) *0.5;
|
|
179
179
|
|
|
180
180
|
if (datum.y1 < ym) {
|
|
181
181
|
//top
|
|
@@ -189,7 +189,7 @@ export class QuadTreeNode extends AABB2 {
|
|
|
189
189
|
this.addDatum(datum);
|
|
190
190
|
}
|
|
191
191
|
} else if (datum.y0 >= ym) {
|
|
192
|
-
//
|
|
192
|
+
//bottom
|
|
193
193
|
if (datum.x1 < xm) {
|
|
194
194
|
//left
|
|
195
195
|
this.bottomLeft.insertDatum(datum);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @param {number} max_distance
|
|
9
9
|
* @param {function(data:T, origin_x:number, origin_y:number, direction_x:number, direction_y:number, max_distance2:number):boolean} [predicate]
|
|
10
10
|
* @param {*} [predicateContext]
|
|
11
|
-
* @returns {
|
|
11
|
+
* @returns {QuadTreeDatum<T>|undefined}
|
|
12
12
|
*/
|
|
13
|
-
export function qt_query_data_raycast<T>(tree: QuadTreeNode<T>, origin_x: number, origin_y: number, direction_x: number, direction_y: number, max_distance: number, predicate: any, predicateContext?: any):
|
|
13
|
+
export function qt_query_data_raycast<T>(tree: QuadTreeNode<T>, origin_x: number, origin_y: number, direction_x: number, direction_y: number, max_distance: number, predicate: any, predicateContext?: any): QuadTreeDatum<T> | undefined;
|
|
14
14
|
//# sourceMappingURL=qt_query_data_raycast.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qt_query_data_raycast.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/2d/quad-tree/qt_query_data_raycast.js"],"names":[],"mappings":"AAQA;;;;;;;;;;;GAWG;AACH,0EATW,MAAM,YACN,MAAM,eACN,MAAM,eACN,MAAM,gBACN,MAAM,2CAGJ,
|
|
1
|
+
{"version":3,"file":"qt_query_data_raycast.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/2d/quad-tree/qt_query_data_raycast.js"],"names":[],"mappings":"AAQA;;;;;;;;;;;GAWG;AACH,0EATW,MAAM,YACN,MAAM,eACN,MAAM,eACN,MAAM,gBACN,MAAM,2CAGJ,mBAAiB,SAAS,CA0EtC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { aabb2_intersects_ray } from "../aabb/aabb2_intersects_ray.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
*
|
|
@@ -16,7 +16,7 @@ const node_stack = [];
|
|
|
16
16
|
* @param {number} max_distance
|
|
17
17
|
* @param {function(data:T, origin_x:number, origin_y:number, direction_x:number, direction_y:number, max_distance2:number):boolean} [predicate]
|
|
18
18
|
* @param {*} [predicateContext]
|
|
19
|
-
* @returns {
|
|
19
|
+
* @returns {QuadTreeDatum<T>|undefined}
|
|
20
20
|
*/
|
|
21
21
|
export function qt_query_data_raycast(
|
|
22
22
|
tree,
|
|
@@ -33,17 +33,26 @@ export function qt_query_data_raycast(
|
|
|
33
33
|
node_stack[stack_pointer] = tree;
|
|
34
34
|
stack_pointer++;
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
*
|
|
38
|
+
* @type {QuadTreeDatum<T>|undefined}
|
|
39
|
+
*/
|
|
36
40
|
let best_match = undefined;
|
|
37
|
-
let
|
|
41
|
+
let best_match_distance = max_distance;
|
|
38
42
|
|
|
39
43
|
while (stack_pointer > 0) {
|
|
40
44
|
|
|
41
45
|
--stack_pointer;
|
|
42
46
|
const node = node_stack[stack_pointer];
|
|
43
47
|
|
|
44
|
-
const d2 =
|
|
48
|
+
const d2 = aabb2_intersects_ray(node.x0, node.y0, node.x1, node.y1, origin_x, origin_y, direction_x, direction_y);
|
|
45
49
|
|
|
46
|
-
if (d2
|
|
50
|
+
if (d2 < 0) {
|
|
51
|
+
// no hit
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (d2 > best_match_distance) {
|
|
47
56
|
// too far, not a match
|
|
48
57
|
continue;
|
|
49
58
|
}
|
|
@@ -54,17 +63,21 @@ export function qt_query_data_raycast(
|
|
|
54
63
|
for (let i = 0; i < data_count; i++) {
|
|
55
64
|
const datum = data[i];
|
|
56
65
|
|
|
57
|
-
const
|
|
66
|
+
const d_to_datum = aabb2_intersects_ray(datum.x0, datum.y0, datum.x1, datum.y1, origin_x, origin_y, direction_x, direction_y);
|
|
67
|
+
|
|
68
|
+
if (d_to_datum < 0) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
58
71
|
|
|
59
|
-
if (
|
|
72
|
+
if (d_to_datum < best_match_distance && predicate.call(
|
|
60
73
|
predicateContext,
|
|
61
74
|
datum.data,
|
|
62
75
|
origin_x, origin_y,
|
|
63
76
|
direction_x, direction_y,
|
|
64
|
-
|
|
77
|
+
best_match_distance
|
|
65
78
|
)) {
|
|
66
79
|
best_match = datum;
|
|
67
|
-
|
|
80
|
+
best_match_distance = d_to_datum;
|
|
68
81
|
}
|
|
69
82
|
}
|
|
70
83
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export const QT_NULL_POINTER: number;
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* NOTE: THIS CODE IS UNFINISHED, IT IS ONLY A SKETCH
|
|
5
|
+
* TODO finish implementation
|
|
6
|
+
*/
|
|
7
|
+
export class QuadTree {
|
|
8
|
+
/**
|
|
9
|
+
* Parameters allow us to set initial bounds to prevent early resizing
|
|
10
|
+
* @param {number} x0
|
|
11
|
+
* @param {number} y0
|
|
12
|
+
* @param {number} x1
|
|
13
|
+
* @param {number} y1
|
|
14
|
+
*/
|
|
15
|
+
constructor(x0?: number, y0?: number, x1?: number, y1?: number);
|
|
16
|
+
get root(): number;
|
|
17
|
+
/**
|
|
18
|
+
* Resize dimensions of the tree to tightly fit all inserted elements
|
|
19
|
+
*/
|
|
20
|
+
shrink(): void;
|
|
21
|
+
/**
|
|
22
|
+
*
|
|
23
|
+
* @param {number[]|Float32Array} output
|
|
24
|
+
*/
|
|
25
|
+
compute_tight_bounds(output: number[] | Float32Array): void;
|
|
26
|
+
/**
|
|
27
|
+
* Rebuild tree but keep all the data
|
|
28
|
+
*/
|
|
29
|
+
rebuild(): void;
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
* @returns {number} ID of data in the tree
|
|
33
|
+
*/
|
|
34
|
+
element_allocate(): number;
|
|
35
|
+
/**
|
|
36
|
+
*
|
|
37
|
+
* @param {number} element
|
|
38
|
+
* @param {number} user_data
|
|
39
|
+
*/
|
|
40
|
+
element_set_user_data(element: number, user_data: number): void;
|
|
41
|
+
/**
|
|
42
|
+
*
|
|
43
|
+
* @param {number} element
|
|
44
|
+
* @return {number}
|
|
45
|
+
*/
|
|
46
|
+
element_get_user_data(element: number): number;
|
|
47
|
+
/**
|
|
48
|
+
*
|
|
49
|
+
* @param {number} element
|
|
50
|
+
* @param {number} x0
|
|
51
|
+
* @param {number} y0
|
|
52
|
+
* @param {number} x1
|
|
53
|
+
* @param {number} y1
|
|
54
|
+
*/
|
|
55
|
+
element_set_bounds_primitive(element: number, x0: number, y0: number, x1: number, y1: number): void;
|
|
56
|
+
/**
|
|
57
|
+
*
|
|
58
|
+
* @param {number} element
|
|
59
|
+
* @return {number} next element in the node
|
|
60
|
+
*/
|
|
61
|
+
element_get_next(element: number): number;
|
|
62
|
+
/**
|
|
63
|
+
*
|
|
64
|
+
* @param {number} x0
|
|
65
|
+
* @param {number} y0
|
|
66
|
+
* @param {number} x1
|
|
67
|
+
* @param {number} y1
|
|
68
|
+
*/
|
|
69
|
+
ensure_bounds(x0: number, y0: number, x1: number, y1: number): void;
|
|
70
|
+
/**
|
|
71
|
+
* Assumes element is allocated and is not present in the tree yet
|
|
72
|
+
* @param {number} element
|
|
73
|
+
*/
|
|
74
|
+
element_insert(element: number): void;
|
|
75
|
+
/**
|
|
76
|
+
*
|
|
77
|
+
* @param {number} node
|
|
78
|
+
* @param {number[]|Uint32Array} destination
|
|
79
|
+
* @param {number} destination_offset
|
|
80
|
+
* @return {number} number of elements read
|
|
81
|
+
*/
|
|
82
|
+
node_read_elements(node: number, destination: number[] | Uint32Array, destination_offset: number): number;
|
|
83
|
+
/**
|
|
84
|
+
*
|
|
85
|
+
* @param {number} datum_id ID of data in tree
|
|
86
|
+
*/
|
|
87
|
+
element_remove(datum_id: number): void;
|
|
88
|
+
/**
|
|
89
|
+
* Remove all data
|
|
90
|
+
*/
|
|
91
|
+
release_all(): void;
|
|
92
|
+
#private;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=QuadTree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QuadTree.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/2d/quad-tree-binary/QuadTree.js"],"names":[],"mappings":"AAMA,qCAA0C;AA4C1C;;;;GAIG;AACH;IAaI;;;;;;OAMG;IACH,iBALW,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,EAIhB;IAED,mBAEC;IAiCD;;OAEG;IACH,eAaC;IAED;;;OAGG;IACH,6BAFW,MAAM,EAAE,GAAC,YAAY,QAmC/B;IAED;;OAEG;IACH,gBAcC;IAED;;;OAGG;IACH,oBAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,+BAHW,MAAM,aACN,MAAM,QAUhB;IAED;;;;OAIG;IACH,+BAHW,MAAM,GACL,MAAM,CAOjB;IAED;;;;;;;OAOG;IACH,sCANW,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QA+BhB;IAED;;;;OAIG;IACH,0BAHW,MAAM,GACL,MAAM,CAOjB;IAsFD;;;;;;OAMG;IACH,kBALW,MAAM,MACN,MAAM,MACN,MAAM,MACN,MAAM,QA6BhB;IAED;;;OAGG;IACH,wBAFW,MAAM,QA4BhB;IAwCD;;;;;;OAMG;IACH,yBALW,MAAM,eACN,MAAM,EAAE,GAAC,WAAW,sBACpB,MAAM,GACL,MAAM,CAmBjB;IA0DD;;;OAGG;IACH,yBAFW,MAAM,QAUhB;IAkJD;;OAEG;IACH,oBAIC;;CACJ"}
|