@woosh/meep-engine 2.34.34 → 2.34.38

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 (60) hide show
  1. package/core/binary/BitSet.js +5 -0
  2. package/core/bvh2/BinaryNode.js +853 -1007
  3. package/core/bvh2/LeafNode.js +80 -82
  4. package/core/bvh2/Node.d.ts +3 -0
  5. package/core/bvh2/Node.js +111 -114
  6. package/core/bvh2/aabb3/AABB3.js +46 -1
  7. package/core/bvh2/aabb3/AABB3.spec.js +27 -1
  8. package/core/bvh2/aabb3/aabb3_from_v3_array.js +34 -0
  9. package/core/bvh2/aabb3/aabb3_from_v3_array_transformed.js +39 -0
  10. package/core/bvh2/aabb3/aabb3_intersects_frustum_degree.js +46 -0
  11. package/core/bvh2/transform/RotationOptimizer.js +2 -262
  12. package/core/bvh2/transform/tryRotateSingleNode.js +259 -0
  13. package/core/bvh2/traversal/ThreeFrustumsIntersectionBVHVisitor.js +16 -35
  14. package/core/bvh2/traversal/queryBinaryNode_FrustumIntersections.js +52 -0
  15. package/core/events/signal/Signal.js +1 -1
  16. package/core/geom/3d/aabb/aabb3_computeDistanceAbovePlane.js +1 -1
  17. package/core/geom/3d/aabb/aabb3_matrix4_project.js +3 -4
  18. package/core/geom/3d/aabb/aabb3_matrix4_project.spec.js +13 -0
  19. package/core/geom/3d/triangle/computeTriangleRayIntersection.js +1 -2
  20. package/core/geom/Quaternion.d.ts +2 -0
  21. package/core/geom/Vector3.js +22 -11
  22. package/engine/asset/loaders/ArrayBufferLoader.js +7 -0
  23. package/engine/asset/loaders/image/ImageRGBADataLoader.js +11 -6
  24. package/engine/ecs/EntityBuilder.js +12 -5
  25. package/engine/ecs/EntityComponentDataset.js +26 -6
  26. package/engine/ecs/attachment/AttachmentSystem.js +1 -1
  27. package/engine/ecs/attachment/TransformAttachmentBinding.js +0 -1
  28. package/engine/ecs/foliage/FoliageVisibilitySetBuilder.js +1 -31
  29. package/engine/ecs/foliage/InstancedFoliage.js +85 -152
  30. package/engine/ecs/foliage/ViewState.js +165 -0
  31. package/engine/ecs/foliage/ecs/Foliage2System.js +82 -19
  32. package/engine/ecs/foliage/ecs/InstancedMeshComponent.js +30 -0
  33. package/engine/ecs/foliage/ecs/InstancedMeshLayer.js +16 -0
  34. package/engine/ecs/foliage/ecs/InstancedMeshUtils.js +2 -2
  35. package/engine/ecs/parent/EntityNode.js +31 -49
  36. package/engine/ecs/sockets/AttachmentSockets.js +11 -1
  37. package/engine/ecs/transform-attachment/TransformAttachment.js +88 -0
  38. package/engine/ecs/transform-attachment/TransformAttachmentSystem.js +59 -0
  39. package/engine/graphics/GraphicsEngine.js +23 -2
  40. package/engine/graphics/ecs/camera/Camera.js +33 -16
  41. package/engine/graphics/ecs/camera/unprojectPoint.js +14 -0
  42. package/engine/graphics/ecs/light/binding/three/ThreeLightBinding.js +22 -17
  43. package/engine/graphics/ecs/mesh/updateNodeByTransformAndBBB.js +21 -21
  44. package/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +9 -1
  45. package/engine/graphics/ecs/mesh-v2/ShadedGeometryFlags.js +4 -1
  46. package/engine/graphics/ecs/mesh-v2/sample/load_gltf.js +38 -8
  47. package/engine/graphics/ecs/water2/shader/AlexWaterShader.js +7 -1
  48. package/engine/graphics/geometry/instancing/InstancedMeshGroup.js +18 -12
  49. package/engine/graphics/micron/build/buildMicronGeometryFromBufferGeometry.js +1 -34
  50. package/engine/graphics/micron/build/fill_patch_geometry_data.js +12 -0
  51. package/engine/graphics/micron/format/MicronGeometry.js +21 -0
  52. package/engine/graphics/micron/format/MicronGeometryPatch.js +0 -7
  53. package/engine/graphics/micron/plugin/MicronRenderPlugin.js +0 -3
  54. package/engine/graphics/micron/prototypeVirtualGeometry.js +69 -1
  55. package/engine/graphics/particles/particular/engine/renderers/billboard/ParticleBillboardMaterial.js +5 -1
  56. package/engine/graphics/render/layers/RenderLayer.js +22 -85
  57. package/engine/graphics/render/visibility/VisibilityComputer.js +23 -103
  58. package/engine/sound/ecs/emitter/SoundEmitterSystem.js +1 -1
  59. package/package.json +1 -1
  60. package/samples/terrain/editor.js +0 -1
