@woosh/meep-engine 2.34.36 → 2.34.40

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.
@@ -25,1146 +25,1152 @@ import { traverseBinaryNodeUsingVisitor } from "./traversal/traverseBinaryNodeUs
25
25
 
26
26
  /**
27
27
  *
28
- * @enum {int}
28
+ * @type {number}
29
29
  */
30
- const ModifierType = {
31
- Protected: 1,
32
- Marked1: 2,
33
- Marked2: 4,
34
- Marked3: 8
35
- };
36
-
30
+ let stackPointer = 0;
37
31
  /**
38
- * @extends {Node}
39
- * @constructor
32
+ *
33
+ * @type {Node[]}
40
34
  */
41
- const BinaryNode = function () {
42
- /**
43
- *
44
- * @type {null|BinaryNode|LeafNode}
45
- */
46
- this.parentNode = null;
47
-
48
- /**
49
- *
50
- * @type {null|Node|LeafNode|BinaryNode}
51
- */
52
- this.left = null;
53
- /**
54
- *
55
- * @type {null|Node|LeafNode|BinaryNode}
56
- */
57
- this.right = null;
35
+ const stack = [];
58
36
 
59
- this.modifiers = 0;
37
+ export class BinaryNode extends Node {
38
+ constructor() {
39
+ super();
40
+
41
+ /**
42
+ *
43
+ * @type {null|BinaryNode|LeafNode}
44
+ */
45
+ this.parentNode = null;
46
+
47
+ /**
48
+ *
49
+ * @type {null|Node|LeafNode|BinaryNode}
50
+ */
51
+ this.left = null;
52
+
53
+ /**
54
+ *
55
+ * @type {null|Node|LeafNode|BinaryNode}
56
+ */
57
+ this.right = null;
58
+
59
+ /**
60
+ * Number of leaf nodes in the hierarchy
61
+ * @deprecated
62
+ * @type {number}
63
+ * @private
64
+ */
65
+ this.leafNodeCount = 0;
66
+
67
+ /**
68
+ * Leaf node will have height 0, root node will have height equal to maximum depth of the tree
69
+ * @type {number}
70
+ */
71
+ this.height = 0;
72
+ }
60
73
 
61
74
  /**
62
- * Number of leaf nodes in the hierarchy
63
- * @type {number}
75
+ * Same as traversePreOrder but without recursion. This runs faster thanks to avoidance of function call overhead. Especially useful for deeper trees.
76
+ * @param {BinaryNode~Visitor} visitor
77
+ * @param [thisArg]
78
+ * @deprecated use visitor-based traversal instead of callback-based
64
79
  */
65
- this.leafNodeCount = 0;
66
-
67
- this.x0 = 0;
68
- this.y0 = 0;
69
- this.z0 = 0;
70
-
71
- this.x1 = 0;
72
- this.y1 = 0;
73
- this.z1 = 0;
74
- };
75
-
76
- BinaryNode.Modifier = ModifierType;
77
-
78
- BinaryNode.prototype = Object.create(Node.prototype);
79
-
80
- /**
81
- * @readonly
82
- * @type {boolean}
83
- */
84
- BinaryNode.prototype.isBinaryNode = true;
85
-
86
- /**
87
- *
88
- * @param {int} modifier
89
- * @param {boolean} flag
90
- */
91
- BinaryNode.prototype.setModifier = function (modifier, flag) {
92
- if (flag) {
93
- this.modifiers |= modifier;
94
- } else {
95
- this.modifiers &= ~modifier;
96
- }
97
- };
98
-
99
- BinaryNode.prototype.isModifierSet = function (modifier) {
100
- return (this.modifiers & modifier) !== 0;
101
- };
80
+ traversePreOrderUsingStack(visitor, thisArg) {
81
+ let visitCount = 0;
102
82
 
103
- BinaryNode.prototype.isProtected = function () {
104
- return this.isModifierSet(ModifierType.Protected);
105
- };
83
+ const stackOffset = stackPointer;
106
84
 
107
- /**
108
- * Contains no children
109
- * @returns {boolean}
110
- */
111
- BinaryNode.prototype.isEmpty = function () {
112
- return this.left === null && this.right === null;
113
- }
85
+ stack[stackPointer++] = this;
86
+ let n;
87
+ while (stackPointer-- > stackOffset) {
114
88
 
89
+ visitCount++;
115
90
 
116
- BinaryNode.prototype.reset = function () {
117
- this.left = null;
118
- this.right = null;
91
+ n = stack[stackPointer];
119
92
 
120
- this.leafNodeCount = 0;
93
+ const traverseDeeper = visitor.call(thisArg, n);
121
94
 
122
- this.setNegativelyInfiniteBounds();
123
- };
95
+ if (traverseDeeper !== false && n.isBinaryNode) {
96
+ if (n.right !== null) {
97
+ stack[stackPointer++] = n.right;
98
+ }
99
+ if (n.left !== null) {
100
+ stack[stackPointer++] = n.left;
101
+ }
102
+ }
103
+ }
124
104
 
125
- /**
126
- *
127
- * @param {Node|BinaryNode} left
128
- * @param {Node|BinaryNode} right
129
- */
130
- BinaryNode.prototype.setChildren = function (left, right) {
131
- this.left = left;
132
- this.right = right;
105
+ // drop stack frame
106
+ stackPointer = stackOffset;
133
107
 
134
- this.updateLeafNodeCount()
108
+ return visitCount;
109
+ }
135
110
 
136
- this.refitFor2();
111
+ /**
112
+ * Traverse leaf nodes in a fast manner
113
+ * @param {function(node:LeafNode)} visitor
114
+ * @param {*} [thisArg]
115
+ */
116
+ traverseLeavesPreOrderUsingStack(visitor, thisArg) {
117
+ let visitCount = 0;
137
118
 
138
- left.parentNode = this;
139
- right.parentNode = this;
140
- };
119
+ const stackOffset = stackPointer;
141
120
 
142
- /**
143
- *
144
- * @param {Node} child
145
- */
146
- BinaryNode.prototype.setChildLeft = function (child) {
147
- assert.isNull(this.left, 'left');
121
+ stack[stackPointer++] = this;
148
122
 
149
- this.left = child;
123
+ let n;
150
124
 
151
- this.updateLeafNodeCount();
125
+ while (stackPointer-- > stackOffset) {
152
126
 
153
- this.refit();
127
+ visitCount++;
154
128
 
155
- child.parentNode = this;
156
- }
129
+ n = stack[stackPointer];
157
130
 
158
- /**
159
- *
160
- * @param {Node} child
161
- */
162
- BinaryNode.prototype.setChildRight = function (child) {
163
- assert.isNull(this.right, 'right');
131
+ if (n.isLeafNode) {
132
+ visitor.call(thisArg, n);
133
+ } else {
134
+ //a binary node
135
+ if (n.right !== null) {
136
+ stack[stackPointer++] = n.right;
137
+ }
138
+ if (n.left !== null) {
139
+ stack[stackPointer++] = n.left;
140
+ }
141
+ }
142
+ }
164
143
 
165
- this.right = child;
144
+ // drop stack frame
145
+ stackPointer = stackOffset;
166
146
 
167
- this.updateLeafNodeCount();
147
+ return visitCount;
148
+ }
168
149
 
169
- this.refit();
170
150
 
171
- child.parentNode = this;
172
- }
151
+ /**
152
+ * Contains no children
153
+ * @returns {boolean}
154
+ */
155
+ isEmpty() {
156
+ return this.left === null && this.right === null;
157
+ }
173
158
 
174
- /**
175
- * @see "Dynamic BVH" by Erin Catto / Blizzard
176
- * @param {AABB3} node
177
- * @param {AABB3} addition
178
- * @returns {number}
179
- */
180
- function inclusion_surface_area_heuristic(node, addition) {
181
- const x0 = min2(node.x0, addition.x0);
182
- const y0 = min2(node.y0, addition.y0);
183
- const z0 = min2(node.z0, addition.z0);
159
+ reset() {
160
+ this.left = null;
161
+ this.right = null;
184
162
 
185
- const x1 = max2(node.x1, addition.x1);
186
- const y1 = max2(node.y1, addition.y1);
187
- const z1 = max2(node.z1, addition.z1);
163
+ this.leafNodeCount = 0;
164
+ this.height = 0;
188
165
 
189
- return _boxSurfaceArea2(x0, y0, z0, x1, y1, z1) - boxSurfaceArea2(node);
190
- }
166
+ this.setNegativelyInfiniteBounds();
167
+ }
191
168
 
192
- /**
193
- *
194
- * @param {BinaryNode|LeafNode} node
195
- * @param {AABB3} addition
196
- */
197
- function inclusion_cost_lower_bound(node, addition) {
198
- let n = node;
199
- let r = boxSurfaceArea2(addition);
169
+ /**
170
+ *
171
+ * @param {Node|BinaryNode} left
172
+ * @param {Node|BinaryNode} right
173
+ */
174
+ setChildren(left, right) {
175
+ this.left = left;
176
+ this.right = right;
200
177
 
201
- while (n !== null) {
178
+ left.parentNode = this;
179
+ right.parentNode = this;
180
+ }
202
181
 
203
- r += n.costForInclusion(addition);
182
+ /**
183
+ *
184
+ * @param {Node} child
185
+ */
186
+ setChildLeft(child) {
187
+ assert.isNull(this.left, 'left');
204
188
 
205
- n = n.parentNode;
189
+ this.left = child;
190
+ child.parentNode = this;
206
191
  }
207
192
 
208
- return r;
209
- }
210
-
211
- /**
212
- * For incremental insertion, a sibling needs to be found, newly inserted node will be parented together with this sibling
213
- * @param {AABB3} box
214
- * @returns {BinaryNode|LeafNode}
215
- */
216
- BinaryNode.prototype.__insertion_findSiblingFor = function (box) {
193
+ /**
194
+ *
195
+ * @param {Node} child
196
+ */
197
+ setChildRight(child) {
198
+ assert.isNull(this.right, 'right');
217
199
 
218
- const open_set = [this];
219
- let cursor = 1;
200
+ this.right = child;
201
+ child.parentNode = this;
202
+ }
220
203
 
221
- let best_cost = Infinity;
222
- let best_node = this;
204
+ /**
205
+ * For incremental insertion, a sibling needs to be found, newly inserted node will be parented together with this sibling
206
+ * @see https://github.com/erincatto/box2d/blob/9dc24a6fd4f32442c4bcf80791de47a0a7d25afb/src/collision/b2_dynamic_tree.cpp#L197
207
+ * @param {AABB3} box
208
+ * @returns {BinaryNode|LeafNode}
209
+ */
210
+ __insertion_findSiblingFor(box) {
223
211
 
224
- while (cursor > 0) {
225
- const node = open_set[--cursor];
212
+ const open_set = [this];
213
+ let cursor = 1;
226
214
 
227
- const cost = inclusion_surface_area_heuristic(node, box);
215
+ let best_cost = Infinity;
216
+ let best_node = this;
228
217
 
229
- if (cost < best_cost) {
230
- best_cost = cost;
231
- best_node = node;
232
- }
218
+ while (cursor > 0) {
219
+ const node = open_set[--cursor];
233
220
 
234
- if (node.isBinaryNode) {
221
+ const cost = inclusion_surface_area_heuristic(node, box);
235
222
 
236
- if (node.isProtected()) {
237
- continue;
223
+ if (cost < best_cost) {
224
+ best_cost = cost;
225
+ best_node = node;
238
226
  }
239
227
 
240
- // consider pushing children into the queue
228
+ if (node.isBinaryNode) {
241
229
 
242
- // compute lower bound for cost
243
- const c_low = inclusion_cost_lower_bound(node, box);
230
+ // consider pushing children into the queue
244
231
 
245
- if (c_low < best_cost) {
246
- // worth-while to explore children
247
- if (node.left !== null) {
248
- open_set[cursor++] = node.left;
249
- }
250
- if (node.right !== null) {
251
- open_set[cursor++] = node.right
232
+ // compute lower bound for cost
233
+ const c_low = inclusion_cost_lower_bound(node, box);
234
+
235
+ if (c_low < best_cost) {
236
+ // worth-while to explore children
237
+ if (node.left !== null) {
238
+ open_set[cursor++] = node.left;
239
+ }
240
+ if (node.right !== null) {
241
+ open_set[cursor++] = node.right
242
+ }
252
243
  }
253
244
  }
254
- }
255
-
256
245
 
257
- }
258
-
259
- return best_node;
260
- }
261
-
262
- /**
263
- *
264
- * @param {AABB3} box
265
- * @returns {Node|BinaryNode|LeafNode}
266
- */
267
- BinaryNode.prototype.findParentFor = function (box) {
268
- let n = this;
269
- let aCost = 0;
270
- let bCost = 0;
271
246
 
272
- for (; ;) {
247
+ }
273
248
 
274
- const a = n.left;
275
- const b = n.right;
249
+ return best_node;
250
+ }
276
251
 
277
- if (a === null || b === null) {
278
- //unbalanced node, good candidate already
279
- return n;
280
- }
252
+ /**
253
+ *
254
+ * @param {AABB3} box
255
+ * @returns {Node|BinaryNode|LeafNode}
256
+ */
257
+ findParentFor(box) {
258
+ let n = this;
259
+ let aCost = 0;
260
+ let bCost = 0;
281
261
 
282
- const aIsBinary = a.isBinaryNode;
283
- const bIsBinary = b.isBinaryNode;
262
+ for (; ;) {
284
263
 
285
- // handle protected nodes
286
- if (aIsBinary && a.isProtected()) {
287
- if (bIsBinary) {
288
- if (b.isProtected()) {
289
- return n;
290
- } else {
291
- n = b;
264
+ const a = n.left;
265
+ const b = n.right;
292
266
 
293
- continue;
294
- }
295
- } else {
296
- return b;
267
+ if (a === null || b === null) {
268
+ //unbalanced node, good candidate already
269
+ return n;
297
270
  }
298
- } else if (bIsBinary && b.isProtected()) {
299
- if (aIsBinary) {
300
- n = a;
301
- } else {
302
- return a;
303
- }
304
- }
305
271
 
306
- aCost = a.costForInclusion(box);
307
- bCost = b.costForInclusion(box);
272
+ const aIsBinary = a.isBinaryNode;
273
+ const bIsBinary = b.isBinaryNode;
308
274
 
309
- if (aCost === bCost) {
275
+ aCost = a.costForInclusion(box);
276
+ bCost = b.costForInclusion(box);
310
277
 
311
- if (a.isLeafNode) {
278
+ if (aCost === bCost) {
312
279
 
313
- return a;
280
+ if (a.isLeafNode) {
314
281
 
315
- } else if (b.isLeafNode) {
282
+ return a;
316
283
 
317
- return b;
284
+ } else if (b.isLeafNode) {
318
285
 
319
- }
286
+ return b;
320
287
 
321
- //change costs to be based on balance
322
- aCost = a.leafNodeCount / boxSurfaceArea2(a);
323
- bCost = b.leafNodeCount / boxSurfaceArea2(b);
324
- }
288
+ }
325
289
 
326
- if (aCost === bCost) {
327
- //still the same, toss a coin
328
- aCost = Math.random();
329
- bCost = 1 - aCost;
330
- }
290
+ //change costs to be based on balance
291
+ aCost = a.leafNodeCount / boxSurfaceArea2(a);
292
+ bCost = b.leafNodeCount / boxSurfaceArea2(b);
293
+ }
331
294
 
332
- if (aCost < bCost) {
333
- if (aIsBinary) {
334
- n = a;
335
- } else {
336
- return a;
295
+ if (aCost === bCost) {
296
+ //still the same, toss a coin
297
+ aCost = Math.random();
298
+ bCost = 1 - aCost;
337
299
  }
338
- } else {
339
- if (bIsBinary) {
340
- n = b;
300
+
301
+ if (aCost < bCost) {
302
+ if (aIsBinary) {
303
+ n = a;
304
+ } else {
305
+ return a;
306
+ }
341
307
  } else {
342
- return b;
308
+ if (bIsBinary) {
309
+ n = b;
310
+ } else {
311
+ return b;
312
+ }
343
313
  }
344
314
  }
345
315
  }
346
- };
347
316
 
348
- BinaryNode.prototype.traverse = function (visitor) {
349
- if (this.left !== null) {
350
- const cA = visitor(this.left);
351
- if (cA !== false) {
352
- this.left.traverse(visitor);
317
+ traverse(visitor) {
318
+ if (this.left !== null) {
319
+ const cA = visitor(this.left);
320
+ if (cA !== false) {
321
+ this.left.traverse(visitor);
322
+ }
353
323
  }
354
- }
355
- if (this.right !== null) {
356
- const cB = visitor(this.right);
357
- if (cB !== false) {
358
- this.right.traverse(visitor);
324
+ if (this.right !== null) {
325
+ const cB = visitor(this.right);
326
+ if (cB !== false) {
327
+ this.right.traverse(visitor);
328
+ }
359
329
  }
360
330
  }
361
- };
362
331
 
363
- /**
364
- * Bottom-up tree traversal, children first
365
- * @param visitor
366
- */
367
- BinaryNode.prototype.traversePostOrder = function (visitor) {
368
- //left
369
- if (this.left instanceof BinaryNode) {
370
- this.left.traversePostOrder(visitor);
371
- } else if (this.left instanceof LeafNode) {
372
- visitor(this.left);
373
- }
374
- //right
375
- if (this.right instanceof BinaryNode) {
376
- this.right.traversePostOrder(visitor);
377
- } else if (this.right instanceof LeafNode) {
378
- visitor(this.right);
379
- }
380
- visitor(this);
381
- };
382
- BinaryNode.prototype.traversePreOrder = function (visitor) {
383
- const carryOn = visitor(this);
384
- if (carryOn !== false) {
332
+ /**
333
+ * Bottom-up tree traversal, children first
334
+ * @param visitor
335
+ */
336
+ traversePostOrder(visitor) {
385
337
  //left
386
338
  if (this.left instanceof BinaryNode) {
387
- this.left.traversePreOrder(visitor);
339
+ this.left.traversePostOrder(visitor);
388
340
  } else if (this.left instanceof LeafNode) {
389
341
  visitor(this.left);
390
342
  }
391
343
  //right
392
344
  if (this.right instanceof BinaryNode) {
393
- this.right.traversePreOrder(visitor);
345
+ this.right.traversePostOrder(visitor);
394
346
  } else if (this.right instanceof LeafNode) {
395
347
  visitor(this.right);
396
348
  }
349
+ visitor(this);
397
350
  }
398
- };
399
-
400
351
 
401
- /**
402
- * Same as traversePreOrder but without recursion. This runs faster thanks to avoidance of function call overhead. Especially useful for deeper trees.
403
- * @param {BinaryNode~Visitor} visitor
404
- * @param thisArg
405
- * @deprecated use visitor-based traversal instead of callback-based
406
- */
407
- BinaryNode.prototype.traversePreOrderUsingStack = (function (visitor, thisArg) {
352
+ traversePreOrder(visitor) {
353
+ const carryOn = visitor(this);
354
+ if (carryOn !== false) {
355
+ //left
356
+ if (this.left instanceof BinaryNode) {
357
+ this.left.traversePreOrder(visitor);
358
+ } else if (this.left instanceof LeafNode) {
359
+ visitor(this.left);
360
+ }
361
+ //right
362
+ if (this.right instanceof BinaryNode) {
363
+ this.right.traversePreOrder(visitor);
364
+ } else if (this.right instanceof LeafNode) {
365
+ visitor(this.right);
366
+ }
367
+ }
368
+ }
408
369
 
370
+ refitFor2() {
371
+ const a = this.left;
372
+ const b = this.right;
409
373
 
410
- let stackPointer = 0;
411
- const stack = [];
374
+ this.x0 = min2(a.x0, b.x0);
375
+ this.y0 = min2(a.y0, b.y0);
376
+ this.z0 = min2(a.z0, b.z0);
412
377
 
413
- function cleanup(position) {
414
- stack.length = position;
415
- stackPointer = position;
378
+ this.x1 = max2(a.x1, b.x1);
379
+ this.y1 = max2(a.y1, b.y1);
380
+ this.z1 = max2(a.z1, b.z1);
416
381
  }
417
382
 
418
- /**
419
- * @param {BinaryNode~Visitor} visitor
420
- * @param {*} [thisArg]
421
- * @returns {number}
422
- */
423
- function traversePreOrderUsingStack(visitor, thisArg) {
424
- let visitCount = 0;
383
+ bubbleRefit() {
384
+ let n = this;
425
385
 
426
- const stackOffset = stackPointer;
386
+ n.refit();
427
387
 
428
- stack[stackPointer++] = this;
429
- let n;
430
- while (stackPointer-- > stackOffset) {
388
+ //bubble up
389
+ for (; ;) {
390
+ //record old node size
391
+ const _x0 = n.x0;
392
+ const _y0 = n.y0;
393
+ const _z0 = n.z0;
394
+ const _x1 = n.x1;
395
+ const _y1 = n.y1;
396
+ const _z1 = n.z1;
431
397
 
432
- visitCount++;
398
+ n = n.parentNode;
433
399
 
434
- n = stack[stackPointer];
400
+ if (n === null) {
401
+ // hit the root
402
+ return;
403
+ }
435
404
 
436
- const traverseDeeper = visitor.call(thisArg, n);
437
405
 
438
- if (traverseDeeper !== false && n.isBinaryNode) {
439
- if (n.right !== null) {
440
- stack[stackPointer++] = n.right;
441
- }
442
- if (n.left !== null) {
443
- stack[stackPointer++] = n.left;
444
- }
406
+ //check if this node violates bounds of the parent
407
+ let flag = false;
408
+
409
+ if (_x0 < n.x0) {
410
+ n.x0 = _x0;
411
+ flag = true;
412
+ }
413
+ if (_y0 < n.y0) {
414
+ n.y0 = _y0;
415
+ flag = true;
416
+ }
417
+ if (_z0 < n.z0) {
418
+ n.z0 = _z0;
419
+ flag = true;
445
420
  }
446
- }
447
421
 
448
- cleanup(stackOffset);
422
+ if (_x1 > n.x1) {
423
+ n.x1 = _x1;
424
+ flag = true;
425
+ }
426
+ if (_y1 > n.y1) {
427
+ n.y1 = _y1;
428
+ flag = true;
429
+ }
430
+ if (_z1 > n.z1) {
431
+ n.z1 = _z1;
432
+ flag = true;
433
+ }
449
434
 
450
- return visitCount;
435
+ if (!flag) {
436
+ //no violation, we can stop here
437
+ break;
438
+ }
439
+ }
451
440
  }
452
441
 
453
- return traversePreOrderUsingStack;
454
- })();
455
-
456
- /**
457
- * Traverse leaf nodes in a fast manner
458
- * @param {function(node:LeafNode)} visitor
459
- * @param {*} [thisArg]
460
- */
461
- BinaryNode.prototype.traverseLeavesPreOrderUsingStack = (function (visitor, thisArg) {
442
+ refit() {
443
+ if (this.left !== null && this.right !== null) {
444
+ this.refitFor2();
445
+ } else if (this.left !== null) {
446
+ this.copy(this.left);
447
+ } else if (this.right !== null) {
448
+ this.copy(this.right);
449
+ }
450
+ }
462
451
 
452
+ sumSurfaceArea() {
453
+ let result = 0;
454
+ this.traversePreOrderUsingStack(function (node) {
455
+ const nodeArea = node.computeSurfaceArea();
456
+ result += nodeArea;
457
+ });
463
458
 
464
- let stackPointer = 0;
465
- const stack = [];
459
+ return result;
460
+ }
466
461
 
467
- function cleanup(position) {
468
- stack.length = position;
469
- stackPointer = position;
462
+ countDescendants() {
463
+ let result = 0;
464
+ this.traversePreOrderUsingStack(function (node) {
465
+ result++;
466
+ });
467
+ return result;
470
468
  }
471
469
 
472
470
  /**
473
- * @param {function(node:LeafNode)} visitor
474
- * @param {*} [thisArg]
475
- * @returns {number}
471
+ * @deprecated Incorrect usage of SAH
472
+ * @return {number}
476
473
  */
477
- function traverseLeavesPreOrderUsingStack(visitor, thisArg) {
478
- let visitCount = 0;
474
+ computeSAH() {
475
+ let leftLeaves, rightLeaves, leftArea, rightArea;
479
476
 
480
- const stackOffset = stackPointer;
477
+ const left = this.left;
481
478
 
482
- stack[stackPointer++] = this;
479
+ if (left === null) {
480
+ leftArea = 0;
481
+ leftLeaves = 0;
482
+ } else {
483
+ leftArea = boxSurfaceArea2(left);
484
+ leftLeaves = left.leafNodeCount;
485
+ }
483
486
 
484
- let n;
487
+ const right = this.right;
488
+ if (right === null) {
489
+ rightArea = 0;
490
+ rightLeaves = 0;
491
+ } else {
492
+ rightArea = boxSurfaceArea2(right);
493
+ rightLeaves = right.leafNodeCount;
494
+ }
485
495
 
486
- while (stackPointer-- > stackOffset) {
496
+ const thisArea = boxSurfaceArea2(this);
487
497
 
488
- visitCount++;
498
+ return surfaceAreaHeuristic(thisArea, leftArea, rightArea, leftLeaves, rightLeaves);
499
+ }
489
500
 
490
- n = stack[stackPointer];
501
+ updateHeight() {
502
+ let node = this;
503
+ let n = 0;
491
504
 
492
- if (n.isLeafNode) {
493
- visitor.call(thisArg, n);
494
- } else {
495
- //a binary node
496
- if (n.right !== null) {
497
- stack[stackPointer++] = n.right;
498
- }
499
- if (n.left !== null) {
500
- stack[stackPointer++] = n.left;
501
- }
505
+ while (node !== null) {
506
+ n = 0;
507
+
508
+ const right = node.right;
509
+
510
+ if (right !== null) {
511
+ n = right.height;
502
512
  }
503
- }
504
513
 
505
- cleanup(stackOffset);
514
+ const left = this.left;
506
515
 
507
- return visitCount;
508
- }
516
+ if (left !== null) {
517
+ n = max2(n, left.height);
518
+ }
509
519
 
510
- return traverseLeavesPreOrderUsingStack;
511
- })();
520
+ node.height = n + 1;
512
521
 
513
- BinaryNode.prototype.refitFor2 = function () {
514
- const a = this.left;
515
- const b = this.right;
522
+ // propagate update up the tree
523
+ node = node.parentNode;
524
+ }
525
+ }
516
526
 
517
- this.x0 = min2(a.x0, b.x0);
518
- this.y0 = min2(a.y0, b.y0);
519
- this.z0 = min2(a.z0, b.z0);
527
+ /**
528
+ * @deprecated
529
+ */
530
+ updateLeafNodeCount() {
531
+ let node = this;
520
532
 
521
- this.x1 = max2(a.x1, b.x1);
522
- this.y1 = max2(a.y1, b.y1);
523
- this.z1 = max2(a.z1, b.z1);
524
- };
533
+ while (node !== null) {
534
+ let n = 0;
525
535
 
526
- BinaryNode.prototype.bubbleRefit = function () {
527
- let n = this;
536
+ const right = node.right;
528
537
 
529
- n.refit();
538
+ if (right !== null) {
539
+ if (right.isLeafNode) {
540
+ n++;
541
+ } else {
542
+ n += right.leafNodeCount;
543
+ }
544
+ }
530
545
 
531
- //bubble up
532
- for (; ;) {
533
- //record old node size
534
- const _x0 = n.x0;
535
- const _y0 = n.y0;
536
- const _z0 = n.z0;
537
- const _x1 = n.x1;
538
- const _y1 = n.y1;
539
- const _z1 = n.z1;
546
+ const left = this.left;
540
547
 
541
- n = n.parentNode;
548
+ if (left !== null) {
549
+ if (left.isLeafNode) {
550
+ n++;
551
+ } else {
552
+ n += left.leafNodeCount;
553
+ }
554
+ }
555
+
556
+ node.leafNodeCount = n;
542
557
 
543
- if (n === null) {
544
- // hit the root
545
- return;
558
+ // propagate update up the tree
559
+ node = node.parentNode;
546
560
  }
547
561
 
562
+ }
548
563
 
549
- //check if this node violates bounds of the parent
550
- let flag = false;
564
+ /**
565
+ * As a result of rotation, this node 'becomes' the right node, and left node get's replaced by the right node with necessary adjustments
566
+ */
567
+ rotateLeft() {
551
568
 
552
- if (_x0 < n.x0) {
553
- n.x0 = _x0;
554
- flag = true;
555
- }
556
- if (_y0 < n.y0) {
557
- n.y0 = _y0;
558
- flag = true;
559
- }
560
- if (_z0 < n.z0) {
561
- n.z0 = _z0;
562
- flag = true;
563
- }
569
+ /**
570
+ *
571
+ * @type {BinaryNode}
572
+ */
573
+ const r = this.right;
564
574
 
565
- if (_x1 > n.x1) {
566
- n.x1 = _x1;
567
- flag = true;
568
- }
569
- if (_y1 > n.y1) {
570
- n.y1 = _y1;
571
- flag = true;
572
- }
573
- if (_z1 > n.z1) {
574
- n.z1 = _z1;
575
- flag = true;
575
+ const l = this.left;
576
+
577
+ const rL = r.left;
578
+ const rR = r.right;
579
+
580
+ this.left = r;
581
+ this.right = rR;
582
+
583
+ if (rR !== null) {
584
+ rR.parentNode = this;
576
585
  }
577
586
 
578
- if (!flag) {
579
- //no violation, we can stop here
580
- break;
587
+ r.left = l;
588
+ if (l !== null) {
589
+ l.parentNode = r;
581
590
  }
591
+
592
+ r.right = rL;
593
+
594
+ r.updateLeafNodeCount();
595
+ r.updateHeight();
596
+
597
+ r.bubbleRefit();
582
598
  }
583
- };
584
- BinaryNode.prototype.refit = function () {
585
- if (this.left !== null && this.right !== null) {
586
- this.refitFor2();
587
- } else if (this.left !== null) {
588
- this.copy(this.left);
589
- } else if (this.right !== null) {
590
- this.copy(this.right);
591
- }
592
- };
593
599
 
594
- /**
595
- *
596
- * @param {Node} n0
597
- * @param {Node} n1
598
- * @return {number}
599
- */
600
- function sortMortonCodes(n0, n1) {
601
- return n0._mortonCode - n1._mortonCode;
602
- }
600
+ rotateRight() {
601
+ const r = this.right;
603
602
 
604
- BinaryNode.prototype.sumSurfaceArea = function () {
605
- let result = 0;
606
- this.traversePreOrderUsingStack(function (node) {
607
- const nodeArea = node.computeSurfaceArea();
608
- result += nodeArea;
609
- });
603
+ const left = this.left;
610
604
 
611
- return result;
612
- };
605
+ const lL = left.left;
606
+ const lR = left.right;
613
607
 
614
- BinaryNode.prototype.countDescendants = function () {
615
- let result = 0;
616
- this.traversePreOrderUsingStack(function (node) {
617
- result++;
618
- });
619
- return result;
620
- };
608
+ if (lL !== null) {
609
+ lL.parentNode = this;
610
+ }
621
611
 
612
+ left.right = r;
613
+ if (r !== null) {
614
+ r.parentNode = left;
615
+ }
622
616
 
623
- /**
624
- *
625
- * @param node
626
- */
627
- function countLeaves(node) {
628
- if (isLeaf(node)) {
629
- return 1;
617
+ left.left = lR;
618
+
619
+ left.updateLeafNodeCount();
620
+ left.updateHeight();
621
+
622
+ left.bubbleRefit();
630
623
  }
631
624
 
632
- let result = 0;
625
+ /**
626
+ *
627
+ * @param {function(index:int):LeafNode} leafFactory
628
+ * @param {int} numNodes
629
+ */
630
+ insertManyBoxes2(leafFactory, numNodes) {
631
+ let i, n;
632
+ //create leaf nodes
633
+ const nodes = new Array(numNodes);
633
634
 
634
- node.traversePreOrderUsingStack(function (node) {
635
- if (isLeaf(node)) {
636
- result++;
637
- }
638
- });
635
+ for (i = 0; i < numNodes; i++) {
636
+ //leaf needs to be set up inside the callback
637
+ n = leafFactory(i);
639
638
 
640
- return result;
641
- }
639
+ nodes[i] = n;
640
+ }
642
641
 
643
- /**
644
- * @deprecated Incorrect usage of SAH
645
- * @return {number}
646
- */
647
- BinaryNode.prototype.computeSAH = function () {
648
- let leftLeaves, rightLeaves, leftArea, rightArea;
642
+ //sort leaves
643
+ arrayQuickSort(nodes, n => n.computeMortonCode(), null, 0, max2(0, numNodes - 1));
649
644
 
650
- const left = this.left;
645
+ let remaining_nodes = numNodes;
651
646
 
652
- if (left === null) {
653
- leftArea = 0;
654
- leftLeaves = 0;
655
- } else {
656
- leftArea = boxSurfaceArea2(left);
657
- leftLeaves = left.leafNodeCount;
658
- }
647
+ while (remaining_nodes > 2) {
659
648
 
660
- const right = this.right;
661
- if (right === null) {
662
- rightArea = 0;
663
- rightLeaves = 0;
664
- } else {
665
- rightArea = boxSurfaceArea2(right);
666
- rightLeaves = right.leafNodeCount;
667
- }
649
+ //pair
650
+ for (i = 0; i < remaining_nodes - 1; i += 2) {
651
+ const a = nodes[i];
652
+ const b = nodes[i + 1];
668
653
 
669
- const thisArea = boxSurfaceArea2(this);
654
+ n = new BinaryNode();
670
655
 
671
- return surfaceAreaHeuristic(thisArea, leftArea, rightArea, leftLeaves, rightLeaves);
672
- };
656
+ n.setChildren(a, b);
673
657
 
674
- BinaryNode.prototype.updateLeafNodeCount = function () {
675
- let node = this;
658
+ n.updateHeight();
659
+ n.refitFor2();
676
660
 
677
- while (node !== null) {
678
- let n = 0;
661
+ nodes[i >> 1] = n;
662
+ }
679
663
 
680
- const right = node.right;
664
+ const numNodesMod2 = remaining_nodes & 1; //faster version of %2
681
665
 
682
- if (right !== null) {
683
- if (right.isLeafNode) {
684
- n++;
685
- } else {
686
- n += right.leafNodeCount;
666
+ if (numNodesMod2 !== 0) {
667
+ //shift remaining node up so it will be considered later
668
+ nodes[i >> 1] = nodes[i];
687
669
  }
670
+
671
+ remaining_nodes = (remaining_nodes >> 1) + numNodesMod2;
688
672
  }
689
673
 
690
- const left = this.left;
674
+ //finally insert these boxes from this node
675
+ for (i = 0; i < remaining_nodes; i++) {
676
+ n = nodes[i];
677
+ this.insertNode(n);
678
+ }
679
+ }
691
680
 
692
- if (left !== null) {
693
- if (left.isLeafNode) {
694
- n++;
695
- } else {
696
- n += left.leafNodeCount;
681
+ /**
682
+ *
683
+ * @param {LeafNode[]} result destination
684
+ * @param {number} x0
685
+ * @param {number} y0
686
+ * @param {number} z0
687
+ * @param {number} x1
688
+ * @param {number} y1
689
+ * @param {number} z1
690
+ * @returns {number} number of objects added to the result
691
+ */
692
+ requestLeafIntersectionsAABB3(result, x0, y0, z0, x1, y1, z1) {
693
+ const startOffset = stackPointer;
694
+
695
+ stack[stackPointer++] = this;
696
+
697
+ let count = 0;
698
+
699
+ while (stackPointer > startOffset) {
700
+ stackPointer--;
701
+ const node = stack[stackPointer];
702
+
703
+ if (!aabb3_intersect_aabb3(
704
+ x0, y0, z0, x1, y1, z1,
705
+ node.x0, node.y0, node.z0, node.x1, node.y1, node.z1
706
+ )) {
707
+ //no overlap
708
+ continue;
697
709
  }
698
- }
699
710
 
700
- node.leafNodeCount = n;
711
+ if (node.isLeafNode) {
712
+ result[count++] = node;
701
713
 
702
- // propagate update up the tree
703
- node = node.parentNode;
704
- }
714
+ count++;
715
+ } else {
716
+ const left = node.left;
717
+ if (left !== null) {
718
+ stack[stackPointer++] = left;
719
+ }
705
720
 
706
- }
721
+ const right = node.right;
722
+ if (right !== null) {
723
+ stack[stackPointer++] = right;
724
+ }
725
+ }
726
+ }
707
727
 
728
+ return count;
729
+ }
708
730
 
709
- /**
710
- * As a result of rotation, this node 'becomes' the right node, and left node get's replaced by the right node with necessary adjustments
711
- */
712
- BinaryNode.prototype.rotateLeft = function () {
731
+ traverseSegmentLeafIntersections(startX, startY, startZ, endX, endY, endZ, visitor) {
732
+ this.traversePreOrder(function (node) {
733
+ let b = node.intersectSegment(startX, startY, startZ, endX, endY, endZ);
734
+ if (!b) {
735
+ return false;
736
+ }
737
+ if (node instanceof LeafNode) {
738
+ visitor(node);
739
+ return false;
740
+ } else {
741
+ return true;
742
+ }
743
+ });
744
+ }
713
745
 
714
746
  /**
715
747
  *
716
- * @type {BinaryNode}
748
+ * @param {Frustum[]} frustums Collection of THREE.js Frustums
749
+ * @param {function(node:LeafNode, boolean)} visitor
750
+ * @returns {number}
751
+ * @deprecated Use {@link ThreeFrustumsIntersectionBVHVisitor}
717
752
  */
718
- const r = this.right;
753
+ threeTraverseFrustumsIntersections(frustums, visitor) {
754
+ const numFrustums = frustums.length;
719
755
 
720
- const l = this.left;
756
+ /**
757
+ *
758
+ * @param {LeafNode|BinaryNode} node
759
+ * @returns {boolean}
760
+ */
761
+ function visitNode(node) {
762
+ let b = false;
763
+ let i = 0;
721
764
 
722
- const rL = r.left;
723
- const rR = r.right;
765
+ for (; i < numFrustums; i++) {
724
766
 
725
- this.left = r;
726
- this.right = rR;
767
+ const degree = node.intersectFrustumDegree(frustums[i]);
727
768
 
728
- if (rR !== null) {
729
- rR.parentNode = this;
730
- }
769
+ if (degree === 2) {
770
+ //completely inside frustum
731
771
 
732
- r.left = l;
733
- if (l !== null) {
734
- l.parentNode = r;
735
- }
772
+ if (node.isLeafNode) {
773
+ visitor(node, true);
774
+ } else {
775
+ node.traverseLeavesPreOrderUsingStack(fastVisitLeafNodeIntersection, visitor);
776
+ }
736
777
 
737
- r.right = rL;
778
+ //prevent further traversal
779
+ return false;
738
780
 
739
- r.updateLeafNodeCount();
781
+ } else if (degree === 1) {
740
782
 
741
- r.bubbleRefit();
742
- };
783
+ //partially inside frustum
784
+ b = true;
743
785
 
744
- BinaryNode.prototype.rotateRight = function () {
745
- const r = this.right;
786
+ }
787
+ }
746
788
 
747
- const l = this.left;
789
+ if (!b) {
790
+ return false;
791
+ }
748
792
 
749
- const lL = l.left;
750
- const lR = l.right;
793
+ if (node.isLeafNode) {
794
+ visitor(node, false);
795
+ }
751
796
 
752
- if (lL !== null) {
753
- lL.parentNode = this;
754
- }
797
+ return true;
798
+ }
755
799
 
756
- l.right = r;
757
- if (r !== null) {
758
- r.parentNode = l;
800
+ return this.traversePreOrderUsingStack(visitNode);
759
801
  }
760
802
 
761
- l.left = lR;
803
+ /**
804
+ * As recursion goes on, we remove frustums and planes from checks reducing total number of checks necessary
805
+ * @param {Array.<THREE.Frustum>} frustums
806
+ * @param {function} visitor
807
+ * @returns {number}
808
+ */
809
+ threeTraverseFrustumsIntersections2(frustums, visitor) {
810
+ const numFrustums = frustums.length;
762
811
 
763
- l.updateLeafNodeCount();
812
+ function visitLeaves(node) {
813
+ if (node instanceof LeafNode) {
814
+ visitor(node);
815
+ return false;
816
+ } else {
817
+ return true;
818
+ }
819
+ }
764
820
 
765
- l.bubbleRefit();
766
- };
821
+ function fastTraverseLeaves(node) {
822
+ if (node instanceof LeafNode) {
823
+ visitor(node);
824
+ } else {
825
+ node.traversePreOrderUsingStack(visitLeaves);
826
+ }
827
+ }
767
828
 
768
- /**
769
- *
770
- * @param {function(index:int):LeafNode} leafFactory
771
- * @param {int} numNodes
772
- */
773
- BinaryNode.prototype.insertManyBoxes2 = function (leafFactory, numNodes) {
774
- let i, n;
775
- //create leaf nodes
776
- const nodes = new Array(numNodes);
777
829
 
778
- for (i = 0; i < numNodes; i++) {
779
- //leaf needs to be set up inside the callback
780
- n = leafFactory(i);
830
+ function isFlagSet(mask, index) {
831
+ return mask & (1 << index) !== 0;
832
+ }
781
833
 
782
- nodes[i] = n;
783
- }
834
+ function clearFlag(mask, index) {
835
+ return mask & ~(1 << index);
836
+ }
784
837
 
785
- //sort leaves
786
- arrayQuickSort(nodes, n => n.computeMortonCode(), null, 0, max2(0, numNodes - 1));
838
+ function removeElementFromClone(original, clone, mask, originalIndex) {
839
+ if (clone === original) {
840
+ //clone planes
841
+ clone = clone.slice();
842
+ }
843
+ //remove the plane from new set
844
+ clone.splice(originalIndex, 1);
845
+ return clone;
846
+ }
787
847
 
788
- let remaining_nodes = numNodes;
848
+ function processVolumesIntersections(node, planeSets, planeSetCount) {
849
+ let newPlaneSets = planeSets;
850
+ for (let iPS = planeSetCount - 1; iPS >= 0; iPS--) {
851
+
852
+ const planes = planeSets[iPS];
853
+ let newPlanes = planes;
854
+ const numPlanes = planes.length;
855
+
856
+ //check plane set
857
+ for (let i = numPlanes - 1; i >= 0; i--) {
858
+ const plane = planes[i];
859
+ const planeSide = node.computePlaneSide(plane);
860
+ if (planeSide < 0) {
861
+ //completely inside
862
+ newPlanes = removeElementFromClone(planes, newPlanes, i);
863
+ } else if (planeSide > 0) {
864
+ //on the wrong side of the plane
865
+ //drop plane set
866
+ newPlaneSets = removeElementFromClone(planeSets, newPlaneSets, iPS);
867
+
868
+ break;
869
+ }
870
+ }
789
871
 
790
- while (remaining_nodes > 2) {
872
+ //update plane sets as needed
873
+ if (newPlanes !== planes && newPlaneSets.indexOf(planes) !== -1) {
874
+ //replace
875
+ if (newPlaneSets === planeSets) {
876
+ //clone if needed
877
+ newPlaneSets = newPlaneSets.slice();
878
+ }
879
+ newPlaneSets[iPS] = newPlanes;
880
+ }
881
+ }
791
882
 
792
- //pair
793
- for (i = 0; i < remaining_nodes - 1; i += 2) {
794
- const a = nodes[i];
795
- const b = nodes[i + 1];
796
- n = new BinaryNode();
797
- n.setChildren(a, b);
798
- nodes[i >> 1] = n;
883
+ return newPlaneSets;
799
884
  }
800
885
 
801
- const numNodesMod2 = remaining_nodes & 1; //faster version of %2
886
+ function traverseVolumeIntersections(node, planeSets, numPlaneSets) {
887
+ function internalVisitor(node) {
888
+ let newPlaneSet = processVolumesIntersections(node, planeSets, numPlaneSets);
889
+ if (newPlaneSet !== planeSets) {
890
+ let newPlaneSetCount = newPlaneSet.length;
891
+ if (newPlaneSetCount !== 0) {
892
+ //some intersections
893
+
894
+ //check if all plane sets are trivial (contain no test planes)
895
+ let trivialPlaneSets = 0;
896
+ for (let i = 0; i < newPlaneSetCount; i++) {
897
+ if (newPlaneSet[i].length === 0) {
898
+ trivialPlaneSets++;
899
+ }
900
+ }
901
+
902
+ if (isLeaf(node)) {
903
+ visitor(node);
904
+ } else {
905
+ if (trivialPlaneSets === newPlaneSetCount) {
906
+ fastTraverseLeaves(node);
907
+ } else {
908
+ traverseVolumeIntersections(node, newPlaneSet, newPlaneSetCount);
909
+ }
910
+ }
911
+ }
912
+ } else {
913
+ if (isLeaf(node)) {
914
+ visitor(node);
915
+ } else {
916
+ return true;
917
+ }
918
+ }
919
+
920
+ return false;
921
+ }
802
922
 
803
- if (numNodesMod2 !== 0) {
804
- //shift remaining node up so it will be considered later
805
- nodes[i >> 1] = nodes[i];
923
+ node.traversePreOrderUsingStack(internalVisitor);
806
924
  }
807
925
 
808
- remaining_nodes = (remaining_nodes >> 1) + numNodesMod2;
809
- }
926
+ function startTraversal(node) {
927
+ let planeSets = [];
928
+ for (let i = 0; i < numFrustums; i++) {
929
+ const frustum = frustums[i];
930
+ const planes = frustum.planes;
931
+ planeSets.push(planes);
932
+ }
933
+ traverseVolumeIntersections(node, planeSets, numFrustums);
934
+ }
810
935
 
811
- //finally insert these boxes from this node
812
- for (i = 0; i < remaining_nodes; i++) {
813
- n = nodes[i];
814
- this.insertNode(n);
936
+ startTraversal(this);
815
937
  }
816
- };
817
-
818
938
 
819
- let stackPointer = 0;
820
- /**
821
- *
822
- * @type {Node[]}
823
- */
824
- const stack = [];
939
+ /**
940
+ *
941
+ * @param {number} startX
942
+ * @param {number} startY
943
+ * @param {number} startZ
944
+ * @param {number} directionX
945
+ * @param {number} directionY
946
+ * @param {number} directionZ
947
+ * @param {function(node:LeafNode)} visitor
948
+ * @param thisArg
949
+ * @deprecated use {@link RaycastBVHVisitor} instead
950
+ */
951
+ traverseRayLeafIntersections(startX, startY, startZ, directionX, directionY, directionZ, visitor, thisArg) {
952
+ this.traversePreOrderUsingStack(function (node) {
953
+ let b = node.intersectRay(startX, startY, startZ, directionX, directionY, directionZ);
825
954
 
826
- /**
827
- *
828
- * @param {LeafNode[]} result destination
829
- * @param {number} x0
830
- * @param {number} y0
831
- * @param {number} z0
832
- * @param {number} x1
833
- * @param {number} y1
834
- * @param {number} z1
835
- * @returns {number} number of objects added to the result
836
- */
837
- BinaryNode.prototype.requestLeafIntersectionsAABB3 = function (result, x0, y0, z0, x1, y1, z1) {
838
- const startOffset = stackPointer;
955
+ if (!b) {
956
+ //no intersection, terminate this branch
957
+ return false;
958
+ }
839
959
 
840
- stack[stackPointer++] = this;
960
+ if (isLeaf(node)) {
961
+ //leaf node, supply to visitor
962
+ visitor.call(thisArg, node);
963
+ return false;
964
+ } else {
965
+ //intermediate node, continue traversal
966
+ return true;
967
+ }
968
+ });
969
+ }
841
970
 
842
- let count = 0;
971
+ /**
972
+ * Fraction of balanced nodes in the tree (those that have both children)
973
+ * @returns {number}
974
+ */
975
+ computeBalanceFactor() {
843
976
 
844
- while (stackPointer > startOffset) {
845
- stackPointer--;
846
- const node = stack[stackPointer];
977
+ const bvhVisitor = new BVHVisitor();
847
978
 
848
- if (!aabb3_intersect_aabb3(
849
- x0, y0, z0, x1, y1, z1,
850
- node.x0, node.y0, node.z0, node.x1, node.y1, node.z1
851
- )) {
852
- //no overlap
853
- continue;
854
- }
855
979
 
856
- if (node.isLeafNode) {
857
- result[count++] = node;
980
+ const depths = [];
858
981
 
859
- count++;
860
- } else {
861
- const left = node.left;
862
- if (left !== null) {
863
- stack[stackPointer++] = left;
864
- }
982
+ bvhVisitor.visitLeaf = (leaf) => {
983
+ const depth = leaf.computeDepth();
865
984
 
866
- const right = node.right;
867
- if (right !== null) {
868
- stack[stackPointer++] = right;
869
- }
870
- }
871
- }
985
+ depths.push(depth);
986
+ };
872
987
 
873
- return count;
874
- };
988
+ traverseBinaryNodeUsingVisitor(this, bvhVisitor)
875
989
 
876
- BinaryNode.prototype.traverseSegmentLeafIntersections = function (startX, startY, startZ, endX, endY, endZ, visitor) {
877
- this.traversePreOrder(function (node) {
878
- let b = node.intersectSegment(startX, startY, startZ, endX, endY, endZ);
879
- if (!b) {
880
- return false;
881
- }
882
- if (node instanceof LeafNode) {
883
- visitor(node);
884
- return false;
990
+ if (depths.length === 0) {
991
+ return 1;
885
992
  } else {
886
- return true;
993
+ return computeSampleStandardDeviation(depths);
887
994
  }
888
- });
889
- };
890
995
 
891
- /**
892
- * @private
893
- * Used in intersection traversal methods. Should be invoked via Function.apply and Function.call only and not be called directly
894
- * @param {LeafNode} node
895
- */
896
- function fastVisitLeafNodeIntersection(node) {
897
- this(node, true);
898
- }
996
+ }
899
997
 
900
- /**
901
- *
902
- * @param {Frustum[]} frustums Collection of THREE.js Frustums
903
- * @param {function(node:LeafNode, boolean)} visitor
904
- * @returns {number}
905
- * @deprecated Use {@link ThreeFrustumsIntersectionBVHVisitor}
906
- */
907
- BinaryNode.prototype.threeTraverseFrustumsIntersections = function (frustums, visitor) {
908
- const numFrustums = frustums.length;
998
+ /**
999
+ * @template T
1000
+ * @param {number} x0
1001
+ * @param {number} y0
1002
+ * @param {number} z0
1003
+ * @param {number} x1
1004
+ * @param {number} y1
1005
+ * @param {number} z1
1006
+ * @param {T} value
1007
+ * @returns {LeafNode}
1008
+ */
1009
+ insert(x0, y0, z0, x1, y1, z1, value) {
1010
+ const leaf = new LeafNode(value, x0, y0, z0, x1, y1, z1);
1011
+ this.insertNode(leaf);
1012
+ return leaf;
1013
+ }
909
1014
 
910
1015
  /**
911
1016
  *
912
- * @param {LeafNode|BinaryNode} node
913
- * @returns {boolean}
1017
+ * @param {Node|BinaryNode|LeafNode} child
914
1018
  */
915
- function visitNode(node) {
916
- let b = false;
917
- let i = 0;
1019
+ insertNode(child) {
918
1020
 
919
- for (; i < numFrustums; i++) {
1021
+ assert.notNull(child, "child");
1022
+ assert.defined(child, "child");
1023
+ assert.isInstanceOf(child, Node, 'child', "Node");
1024
+ assert.equal(child.parentNode, null, "parentNode must be null, otherwise Node still belongs to another tree");
920
1025
 
921
- const degree = node.intersectFrustumDegree(frustums[i]);
1026
+ const sibling = this.__insertion_findSiblingFor(child);
922
1027
 
923
- if (degree === 2) {
924
- //completely inside frustum
1028
+ const sibling_parent = sibling.parentNode;
1029
+ let parent = sibling_parent;
925
1030
 
926
- if (node.isLeafNode) {
927
- visitor(node, true);
928
- } else {
929
- node.traverseLeavesPreOrderUsingStack(fastVisitLeafNodeIntersection, visitor);
930
- }
931
-
932
- //prevent further traversal
933
- return false;
1031
+ if (sibling_parent === null) {
1032
+ // "sibling" is the root node
934
1033
 
935
- } else if (degree === 1) {
1034
+ parent = sibling;
936
1035
 
937
- //partially inside frustum
938
- b = true;
1036
+ if (sibling.left === null) {
1037
+ sibling.setChildLeft(child);
1038
+ } else if (sibling.right === null) {
1039
+ sibling.setChildRight(child);
1040
+ } else {
1041
+ parent = new BinaryNode();
939
1042
 
1043
+ parent.setChildren(sibling.right, child);
1044
+ sibling.right = parent;
1045
+ parent.parentNode = sibling;
940
1046
  }
941
- }
942
1047
 
943
- if (!b) {
944
- return false;
945
- }
1048
+ } else {
946
1049
 
947
- if (node.isLeafNode) {
948
- visitor(node, false);
949
- }
1050
+ // non-root
950
1051
 
951
- return true;
952
- }
1052
+ if (sibling_parent.left === sibling && sibling_parent.right === null) {
953
1053
 
954
- return this.traversePreOrderUsingStack(visitNode);
955
- };
1054
+ // right node is free, take it
1055
+ sibling_parent.setChildRight(child);
956
1056
 
957
- /**
958
- * As recursion goes on, we remove frustums and planes from checks reducing total number of checks necessary
959
- * @param {Array.<THREE.Frustum>} frustums
960
- * @param {function} visitor
961
- * @returns {number}
962
- */
963
- BinaryNode.prototype.threeTraverseFrustumsIntersections2 = function (frustums, visitor) {
964
- const numFrustums = frustums.length;
1057
+ } else if (sibling_parent.right === sibling && sibling_parent.left === null) {
965
1058
 
966
- function visitLeaves(node) {
967
- if (node instanceof LeafNode) {
968
- visitor(node);
969
- return false;
970
- } else {
971
- return true;
972
- }
973
- }
1059
+ // left node is free, take it
1060
+ sibling_parent.setChildLeft(child);
1061
+
1062
+ } else {
1063
+
1064
+ parent = transplantNewCommonParent(sibling, child)
1065
+
1066
+ }
974
1067
 
975
- function fastTraverseLeaves(node) {
976
- if (node instanceof LeafNode) {
977
- visitor(node);
978
- } else {
979
- node.traversePreOrderUsingStack(visitLeaves);
980
1068
  }
981
- }
982
1069
 
1070
+ // walk back up the tree fixing heights and bounds
1071
+ while (parent !== null) {
1072
+ // parent = this.balance();
983
1073
 
984
- function isFlagSet(mask, index) {
985
- return mask & (1 << index) !== 0;
986
- }
1074
+ const left = parent.left;
1075
+ const right = parent.right;
987
1076
 
988
- function clearFlag(mask, index) {
989
- return mask & ~(1 << index);
990
- }
1077
+ parent.height = 1 + max2(left !== null ? left.height : 0, right !== null ? right.height : 0);
1078
+ parent.refit();
991
1079
 
992
- function removeElementFromClone(original, clone, mask, originalIndex) {
993
- if (clone === original) {
994
- //clone planes
995
- clone = clone.slice();
1080
+ parent = parent.parentNode;
996
1081
  }
997
- //remove the plane from new set
998
- clone.splice(originalIndex, 1);
999
- return clone;
1000
1082
  }
1001
1083
 
1002
- function processVolumesIntersections(node, planeSets, planeSetCount) {
1003
- let newPlaneSets = planeSets;
1004
- for (let iPS = planeSetCount - 1; iPS >= 0; iPS--) {
1005
-
1006
- const planes = planeSets[iPS];
1007
- let newPlanes = planes;
1008
- const numPlanes = planes.length;
1009
-
1010
- //check plane set
1011
- for (let i = numPlanes - 1; i >= 0; i--) {
1012
- const plane = planes[i];
1013
- const planeSide = node.computePlaneSide(plane);
1014
- if (planeSide < 0) {
1015
- //completely inside
1016
- newPlanes = removeElementFromClone(planes, newPlanes, i);
1017
- } else if (planeSide > 0) {
1018
- //on the wrong side of the plane
1019
- //drop plane set
1020
- newPlaneSets = removeElementFromClone(planeSets, newPlaneSets, iPS);
1021
-
1022
- break;
1023
- }
1024
- }
1084
+ /**
1085
+ * @param {boolean} deep
1086
+ * @returns {BinaryNode}
1087
+ */
1088
+ clone(deep = false) {
1089
+ const clone = new BinaryNode();
1025
1090
 
1026
- //update plane sets as needed
1027
- if (newPlanes !== planes && newPlaneSets.indexOf(planes) !== -1) {
1028
- //replace
1029
- if (newPlaneSets === planeSets) {
1030
- //clone if needed
1031
- newPlaneSets = newPlaneSets.slice();
1032
- }
1033
- newPlaneSets[iPS] = newPlanes;
1034
- }
1035
- }
1091
+ clone.x0 = this.x0;
1092
+ clone.y0 = this.y0;
1093
+ clone.z0 = this.z0;
1094
+ clone.x1 = this.x1;
1095
+ clone.y1 = this.y1;
1096
+ clone.z1 = this.z1;
1036
1097
 
1037
- return newPlaneSets;
1038
- }
1098
+ clone.leafNodeCount = this.leafNodeCount;
1039
1099
 
1040
- function traverseVolumeIntersections(node, planeSets, numPlaneSets) {
1041
- function internalVisitor(node) {
1042
- let newPlaneSet = processVolumesIntersections(node, planeSets, numPlaneSets);
1043
- if (newPlaneSet !== planeSets) {
1044
- let newPlaneSetCount = newPlaneSet.length;
1045
- if (newPlaneSetCount !== 0) {
1046
- //some intersections
1047
-
1048
- //check if all plane sets are trivial (contain no test planes)
1049
- let trivialPlaneSets = 0;
1050
- for (let i = 0; i < newPlaneSetCount; i++) {
1051
- if (newPlaneSet[i].length === 0) {
1052
- trivialPlaneSets++;
1053
- }
1054
- }
1100
+ clone.parentNode = this.parentNode;
1055
1101
 
1056
- if (isLeaf(node)) {
1057
- visitor(node);
1058
- } else {
1059
- if (trivialPlaneSets === newPlaneSetCount) {
1060
- fastTraverseLeaves(node);
1061
- } else {
1062
- traverseVolumeIntersections(node, newPlaneSet, newPlaneSetCount);
1063
- }
1064
- }
1065
- }
1066
- } else {
1067
- if (isLeaf(node)) {
1068
- visitor(node);
1069
- } else {
1070
- return true;
1071
- }
1102
+ if (deep === true) {
1103
+ if (this.left !== null) {
1104
+ clone.left = this.left.clone(true);
1105
+ clone.left.parentNode = clone;
1072
1106
  }
1073
-
1074
- return false;
1107
+ if (this.right !== null) {
1108
+ clone.right = this.right.clone(true);
1109
+ clone.right.parentNode = clone;
1110
+ }
1111
+ } else {
1112
+ clone.left = this.left;
1113
+ clone.right = this.right;
1075
1114
  }
1076
1115
 
1077
- node.traversePreOrderUsingStack(internalVisitor);
1078
- }
1116
+ clone.modifiers = this.modifiers;
1079
1117
 
1080
- function startTraversal(node) {
1081
- let planeSets = [];
1082
- for (let i = 0; i < numFrustums; i++) {
1083
- const frustum = frustums[i];
1084
- const planes = frustum.planes;
1085
- planeSets.push(planes);
1086
- }
1087
- traverseVolumeIntersections(node, planeSets, numFrustums);
1118
+ return clone;
1088
1119
  }
1089
-
1090
- startTraversal(this);
1091
- };
1120
+ }
1092
1121
 
1093
1122
  /**
1094
- *
1095
- * @param {number} startX
1096
- * @param {number} startY
1097
- * @param {number} startZ
1098
- * @param {number} directionX
1099
- * @param {number} directionY
1100
- * @param {number} directionZ
1101
- * @param {function(node:LeafNode)} visitor
1102
- * @param thisArg
1103
- * @deprecated use {@link RaycastBVHVisitor} instead
1123
+ * @readonly
1124
+ * @type {boolean}
1104
1125
  */
1105
- BinaryNode.prototype.traverseRayLeafIntersections = function (startX, startY, startZ, directionX, directionY, directionZ, visitor, thisArg) {
1106
- this.traversePreOrderUsingStack(function (node) {
1107
- let b = node.intersectRay(startX, startY, startZ, directionX, directionY, directionZ);
1108
-
1109
- if (!b) {
1110
- //no intersection, terminate this branch
1111
- return false;
1112
- }
1113
-
1114
- if (isLeaf(node)) {
1115
- //leaf node, supply to visitor
1116
- visitor.call(thisArg, node);
1117
- return false;
1118
- } else {
1119
- //intermediate node, continue traversal
1120
- return true;
1121
- }
1122
- });
1123
- };
1126
+ BinaryNode.prototype.isBinaryNode = true;
1124
1127
 
1125
1128
  /**
1126
- * Fraction of balanced nodes in the tree (those that have both children)
1129
+ * @see "Dynamic BVH" by Erin Catto / Blizzard
1130
+ * @param {AABB3} node
1131
+ * @param {AABB3} addition
1127
1132
  * @returns {number}
1128
1133
  */
1129
- BinaryNode.prototype.computeBalanceFactor = function () {
1130
-
1131
- const bvhVisitor = new BVHVisitor();
1134
+ function inclusion_surface_area_heuristic(node, addition) {
1135
+ const x0 = min2(node.x0, addition.x0);
1136
+ const y0 = min2(node.y0, addition.y0);
1137
+ const z0 = min2(node.z0, addition.z0);
1132
1138
 
1139
+ const x1 = max2(node.x1, addition.x1);
1140
+ const y1 = max2(node.y1, addition.y1);
1141
+ const z1 = max2(node.z1, addition.z1);
1133
1142
 
1134
- const depths = [];
1143
+ return _boxSurfaceArea2(x0, y0, z0, x1, y1, z1) - boxSurfaceArea2(node);
1144
+ }
1135
1145
 
1136
- bvhVisitor.visitLeaf = (leaf) => {
1137
- const depth = leaf.computeDepth();
1146
+ /**
1147
+ *
1148
+ * @param {BinaryNode|LeafNode} node
1149
+ * @param {AABB3} addition
1150
+ */
1151
+ function inclusion_cost_lower_bound(node, addition) {
1152
+ let n = node;
1153
+ let r = boxSurfaceArea2(addition);
1138
1154
 
1139
- depths.push(depth);
1140
- };
1155
+ while (n !== null) {
1141
1156
 
1142
- traverseBinaryNodeUsingVisitor(this, bvhVisitor)
1157
+ r += n.costForInclusion(addition);
1143
1158
 
1144
- if (depths.length === 0) {
1145
- return 1;
1146
- } else {
1147
- return computeSampleStandardDeviation(depths);
1159
+ n = n.parentNode;
1148
1160
  }
1149
1161
 
1150
- };
1162
+ return r;
1163
+ }
1164
+
1151
1165
 
1152
1166
  /**
1153
- * @template T
1154
- * @param {number} x0
1155
- * @param {number} y0
1156
- * @param {number} z0
1157
- * @param {number} x1
1158
- * @param {number} y1
1159
- * @param {number} z1
1160
- * @param {T} value
1161
- * @returns {LeafNode}
1167
+ * @private
1168
+ * Used in intersection traversal methods. Should be invoked via Function.apply and Function.call only and not be called directly
1169
+ * @param {LeafNode} node
1162
1170
  */
1163
- BinaryNode.prototype.insert = function (x0, y0, z0, x1, y1, z1, value) {
1164
- const leaf = new LeafNode(value, x0, y0, z0, x1, y1, z1);
1165
- this.insertNode(leaf);
1166
- return leaf;
1167
- };
1171
+ function fastVisitLeafNodeIntersection(node) {
1172
+ this(node, true);
1173
+ }
1168
1174
 
1169
1175
  /**
1170
1176
  *
@@ -1188,105 +1194,6 @@ function transplantNewCommonParent(node, child) {
1188
1194
  bNode.setChildren(node, child);
1189
1195
  bNode.parentNode = parent;
1190
1196
 
1191
- parent.bubbleExpandToFit(bNode);
1192
-
1193
- parent.updateLeafNodeCount();
1194
-
1195
1197
  return bNode;
1196
1198
  }
1197
1199
 
1198
- /**
1199
- *
1200
- * @param {Node|BinaryNode|LeafNode} child
1201
- */
1202
- BinaryNode.prototype.insertNode = function (child) {
1203
-
1204
- assert.notNull(child, "child");
1205
- assert.defined(child, "child");
1206
- assert.isInstanceOf(child, Node, 'child', "Node");
1207
- assert.equal(child.parentNode, null, "parentNode must be null, otherwise Node still belongs to another tree");
1208
-
1209
- const sibling = this.__insertion_findSiblingFor(child);
1210
-
1211
- const sibling_parent = sibling.parentNode;
1212
- if (sibling_parent === null) {
1213
- // root node
1214
-
1215
- if (sibling.left === null) {
1216
- sibling.setChildLeft(child);
1217
- } else if (sibling.right === null) {
1218
- sibling.setChildRight(child);
1219
- } else {
1220
- const parent = new BinaryNode();
1221
-
1222
- parent.setChildren(sibling.right, child);
1223
- sibling.right = parent;
1224
- parent.parentNode = sibling;
1225
- }
1226
-
1227
- } else {
1228
-
1229
- // non-root
1230
-
1231
- if (sibling_parent.left === sibling && sibling_parent.right === null) {
1232
-
1233
- // right node is free, take it
1234
- sibling_parent.setChildRight(child);
1235
-
1236
- } else if (sibling_parent.right === sibling && sibling_parent.left === null) {
1237
-
1238
- // left node is free, take it
1239
- sibling_parent.setChildLeft(child);
1240
-
1241
- } else {
1242
-
1243
- transplantNewCommonParent(sibling, child)
1244
-
1245
- }
1246
-
1247
- sibling_parent.bubbleRefit();
1248
-
1249
- }
1250
- }
1251
-
1252
- /**
1253
- * @param {boolean} deep
1254
- * @returns {BinaryNode}
1255
- */
1256
- BinaryNode.prototype.clone = function (deep = false) {
1257
- const clone = new BinaryNode();
1258
-
1259
- clone.x0 = this.x0;
1260
- clone.y0 = this.y0;
1261
- clone.z0 = this.z0;
1262
- clone.x1 = this.x1;
1263
- clone.y1 = this.y1;
1264
- clone.z1 = this.z1;
1265
-
1266
- clone.leafNodeCount = this.leafNodeCount;
1267
-
1268
- clone.parentNode = this.parentNode;
1269
-
1270
- if (deep === true) {
1271
- if (this.left !== null) {
1272
- clone.left = this.left.clone(true);
1273
- clone.left.parentNode = clone;
1274
- }
1275
- if (this.right !== null) {
1276
- clone.right = this.right.clone(true);
1277
- clone.right.parentNode = clone;
1278
- }
1279
- } else {
1280
- clone.left = this.left;
1281
- clone.right = this.right;
1282
- }
1283
-
1284
- clone.modifiers = this.modifiers;
1285
-
1286
- return clone;
1287
- };
1288
-
1289
-
1290
- export {
1291
- BinaryNode
1292
- };