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