@@ -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;
482
+ const left = this.left;
481
483
 
482
- stack[stackPointer++] = this;
484
+ if (left === null) {
485
+ leftArea = 0;
486
+ leftLeaves = 0;
487
+ } else {
488
+ leftArea = boxSurfaceArea2(left);
489
+ leftLeaves = left.leafNodeCount;
490
+ }
483
491
 
484
- 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
+ }
485
500
 
486
- while (stackPointer-- > stackOffset) {
501
+ const thisArea = boxSurfaceArea2(this);
487
502
 
488
- visitCount++;
503
+ return surfaceAreaHeuristic(thisArea, leftArea, rightArea, leftLeaves, rightLeaves);
504
+ }
489
505
 
490
- n = stack[stackPointer];
506
+ updateLeafNodeCount() {
507
+ let node = this;
491
508
 
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;
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;
498
519
  }
499
- if (n.left !== null) {
500
- stack[stackPointer++] = n.left;
520
+ }
521
+
522
+ const left = this.left;
523
+
524
+ if (left !== null) {
525
+ if (left.isLeafNode) {
526
+ n++;
527
+ } else {
528
+ n += left.leafNodeCount;
501
529
  }
502
530
  }
503
- }
504
531
 
505
- cleanup(stackOffset);
532
+ node.leafNodeCount = n;
533
+
534
+ // propagate update up the tree
535
+ node = node.parentNode;
536
+ }
506
537
 
507
- return visitCount;
508
538
  }
509
539
 
510
- return traverseLeavesPreOrderUsingStack;
511
- })();
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() {
512
544
 
513
- BinaryNode.prototype.refitFor2 = function () {
514
- const a = this.left;
515
- const b = this.right;
545
+ /**
546
+ *
547
+ * @type {BinaryNode}
548
+ */
549
+ const r = this.right;
516
550
 
517
- this.x0 = min2(a.x0, b.x0);
518
- this.y0 = min2(a.y0, b.y0);
519
- this.z0 = min2(a.z0, b.z0);
551
+ const l = this.left;
520
552
 
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
- };
553
+ const rL = r.left;
554
+ const rR = r.right;
525
555
 
526
- BinaryNode.prototype.bubbleRefit = function () {
527
- let n = this;
556
+ this.left = r;
557
+ this.right = rR;
528
558
 
529
- n.refit();
559
+ if (rR !== null) {
560
+ rR.parentNode = this;
561
+ }
530
562
 
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;
563
+ r.left = l;
564
+ if (l !== null) {
565
+ l.parentNode = r;
566
+ }
540
567
 
541
- n = n.parentNode;
568
+ r.right = rL;
542
569
 
543
- if (n === null) {
544
- return;
545
- }
570
+ r.updateLeafNodeCount();
546
571
 
547
- //check if this node violates bounds of the parent
572
+ r.bubbleRefit();
573
+ }
548
574
 
549
- let flag = false;
575
+ rotateRight() {
576
+ const r = this.right;
550
577
 
551
- if (_x0 < n.x0) {
552
- n.x0 = _x0;
553
- flag = true;
554
- }
555
- if (_y0 < n.y0) {
556
- n.y0 = _y0;
557
- flag = true;
558
- }
559
- if (_z0 < n.z0) {
560
- n.z0 = _z0;
561
- flag = true;
562
- }
578
+ const l = this.left;
563
579
 
564
- if (_x1 > n.x1) {
565
- n.x1 = _x1;
566
- flag = true;
567
- }
568
- if (_y1 > n.y1) {
569
- n.y1 = _y1;
570
- flag = true;
571
- }
572
- if (_z1 > n.z1) {
573
- n.z1 = _z1;
574
- flag = true;
580
+ const lL = l.left;
581
+ const lR = l.right;
582
+
583
+ if (lL !== null) {
584
+ lL.parentNode = this;
575
585
  }
576
586
 
577
- if (!flag) {
578
- //no violation, we can stop here
579
- break;
587
+ l.right = r;
588
+ if (r !== null) {
589
+ r.parentNode = l;
580
590
  }
581
- }
582
- };
583
- BinaryNode.prototype.refit = function () {
584
- if (this.left !== null && this.right !== null) {
585
- this.refitFor2();
586
- } else if (this.left !== null) {
587
- this.copy(this.left);
588
- } else if (this.right !== null) {
589
- this.copy(this.right);
590
- }
591
- };
592
591
 
593
- /**
594
- *
595
- * @param {Node} n0
596
- * @param {Node} n1
597
- * @return {number}
598
- */
599
- function sortMortonCodes(n0, n1) {
600
- return n0._mortonCode - n1._mortonCode;
601
- }
592
+ l.left = lR;
602
593
 
603
- BinaryNode.prototype.sumSurfaceArea = function () {
604
- let result = 0;
605
- this.traversePreOrderUsingStack(function (node) {
606
- const nodeArea = node.computeSurfaceArea();
607
- result += nodeArea;
608
- });
594
+ l.updateLeafNodeCount();
609
595
 
610
- return result;
611
- };
596
+ l.bubbleRefit();
597
+ }
612
598
 
613
- BinaryNode.prototype.countDescendants = function () {
614
- let result = 0;
615
- this.traversePreOrderUsingStack(function (node) {
616
- result++;
617
- });
618
- return result;
619
- };
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);
620
608
 
