@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.
- package/core/assert.js +11 -0
- package/core/binary/BitSet.js +5 -0
- package/core/bvh2/BinaryNode.js +880 -848
- package/core/bvh2/LeafNode.js +80 -82
- package/core/bvh2/Node.d.ts +3 -0
- package/core/bvh2/Node.js +111 -114
- package/core/bvh2/aabb3/AABB3.js +50 -4
- package/core/bvh2/aabb3/AABB3.spec.js +27 -1
- package/core/bvh2/aabb3/AABB3Math.js +1 -1
- package/core/bvh2/aabb3/aabb3_from_v3_array.js +34 -0
- package/core/bvh2/aabb3/aabb3_from_v3_array_transformed.js +39 -0
- package/core/bvh2/aabb3/aabb3_intersects_frustum_degree.js +46 -0
- package/core/bvh2/transform/RotationOptimizer.js +2 -262
- package/core/bvh2/transform/tryRotateSingleNode.js +259 -0
- package/core/bvh2/traversal/ThreeFrustumsIntersectionBVHVisitor.js +16 -35
- package/core/bvh2/traversal/queryBinaryNode_FrustumIntersections.js +52 -0
- package/core/collection/list/List.js +0 -34
- package/core/events/signal/Signal.js +1 -1
- package/core/geom/3d/aabb/aabb3_matrix4_project.js +3 -4
- package/core/geom/3d/aabb/aabb3_matrix4_project.spec.js +13 -0
- package/core/geom/3d/triangle/computeTriangleRayIntersection.js +1 -2
- package/core/geom/Quaternion.d.ts +2 -0
- package/core/geom/Vector3.js +22 -11
- package/engine/asset/loaders/ArrayBufferLoader.js +7 -0
- package/engine/asset/loaders/image/ImageRGBADataLoader.js +11 -6
- package/engine/ecs/EntityBuilder.js +12 -5
- package/engine/ecs/EntityComponentDataset.js +26 -6
- package/engine/ecs/attachment/AttachmentSystem.js +1 -1
- package/engine/ecs/attachment/TransformAttachmentBinding.js +0 -1
- package/engine/ecs/foliage/FoliageVisibilitySetBuilder.js +1 -31
- package/engine/ecs/foliage/InstancedFoliage.js +85 -152
- package/engine/ecs/foliage/ViewState.js +190 -0
- package/engine/ecs/foliage/ecs/Foliage2System.js +82 -19
- package/engine/ecs/foliage/ecs/InstancedMeshComponent.js +30 -0
- package/engine/ecs/foliage/ecs/InstancedMeshLayer.js +16 -0
- package/engine/ecs/foliage/ecs/InstancedMeshUtils.js +2 -2
- package/engine/ecs/parent/EntityNode.js +31 -49
- package/engine/ecs/sockets/AttachmentSockets.js +11 -1
- package/engine/ecs/transform-attachment/TransformAttachment.js +88 -0
- package/engine/ecs/transform-attachment/TransformAttachmentSystem.js +59 -0
- package/engine/graphics/GraphicsEngine.js +23 -2
- package/engine/graphics/ecs/camera/Camera.js +33 -16
- package/engine/graphics/ecs/camera/unprojectPoint.js +14 -0
- package/engine/graphics/ecs/light/binding/three/ThreeLightBinding.js +2 -19
- package/engine/graphics/ecs/mesh/updateNodeByTransformAndBBB.js +21 -21
- package/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +9 -1
- package/engine/graphics/ecs/mesh-v2/ShadedGeometryFlags.js +4 -1
- package/engine/graphics/ecs/mesh-v2/sample/load_gltf.js +40 -8
- package/engine/graphics/ecs/mesh-v2/three_object_to_entity_composition.js +3 -3
- package/engine/graphics/ecs/water2/shader/AlexWaterShader.js +7 -1
- package/engine/graphics/geometry/instancing/InstancedMeshGroup.js +17 -11
- package/engine/graphics/micron/build/buildMicronGeometryFromBufferGeometry.js +1 -34
- package/engine/graphics/micron/build/fill_patch_geometry_data.js +12 -0
- package/engine/graphics/micron/format/MicronGeometry.js +21 -0
- package/engine/graphics/micron/plugin/MicronRenderPlugin.js +0 -3
- package/engine/graphics/micron/prototypeVirtualGeometry.js +69 -1
- package/engine/graphics/particles/particular/engine/renderers/billboard/ParticleBillboardMaterial.js +5 -1
- package/engine/graphics/render/layers/RenderLayer.js +22 -85
- package/engine/graphics/render/visibility/VisibilityComputer.js +23 -103
- package/engine/sound/ecs/emitter/SoundEmitterSystem.js +1 -1
- package/package.json +1 -1
- package/samples/terrain/editor.js +0 -1
package/core/bvh2/BinaryNode.js
CHANGED
|
@@ -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
|
-
* @
|
|
28
|
+
* @type {number}
|
|
29
29
|
*/
|
|
30
|
-
|
|
31
|
-
Protected: 1,
|
|
32
|
-
Marked1: 2,
|
|
33
|
-
Marked2: 4,
|
|
34
|
-
Marked3: 8
|
|
35
|
-
};
|
|
36
|
-
|
|
30
|
+
let stackPointer = 0;
|
|
37
31
|
/**
|
|
38
|
-
*
|
|
39
|
-
* @
|
|
32
|
+
*
|
|
33
|
+
* @type {Node[]}
|
|
40
34
|
*/
|
|
41
|
-
const
|
|
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
|
-
* @
|
|
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
|
-
|
|
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
|
-
* @
|
|
105
|
+
* Traverse leaf nodes in a fast manner
|
|
106
|
+
* @param {function(node:LeafNode)} visitor
|
|
107
|
+
* @param {*} [thisArg]
|
|
51
108
|
*/
|
|
52
|
-
|
|
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
|
-
* @
|
|
145
|
+
* Contains no children
|
|
146
|
+
* @returns {boolean}
|
|
56
147
|
*/
|
|
57
|
-
|
|
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
|
-
|
|
156
|
+
this.leafNodeCount = 0;
|
|
157
|
+
|
|
158
|
+
this.setNegativelyInfiniteBounds();
|
|
159
|
+
}
|
|
60
160
|
|
|
61
161
|
/**
|
|
62
|
-
*
|
|
63
|
-
* @
|
|
162
|
+
*
|
|
163
|
+
* @param {Node|BinaryNode} left
|
|
164
|
+
* @param {Node|BinaryNode} right
|
|
64
165
|
*/
|
|
65
|
-
|
|
166
|
+
setChildren(left, right) {
|
|
167
|
+
this.left = left;
|
|
168
|
+
this.right = right;
|
|
66
169
|
|
|
67
|
-
|
|
68
|
-
this.y0 = 0;
|
|
69
|
-
this.z0 = 0;
|
|
170
|
+
this.updateLeafNodeCount()
|
|
70
171
|
|
|
71
|
-
|
|
72
|
-
this.y1 = 0;
|
|
73
|
-
this.z1 = 0;
|
|
74
|
-
};
|
|
172
|
+
this.refitFor2();
|
|
75
173
|
|
|
76
|
-
|
|
174
|
+
left.parentNode = this;
|
|
175
|
+
right.parentNode = this;
|
|
176
|
+
}
|
|
77
177
|
|
|
78
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
194
|
+
/**
|
|
195
|
+
*
|
|
196
|
+
* @param {Node} child
|
|
197
|
+
*/
|
|
198
|
+
setChildRight(child) {
|
|
199
|
+
assert.isNull(this.right, 'right');
|
|
102
200
|
|
|
103
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
this.right = null;
|
|
207
|
+
child.parentNode = this;
|
|
208
|
+
}
|
|
119
209
|
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
223
|
+
while (cursor > 0) {
|
|
224
|
+
const node = open_set[--cursor];
|
|
135
225
|
|
|
136
|
-
|
|
226
|
+
const cost = inclusion_surface_area_heuristic(node, box);
|
|
137
227
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
228
|
+
if (cost < best_cost) {
|
|
229
|
+
best_cost = cost;
|
|
230
|
+
best_node = node;
|
|
231
|
+
}
|
|
141
232
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
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
|
-
|
|
161
|
-
|
|
254
|
+
return best_node;
|
|
255
|
+
}
|
|
162
256
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
n
|
|
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
|
-
|
|
185
|
-
|
|
277
|
+
const aIsBinary = a.isBinaryNode;
|
|
278
|
+
const bIsBinary = b.isBinaryNode;
|
|
186
279
|
|
|
187
|
-
|
|
280
|
+
aCost = a.costForInclusion(box);
|
|
281
|
+
bCost = b.costForInclusion(box);
|
|
188
282
|
|
|
189
|
-
if (
|
|
283
|
+
if (aCost === bCost) {
|
|
190
284
|
|
|
191
|
-
|
|
285
|
+
if (a.isLeafNode) {
|
|
192
286
|
|
|
193
|
-
|
|
287
|
+
return a;
|
|
194
288
|
|
|
195
|
-
|
|
289
|
+
} else if (b.isLeafNode) {
|
|
196
290
|
|
|
197
|
-
|
|
291
|
+
return b;
|
|
198
292
|
|
|
199
|
-
|
|
200
|
-
aCost = a.computeSAH();
|
|
201
|
-
bCost = b.computeSAH();
|
|
202
|
-
}
|
|
293
|
+
}
|
|
203
294
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
217
|
-
if (
|
|
218
|
-
|
|
305
|
+
|
|
306
|
+
if (aCost < bCost) {
|
|
307
|
+
if (aIsBinary) {
|
|
308
|
+
n = a;
|
|
309
|
+
} else {
|
|
310
|
+
return a;
|
|
311
|
+
}
|
|
219
312
|
} else {
|
|
220
|
-
|
|
313
|
+
if (bIsBinary) {
|
|
314
|
+
n = b;
|
|
315
|
+
} else {
|
|
316
|
+
return b;
|
|
317
|
+
}
|
|
221
318
|
}
|
|
222
319
|
}
|
|
223
320
|
}
|
|
224
|
-
};
|
|
225
321
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
281
|
-
|
|
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
|
-
|
|
289
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
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
|
-
|
|
391
|
+
n.refit();
|
|
305
392
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
403
|
+
n = n.parentNode;
|
|
311
404
|
|
|
312
|
-
n
|
|
405
|
+
if (n === null) {
|
|
406
|
+
// hit the root
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
313
409
|
|
|
314
|
-
const traverseDeeper = visitor.call(thisArg, n);
|
|
315
410
|
|
|
316
|
-
if
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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
|
-
|
|
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
|
-
|
|
440
|
+
if (!flag) {
|
|
441
|
+
//no violation, we can stop here
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
329
445
|
}
|
|
330
446
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
343
|
-
|
|
464
|
+
return result;
|
|
465
|
+
}
|
|
344
466
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
467
|
+
countDescendants() {
|
|
468
|
+
let result = 0;
|
|
469
|
+
this.traversePreOrderUsingStack(function (node) {
|
|
470
|
+
result++;
|
|
471
|
+
});
|
|
472
|
+
return result;
|
|
348
473
|
}
|
|
349
474
|
|
|
350
475
|
/**
|
|
351
|
-
* @
|
|
352
|
-
* @
|
|
353
|
-
* @returns {number}
|
|
476
|
+
* @deprecated Incorrect usage of SAH
|
|
477
|
+
* @return {number}
|
|
354
478
|
*/
|
|
355
|
-
|
|
356
|
-
let
|
|
479
|
+
computeSAH() {
|
|
480
|
+
let leftLeaves, rightLeaves, leftArea, rightArea;
|
|
357
481
|
|
|
358
|
-
const
|
|
482
|
+
const left = this.left;
|
|
359
483
|
|
|
360
|
-
|
|
484
|
+
if (left === null) {
|
|
485
|
+
leftArea = 0;
|
|
486
|
+
leftLeaves = 0;
|
|
487
|
+
} else {
|
|
488
|
+
leftArea = boxSurfaceArea2(left);
|
|
489
|
+
leftLeaves = left.leafNodeCount;
|
|
490
|
+
}
|
|
361
491
|
|
|
362
|
-
|
|
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
|
-
|
|
501
|
+
const thisArea = boxSurfaceArea2(this);
|
|
365
502
|
|
|
366
|
-
|
|
503
|
+
return surfaceAreaHeuristic(thisArea, leftArea, rightArea, leftLeaves, rightLeaves);
|
|
504
|
+
}
|
|
367
505
|
|
|
368
|
-
|
|
506
|
+
updateLeafNodeCount() {
|
|
507
|
+
let node = this;
|
|
369
508
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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
|
-
|
|
522
|
+
const left = this.left;
|
|
384
523
|
|
|
385
|
-
|
|
386
|
-
|
|
524
|
+
if (left !== null) {
|
|
525
|
+
if (left.isLeafNode) {
|
|
526
|
+
n++;
|
|
527
|
+
} else {
|
|
528
|
+
n += left.leafNodeCount;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
387
531
|
|
|
388
|
-
|
|
389
|
-
})();
|
|
532
|
+
node.leafNodeCount = n;
|
|
390
533
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
534
|
+
// propagate update up the tree
|
|
535
|
+
node = node.parentNode;
|
|
536
|
+
}
|
|
394
537
|
|
|
395
|
-
|
|
396
|
-
this.y0 = min2(a.y0, b.y0);
|
|
397
|
-
this.z0 = min2(a.z0, b.z0);
|
|
538
|
+
}
|
|
398
539
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
405
|
-
|
|
545
|
+
/**
|
|
546
|
+
*
|
|
547
|
+
* @type {BinaryNode}
|
|
548
|
+
*/
|
|
549
|
+
const r = this.right;
|
|
406
550
|
|
|
407
|
-
|
|
551
|
+
const l = this.left;
|
|
408
552
|
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
556
|
+
this.left = r;
|
|
557
|
+
this.right = rR;
|
|
420
558
|
|
|
421
|
-
if (
|
|
422
|
-
|
|
559
|
+
if (rR !== null) {
|
|
560
|
+
rR.parentNode = this;
|
|
423
561
|
}
|
|
424
562
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
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
|
-
|
|
489
|
-
|
|
580
|
+
const lL = l.left;
|
|
581
|
+
const lR = l.right;
|
|
490
582
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
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
|
-
|
|
503
|
-
*/
|
|
504
|
-
function countLeaves(node) {
|
|
505
|
-
if (isLeaf(node)) {
|
|
506
|
-
return 1;
|
|
594
|
+
l.updateLeafNodeCount();
|
|
595
|
+
|
|
596
|
+
l.bubbleRefit();
|
|
507
597
|
}
|
|
508
598
|
|
|
509
|
-
|
|
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
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
-
|
|
546
|
-
};
|
|
632
|
+
const numNodesMod2 = remaining_nodes & 1; //faster version of %2
|
|
547
633
|
|
|
548
|
-
|
|
549
|
-
|
|
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
|
-
|
|
639
|
+
remaining_nodes = (remaining_nodes >> 1) + numNodesMod2;
|
|
640
|
+
}
|
|
552
641
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
n
|
|
556
|
-
|
|
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
|
-
|
|
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
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
-
|
|
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
|
-
|
|
574
|
-
|
|
689
|
+
const right = node.right;
|
|
690
|
+
if (right !== null) {
|
|
691
|
+
stack[stackPointer++] = right;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
575
695
|
|
|
576
|
-
|
|
577
|
-
// bubble up
|
|
578
|
-
parentNode.updateLeafNodeCount();
|
|
696
|
+
return count;
|
|
579
697
|
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
698
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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
|
-
* @
|
|
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
|
-
|
|
721
|
+
threeTraverseFrustumsIntersections(frustums, visitor) {
|
|
722
|
+
const numFrustums = frustums.length;
|
|
593
723
|
|
|
594
|
-
|
|
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
|
-
|
|
597
|
-
const rR = r.right;
|
|
733
|
+
for (; i < numFrustums; i++) {
|
|
598
734
|
|
|
599
|
-
|
|
600
|
-
this.right = rR;
|
|
735
|
+
const degree = node.intersectFrustumDegree(frustums[i]);
|
|
601
736
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
}
|
|
737
|
+
if (degree === 2) {
|
|
738
|
+
//completely inside frustum
|
|
605
739
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
740
|
+
if (node.isLeafNode) {
|
|
741
|
+
visitor(node, true);
|
|
742
|
+
} else {
|
|
743
|
+
node.traverseLeavesPreOrderUsingStack(fastVisitLeafNodeIntersection, visitor);
|
|
744
|
+
}
|
|
610
745
|
|
|
611
|
-
|
|
746
|
+
//prevent further traversal
|
|
747
|
+
return false;
|
|
612
748
|
|
|
613
|
-
|
|
749
|
+
} else if (degree === 1) {
|
|
614
750
|
|
|
615
|
-
|
|
616
|
-
|
|
751
|
+
//partially inside frustum
|
|
752
|
+
b = true;
|
|
617
753
|
|
|
618
|
-
|
|
619
|
-
|
|
754
|
+
}
|
|
755
|
+
}
|
|
620
756
|
|
|
621
|
-
|
|
757
|
+
if (!b) {
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
622
760
|
|
|
623
|
-
|
|
624
|
-
|
|
761
|
+
if (node.isLeafNode) {
|
|
762
|
+
visitor(node, false);
|
|
763
|
+
}
|
|
625
764
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
629
767
|
|
|
630
|
-
|
|
631
|
-
if (r !== null) {
|
|
632
|
-
r.parentNode = l;
|
|
768
|
+
return this.traversePreOrderUsingStack(visitNode);
|
|
633
769
|
}
|
|
634
770
|
|
|
635
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
798
|
+
function isFlagSet(mask, index) {
|
|
799
|
+
return mask & (1 << index) !== 0;
|
|
800
|
+
}
|
|
655
801
|
|
|
656
|
-
|
|
657
|
-
|
|
802
|
+
function clearFlag(mask, index) {
|
|
803
|
+
return mask & ~(1 << index);
|
|
804
|
+
}
|
|
658
805
|
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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
|
-
|
|
703
|
-
|
|
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
|
-
|
|
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
|
-
|
|
939
|
+
/**
|
|
940
|
+
* Fraction of balanced nodes in the tree (those that have both children)
|
|
941
|
+
* @returns {number}
|
|
942
|
+
*/
|
|
943
|
+
computeBalanceFactor() {
|
|
717
944
|
|
|
718
|
-
|
|
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
|
-
|
|
731
|
-
result[count++] = node;
|
|
948
|
+
const depths = [];
|
|
732
949
|
|
|
733
|
-
|
|
734
|
-
|
|
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
|
-
|
|
741
|
-
|
|
742
|
-
stack[stackPointer++] = right;
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
}
|
|
953
|
+
depths.push(depth);
|
|
954
|
+
};
|
|
746
955
|
|
|
747
|
-
|
|
748
|
-
};
|
|
956
|
+
traverseBinaryNodeUsingVisitor(this, bvhVisitor)
|
|
749
957
|
|
|
750
|
-
|
|
751
|
-
|
|
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
|
|
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
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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 {
|
|
787
|
-
* @returns {boolean}
|
|
985
|
+
* @param {Node|BinaryNode|LeafNode} child
|
|
788
986
|
*/
|
|
789
|
-
|
|
790
|
-
let b = false;
|
|
791
|
-
let i = 0;
|
|
792
|
-
|
|
793
|
-
for (; i < numFrustums; i++) {
|
|
987
|
+
insertNode(child) {
|
|
794
988
|
|
|
795
|
-
|
|
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
|
-
|
|
798
|
-
//completely inside frustum
|
|
994
|
+
const sibling = this.__insertion_findSiblingFor(child);
|
|
799
995
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
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
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
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
|
-
|
|
818
|
-
return false;
|
|
819
|
-
}
|
|
1012
|
+
} else {
|
|
820
1013
|
|
|
821
|
-
|
|
822
|
-
visitor(node, false);
|
|
823
|
-
}
|
|
1014
|
+
// non-root
|
|
824
1015
|
|
|
825
|
-
|
|
826
|
-
}
|
|
1016
|
+
if (sibling_parent.left === sibling && sibling_parent.right === null) {
|
|
827
1017
|
|
|
828
|
-
|
|
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
|
-
|
|
841
|
-
|
|
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
|
-
|
|
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
|
-
|
|
859
|
-
return mask & (1 << index) !== 0;
|
|
860
|
-
}
|
|
1030
|
+
}
|
|
861
1031
|
|
|
862
|
-
|
|
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
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
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
|
-
|
|
912
|
-
}
|
|
1051
|
+
clone.leafNodeCount = this.leafNodeCount;
|
|
913
1052
|
|
|
914
|
-
|
|
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
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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
|
-
|
|
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
|
-
|
|
952
|
-
}
|
|
1069
|
+
clone.modifiers = this.modifiers;
|
|
953
1070
|
|
|
954
|
-
|
|
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
|
-
|
|
965
|
-
|
|
1075
|
+
/**
|
|
1076
|
+
* @readonly
|
|
1077
|
+
* @type {boolean}
|
|
1078
|
+
*/
|
|
1079
|
+
BinaryNode.prototype.isBinaryNode = true;
|
|
966
1080
|
|
|
967
1081
|
/**
|
|
968
|
-
*
|
|
969
|
-
* @param {
|
|
970
|
-
* @param {
|
|
971
|
-
* @
|
|
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
|
-
|
|
980
|
-
|
|
981
|
-
|
|
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
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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
|
-
|
|
989
|
-
|
|
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
|
-
*
|
|
1001
|
-
* @
|
|
1100
|
+
*
|
|
1101
|
+
* @param {BinaryNode|LeafNode} node
|
|
1102
|
+
* @param {AABB3} addition
|
|
1002
1103
|
*/
|
|
1003
|
-
|
|
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
|
-
|
|
1110
|
+
r += n.costForInclusion(addition);
|
|
1006
1111
|
|
|
1112
|
+
n = n.parentNode;
|
|
1113
|
+
}
|
|
1007
1114
|
|
|
1008
|
-
|
|
1115
|
+
return r;
|
|
1116
|
+
}
|
|
1009
1117
|
|
|
1010
|
-
bvhVisitor.visitLeaf = (leaf) => {
|
|
1011
|
-
const depth = leaf.computeDepth();
|
|
1012
1118
|
|
|
1013
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
1029
|
-
* @param {
|
|
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
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
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
|
-
};
|