Skip to content

Commit 7b043b6

Browse files
authored
Add files via upload
1 parent 5a3fd8a commit 7b043b6

File tree

1 file changed

+324
-0
lines changed

1 file changed

+324
-0
lines changed

js/BVH_Quick_Builder.js

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
let buildnodes = [];
2+
let leftWorklist = [];
3+
let rightWorklist = [];
4+
let nodesUsed = 1;
5+
let aabb_array_copy;
6+
let k, value, side0, side1, side2;
7+
let bestSplit, goodSplit, okaySplit;
8+
let bestAxis, goodAxis, okayAxis;
9+
let currentMinCorner = new THREE.Vector3();
10+
let currentMaxCorner = new THREE.Vector3();
11+
let testMinCorner = new THREE.Vector3();
12+
let testMaxCorner = new THREE.Vector3();
13+
let testCentroid = new THREE.Vector3();
14+
let spatialAverage = new THREE.Vector3();
15+
16+
17+
function BVH_Node()
18+
{
19+
this.minCorner = new THREE.Vector3();
20+
this.maxCorner = new THREE.Vector3();
21+
this.primitiveCount = 0;
22+
this.leafOrChild_ID = 0;
23+
}
24+
25+
26+
function BVH_Create_Node(worklist, nodeIndex)
27+
{
28+
// re-initialize bounding box extents
29+
currentMinCorner.set(Infinity, Infinity, Infinity);
30+
currentMaxCorner.set(-Infinity, -Infinity, -Infinity);
31+
32+
if (worklist.length == 1)
33+
{
34+
// if we're down to 1 primitive aabb, quickly create a leaf node and return.
35+
k = worklist[0];
36+
// create leaf node
37+
let leafNode = buildnodes[nodeIndex];
38+
leafNode.minCorner.set(aabb_array_copy[9 * k + 0], aabb_array_copy[9 * k + 1], aabb_array_copy[9 * k + 2]);
39+
leafNode.maxCorner.set(aabb_array_copy[9 * k + 3], aabb_array_copy[9 * k + 4], aabb_array_copy[9 * k + 5]);
40+
leafNode.primitiveCount = 1;
41+
leafNode.leafOrChild_ID = k;
42+
return;
43+
} // end if (worklist.length == 1)
44+
45+
else if (worklist.length > 1)
46+
{
47+
// this is where the real work happens: we must sort an arbitrary number of primitives (usually triangles).
48+
// to get a balanced tree, we hope for about half to be placed in left child, other half to be placed in right child.
49+
50+
// construct/grow bounding box around all of the current worklist's primitives
51+
for (let i = 0; i < worklist.length; i++)
52+
{
53+
k = worklist[i];
54+
testMinCorner.set(aabb_array_copy[9 * k + 0], aabb_array_copy[9 * k + 1], aabb_array_copy[9 * k + 2]);
55+
testMaxCorner.set(aabb_array_copy[9 * k + 3], aabb_array_copy[9 * k + 4], aabb_array_copy[9 * k + 5]);
56+
currentMinCorner.min(testMinCorner);
57+
currentMaxCorner.max(testMaxCorner);
58+
}
59+
60+
// create an inner node to represent this newly grown bounding box
61+
let innerNode = buildnodes[nodeIndex];
62+
// this inner node will spawn 2 children: a leftChild and a rightChild
63+
nodesUsed++; // 'nodesUsed' now matches the index of the leftChild (the 1st child to be created)
64+
innerNode.minCorner.copy(currentMinCorner);
65+
innerNode.maxCorner.copy(currentMaxCorner);
66+
innerNode.primitiveCount = 0;//worklist.length;
67+
innerNode.leafOrChild_ID = nodesUsed; // 'innerNode.leafOrChild_ID' now also points to the index of the leftChild
68+
// we must now incrememnt the nodesUsed counter, because a 2nd child (the rightChild) will also be created
69+
nodesUsed++; // 'nodesUsed' now matches the index of the rightChild (the 2nd child to be created)
70+
71+
let leftChildIndex = innerNode.leafOrChild_ID;
72+
let rightChildIndex = innerNode.leafOrChild_ID + 1; // the rightChild's index is always the leftChild's index + 1
73+
74+
// Begin Spatial Median split plane determination and primitive sorting
75+
76+
side0 = currentMaxCorner.x - currentMinCorner.x; // length along X-axis
77+
side1 = currentMaxCorner.y - currentMinCorner.y; // length along Y-axis
78+
side2 = currentMaxCorner.z - currentMinCorner.z; // length along Z-axis
79+
80+
// calculate the middle point of this newly-grown bounding box (aka the 'spatial median')
81+
// this simply uses the spatial average of the longest box extent to determine the split plane,
82+
// which is very fast and results in a fair quality, fairly balanced binary tree structure
83+
spatialAverage.copy(currentMinCorner).add(currentMaxCorner).multiplyScalar(0.5);
84+
85+
// initialize variables
86+
bestAxis = 0; goodAxis = 1; okayAxis = 2;
87+
bestSplit = spatialAverage.x; goodSplit = spatialAverage.y; okaySplit = spatialAverage.z;
88+
89+
// determine the longest extent of the box, and start with that as splitting dimension
90+
if (side0 >= side1 && side0 >= side2)
91+
{
92+
bestAxis = 0;
93+
bestSplit = spatialAverage.x;
94+
if (side1 >= side2)
95+
{
96+
goodAxis = 1;
97+
goodSplit = spatialAverage.y;
98+
okayAxis = 2;
99+
okaySplit = spatialAverage.z;
100+
}
101+
else
102+
{
103+
goodAxis = 2;
104+
goodSplit = spatialAverage.z;
105+
okayAxis = 1;
106+
okaySplit = spatialAverage.y;
107+
}
108+
}
109+
else if (side1 >= side0 && side1 >= side2)
110+
{
111+
bestAxis = 1;
112+
bestSplit = spatialAverage.y;
113+
if (side0 >= side2)
114+
{
115+
goodAxis = 0;
116+
goodSplit = spatialAverage.x;
117+
okayAxis = 2;
118+
okaySplit = spatialAverage.z;
119+
}
120+
else
121+
{
122+
goodAxis = 2;
123+
goodSplit = spatialAverage.z;
124+
okayAxis = 0;
125+
okaySplit = spatialAverage.x;
126+
}
127+
}
128+
else // if (side2 >= side0 && side2 >= side1)
129+
{
130+
bestAxis = 2;
131+
bestSplit = spatialAverage.z;
132+
if (side0 >= side1)
133+
{
134+
goodAxis = 0;
135+
goodSplit = spatialAverage.x;
136+
okayAxis = 1;
137+
okaySplit = spatialAverage.y;
138+
}
139+
else
140+
{
141+
goodAxis = 1;
142+
goodSplit = spatialAverage.y;
143+
okayAxis = 0;
144+
okaySplit = spatialAverage.x;
145+
}
146+
}
147+
148+
149+
// try best axis first, then try the other two if necessary
150+
for (let axis = 0; axis < 3; axis++)
151+
{
152+
// distribute the triangle AABBs in either the left child or right child
153+
leftWorklist = [];
154+
rightWorklist = [];
155+
156+
// this loop is to count how many elements we will need for the left branch and the right branch
157+
for (let i = 0; i < worklist.length; i++)
158+
{
159+
k = worklist[i];
160+
testCentroid.set(aabb_array_copy[9 * k + 6], aabb_array_copy[9 * k + 7], aabb_array_copy[9 * k + 8]);
161+
162+
// get bbox center
163+
if (bestAxis == 0) value = testCentroid.x; // X-axis
164+
else if (bestAxis == 1) value = testCentroid.y; // Y-axis
165+
else value = testCentroid.z; // Z-axis
166+
167+
if (value < bestSplit)
168+
leftWorklist.push(k);
169+
else
170+
rightWorklist.push(k);
171+
172+
}
173+
174+
if (leftWorklist.length > 0 && rightWorklist.length > 0)
175+
{
176+
break; // success, move on to the next part
177+
}
178+
else// if (leftWorklist.length == 0 || rightWorklist.length == 0)
179+
{
180+
// try another axis
181+
if (axis == 0)
182+
{
183+
bestAxis = goodAxis;
184+
bestSplit = goodSplit;
185+
}
186+
else if (axis == 1)
187+
{
188+
bestAxis = okayAxis;
189+
bestSplit = okaySplit;
190+
}
191+
}
192+
193+
} // end for (let axis = 0; axis < 3; axis++)
194+
195+
196+
// if the below if statement is true, then we have successfully sorted the primitive(triangle) AABBs
197+
if (leftWorklist.length > 0 && rightWorklist.length > 0)
198+
{
199+
let leftWorklistCopy = new Uint32Array(leftWorklist);
200+
let rightWorklistCopy = new Uint32Array(rightWorklist);
201+
// recurse
202+
BVH_Create_Node(leftWorklistCopy, leftChildIndex);
203+
BVH_Create_Node(rightWorklistCopy, rightChildIndex);
204+
}
205+
206+
else //if (leftWorklist.length == 0 || rightWorklist.length == 0)
207+
{
208+
// if we reached this point, the builder failed to find a decent splitting plane axis, so
209+
// we try another strategy to populate the current leftWorkLists and rightWorklists.
210+
leftWorklist = [];
211+
rightWorklist = [];
212+
213+
spatialAverage.set(0, 0, 0);
214+
215+
// this loop is to count how many elements we will need for the left branch and the right branch
216+
for (let i = 0; i < worklist.length; i++)
217+
{
218+
k = worklist[i];
219+
testCentroid.set(aabb_array_copy[9 * k + 6], aabb_array_copy[9 * k + 7], aabb_array_copy[9 * k + 8]);
220+
spatialAverage.add(testCentroid);
221+
}
222+
spatialAverage.multiplyScalar(1 / worklist.length);
223+
224+
for (let i = 0; i < worklist.length; i++)
225+
{
226+
k = worklist[i];
227+
testCentroid.set(aabb_array_copy[9 * k + 6], aabb_array_copy[9 * k + 7], aabb_array_copy[9 * k + 8]);
228+
229+
if (testCentroid.x != spatialAverage.x)
230+
{
231+
if (testCentroid.x < spatialAverage.x)
232+
leftWorklist.push(k);
233+
else
234+
rightWorklist.push(k);
235+
}
236+
else if (testCentroid.y != spatialAverage.y)
237+
{
238+
if (testCentroid.y < spatialAverage.y)
239+
leftWorklist.push(k);
240+
else
241+
rightWorklist.push(k);
242+
}
243+
else if (testCentroid.z != spatialAverage.z)
244+
{
245+
if (testCentroid.z < spatialAverage.z)
246+
leftWorklist.push(k);
247+
else
248+
rightWorklist.push(k);
249+
}
250+
}
251+
252+
let leftWorklistCopy = new Uint32Array(leftWorklist);
253+
let rightWorklistCopy = new Uint32Array(rightWorklist);
254+
// recurse
255+
BVH_Create_Node(leftWorklistCopy, leftChildIndex);
256+
BVH_Create_Node(rightWorklistCopy, rightChildIndex);
257+
258+
} // end else //if (leftWorklist.length == 0 || rightWorklist.length == 0)
259+
260+
if (leftWorklist.length == 0 || rightWorklist.length == 0)
261+
{
262+
//console.log("entered fail case, worklist item count: " + worklist.length);
263+
// if we reached this point, the builder failed to find a decent splitting plane axis, so
264+
// manually populate the current leftWorkLists and rightWorklists.
265+
leftWorklist = [];
266+
rightWorklist = [];
267+
268+
for (let i = 0; i < worklist.length; i++)
269+
{
270+
k = worklist[i];
271+
if (i % 2 != 0)
272+
leftWorklist.push(k);
273+
else
274+
rightWorklist.push(k);
275+
}
276+
277+
let leftWorklistCopy = new Uint32Array(leftWorklist);
278+
let rightWorklistCopy = new Uint32Array(rightWorklist);
279+
// recurse
280+
BVH_Create_Node(leftWorklistCopy, leftChildIndex);
281+
BVH_Create_Node(rightWorklistCopy, rightChildIndex);
282+
} // end if (leftWorklist.length == 0 || rightWorklist.length == 0)
283+
284+
} // end if (worklist.length > 1)
285+
286+
287+
return; // finished
288+
289+
} // end function BVH_Create_Node(worklist, nodeIndex)
290+
291+
292+
293+
function BVH_QuickBuild(primitiveAABB_IndexList, aabb_array)
294+
{
295+
// the user of this builder has to supply the aabb_array. Then we make a copy of the aabb_array,
296+
// so that this builder can use it, while referring to it with a generic variable name (like 'aabb_array_copy').
297+
// This allows users to build multiple different BVHs for all scene models, while using the same BVH_QuickBuild() function
298+
aabb_array_copy = new Float32Array(aabb_array);
299+
300+
// the 'primitiveAABB_IndexList' is a raw list of integer numbers in simple sequential order [0,1,2,3,4,5,6,..N-1](one for every primitive),
301+
// where each number refers to a unique triangle (or other type of primitive) from the model's unordered 'triangle soup'(or 'primitive soup').
302+
303+
// now build root node (root nodeIndex = 0), and then recursively build the rest of the binary tree
304+
BVH_Create_Node(primitiveAABB_IndexList, 0);
305+
306+
let nx8 = 0;
307+
// Copy the buildnodes array into the aabb_array
308+
for (let n = 0; n < buildnodes.length; n++)
309+
{
310+
nx8 = n * 8;
311+
// slot 0
312+
aabb_array[nx8 + 0] = buildnodes[n].minCorner.x; // r or x component
313+
aabb_array[nx8 + 1] = buildnodes[n].minCorner.y; // g or y component
314+
aabb_array[nx8 + 2] = buildnodes[n].minCorner.z; // b or z component
315+
aabb_array[nx8 + 3] = buildnodes[n].maxCorner.x; // a or w component
316+
317+
// slot 1
318+
aabb_array[nx8 + 4] = buildnodes[n].maxCorner.y; // r or x component
319+
aabb_array[nx8 + 5] = buildnodes[n].maxCorner.z; // g or y component
320+
aabb_array[nx8 + 6] = buildnodes[n].primitiveCount; // b or z component
321+
aabb_array[nx8 + 7] = buildnodes[n].leafOrChild_ID; // a or w component
322+
}
323+
324+
} // end function BVH_QuickBuild(primitiveAABB_IndexList, aabb_array)

0 commit comments

Comments
 (0)