609
+ for (i = 0; i < numNodes; i++) {
610
+ //leaf needs to be set up inside the callback
611
+ n = leafFactory(i);
621
612
 
622
- /**
623
- *
624
- * @param node
625
- */
626
- function countLeaves(node) {
627
- if (isLeaf(node)) {
628
- return 1;
629
- }
613
+ nodes[i] = n;
614
+ }
630
615
 
631
- let result = 0;
616
+ //sort leaves
617
+ arrayQuickSort(nodes, n => n.computeMortonCode(), null, 0, max2(0, numNodes - 1));
632
618
 
633
- node.traversePreOrderUsingStack(function (node) {
634
- if (isLeaf(node)) {
635
- result++;
636
- }
637
- });
619
+ let remaining_nodes = numNodes;
638
620
 
639
- return result;
640
- }
621
+ while (remaining_nodes > 2) {
641
622
 
642
- /**
643
- * @deprecated Incorrect usage of SAH
644
- * @return {number}
645
- */
646
- BinaryNode.prototype.computeSAH = function () {
647
- let leftLeaves, rightLeaves, leftArea, rightArea;
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
+ }
648
631
 
649
- const left = this.left;
632
+ const numNodesMod2 = remaining_nodes & 1; //faster version of %2
650
633
 
651
- if (left === null) {
652
- leftArea = 0;
653
- leftLeaves = 0;
654
- } else {
655
- leftArea = boxSurfaceArea2(left);
656
- leftLeaves = left.leafNodeCount;
657
- }
634
+ if (numNodesMod2 !== 0) {
635
+ //shift remaining node up so it will be considered later
636
+ nodes[i >> 1] = nodes[i];
637
+ }
658
638
 
659
- const right = this.right;
660
- if (right === null) {
661
- rightArea = 0;
662
- rightLeaves = 0;
663
- } else {
664
- rightArea = boxSurfaceArea2(right);
665
- rightLeaves = right.leafNodeCount;
666
- }
639
+ remaining_nodes = (remaining_nodes >> 1) + numNodesMod2;
640
+ }
667
641
 
668
- const thisArea = boxSurfaceArea2(this);
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
+ }
647
+ }
669
648
 
670
- return surfaceAreaHeuristic(thisArea, leftArea, rightArea, leftLeaves, rightLeaves);
671
- };
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;
672
662
 
673
- BinaryNode.prototype.updateLeafNodeCount = function () {
674
- let n = 0;
663
+ stack[stackPointer++] = this;
675
664
 
676
- const right = this.right;
665
+ let count = 0;
677
666
 
678
- if (right !== null) {
679
- if (right.isLeafNode) {
680
- n++;
681
- } else {
682
- n += right.leafNodeCount;
683
- }
684
- }
667
+ while (stackPointer > startOffset) {
668
+ stackPointer--;
669
+ const node = stack[stackPointer];
685
670
 
686
- const left = this.left;
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
+ }
687
678
 
688
- if (left !== null) {
689
- if (left.isLeafNode) {
690
- n++;
691
- } else {
692
- n += left.leafNodeCount;
693
- }
694
- }
679
+ if (node.isLeafNode) {
680
+ result[count++] = node;
695
681
 
696
- this.leafNodeCount = n;
682
+ count++;
683
+ } else {
684
+ const left = node.left;
685
+ if (left !== null) {
686
+ stack[stackPointer++] = left;
687
+ }
697
688
 
698
- // propagate update up the tree
699
- let parentNode = this.parentNode;
689
+ const right = node.right;
690
+ if (right !== null) {
691
+ stack[stackPointer++] = right;
692
+ }
693
+ }
694
+ }
700
695
 
701
- if (parentNode !== null) {
702
- // bubble up
703
- parentNode.updateLeafNodeCount();
696
+ return count;
704
697
  }
705
- }
706
-
707
698
 
708
- /**
709
- * As a result of rotation, this node 'becomes' the right node, and left node get's replaced by the right node with necessary adjustments
710
- */
711
- 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
+ }
712
713
 
713
714
  /**
714
715
  *
715
- * @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}
716
720
  */
