@woosh/meep-engine 2.34.36 → 2.34.37

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