@woosh/meep-engine 2.90.0 → 2.91.1
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/bundle-worker-terrain.js +1 -1
- package/build/meep.cjs +20 -9
- package/build/meep.min.js +1 -1
- package/build/meep.module.js +20 -9
- package/package.json +1 -1
- package/src/core/binary/de_interleave_2_bits.spec.d.ts.map +1 -0
- package/src/core/binary/de_interleave_bits_by_2.d.ts.map +1 -0
- package/src/core/binary/reinterpret_float32_as_int32.d.ts.map +1 -0
- package/src/core/binary/split_by_2.d.ts.map +1 -0
- package/src/core/binary/split_by_3.d.ts.map +1 -0
- package/src/core/bvh2/binary/2/BinaryUint32BVH.d.ts.map +1 -1
- package/src/core/bvh2/binary/2/BinaryUint32BVH.js +3 -4
- package/src/core/bvh2/bvh3/BVH.js +1 -1
- package/src/core/geom/2d/aabb/AABB2.d.ts.map +1 -1
- package/src/core/geom/2d/aabb/AABB2.js +2 -4
- package/src/core/geom/2d/aabb/aabb2_array_combine.d.ts +11 -0
- package/src/core/geom/2d/aabb/aabb2_array_combine.d.ts.map +1 -0
- package/src/core/geom/2d/aabb/aabb2_array_combine.js +35 -0
- package/src/core/geom/2d/aabb/aabb2_array_set.d.ts +11 -0
- package/src/core/geom/2d/aabb/aabb2_array_set.d.ts.map +1 -0
- package/src/core/geom/2d/aabb/aabb2_array_set.js +21 -0
- package/src/core/geom/2d/aabb/aabb2_compute_area.d.ts +10 -0
- package/src/core/geom/2d/aabb/aabb2_compute_area.d.ts.map +1 -0
- package/src/core/geom/2d/aabb/aabb2_compute_area.js +14 -0
- package/src/core/geom/2d/bvh/BVH2D.d.ts +296 -0
- package/src/core/geom/2d/bvh/BVH2D.d.ts.map +1 -0
- package/src/core/geom/2d/bvh/BVH2D.js +1143 -0
- package/src/core/geom/2d/bvh/BVH2D.spec.d.ts +2 -0
- package/src/core/geom/2d/bvh/BVH2D.spec.d.ts.map +1 -0
- package/src/core/geom/2d/bvh/BVH2D.spec.js +358 -0
- package/src/core/geom/2d/bvh/bvh2d_query_all_data_by_circle.d.ts +13 -0
- package/src/core/geom/2d/bvh/bvh2d_query_all_data_by_circle.d.ts.map +1 -0
- package/src/core/geom/2d/bvh/bvh2d_query_all_data_by_circle.js +83 -0
- package/src/core/geom/2d/lt-grid/LooseTightGrid.js +2 -2
- package/src/core/geom/2d/quad-tree/QuadTreeNode.spec.js +1 -1
- package/src/core/geom/2d/r-tree/StaticR2Tree.d.ts +79 -0
- package/src/core/geom/2d/r-tree/StaticR2Tree.d.ts.map +1 -0
- package/src/core/geom/2d/r-tree/StaticR2Tree.js +384 -0
- package/src/core/geom/2d/r-tree/StaticR2Tree.spec.d.ts +2 -0
- package/src/core/geom/2d/r-tree/StaticR2Tree.spec.d.ts.map +1 -0
- package/src/core/geom/2d/r-tree/StaticR2Tree.spec.js +62 -0
- package/src/core/geom/3d/morton/mortonEncode_magicbits.js +1 -1
- package/src/core/geom/3d/topology/struct/binary/io/OrderedEdge.js +1 -1
- package/src/engine/graphics/texture/virtual/tile/compose_tile_address.js +1 -1
- package/src/engine/graphics/texture/virtual/tile/tile_address_to_finger_print.js +1 -1
- package/src/core/geom/3d/morton/de_interleave_2_bits.spec.d.ts.map +0 -1
- package/src/core/geom/3d/morton/de_interleave_bits_by_2.d.ts.map +0 -1
- package/src/core/geom/3d/morton/reinterpret_float32_as_int32.d.ts.map +0 -1
- package/src/core/geom/3d/morton/split_by_2.d.ts.map +0 -1
- package/src/core/geom/3d/morton/split_by_3.d.ts.map +0 -1
- /package/src/core/{geom/3d/morton → binary}/de_interleave_2_bits.spec.d.ts +0 -0
- /package/src/core/{geom/3d/morton → binary}/de_interleave_2_bits.spec.js +0 -0
- /package/src/core/{geom/3d/morton → binary}/de_interleave_bits_by_2.d.ts +0 -0
- /package/src/core/{geom/3d/morton → binary}/de_interleave_bits_by_2.js +0 -0
- /package/src/core/{geom/3d/morton → binary}/reinterpret_float32_as_int32.d.ts +0 -0
- /package/src/core/{geom/3d/morton → binary}/reinterpret_float32_as_int32.js +0 -0
- /package/src/core/{geom/3d/morton → binary}/split_by_2.d.ts +0 -0
- /package/src/core/{geom/3d/morton → binary}/split_by_2.js +0 -0
- /package/src/core/{geom/3d/morton → binary}/split_by_3.d.ts +0 -0
- /package/src/core/{geom/3d/morton → binary}/split_by_3.js +0 -0
|
@@ -0,0 +1,1143 @@
|
|
|
1
|
+
import { assert } from "../../../assert.js";
|
|
2
|
+
import { UINT32_MAX } from "../../../binary/UINT32_MAX.js";
|
|
3
|
+
import { array_copy } from "../../../collection/array/array_copy.js";
|
|
4
|
+
import { typed_array_copy } from "../../../collection/array/typed/typed_array_copy.js";
|
|
5
|
+
import { max2 } from "../../../math/max2.js";
|
|
6
|
+
import { min2 } from "../../../math/min2.js";
|
|
7
|
+
import { aabb2_compute_area } from "../aabb/aabb2_compute_area.js";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export const COLUMN_PARENT = 4;
|
|
11
|
+
export const COLUMN_CHILD_1 = 5;
|
|
12
|
+
export const COLUMN_CHILD_2 = 6;
|
|
13
|
+
export const COLUMN_HEIGHT = 7;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A non-leaf node have both CHILD_1 and CHILD_2 set, when CHILD_1 is not set - it's a leaf node
|
|
17
|
+
* So we can utilize space of CHILD_2 to store USER_DATA, hence there is overlap in schema
|
|
18
|
+
* @readonly
|
|
19
|
+
* @type {number}
|
|
20
|
+
*/
|
|
21
|
+
export const COLUMN_USER_DATA = COLUMN_CHILD_2;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
*
|
|
25
|
+
* @type {number}
|
|
26
|
+
*/
|
|
27
|
+
export const NULL_NODE = UINT32_MAX;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @readonly
|
|
31
|
+
* @type {number}
|
|
32
|
+
*/
|
|
33
|
+
const CAPACITY_GROW_MULTIPLIER = 1.2;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @readonly
|
|
37
|
+
* @type {number}
|
|
38
|
+
*/
|
|
39
|
+
const CAPACITY_GROW_MIN_STEP = 64;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* How many words are used for a single NODE in the tree
|
|
43
|
+
* One "word" is 4 bytes for the sake of alignment
|
|
44
|
+
* @readonly
|
|
45
|
+
* @type {number}
|
|
46
|
+
*/
|
|
47
|
+
export const ELEMENT_WORD_COUNT = 8;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* How many nodes can be stored in the newly constructed tree before allocation needs to take place
|
|
51
|
+
* @readonly
|
|
52
|
+
* @type {number}
|
|
53
|
+
*/
|
|
54
|
+
const INITIAL_CAPACITY = 128;
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Tree can not contain more than this number of nodes
|
|
59
|
+
* @type {number}
|
|
60
|
+
*/
|
|
61
|
+
const NODE_CAPACITY_LIMIT = Math.floor(UINT32_MAX / (ELEMENT_WORD_COUNT * 4));
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 2D Bounding Volume Hierarchy implementation.
|
|
65
|
+
* Based on BVH (3D) implementation
|
|
66
|
+
* @class
|
|
67
|
+
*/
|
|
68
|
+
export class BVH2D {
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
*
|
|
72
|
+
* @type {ArrayBuffer}
|
|
73
|
+
* @private
|
|
74
|
+
*/
|
|
75
|
+
__data_buffer = new ArrayBuffer(INITIAL_CAPACITY * ELEMENT_WORD_COUNT * 4);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
*
|
|
79
|
+
* @type {Float32Array}
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
82
|
+
__data_float32 = new Float32Array(this.__data_buffer);
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
*
|
|
86
|
+
* @type {Uint32Array}
|
|
87
|
+
* @private
|
|
88
|
+
*/
|
|
89
|
+
__data_uint32 = new Uint32Array(this.__data_buffer);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* How many nodes are currently reserved, this will grow automatically through {@link #allocate_node} method usage
|
|
93
|
+
* @type {number}
|
|
94
|
+
* @private
|
|
95
|
+
*/
|
|
96
|
+
__capacity = INITIAL_CAPACITY;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Number of used nodes. These are either live nodes, or node sitting in the {@link #__free} pool
|
|
100
|
+
* @type {number}
|
|
101
|
+
* @private
|
|
102
|
+
*/
|
|
103
|
+
__size = 0;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Indices of released nodes. Nodes are pulled from here first if available, before the whole tree gets resized
|
|
107
|
+
* @type {number[]}
|
|
108
|
+
* @private
|
|
109
|
+
*/
|
|
110
|
+
__free = [];
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Pointer into __free array that's used as a stack, so this pointer represents top of the stack
|
|
114
|
+
* @type {number}
|
|
115
|
+
* @private
|
|
116
|
+
*/
|
|
117
|
+
__free_pointer = 0;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Root node of the hierarchy
|
|
121
|
+
* @type {number}
|
|
122
|
+
* @private
|
|
123
|
+
*/
|
|
124
|
+
__root = NULL_NODE;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
*
|
|
128
|
+
* @returns {number}
|
|
129
|
+
*/
|
|
130
|
+
get root() {
|
|
131
|
+
return this.__root;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
*
|
|
136
|
+
* @returns {number}
|
|
137
|
+
*/
|
|
138
|
+
get node_capacity() {
|
|
139
|
+
return this.__capacity;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
*
|
|
144
|
+
* @param {number} v
|
|
145
|
+
*/
|
|
146
|
+
set node_capacity(v) {
|
|
147
|
+
if (this.__size > v) {
|
|
148
|
+
throw new Error(`Can't shrink capacity to ${v}, because it's below occupancy(${this.__size}).`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
this.__set_capacity(v);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
__grow_capacity() {
|
|
155
|
+
if (this.__capacity >= NODE_CAPACITY_LIMIT) {
|
|
156
|
+
throw new Error('Can not grow capacity, already at maximum platform limit');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let new_capacity = Math.ceil(max2(
|
|
160
|
+
this.__capacity * CAPACITY_GROW_MULTIPLIER,
|
|
161
|
+
this.__capacity + CAPACITY_GROW_MIN_STEP
|
|
162
|
+
));
|
|
163
|
+
|
|
164
|
+
if (new_capacity > NODE_CAPACITY_LIMIT) {
|
|
165
|
+
// can not grow as much as we'd like, but we can still grow up to the limit
|
|
166
|
+
new_capacity = NODE_CAPACITY_LIMIT;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.__set_capacity(new_capacity);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
*
|
|
174
|
+
* @param {number} new_capacity in number of nodes
|
|
175
|
+
* @private
|
|
176
|
+
*/
|
|
177
|
+
__set_capacity(new_capacity) {
|
|
178
|
+
assert.isNonNegativeInteger(new_capacity, 'new_capacity');
|
|
179
|
+
|
|
180
|
+
if (this.__capacity === new_capacity) {
|
|
181
|
+
// already at the exact desired capacity
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const old_data_uint32 = this.__data_uint32;
|
|
186
|
+
|
|
187
|
+
const new_data_buffer = new ArrayBuffer(new_capacity * ELEMENT_WORD_COUNT * 4);
|
|
188
|
+
|
|
189
|
+
this.__data_buffer = new_data_buffer;
|
|
190
|
+
|
|
191
|
+
this.__data_float32 = new Float32Array(new_data_buffer);
|
|
192
|
+
this.__data_uint32 = new Uint32Array(new_data_buffer);
|
|
193
|
+
|
|
194
|
+
// copy old data into new buffer
|
|
195
|
+
typed_array_copy(old_data_uint32, this.__data_uint32);
|
|
196
|
+
|
|
197
|
+
this.__capacity = new_capacity;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Trim allocated memory region to only contain allocated nodes
|
|
202
|
+
*/
|
|
203
|
+
trim() {
|
|
204
|
+
if (this.__capacity > this.__size) {
|
|
205
|
+
this.__set_capacity(this.__size);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
*
|
|
211
|
+
* @returns {number}
|
|
212
|
+
*/
|
|
213
|
+
allocate_node() {
|
|
214
|
+
let result;
|
|
215
|
+
const free_stack_top = this.__free_pointer;
|
|
216
|
+
|
|
217
|
+
if (free_stack_top > 0) {
|
|
218
|
+
|
|
219
|
+
// nodes in the free pool
|
|
220
|
+
|
|
221
|
+
const free_index = free_stack_top - 1;
|
|
222
|
+
|
|
223
|
+
result = this.__free[free_index];
|
|
224
|
+
|
|
225
|
+
this.__free_pointer = free_index;
|
|
226
|
+
|
|
227
|
+
} else {
|
|
228
|
+
|
|
229
|
+
result = this.__size;
|
|
230
|
+
|
|
231
|
+
if (result >= this.__capacity) {
|
|
232
|
+
// grow capacity
|
|
233
|
+
// console.log(`#GROW`);
|
|
234
|
+
this.__grow_capacity();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
this.__size++;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// initialize node
|
|
241
|
+
|
|
242
|
+
const address = ELEMENT_WORD_COUNT * result;
|
|
243
|
+
|
|
244
|
+
const float32 = this.__data_float32;
|
|
245
|
+
|
|
246
|
+
// write AABB
|
|
247
|
+
float32[address] = Number.POSITIVE_INFINITY;
|
|
248
|
+
float32[address + 1] = Number.POSITIVE_INFINITY;
|
|
249
|
+
float32[address + 2] = Number.NEGATIVE_INFINITY;
|
|
250
|
+
float32[address + 3] = Number.NEGATIVE_INFINITY;
|
|
251
|
+
|
|
252
|
+
const uint32 = this.__data_uint32;
|
|
253
|
+
|
|
254
|
+
uint32[address + COLUMN_PARENT] = NULL_NODE; // parent
|
|
255
|
+
uint32[address + COLUMN_CHILD_1] = NULL_NODE; // child-1
|
|
256
|
+
uint32[address + COLUMN_CHILD_2] = NULL_NODE; // child-2 / user-data
|
|
257
|
+
uint32[address + COLUMN_HEIGHT] = 0; // height
|
|
258
|
+
|
|
259
|
+
// console.log(`#ALLOCATED: ${result}`);
|
|
260
|
+
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Release memory used by the node back into the pool
|
|
266
|
+
* NOTE: Make sure that the node is not "live" (not attached to the hierarchy), otherwise this operation may corrupt the tree
|
|
267
|
+
* @param {number} id
|
|
268
|
+
*/
|
|
269
|
+
release_node(id) {
|
|
270
|
+
assert.isNonNegativeInteger(id, 'id');
|
|
271
|
+
// no reset required as that's handled in the allocation method
|
|
272
|
+
this.__free[this.__free_pointer++] = id;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
*
|
|
277
|
+
* @param {number} id
|
|
278
|
+
* @returns {boolean}
|
|
279
|
+
*/
|
|
280
|
+
node_is_leaf(id) {
|
|
281
|
+
assert.isNonNegativeInteger(id, 'id');
|
|
282
|
+
const child_1 = this.__data_uint32[ELEMENT_WORD_COUNT * id + COLUMN_CHILD_1];
|
|
283
|
+
|
|
284
|
+
return child_1 === NULL_NODE;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
*
|
|
289
|
+
* @param {number} id
|
|
290
|
+
* @returns {number}
|
|
291
|
+
*/
|
|
292
|
+
node_get_user_data(id) {
|
|
293
|
+
assert.isNonNegativeInteger(id, 'id');
|
|
294
|
+
return this.__data_uint32[ELEMENT_WORD_COUNT * id + COLUMN_USER_DATA];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
*
|
|
299
|
+
* @param {number} id
|
|
300
|
+
* @param {number} value
|
|
301
|
+
*/
|
|
302
|
+
node_set_user_data(id, value) {
|
|
303
|
+
assert.isNonNegativeInteger(id, 'id');
|
|
304
|
+
assert.isNonNegativeInteger(value, 'value');
|
|
305
|
+
|
|
306
|
+
this.__data_uint32[ELEMENT_WORD_COUNT * id + COLUMN_USER_DATA] = value;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
*
|
|
311
|
+
* @param {number} id
|
|
312
|
+
* @returns {number}
|
|
313
|
+
*/
|
|
314
|
+
node_get_child1(id) {
|
|
315
|
+
assert.isNonNegativeInteger(id, 'id');
|
|
316
|
+
return this.__data_uint32[ELEMENT_WORD_COUNT * id + COLUMN_CHILD_1];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
*
|
|
321
|
+
* @param {number} node
|
|
322
|
+
* @param {number} child1
|
|
323
|
+
*/
|
|
324
|
+
node_set_child1(node, child1) {
|
|
325
|
+
this.__data_uint32[ELEMENT_WORD_COUNT * node + COLUMN_CHILD_1] = child1;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
*
|
|
330
|
+
* @param {number} id
|
|
331
|
+
* @returns {number}
|
|
332
|
+
*/
|
|
333
|
+
node_get_child2(id) {
|
|
334
|
+
assert.isNonNegativeInteger(id, 'id');
|
|
335
|
+
return this.__data_uint32[ELEMENT_WORD_COUNT * id + COLUMN_CHILD_2];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
*
|
|
340
|
+
* @param {number} node
|
|
341
|
+
* @param {number} child2
|
|
342
|
+
*/
|
|
343
|
+
node_set_child2(node, child2) {
|
|
344
|
+
this.__data_uint32[ELEMENT_WORD_COUNT * node + COLUMN_CHILD_2] = child2;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
*
|
|
349
|
+
* @param {number} id
|
|
350
|
+
* @returns {number}
|
|
351
|
+
*/
|
|
352
|
+
node_get_parent(id) {
|
|
353
|
+
assert.isNonNegativeInteger(id, 'id');
|
|
354
|
+
return this.__data_uint32[ELEMENT_WORD_COUNT * id + COLUMN_PARENT];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
*
|
|
359
|
+
* @param {number} node
|
|
360
|
+
* @param {number} parent
|
|
361
|
+
*/
|
|
362
|
+
node_set_parent(node, parent) {
|
|
363
|
+
this.__data_uint32[ELEMENT_WORD_COUNT * node + COLUMN_PARENT] = parent;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
*
|
|
369
|
+
* @param {number} id
|
|
370
|
+
* @returns {number}
|
|
371
|
+
*/
|
|
372
|
+
node_get_height(id) {
|
|
373
|
+
assert.isNonNegativeInteger(id, 'id');
|
|
374
|
+
return this.__data_uint32[ELEMENT_WORD_COUNT * id + COLUMN_HEIGHT];
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
*
|
|
379
|
+
* @param {number} id
|
|
380
|
+
* @param {number} height
|
|
381
|
+
*/
|
|
382
|
+
node_set_height(id, height) {
|
|
383
|
+
assert.isNonNegativeInteger(id, 'id');
|
|
384
|
+
this.__data_uint32[ELEMENT_WORD_COUNT * id + COLUMN_HEIGHT] = height;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
*
|
|
389
|
+
* @param {number} id
|
|
390
|
+
* @param {number[]|Float32Array} result
|
|
391
|
+
*/
|
|
392
|
+
node_get_aabb(id, result) {
|
|
393
|
+
assert.isNonNegativeInteger(id, 'id');
|
|
394
|
+
|
|
395
|
+
const address = ELEMENT_WORD_COUNT * id;
|
|
396
|
+
const float32 = this.__data_float32;
|
|
397
|
+
|
|
398
|
+
result[0] = float32[address];
|
|
399
|
+
result[1] = float32[address + 1];
|
|
400
|
+
|
|
401
|
+
result[2] = float32[address + 2];
|
|
402
|
+
result[3] = float32[address + 3];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
*
|
|
407
|
+
* @param {number} id
|
|
408
|
+
* @param {number[]|ArrayLike<number>} aabb
|
|
409
|
+
*/
|
|
410
|
+
node_set_aabb(id, aabb) {
|
|
411
|
+
assert.isNonNegativeInteger(id, 'id');
|
|
412
|
+
|
|
413
|
+
assert.notNaN(aabb[0], 'aabb[0] x0');
|
|
414
|
+
assert.notNaN(aabb[1], 'aabb[1] y0');
|
|
415
|
+
assert.notNaN(aabb[2], 'aabb[3] x1');
|
|
416
|
+
assert.notNaN(aabb[3], 'aabb[4] y1');
|
|
417
|
+
|
|
418
|
+
const address = ELEMENT_WORD_COUNT * id;
|
|
419
|
+
const float32 = this.__data_float32;
|
|
420
|
+
|
|
421
|
+
float32[address] = aabb[0];
|
|
422
|
+
float32[address + 1] = aabb[1];
|
|
423
|
+
|
|
424
|
+
float32[address + 2] = aabb[2];
|
|
425
|
+
float32[address + 3] = aabb[3];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
*
|
|
430
|
+
* @param {number} id
|
|
431
|
+
* @param {number[]} aabb
|
|
432
|
+
*/
|
|
433
|
+
node_move_aabb(id, aabb) {
|
|
434
|
+
// TODO only refit for small changes, and re-insert for large changes
|
|
435
|
+
this.node_set_aabb(id, aabb);
|
|
436
|
+
|
|
437
|
+
const parent = this.__data_uint32[ELEMENT_WORD_COUNT * id + COLUMN_PARENT];
|
|
438
|
+
|
|
439
|
+
if (parent !== NULL_NODE) {
|
|
440
|
+
this.bubble_up_refit(parent);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
*
|
|
446
|
+
* @param {number} id
|
|
447
|
+
* @param {number} x0
|
|
448
|
+
* @param {number} y0
|
|
449
|
+
* @param {number} z0
|
|
450
|
+
* @param {number} x1
|
|
451
|
+
* @param {number} y1
|
|
452
|
+
* @param {number} z1
|
|
453
|
+
*/
|
|
454
|
+
node_set_aabb_primitive(
|
|
455
|
+
id,
|
|
456
|
+
x0, y0,
|
|
457
|
+
x1, y1
|
|
458
|
+
) {
|
|
459
|
+
assert.isNonNegativeInteger(id, 'id');
|
|
460
|
+
|
|
461
|
+
const address = ELEMENT_WORD_COUNT * id;
|
|
462
|
+
const float32 = this.__data_float32;
|
|
463
|
+
|
|
464
|
+
float32[address] = x0;
|
|
465
|
+
float32[address + 1] = y0;
|
|
466
|
+
|
|
467
|
+
float32[address + 2] = x1;
|
|
468
|
+
float32[address + 3] = y1;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
*
|
|
473
|
+
* @param {number} id
|
|
474
|
+
* @returns {number}
|
|
475
|
+
*/
|
|
476
|
+
node_get_surface_area(id) {
|
|
477
|
+
assert.isNonNegativeInteger(id, 'id');
|
|
478
|
+
|
|
479
|
+
const address = ELEMENT_WORD_COUNT * id;
|
|
480
|
+
const float32 = this.__data_float32;
|
|
481
|
+
|
|
482
|
+
const x0 = float32[address];
|
|
483
|
+
const y0 = float32[address + 1];
|
|
484
|
+
|
|
485
|
+
const x1 = float32[address + 2];
|
|
486
|
+
const y1 = float32[address + 3];
|
|
487
|
+
|
|
488
|
+
return aabb2_compute_area(
|
|
489
|
+
x0, y0,
|
|
490
|
+
x1, y1
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
*
|
|
496
|
+
* @param {number} index_a
|
|
497
|
+
* @param {number} index_b
|
|
498
|
+
* @returns {number}
|
|
499
|
+
*/
|
|
500
|
+
node_get_combined_surface_area(index_a, index_b) {
|
|
501
|
+
const address_a = ELEMENT_WORD_COUNT * index_a;
|
|
502
|
+
const address_b = ELEMENT_WORD_COUNT * index_b;
|
|
503
|
+
|
|
504
|
+
const float32 = this.__data_float32;
|
|
505
|
+
|
|
506
|
+
const a_x0 = float32[address_a];
|
|
507
|
+
const b_x0 = float32[address_b];
|
|
508
|
+
const x0 = min2(a_x0, b_x0);
|
|
509
|
+
|
|
510
|
+
const a_y0 = float32[address_a + 1];
|
|
511
|
+
const b_y0 = float32[address_b + 1];
|
|
512
|
+
const y0 = min2(a_y0, b_y0);
|
|
513
|
+
|
|
514
|
+
const a_x1 = float32[address_a + 2];
|
|
515
|
+
const b_x1 = float32[address_b + 2];
|
|
516
|
+
const x1 = max2(a_x1, b_x1);
|
|
517
|
+
|
|
518
|
+
const a_y1 = float32[address_a + 3];
|
|
519
|
+
const b_y1 = float32[address_b + 3];
|
|
520
|
+
const y1 = max2(a_y1, b_y1);
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
return aabb2_compute_area(
|
|
524
|
+
x0, y0,
|
|
525
|
+
x1, y1,
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
*
|
|
531
|
+
* @param {number} destination
|
|
532
|
+
* @param {number} index_a
|
|
533
|
+
* @param {number} index_b
|
|
534
|
+
*/
|
|
535
|
+
node_set_combined_aabb(destination, index_a, index_b) {
|
|
536
|
+
const address_a = ELEMENT_WORD_COUNT * index_a;
|
|
537
|
+
const address_b = ELEMENT_WORD_COUNT * index_b;
|
|
538
|
+
|
|
539
|
+
const float32 = this.__data_float32;
|
|
540
|
+
|
|
541
|
+
const a_x0 = float32[address_a];
|
|
542
|
+
const b_x0 = float32[address_b];
|
|
543
|
+
const x0 = min2(a_x0, b_x0);
|
|
544
|
+
|
|
545
|
+
const a_y0 = float32[address_a + 1];
|
|
546
|
+
const b_y0 = float32[address_b + 1];
|
|
547
|
+
const y0 = min2(a_y0, b_y0);
|
|
548
|
+
|
|
549
|
+
const a_x1 = float32[address_a + 2];
|
|
550
|
+
const b_x1 = float32[address_b + 2];
|
|
551
|
+
const x1 = max2(a_x1, b_x1);
|
|
552
|
+
|
|
553
|
+
const a_y1 = float32[address_a + 3];
|
|
554
|
+
const b_y1 = float32[address_b + 3];
|
|
555
|
+
const y1 = max2(a_y1, b_y1);
|
|
556
|
+
|
|
557
|
+
const address_destination = destination * ELEMENT_WORD_COUNT;
|
|
558
|
+
|
|
559
|
+
float32[address_destination] = x0;
|
|
560
|
+
float32[address_destination + 1] = y0;
|
|
561
|
+
|
|
562
|
+
float32[address_destination + 2] = x1;
|
|
563
|
+
float32[address_destination + 3] = y1;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
*
|
|
568
|
+
* @param {number} leaf
|
|
569
|
+
* @returns {void}
|
|
570
|
+
*/
|
|
571
|
+
insert_leaf(leaf) {
|
|
572
|
+
assert.isNonNegativeInteger(leaf, 'leaf');
|
|
573
|
+
assert.equal(this.node_is_leaf(leaf), true, 'not is not a leaf');
|
|
574
|
+
|
|
575
|
+
let uint32 = this.__data_uint32;
|
|
576
|
+
|
|
577
|
+
if (this.__root === NULL_NODE) {
|
|
578
|
+
// special case - no root, set this node as a root
|
|
579
|
+
this.__root = leaf;
|
|
580
|
+
|
|
581
|
+
uint32[leaf * ELEMENT_WORD_COUNT + COLUMN_PARENT] = NULL_NODE;
|
|
582
|
+
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Find the best sibling for this node
|
|
587
|
+
let index = this.__root;
|
|
588
|
+
|
|
589
|
+
while (this.node_is_leaf(index) === false) {
|
|
590
|
+
const node_address = index * ELEMENT_WORD_COUNT;
|
|
591
|
+
|
|
592
|
+
const child1 = uint32[node_address + COLUMN_CHILD_1];
|
|
593
|
+
const child2 = uint32[node_address + COLUMN_CHILD_2];
|
|
594
|
+
|
|
595
|
+
const area = this.node_get_surface_area(index);
|
|
596
|
+
|
|
597
|
+
const combinedArea = this.node_get_combined_surface_area(index, leaf);
|
|
598
|
+
|
|
599
|
+
// Cost of creating a new parent for this node and the new leaf
|
|
600
|
+
const cost = 2.0 * combinedArea;
|
|
601
|
+
|
|
602
|
+
// Minimum cost of pushing the leaf further down the tree
|
|
603
|
+
const inheritanceCost = 2.0 * (combinedArea - area);
|
|
604
|
+
|
|
605
|
+
// Cost of descending into child1
|
|
606
|
+
let cost1;
|
|
607
|
+
if (this.node_is_leaf(child1)) {
|
|
608
|
+
cost1 = this.node_get_combined_surface_area(leaf, child1) + inheritanceCost;
|
|
609
|
+
} else {
|
|
610
|
+
|
|
611
|
+
const oldArea = this.node_get_surface_area(child1);
|
|
612
|
+
const newArea = this.node_get_combined_surface_area(leaf, child1);
|
|
613
|
+
cost1 = (newArea - oldArea) + inheritanceCost;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Cost of descending into child2
|
|
617
|
+
let cost2;
|
|
618
|
+
if (this.node_is_leaf(child2)) {
|
|
619
|
+
cost2 = this.node_get_combined_surface_area(leaf, child2) + inheritanceCost;
|
|
620
|
+
} else {
|
|
621
|
+
const oldArea = this.node_get_surface_area(child2);
|
|
622
|
+
const newArea = this.node_get_combined_surface_area(leaf, child2);
|
|
623
|
+
cost2 = newArea - oldArea + inheritanceCost;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Descend according to the minimum cost.
|
|
627
|
+
if (cost < cost1 && cost < cost2) {
|
|
628
|
+
break;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Descend
|
|
632
|
+
if (cost1 < cost2) {
|
|
633
|
+
index = child1;
|
|
634
|
+
} else {
|
|
635
|
+
index = child2;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const sibling = index;
|
|
640
|
+
|
|
641
|
+
// Create a new parent.
|
|
642
|
+
const oldParent = uint32[sibling * ELEMENT_WORD_COUNT + COLUMN_PARENT];
|
|
643
|
+
const newParent = this.allocate_node();
|
|
644
|
+
|
|
645
|
+
uint32 = this.__data_uint32; // reference can be invalidated after allocation, re-bind
|
|
646
|
+
|
|
647
|
+
uint32[newParent * ELEMENT_WORD_COUNT + COLUMN_PARENT] = oldParent;
|
|
648
|
+
this.node_set_combined_aabb(newParent, leaf, sibling);
|
|
649
|
+
uint32[newParent * ELEMENT_WORD_COUNT + COLUMN_HEIGHT] = uint32[sibling * ELEMENT_WORD_COUNT + COLUMN_HEIGHT] + 1;
|
|
650
|
+
|
|
651
|
+
if (oldParent !== NULL_NODE) {
|
|
652
|
+
// The sibling was not the root.
|
|
653
|
+
if (uint32[oldParent * ELEMENT_WORD_COUNT + COLUMN_CHILD_1] === sibling) {
|
|
654
|
+
uint32[oldParent * ELEMENT_WORD_COUNT + COLUMN_CHILD_1] = newParent;
|
|
655
|
+
} else {
|
|
656
|
+
assert.equal(uint32[oldParent * ELEMENT_WORD_COUNT + COLUMN_CHILD_2], sibling);
|
|
657
|
+
|
|
658
|
+
uint32[oldParent * ELEMENT_WORD_COUNT + COLUMN_CHILD_2] = newParent;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
} else {
|
|
662
|
+
// The sibling was the root.
|
|
663
|
+
this.__root = newParent;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
uint32[newParent * ELEMENT_WORD_COUNT + COLUMN_CHILD_1] = sibling;
|
|
667
|
+
uint32[newParent * ELEMENT_WORD_COUNT + COLUMN_CHILD_2] = leaf;
|
|
668
|
+
|
|
669
|
+
uint32[sibling * ELEMENT_WORD_COUNT + COLUMN_PARENT] = newParent;
|
|
670
|
+
uint32[leaf * ELEMENT_WORD_COUNT + COLUMN_PARENT] = newParent;
|
|
671
|
+
|
|
672
|
+
// Walk back up the tree fixing heights and AABBs
|
|
673
|
+
this.bubble_up_update(newParent);
|
|
674
|
+
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* refit and update nodes up the tree. Only updates bounds
|
|
679
|
+
* NOTE: Does not update "height"
|
|
680
|
+
* @param {number} parent
|
|
681
|
+
* @private
|
|
682
|
+
*/
|
|
683
|
+
bubble_up_refit(parent) {
|
|
684
|
+
let index = parent;
|
|
685
|
+
|
|
686
|
+
const uint32 = this.__data_uint32;
|
|
687
|
+
|
|
688
|
+
do {
|
|
689
|
+
index = this.balance(index);
|
|
690
|
+
|
|
691
|
+
const address = index * ELEMENT_WORD_COUNT;
|
|
692
|
+
|
|
693
|
+
const child1 = uint32[address + COLUMN_CHILD_1];
|
|
694
|
+
const child2 = uint32[address + COLUMN_CHILD_2];
|
|
695
|
+
|
|
696
|
+
assert.notEqual(child1, NULL_NODE, 'child1 is null');
|
|
697
|
+
assert.notEqual(child2, NULL_NODE, 'child2 is null');
|
|
698
|
+
|
|
699
|
+
assert.notEqual(child1, index, 'child1 is equal to parent');
|
|
700
|
+
assert.notEqual(child2, index, 'child2 is equal to parent');
|
|
701
|
+
|
|
702
|
+
this.node_set_combined_aabb(index, child1, child2);
|
|
703
|
+
|
|
704
|
+
index = uint32[address + COLUMN_PARENT];
|
|
705
|
+
} while (index !== NULL_NODE)
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* refit and update nodes up the tree
|
|
710
|
+
* @param {number} parent
|
|
711
|
+
* @private
|
|
712
|
+
*/
|
|
713
|
+
bubble_up_update(parent) {
|
|
714
|
+
let index = parent;
|
|
715
|
+
|
|
716
|
+
const uint32 = this.__data_uint32;
|
|
717
|
+
|
|
718
|
+
while (index !== NULL_NODE) {
|
|
719
|
+
index = this.balance(index);
|
|
720
|
+
|
|
721
|
+
const node_address = index * ELEMENT_WORD_COUNT;
|
|
722
|
+
|
|
723
|
+
const child1 = uint32[node_address + COLUMN_CHILD_1];
|
|
724
|
+
const child2 = uint32[node_address + COLUMN_CHILD_2];
|
|
725
|
+
|
|
726
|
+
assert.notEqual(child1, NULL_NODE, 'child1 is null');
|
|
727
|
+
assert.notEqual(child2, NULL_NODE, 'child2 is null');
|
|
728
|
+
|
|
729
|
+
assert.notEqual(child1, index, 'child1 is equal to parent');
|
|
730
|
+
assert.notEqual(child2, index, 'child2 is equal to parent');
|
|
731
|
+
|
|
732
|
+
uint32[node_address + COLUMN_HEIGHT] = 1 + max2(
|
|
733
|
+
uint32[child1 * ELEMENT_WORD_COUNT + COLUMN_HEIGHT],
|
|
734
|
+
uint32[child2 * ELEMENT_WORD_COUNT + COLUMN_HEIGHT],
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
this.node_set_combined_aabb(index, child1, child2);
|
|
738
|
+
|
|
739
|
+
index = uint32[node_address + COLUMN_PARENT];
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* NOTE: Leaf node is not released, make sure to call {@link #release_node} separately when you no longer need the leaf node
|
|
745
|
+
* @param {number} leaf
|
|
746
|
+
* @returns {void}
|
|
747
|
+
*/
|
|
748
|
+
remove_leaf(leaf) {
|
|
749
|
+
assert.isNonNegativeInteger(leaf, 'leaf');
|
|
750
|
+
|
|
751
|
+
if (leaf === this.__root) {
|
|
752
|
+
this.__root = NULL_NODE;
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const uint32 = this.__data_uint32;
|
|
757
|
+
|
|
758
|
+
const parent = uint32[leaf * ELEMENT_WORD_COUNT + COLUMN_PARENT];
|
|
759
|
+
const grandParent = uint32[parent * ELEMENT_WORD_COUNT + COLUMN_PARENT];
|
|
760
|
+
|
|
761
|
+
let sibling;
|
|
762
|
+
|
|
763
|
+
const parent_child1 = uint32[parent * ELEMENT_WORD_COUNT + COLUMN_CHILD_1];
|
|
764
|
+
|
|
765
|
+
if (parent_child1 === leaf) {
|
|
766
|
+
sibling = uint32[parent * ELEMENT_WORD_COUNT + COLUMN_CHILD_2];
|
|
767
|
+
} else {
|
|
768
|
+
sibling = parent_child1;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (grandParent !== NULL_NODE) {
|
|
772
|
+
// Destroy parent and connect sibling to grandParent.
|
|
773
|
+
const grand_parent_child1 = uint32[grandParent * ELEMENT_WORD_COUNT + COLUMN_CHILD_1];
|
|
774
|
+
if (grand_parent_child1 === parent) {
|
|
775
|
+
uint32[grandParent * ELEMENT_WORD_COUNT + COLUMN_CHILD_1] = sibling;
|
|
776
|
+
} else {
|
|
777
|
+
uint32[grandParent * ELEMENT_WORD_COUNT + COLUMN_CHILD_2] = sibling;
|
|
778
|
+
}
|
|
779
|
+
uint32[sibling * ELEMENT_WORD_COUNT + COLUMN_PARENT] = grandParent;
|
|
780
|
+
|
|
781
|
+
this.release_node(parent);
|
|
782
|
+
|
|
783
|
+
// Adjust ancestor bounds.
|
|
784
|
+
this.bubble_up_update(grandParent);
|
|
785
|
+
|
|
786
|
+
} else {
|
|
787
|
+
this.__root = sibling;
|
|
788
|
+
uint32[sibling * ELEMENT_WORD_COUNT + COLUMN_PARENT] = NULL_NODE;
|
|
789
|
+
this.release_node(parent);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Perform a left or right rotation if node A is imbalanced.
|
|
795
|
+
* Returns the new root index.
|
|
796
|
+
* @param {number} iA
|
|
797
|
+
* @returns {number}
|
|
798
|
+
* @private
|
|
799
|
+
*/
|
|
800
|
+
balance(iA) {
|
|
801
|
+
assert.notEqual(iA, NULL_NODE, 'input is a null node');
|
|
802
|
+
|
|
803
|
+
//b2TreeNode* A = m_nodes + iA;
|
|
804
|
+
const uint32 = this.__data_uint32;
|
|
805
|
+
|
|
806
|
+
if (this.node_is_leaf(iA) || uint32[iA * ELEMENT_WORD_COUNT + COLUMN_HEIGHT] < 2) {
|
|
807
|
+
return iA;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const iB = uint32[iA * ELEMENT_WORD_COUNT + COLUMN_CHILD_1];
|
|
811
|
+
const iC = uint32[iA * ELEMENT_WORD_COUNT + COLUMN_CHILD_2];
|
|
812
|
+
|
|
813
|
+
assert.notEqual(iA, iB, 'child1 equal to parent');
|
|
814
|
+
assert.notEqual(iA, iB, 'child2 equal to parent');
|
|
815
|
+
|
|
816
|
+
assert.greaterThanOrEqual(iB, 0);
|
|
817
|
+
assert.lessThan(iB, this.node_capacity);
|
|
818
|
+
assert.greaterThanOrEqual(iC, 0);
|
|
819
|
+
assert.lessThan(iC, this.node_capacity);
|
|
820
|
+
|
|
821
|
+
//b2TreeNode* B = m_nodes + iB;
|
|
822
|
+
//b2TreeNode* C = m_nodes + iC;
|
|
823
|
+
|
|
824
|
+
const balance = uint32[iC * ELEMENT_WORD_COUNT + COLUMN_HEIGHT] - uint32[iB * ELEMENT_WORD_COUNT + COLUMN_HEIGHT];
|
|
825
|
+
|
|
826
|
+
// Rotate C up
|
|
827
|
+
if (balance > 1) {
|
|
828
|
+
const iF = uint32[iC * ELEMENT_WORD_COUNT + COLUMN_CHILD_1];
|
|
829
|
+
const iG = uint32[iC * ELEMENT_WORD_COUNT + COLUMN_CHILD_2];
|
|
830
|
+
|
|
831
|
+
assert.notEqual(iC, iF, 'child1 equal to parent');
|
|
832
|
+
assert.notEqual(iC, iG, 'child2 equal to parent');
|
|
833
|
+
|
|
834
|
+
// b2TreeNode* F = m_nodes + iF;
|
|
835
|
+
// b2TreeNode* G = m_nodes + iG;
|
|
836
|
+
|
|
837
|
+
assert.greaterThanOrEqual(iF, 0);
|
|
838
|
+
assert.lessThan(iF, this.node_capacity);
|
|
839
|
+
assert.greaterThanOrEqual(iG, 0);
|
|
840
|
+
assert.lessThan(iG, this.node_capacity);
|
|
841
|
+
|
|
842
|
+
// Swap A and C
|
|
843
|
+
uint32[iC * ELEMENT_WORD_COUNT + COLUMN_CHILD_1] = iA;
|
|
844
|
+
|
|
845
|
+
const a_parent = uint32[iA * ELEMENT_WORD_COUNT + COLUMN_PARENT];
|
|
846
|
+
|
|
847
|
+
uint32[iC * ELEMENT_WORD_COUNT + COLUMN_PARENT] = a_parent;
|
|
848
|
+
uint32[iA * ELEMENT_WORD_COUNT + COLUMN_PARENT] = iC;
|
|
849
|
+
|
|
850
|
+
// A's old parent should point to C
|
|
851
|
+
if (a_parent !== NULL_NODE) {
|
|
852
|
+
assert.notEqual(a_parent, iC);
|
|
853
|
+
if (uint32[a_parent * ELEMENT_WORD_COUNT + COLUMN_CHILD_1] === iA) {
|
|
854
|
+
uint32[a_parent * ELEMENT_WORD_COUNT + COLUMN_CHILD_1] = iC;
|
|
855
|
+
} else {
|
|
856
|
+
//b2Assert(m_nodes[C->parent].child2 == iA);
|
|
857
|
+
uint32[a_parent * ELEMENT_WORD_COUNT + COLUMN_CHILD_2] = iC;
|
|
858
|
+
}
|
|
859
|
+
} else {
|
|
860
|
+
this.__root = iC;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Rotate
|
|
864
|
+
if (uint32[iF * ELEMENT_WORD_COUNT + COLUMN_HEIGHT] > uint32[iG * ELEMENT_WORD_COUNT + COLUMN_HEIGHT]) {
|
|
865
|
+
|
|
866
|
+
uint32[iC * ELEMENT_WORD_COUNT + COLUMN_CHILD_2] = iF;
|
|
867
|
+
uint32[iA * ELEMENT_WORD_COUNT + COLUMN_CHILD_2] = iG;
|
|
868
|
+
|
|
869
|
+
uint32[iG * ELEMENT_WORD_COUNT + COLUMN_PARENT] = iA;
|
|
870
|
+
|
|
871
|
+
this.node_set_combined_aabb(iA, iB, iG);
|
|
872
|
+
this.node_set_combined_aabb(iC, iA, iF);
|
|
873
|
+
|
|
874
|
+
uint32[iA * ELEMENT_WORD_COUNT + COLUMN_HEIGHT] = 1 + max2(
|
|
875
|
+
uint32[iB * ELEMENT_WORD_COUNT + COLUMN_HEIGHT],
|
|
876
|
+
uint32[iG * ELEMENT_WORD_COUNT + COLUMN_HEIGHT]
|
|
877
|
+
);
|
|
878
|
+
|
|
879
|
+
uint32[iC * ELEMENT_WORD_COUNT + COLUMN_HEIGHT] = 1 + max2(
|
|
880
|
+
uint32[iA * ELEMENT_WORD_COUNT + COLUMN_HEIGHT],
|
|
881
|
+
uint32[iF * ELEMENT_WORD_COUNT + COLUMN_HEIGHT]
|
|
882
|
+
);
|
|
883
|
+
|
|
884
|
+
} else {
|
|
885
|
+
uint32[iC * ELEMENT_WORD_COUNT + COLUMN_CHILD_2] = iG;
|
|
886
|
+
uint32[iA * ELEMENT_WORD_COUNT + COLUMN_CHILD_2] = iF;
|
|
887
|
+
|
|
888
|
+
uint32[iF * ELEMENT_WORD_COUNT + COLUMN_PARENT] = iA;
|
|
889
|
+
|
|
890
|
+
this.node_set_combined_aabb(iA, iB, iF);
|
|
891
|
+
this.node_set_combined_aabb(iC, iA, iG);
|
|
892
|
+
|
|
893
|
+
uint32[iA * ELEMENT_WORD_COUNT + COLUMN_HEIGHT] = 1 + max2(
|
|
894
|
+
uint32[iB * ELEMENT_WORD_COUNT + COLUMN_HEIGHT],
|
|
895
|
+
uint32[iF * ELEMENT_WORD_COUNT + COLUMN_HEIGHT]
|
|
896
|
+
);
|
|
897
|
+
|
|
898
|
+
uint32[iC * ELEMENT_WORD_COUNT + COLUMN_HEIGHT] = 1 + max2(
|
|
899
|
+
uint32[iA * ELEMENT_WORD_COUNT + COLUMN_HEIGHT],
|
|
900
|
+
uint32[iG * ELEMENT_WORD_COUNT + COLUMN_HEIGHT]
|
|
901
|
+
);
|
|
902
|
+
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
assert.notEqual(iC, uint32[iC * ELEMENT_WORD_COUNT + COLUMN_CHILD_1]);
|
|
906
|
+
assert.notEqual(iC, uint32[iC * ELEMENT_WORD_COUNT + COLUMN_CHILD_2]);
|
|
907
|
+
|
|
908
|
+
return iC;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// Rotate B up
|
|
912
|
+
if (balance < -1) {
|
|
913
|
+
const iD = uint32[iB * ELEMENT_WORD_COUNT + COLUMN_CHILD_1];
|
|
914
|
+
const iE = uint32[iB * ELEMENT_WORD_COUNT + COLUMN_CHILD_2];
|
|
915
|
+
|
|
916
|
+
assert.notEqual(iB, iD, 'child1 equal to parent');
|
|
917
|
+
assert.notEqual(iB, iE, 'child2 equal to parent');
|
|
918
|
+
|
|
919
|
+
assert.greaterThanOrEqual(iD, 0);
|
|
920
|
+
assert.lessThan(iD, this.node_capacity);
|
|
921
|
+
assert.greaterThanOrEqual(iE, 0);
|
|
922
|
+
assert.lessThan(iE, this.node_capacity);
|
|
923
|
+
|
|
924
|
+
// Swap A and B
|
|
925
|
+
uint32[iB * ELEMENT_WORD_COUNT + COLUMN_CHILD_1] = iA;
|
|
926
|
+
const a_parent = uint32[iA * ELEMENT_WORD_COUNT + COLUMN_PARENT];
|
|
927
|
+
uint32[iB * ELEMENT_WORD_COUNT + COLUMN_PARENT] = a_parent;
|
|
928
|
+
uint32[iA * ELEMENT_WORD_COUNT + COLUMN_PARENT] = iB;
|
|
929
|
+
|
|
930
|
+
// A's old parent should point to B
|
|
931
|
+
if (a_parent !== NULL_NODE) {
|
|
932
|
+
if (uint32[a_parent * ELEMENT_WORD_COUNT + COLUMN_CHILD_1] === iA) {
|
|
933
|
+
uint32[a_parent * ELEMENT_WORD_COUNT + COLUMN_CHILD_1] = iB;
|
|
934
|
+
} else {
|
|
935
|
+
//b2Assert(m_nodes[B->parent].child2 == iA);
|
|
936
|
+
assert.equal(uint32[uint32[iB * ELEMENT_WORD_COUNT + COLUMN_PARENT] * ELEMENT_WORD_COUNT + COLUMN_CHILD_2], iA);
|
|
937
|
+
|
|
938
|
+
uint32[a_parent * ELEMENT_WORD_COUNT + COLUMN_CHILD_2] = iB;
|
|
939
|
+
}
|
|
940
|
+
} else {
|
|
941
|
+
this.__root = iB;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// Rotate
|
|
945
|
+
if (uint32[iD * ELEMENT_WORD_COUNT + COLUMN_HEIGHT] > uint32[iE * ELEMENT_WORD_COUNT + COLUMN_HEIGHT]) {
|
|
946
|
+
|
|
947
|
+
uint32[iB * ELEMENT_WORD_COUNT + COLUMN_CHILD_2] = iD;
|
|
948
|
+
uint32[iA * ELEMENT_WORD_COUNT + COLUMN_CHILD_1] = iE;
|
|
949
|
+
uint32[iE * ELEMENT_WORD_COUNT + COLUMN_PARENT] = iA;
|
|
950
|
+
|
|
951
|
+
this.node_set_combined_aabb(iA, iC, iE);
|
|
952
|
+
this.node_set_combined_aabb(iB, iA, iD);
|
|
953
|
+
|
|
954
|
+
uint32[iA * ELEMENT_WORD_COUNT + COLUMN_HEIGHT] = 1 + max2(
|
|
955
|
+
uint32[iC * ELEMENT_WORD_COUNT + COLUMN_HEIGHT],
|
|
956
|
+
uint32[iE * ELEMENT_WORD_COUNT + COLUMN_HEIGHT]
|
|
957
|
+
);
|
|
958
|
+
|
|
959
|
+
uint32[iB * ELEMENT_WORD_COUNT + COLUMN_HEIGHT] = 1 + max2(
|
|
960
|
+
uint32[iA * ELEMENT_WORD_COUNT + COLUMN_HEIGHT],
|
|
961
|
+
uint32[iD * ELEMENT_WORD_COUNT + COLUMN_HEIGHT]
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
} else {
|
|
965
|
+
|
|
966
|
+
uint32[iB * ELEMENT_WORD_COUNT + COLUMN_CHILD_2] = iE;
|
|
967
|
+
uint32[iA * ELEMENT_WORD_COUNT + COLUMN_CHILD_1] = iD;
|
|
968
|
+
uint32[iD * ELEMENT_WORD_COUNT + COLUMN_PARENT] = iA;
|
|
969
|
+
|
|
970
|
+
this.node_set_combined_aabb(iA, iC, iD);
|
|
971
|
+
this.node_set_combined_aabb(iB, iA, iE);
|
|
972
|
+
|
|
973
|
+
uint32[iA * ELEMENT_WORD_COUNT + COLUMN_HEIGHT] = 1 + max2(
|
|
974
|
+
uint32[iC * ELEMENT_WORD_COUNT + COLUMN_HEIGHT],
|
|
975
|
+
uint32[iD * ELEMENT_WORD_COUNT + COLUMN_HEIGHT]
|
|
976
|
+
);
|
|
977
|
+
|
|
978
|
+
uint32[iB * ELEMENT_WORD_COUNT + COLUMN_HEIGHT] = 1 + max2(
|
|
979
|
+
uint32[iA * ELEMENT_WORD_COUNT + COLUMN_HEIGHT],
|
|
980
|
+
uint32[iE * ELEMENT_WORD_COUNT + COLUMN_HEIGHT]
|
|
981
|
+
);
|
|
982
|
+
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
assert.notEqual(iB, uint32[iB * ELEMENT_WORD_COUNT + COLUMN_CHILD_1]);
|
|
986
|
+
assert.notEqual(iB, uint32[iB * ELEMENT_WORD_COUNT + COLUMN_CHILD_2]);
|
|
987
|
+
|
|
988
|
+
return iB;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
assert.notEqual(iA, uint32[iA * ELEMENT_WORD_COUNT + COLUMN_CHILD_1]);
|
|
992
|
+
assert.notEqual(iA, uint32[iA * ELEMENT_WORD_COUNT + COLUMN_CHILD_2]);
|
|
993
|
+
|
|
994
|
+
// no rotation
|
|
995
|
+
return iA;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* Release all nodes, this essentially resets the tree to empty state
|
|
1000
|
+
* NOTE: For performance reasons, released memory is not reset, this means that attempting to access cleared nodes' memory will yield garbage data
|
|
1001
|
+
*/
|
|
1002
|
+
release_all() {
|
|
1003
|
+
this.__root = NULL_NODE;
|
|
1004
|
+
this.__size = 0;
|
|
1005
|
+
this.__free_pointer = 0;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
*
|
|
1010
|
+
* @param {function(node:number, tree:BVH):void} callback
|
|
1011
|
+
* @param {*} [ctx]
|
|
1012
|
+
*/
|
|
1013
|
+
traverse(callback, ctx) {
|
|
1014
|
+
let cursor = 0;
|
|
1015
|
+
const stack = [];
|
|
1016
|
+
|
|
1017
|
+
const root = this.__root;
|
|
1018
|
+
|
|
1019
|
+
if (root !== NULL_NODE) {
|
|
1020
|
+
stack[cursor++] = root;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
const uint32 = this.__data_uint32;
|
|
1024
|
+
|
|
1025
|
+
while (cursor > 0) {
|
|
1026
|
+
cursor--;
|
|
1027
|
+
|
|
1028
|
+
const node = stack[cursor];
|
|
1029
|
+
|
|
1030
|
+
callback.call(ctx, node, this);
|
|
1031
|
+
|
|
1032
|
+
const node_address = node * ELEMENT_WORD_COUNT;
|
|
1033
|
+
|
|
1034
|
+
const child1 = uint32[node_address + COLUMN_CHILD_1];
|
|
1035
|
+
const child2 = uint32[node_address + COLUMN_CHILD_2];
|
|
1036
|
+
|
|
1037
|
+
if (child1 !== NULL_NODE) {
|
|
1038
|
+
stack[cursor++] = child2;
|
|
1039
|
+
stack[cursor++] = child1;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
*
|
|
1047
|
+
* @param {number[]} destination
|
|
1048
|
+
* @param {number} destination_offset
|
|
1049
|
+
* @returns {number}
|
|
1050
|
+
*/
|
|
1051
|
+
collect_nodes_all(destination, destination_offset) {
|
|
1052
|
+
|
|
1053
|
+
let i = destination_offset;
|
|
1054
|
+
|
|
1055
|
+
this.traverse(n => {
|
|
1056
|
+
destination[i++] = n;
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
return i - destination_offset;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Update parent and child links of a given node to point to a new location, useful for re-locating nodes
|
|
1064
|
+
* @param {number} node node to update
|
|
1065
|
+
* @param {number} destination Where updated links should point to
|
|
1066
|
+
* @private
|
|
1067
|
+
*/
|
|
1068
|
+
__move_node_links(node, destination) {
|
|
1069
|
+
|
|
1070
|
+
const uint32 = this.__data_uint32;
|
|
1071
|
+
|
|
1072
|
+
const source_address = node * ELEMENT_WORD_COUNT;
|
|
1073
|
+
|
|
1074
|
+
// update children of a
|
|
1075
|
+
const child1 = uint32[source_address + COLUMN_CHILD_1];
|
|
1076
|
+
const child2 = uint32[source_address + COLUMN_CHILD_2];
|
|
1077
|
+
|
|
1078
|
+
if (child1 !== NULL_NODE) {
|
|
1079
|
+
|
|
1080
|
+
uint32[child1 * ELEMENT_WORD_COUNT + COLUMN_PARENT] = destination;
|
|
1081
|
+
uint32[child2 * ELEMENT_WORD_COUNT + COLUMN_PARENT] = destination;
|
|
1082
|
+
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// update parent of a
|
|
1086
|
+
const parent = uint32[source_address + COLUMN_PARENT];
|
|
1087
|
+
|
|
1088
|
+
if (parent !== NULL_NODE) {
|
|
1089
|
+
const parent_child1 = uint32[parent * ELEMENT_WORD_COUNT + COLUMN_CHILD_1];
|
|
1090
|
+
|
|
1091
|
+
if (parent_child1 === node) {
|
|
1092
|
+
uint32[parent * ELEMENT_WORD_COUNT + COLUMN_CHILD_1] = destination;
|
|
1093
|
+
} else {
|
|
1094
|
+
uint32[parent * ELEMENT_WORD_COUNT + COLUMN_CHILD_2] = destination;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
/**
|
|
1100
|
+
* Swap two nodes in memory
|
|
1101
|
+
* @param {number} a
|
|
1102
|
+
* @param {number} b
|
|
1103
|
+
* @returns {boolean}
|
|
1104
|
+
*/
|
|
1105
|
+
swap_nodes(a, b) {
|
|
1106
|
+
// console.log(`swap ${a} - ${b}`)
|
|
1107
|
+
|
|
1108
|
+
const uint32 = this.__data_uint32;
|
|
1109
|
+
|
|
1110
|
+
const address_a = a * ELEMENT_WORD_COUNT;
|
|
1111
|
+
const address_b = b * ELEMENT_WORD_COUNT;
|
|
1112
|
+
|
|
1113
|
+
|
|
1114
|
+
if (uint32[address_a + COLUMN_PARENT] === b) {
|
|
1115
|
+
// attempting to swap direct parent/child, this is unsupported
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
1118
|
+
if (uint32[address_b + COLUMN_PARENT] === a) {
|
|
1119
|
+
// attempting to swap direct parent/child, this is unsupported
|
|
1120
|
+
return false;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
this.__move_node_links(a, b);
|
|
1124
|
+
this.__move_node_links(b, a);
|
|
1125
|
+
|
|
1126
|
+
// copy A to temp buffer
|
|
1127
|
+
array_copy(uint32, address_a, this.__free, this.__free_pointer, ELEMENT_WORD_COUNT);
|
|
1128
|
+
|
|
1129
|
+
// write data
|
|
1130
|
+
array_copy(uint32, address_b, uint32, address_a, ELEMENT_WORD_COUNT);
|
|
1131
|
+
array_copy(this.__free, this.__free_pointer, uint32, address_b, ELEMENT_WORD_COUNT);
|
|
1132
|
+
|
|
1133
|
+
// update root as necessary
|
|
1134
|
+
if (this.__root === a) {
|
|
1135
|
+
this.__root = b;
|
|
1136
|
+
} else if (this.__root === b) {
|
|
1137
|
+
this.__root = a;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|