717
- const r = this.right;
721
+ threeTraverseFrustumsIntersections(frustums, visitor) {
722
+ const numFrustums = frustums.length;
718
723
 
719
- 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;
720
732
 
721
- const rL = r.left;
722
- const rR = r.right;
733
+ for (; i < numFrustums; i++) {
723
734
 
724
- this.left = r;
725
- this.right = rR;
735
+ const degree = node.intersectFrustumDegree(frustums[i]);
726
736
 
727
- if (rR !== null) {
728
- rR.parentNode = this;
729
- }
737
+ if (degree === 2) {
738
+ //completely inside frustum
730
739
 
731
- r.left = l;
732
- if (l !== null) {
733
- l.parentNode = r;
734
- }
740
+ if (node.isLeafNode) {
741
+ visitor(node, true);
742
+ } else {
743
+ node.traverseLeavesPreOrderUsingStack(fastVisitLeafNodeIntersection, visitor);
744
+ }
735
745
 
736
- r.right = rL;
746
+ //prevent further traversal
747
+ return false;
737
748
 
738
- r.updateLeafNodeCount();
749
+ } else if (degree === 1) {
739
750
 
740
- r.bubbleRefit();
741
- };
751
+ //partially inside frustum
752
+ b = true;
742
753
 
743
- BinaryNode.prototype.rotateRight = function () {
744
- const r = this.right;
754
+ }
755
+ }
745
756
 
746
- const l = this.left;
757
+ if (!b) {
758
+ return false;
759
+ }
747
760
 
748
- const lL = l.left;
749
- const lR = l.right;
761
+ if (node.isLeafNode) {
762
+ visitor(node, false);
763
+ }
750
764
 
751
- if (lL !== null) {
752
- lL.parentNode = this;
753
- }
765
+ return true;
766
+ }
754
767
 
755
- l.right = r;
756
- if (r !== null) {
757
- r.parentNode = l;
768
+ return this.traversePreOrderUsingStack(visitNode);
758
769
  }
759
770
 
760
- 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;
761
779
 
762
- l.updateLeafNodeCount();
780
+ function visitLeaves(node) {
781
+ if (node instanceof LeafNode) {
782
+ visitor(node);
783
+ return false;
784
+ } else {
785
+ return true;
786
+ }
787
+ }
763
788
 
764
- l.bubbleRefit();
765
- };
789
+ function fastTraverseLeaves(node) {
790
+ if (node instanceof LeafNode) {
791
+ visitor(node);
792
+ } else {
793
+ node.traversePreOrderUsingStack(visitLeaves);
794
+ }
795
+ }
766
796
 
767
- /**
768
- *
769
- * @param {function(index:int):LeafNode} leafFactory
770
- * @param {int} numNodes
771
- */
772
- BinaryNode.prototype.insertManyBoxes2 = function (leafFactory, numNodes) {
773
- let i, n;
774
- //create leaf nodes
775
- const nodes = new Array(numNodes);
776
797
 
777
- for (i = 0; i < numNodes; i++) {
778
- //leaf needs to be set up inside the callback
779
- n = leafFactory(i);
798
+ function isFlagSet(mask, index) {
799
+ return mask & (1 << index) !== 0;
800
+ }
780
801
 
781
- nodes[i] = n;
782
- }
802
+ function clearFlag(mask, index) {
803
+ return mask & ~(1 << index);
804
+ }
783
805
 
784
- //sort leaves
785
- 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
+ }
786
815
 
787
- 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
+ }
788
839
 
789
- 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
+ }
790
850
 
791
- //pair
792
- for (i = 0; i < remaining_nodes - 1; i += 2) {
793
- const a = nodes[i];
794
- const b = nodes[i + 1];
795
- n = new BinaryNode();
796
- n.setChildren(a, b);
797
- nodes[i >> 1] = n;
851
+ return newPlaneSets;
798
852
  }
799
853
 
800
- 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
+ }
801
890
 
802
- if (numNodesMod2 !== 0) {
803
- //shift remaining node up so it will be considered later
804
- nodes[i >> 1] = nodes[i];
891
+ node.traversePreOrderUsingStack(internalVisitor);
805
892
  }
806
893
 
807
- remaining_nodes = (remaining_nodes >> 1) + numNodesMod2;
808
- }
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
+ }
809
903
 
810
- //finally insert these boxes from this node
811
- for (i = 0; i < remaining_nodes; i++) {
812
- n = nodes[i];
813
- this.insertNode(n);
904
+ startTraversal(this);
814
905
  }
815
- };
816
-
817
906
 
818
- let stackPointer = 0;
819
- /**
820
- *
821
- * @type {Node[]}
822
- */
823
- 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);
824
922
 
825
- /**
826
- *
827
- * @param {LeafNode[]} result destination
828
- * @param {number} x0
829
- * @param {number} y0
830
- * @param {number} z0
831
- * @param {number} x1
832
- * @param {number} y1
833
- * @param {number} z1
834
- * @returns {number} number of objects added to the result
835
- */
836
- BinaryNode.prototype.requestLeafIntersectionsAABB3 = function (result, x0, y0, z0, x1, y1, z1) {
837
- const startOffset = stackPointer;
923
+ if (!b) {
924
+ //no intersection, terminate this branch
925
+ return false;
926
+ }
838
927
 
839
- 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
+ }
840
938
 
841
- let count = 0;
939
+ /**
940
+ * Fraction of balanced nodes in the tree (those that have both children)
941
+ * @returns {number}
942
+ */
943
+ computeBalanceFactor() {
842
944
 
843
- while (stackPointer > startOffset) {
844
- stackPointer--;
845
- const node = stack[stackPointer];
945
+ const bvhVisitor = new BVHVisitor();
846
946
 
847
- if (!aabb3_intersect_aabb3(
848
- x0, y0, z0, x1, y1, z1,
849
- node.x0, node.y0, node.z0, node.x1, node.y1, node.z1
850
- )) {
851
- //no overlap
852
- continue;
853
- }
854
947
 
855
- if (node.isLeafNode) {
856
- result[count++] = node;
948
+ const depths = [];
857
949
 
858
- count++;
859
- } else {
860
- const left = node.left;
861
- if (left !== null) {
862
- stack[stackPointer++] = left;
863
- }
950
+ bvhVisitor.visitLeaf = (leaf) => {
951
+ const depth = leaf.computeDepth();
864
952
 
865
- const right = node.right;
866
- if (right !== null) {
867
- stack[stackPointer++] = right;
868
- }
869
- }
870
- }
953
+ depths.push(depth);
954
+ };
871
955
 
872
- return count;
873
- };
956
+ traverseBinaryNodeUsingVisitor(this, bvhVisitor)
874
957
 
875
- BinaryNode.prototype.traverseSegmentLeafIntersections = function (startX, startY, startZ, endX, endY, endZ, visitor) {
876
- this.traversePreOrder(function (node) {
877
- let b = node.intersectSegment(startX, startY, startZ, endX, endY, endZ);
878
- if (!b) {
879
- return false;
880
- }
881
- if (node instanceof LeafNode) {
882
- visitor(node);
883
- return false;
958
+ if (depths.length === 0) {
959
+ return 1;
884
960
  } else {
885
- return true;
961
+ return computeSampleStandardDeviation(depths);
886
962
  }
887
- });
888
- };
889
963
 
890
- /**
891
- * @private
892
- * Used in intersection traversal methods. Should be invoked via Function.apply and Function.call only and not be called directly
893
- * @param {LeafNode} node
894
- */
895
- function fastVisitLeafNodeIntersection(node) {
896
- this(node, true);
897
- }
964
+ }
898
965
 
899
- /**
900
- *
901
- * @param {Frustum[]} frustums Collection of THREE.js Frustums
902
- * @param {function(node:LeafNode, boolean)} visitor
903
- * @returns {number}
904
- * @deprecated Use {@link ThreeFrustumsIntersectionBVHVisitor}
905
- */
906
- BinaryNode.prototype.threeTraverseFrustumsIntersections = function (frustums, visitor) {
907
- 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
+ }
908
982
 
909
983
  /**
910
984
  *
911
- * @param {LeafNode|BinaryNode} node
912
- * @returns {boolean}
985
+ * @param {Node|BinaryNode|LeafNode} child
913
986
  */
914
- function visitNode(node) {
915
- let b = false;
916
- let i = 0;
917
-
918
- for (; i < numFrustums; i++) {
919
-
920
- const degree = node.intersectFrustumDegree(frustums[i]);
921
-
922
- if (degree === 2) {
923
- //completely inside frustum
987
+ insertNode(child) {
924
988
 
925
- if (node.isLeafNode) {
926
- visitor(node, true);
927
- } else {
928
- node.traverseLeavesPreOrderUsingStack(fastVisitLeafNodeIntersection, visitor);
929
- }
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");
930
993
 
931
- //prevent further traversal
932
- return false;
994
+ const sibling = this.__insertion_findSiblingFor(child);
933
995
 
934
- } else if (degree === 1) {
996
+ const sibling_parent = sibling.parentNode;
997
+ if (sibling_parent === null) {
998
+ // root node
935
999
 
936
- //partially inside frustum
937
- 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();
938
1006
 
1007
+ parent.setChildren(sibling.right, child);
1008
+ sibling.right = parent;
1009
+ parent.parentNode = sibling;
939
1010
  }
940
- }
941
1011
 
942
- if (!b) {
943
- return false;
944
- }
1012
+ } else {
945
1013
 
946
- if (node.isLeafNode) {
947
- visitor(node, false);
948
- }
1014
+ // non-root
949
1015
 
950
- return true;
951
- }
1016
+ if (sibling_parent.left === sibling && sibling_parent.right === null) {
952
1017
 
953
- return this.traversePreOrderUsingStack(visitNode);
954
- };
1018
+ // right node is free, take it
1019
+ sibling_parent.setChildRight(child);
955
1020
 
956
- /**
957
- * As recursion goes on, we remove frustums and planes from checks reducing total number of checks necessary
958
- * @param {Array.<THREE.Frustum>} frustums
959
- * @param {function} visitor
960
- * @returns {number}
961
- */
962
- BinaryNode.prototype.threeTraverseFrustumsIntersections2 = function (frustums, visitor) {
963
- const numFrustums = frustums.length;
1021
+ } else if (sibling_parent.right === sibling && sibling_parent.left === null) {
964
1022
 
965
- function visitLeaves(node) {
966
- if (node instanceof LeafNode) {
967
- visitor(node);
968
- return false;
969
- } else {
970
- return true;
971
- }
972
- }
1023
+ // left node is free, take it
1024
+ sibling_parent.setChildLeft(child);
973
1025
 
974
- function fastTraverseLeaves(node) {
975
- if (node instanceof LeafNode) {
976
- visitor(node);
977
- } else {
978
- node.traversePreOrderUsingStack(visitLeaves);
979
- }
980
- }
1026
+ } else {
981
1027
 
1028
+ transplantNewCommonParent(sibling, child)
982
1029
 
983
- function isFlagSet(mask, index) {
984
- return mask & (1 << index) !== 0;
985
- }
1030
+ }
986
1031
 
987
- function clearFlag(mask, index) {
988
- return mask & ~(1 << index);
989
- }
1032
+ sibling_parent.bubbleRefit();
990
1033
 
991
- function removeElementFromClone(original, clone, mask, originalIndex) {
992
- if (clone === original) {
993
- //clone planes
994
- clone = clone.slice();
995
1034
  }
996
- //remove the plane from new set
997
- clone.splice(originalIndex, 1);
998
- return clone;
999
1035
  }
1000
1036
 
1001
- function processVolumesIntersections(node, planeSets, planeSetCount) {
1002
- let newPlaneSets = planeSets;
1003
- for (let iPS = planeSetCount - 1; iPS >= 0; iPS--) {
1004
-
1005
- const planes = planeSets[iPS];
1006
- let newPlanes = planes;
1007
- const numPlanes = planes.length;
1008
-
1009
- //check plane set
1010
- for (let i = numPlanes - 1; i >= 0; i--) {
1011
- const plane = planes[i];
1012
- const planeSide = node.computePlaneSide(plane);
1013
- if (planeSide < 0) {
1014
- //completely inside
1015
- newPlanes = removeElementFromClone(planes, newPlanes, i);
1016
- } else if (planeSide > 0) {
1017
- //on the wrong side of the plane
1018
- //drop plane set
1019
- newPlaneSets = removeElementFromClone(planeSets, newPlaneSets, iPS);
1020
-
1021
- break;
1022
- }
1023
- }
1037
+ /**
1038
+ * @param {boolean} deep
1039
+ * @returns {BinaryNode}
1040
+ */
1041
+ clone(deep = false) {
1042
+ const clone = new BinaryNode();
1024
1043
 
1025
- //update plane sets as needed
1026
- if (newPlanes !== planes && newPlaneSets.indexOf(planes) !== -1) {
1027
- //replace
1028
- if (newPlaneSets === planeSets) {
1029
- //clone if needed
1030
- newPlaneSets = newPlaneSets.slice();
1031
- }
1032
- newPlaneSets[iPS] = newPlanes;
1033
- }
1034
- }
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;
1035
1050
 
1036
- return newPlaneSets;
1037
- }
1051
+ clone.leafNodeCount = this.leafNodeCount;
1038
1052
 
1039
- function traverseVolumeIntersections(node, planeSets, numPlaneSets) {
1040
- function internalVisitor(node) {
1041
- let newPlaneSet = processVolumesIntersections(node, planeSets, numPlaneSets);
1042
- if (newPlaneSet !== planeSets) {
1043
- let newPlaneSetCount = newPlaneSet.length;
1044
- if (newPlaneSetCount !== 0) {
1045
- //some intersections
1046
-
1047
- //check if all plane sets are trivial (contain no test planes)
1048
- let trivialPlaneSets = 0;
1049
- for (let i = 0; i < newPlaneSetCount; i++) {
1050
- if (newPlaneSet[i].length === 0) {
1051
- trivialPlaneSets++;
1052
- }
1053
- }
1053
+ clone.parentNode = this.parentNode;
1054
1054
 
1055
- if (isLeaf(node)) {
1056
- visitor(node);
1057
- } else {
1058
- if (trivialPlaneSets === newPlaneSetCount) {
1059
- fastTraverseLeaves(node);
1060
- } else {
1061
- traverseVolumeIntersections(node, newPlaneSet, newPlaneSetCount);
1062
- }
1063
- }
1064
- }
1065
- } else {
1066
- if (isLeaf(node)) {
1067
- visitor(node);
1068
- } else {
1069
- return true;
1070
- }
1055
+ if (deep === true) {
1056
+ if (this.left !== null) {
1057
+ clone.left = this.left.clone(true);
1058
+ clone.left.parentNode = clone;
1071
1059
  }
1072
-
1073
- 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;
1074
1067
  }
1075
1068
 
1076
- node.traversePreOrderUsingStack(internalVisitor);
1077
- }
1069
+ clone.modifiers = this.modifiers;
1078
1070
 
1079
- function startTraversal(node) {
1080
- let planeSets = [];
1081
- for (let i = 0; i < numFrustums; i++) {
1082
- const frustum = frustums[i];
1083
- const planes = frustum.planes;
1084
- planeSets.push(planes);
1085
- }
1086
- traverseVolumeIntersections(node, planeSets, numFrustums);
1071
+ return clone;
1087
1072
  }
1073
+ }
1088
1074
 
1089
- startTraversal(this);
1090
- };
1075
+ /**
1076
+ * @readonly
1077
+ * @type {boolean}
1078
+ */
1079
+ BinaryNode.prototype.isBinaryNode = true;
1091
1080
 
1092
1081
  /**
1093
- *
1094
- * @param {number} startX
1095
- * @param {number} startY
1096
- * @param {number} startZ
1097
- * @param {number} directionX
1098
- * @param {number} directionY
1099
- * @param {number} directionZ
1100
- * @param {function(node:LeafNode)} visitor
1101
- * @param thisArg
1102
- * @deprecated use {@link RaycastBVHVisitor} instead
1082
+ * @see "Dynamic BVH" by Erin Catto / Blizzard
1083
+ * @param {AABB3} node
1084
+ * @param {AABB3} addition
1085
+ * @returns {number}
1103
1086
  */
1104
- BinaryNode.prototype.traverseRayLeafIntersections = function (startX, startY, startZ, directionX, directionY, directionZ, visitor, thisArg) {
1105
- this.traversePreOrderUsingStack(function (node) {
1106
- 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);
1107
1091
 
1108
- if (!b) {
1109
- //no intersection, terminate this branch
1110
- return false;
1111
- }
1092
+ const x1 = max2(node.x1, addition.x1);
1093
+ const y1 = max2(node.y1, addition.y1);
1094
+ const z1 = max2(node.z1, addition.z1);
1112
1095
 
1113
- if (isLeaf(node)) {
1114
- //leaf node, supply to visitor
1115
- visitor.call(thisArg, node);
1116
- return false;
1117
- } else {
1118
- //intermediate node, continue traversal
1119
- return true;
1120
- }
1121
- });
1122
- };
1096
+ return _boxSurfaceArea2(x0, y0, z0, x1, y1, z1) - boxSurfaceArea2(node);
1097
+ }
1123
1098
 
1124
1099
  /**
1125
- * Fraction of balanced nodes in the tree (those that have both children)
1126
- * @returns {number}
1100
+ *
1101
+ * @param {BinaryNode|LeafNode} node
1102
+ * @param {AABB3} addition
1127
1103
  */
1128
- BinaryNode.prototype.computeBalanceFactor = function () {
1104
+ function inclusion_cost_lower_bound(node, addition) {
1105
+ let n = node;
1106
+ let r = boxSurfaceArea2(addition);
1129
1107
 
1130
- const bvhVisitor = new BVHVisitor();
1108
+ while (n !== null) {
1131
1109
 
1110
+ r += n.costForInclusion(addition);
1132
1111
 
1133
- const depths = [];
1112
+ n = n.parentNode;
1113
+ }
1134
1114
 
1135
- bvhVisitor.visitLeaf = (leaf) => {
1136
- const depth = leaf.computeDepth();
1115
+ return r;
1116
+ }
1137
1117
 
1138
- depths.push(depth);
1139
- };
1140
1118
 
1141
- traverseBinaryNodeUsingVisitor(this, bvhVisitor)
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
+ }
1128
+
1142
1129
 
1143
- if (depths.length === 0) {
1130
+ /**
1131
+ *
1132
+ * @param node
1133
+ */
1134
+ function countLeaves(node) {
1135
+ if (isLeaf(node)) {
1144
1136
  return 1;
1145
- } else {
1146
- return computeSampleStandardDeviation(depths);
1147
1137
  }
1148
1138
 
1149
- };
1139
+ let result = 0;
1140
+
1141
+ node.traversePreOrderUsingStack(function (node) {
1142
+ if (isLeaf(node)) {
1143
+ result++;
1144
+ }
1145
+ });
1146
+
1147
+ return result;
1148
+ }
1149
+
1150
1150
 
1151
1151
  /**
1152
- *
1153
- * @param {number} x0
1154
- * @param {number} y0
1155
- * @param {number} z0
1156
- * @param {number} x1
1157
- * @param {number} y1
1158
- * @param {number} z1
1159
- * @param {*} value
1160
- * @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
1161
1155
  */
1162
- BinaryNode.prototype.insert = function (x0, y0, z0, x1, y1, z1, value) {
1163
- const leaf = new LeafNode(value);
1164
- leaf.setBounds(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,148 +1186,3 @@ function transplantNewCommonParent(node, child) {
1195
1186
  return bNode;
1196
1187
  }
1197
1188
 
1198
-
1199
- /**
1200
- *
1201
- * @param {Node|BinaryNode|LeafNode} child
1202
- */
1203
- BinaryNode.prototype.insertNode = function (child) {
1204
-
1205
- assert.notNull(child, "child");
1206
- assert.defined(child, "child");
1207
- assert.isInstanceOf(child, Node, 'child', "Node");
1208
- assert.equal(child.parentNode, null, "parentNode must be null, otherwise Node still belongs to another tree");
1209
-
1210
- const sibling = this.__insertion_findSiblingFor(child);
1211
-
1212
- const sibling_parent = sibling.parentNode;
1213
- if (sibling_parent === null) {
1214
- // root node
1215
-
1216
- if (sibling.left === null) {
1217
- sibling.setChildLeft(child);
1218
- } else if (sibling.right === null) {
1219
- sibling.setChildRight(child);
1220
- } else {
1221
- const parent = new BinaryNode();
1222
-
1223
- parent.setChildren(sibling.right, child);
1224
- sibling.right = parent;
1225
- parent.parentNode = sibling;
1226
- }
1227
-
1228
- } else {
1229
-
1230
- // non-root
1231
-
1232
- if (sibling_parent.left === sibling && sibling_parent.right === null) {
1233
-
1234
- // right node is free, take it
1235
- sibling_parent.setChildRight(child);
1236
-
1237
- } else if (sibling_parent.right === sibling && sibling_parent.left === null) {
1238
-
1239
- // left node is free, take it
1240
- sibling_parent.setChildLeft(child);
1241
-
1242
- } else {
1243
-
1244
- transplantNewCommonParent(sibling, child)
1245
-
1246
- }
1247
-
1248
- sibling_parent.bubbleRefit();
1249
-
1250
- }
1251
- }
1252
-
1253
- /**
1254
- * @deprecated
1255
- * @param {Node|BinaryNode|LeafNode} child
1256
- */
1257
- BinaryNode.prototype.insertNode_old = function (child) {
1258
- assert.notNull(child, "child");
1259
- assert.defined(child, "child");
1260
- assert.isInstanceOf(child, Node, 'child', "Node");
1261
- assert.equal(child.parentNode, null, "parentNode must be null, otherwise Node still belongs to another tree");
1262
-
1263
- const node = this.findParentFor(child);
1264
-
1265
- let bNode;
1266
-
1267
- if (
1268
- node.isBinaryNode &&
1269
- !(node.isProtected() && node !== this) // protected node, can't insert into this subtree
1270
- ) {
1271
-
1272
- if (node.left === null) {
1273
- node.left = child;
1274
- child.parentNode = node;
1275
- node.bubbleExpandToFit(child);
1276
-
1277
- } else if (node.right === null) {
1278
-
1279
- node.right = child;
1280
- child.parentNode = node;
1281
- node.bubbleExpandToFit(child);
1282
-
1283
- } else {
1284
- //take right child and insert another binary node there
1285
- bNode = new BinaryNode();
1286
- bNode.setChildren(node.right, child);
1287
- //
1288
- node.right = bNode;
1289
- bNode.parentNode = node;
1290
- node.bubbleExpandToFit(bNode);
1291
- }
1292
-
1293
- node.updateLeafNodeCount();
1294
- } else {
1295
- //need to do transplanting and introduce a new common parent
1296
- transplantNewCommonParent(node, child);
1297
-
1298
- }
1299
-
1300
- };
1301
-
1302
- /**
1303
- * @param {boolean} deep
1304
- * @returns {BinaryNode}
1305
- */
1306
- BinaryNode.prototype.clone = function (deep = false) {
1307
- const clone = new BinaryNode();
1308
-
1309
- clone.x0 = this.x0;
1310
- clone.y0 = this.y0;
1311
- clone.z0 = this.z0;
1312
- clone.x1 = this.x1;
1313
- clone.y1 = this.y1;
1314
- clone.z1 = this.z1;
1315
-
1316
- clone.leafNodeCount = this.leafNodeCount;
1317
-
1318
- clone.parentNode = this.parentNode;
1319
-
1320
- if (deep === true) {
1321
- if (this.left !== null) {
1322
- clone.left = this.left.clone(true);
1323
- clone.left.parentNode = clone;
1324
- }
1325
- if (this.right !== null) {
1326
- clone.right = this.right.clone(true);
1327
- clone.right.parentNode = clone;
1328
- }
1329
- } else {
1330
- clone.left = this.left;
1331
- clone.right = this.right;
1332
- }
1333
-
1334
- clone.modifiers = this.modifiers;
1335
-
1336
- return clone;
1337
- };
1338
-
1339
-
1340
- export {
1341
- BinaryNode
1342
- };