Last active
February 20, 2025 21:15
-
-
Save erichlof/0f054df0ed07b9557a017d17d41dff09 to your computer and use it in GitHub Desktop.
BVH_Quick_Builder.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let buildnodes = []; | |
let leftWorklist = []; | |
let rightWorklist = []; | |
let nodesUsed = 1; | |
let aabb_array_copy; | |
let k, value, side0, side1, side2; | |
let bestSplit, goodSplit, okaySplit; | |
let bestAxis, goodAxis, okayAxis; | |
let currentMinCorner = new THREE.Vector3(); | |
let currentMaxCorner = new THREE.Vector3(); | |
let testMinCorner = new THREE.Vector3(); | |
let testMaxCorner = new THREE.Vector3(); | |
let testCentroid = new THREE.Vector3(); | |
let spatialAverage = new THREE.Vector3(); | |
function BVH_Node() | |
{ | |
this.minCorner = new THREE.Vector3(); | |
this.maxCorner = new THREE.Vector3(); | |
this.primitiveCount = 0; | |
this.leafOrChild_ID = 0; | |
} | |
function BVH_Create_Node(worklist, nodeIndex) | |
{ | |
// re-initialize bounding box extents | |
currentMinCorner.set(Infinity, Infinity, Infinity); | |
currentMaxCorner.set(-Infinity, -Infinity, -Infinity); | |
if (worklist.length == 1) | |
{ | |
// if we're down to 1 primitive aabb, quickly create a leaf node and return. | |
k = worklist[0]; | |
// create leaf node | |
let leafNode = buildnodes[nodeIndex]; | |
leafNode.minCorner.set(aabb_array_copy[9 * k + 0], aabb_array_copy[9 * k + 1], aabb_array_copy[9 * k + 2]); | |
leafNode.maxCorner.set(aabb_array_copy[9 * k + 3], aabb_array_copy[9 * k + 4], aabb_array_copy[9 * k + 5]); | |
leafNode.primitiveCount = 1; | |
leafNode.leafOrChild_ID = k; | |
return; | |
} // end if (worklist.length == 1) | |
else if (worklist.length > 1) | |
{ | |
// this is where the real work happens: we must sort an arbitrary number of primitives (usually triangles). | |
// to get a balanced tree, we hope for about half to be placed in left child, other half to be placed in right child. | |
// construct/grow bounding box around all of the current worklist's primitives | |
for (let i = 0; i < worklist.length; i++) | |
{ | |
k = worklist[i]; | |
testMinCorner.set(aabb_array_copy[9 * k + 0], aabb_array_copy[9 * k + 1], aabb_array_copy[9 * k + 2]); | |
testMaxCorner.set(aabb_array_copy[9 * k + 3], aabb_array_copy[9 * k + 4], aabb_array_copy[9 * k + 5]); | |
currentMinCorner.min(testMinCorner); | |
currentMaxCorner.max(testMaxCorner); | |
} | |
// create an inner node to represent this newly grown bounding box | |
let innerNode = buildnodes[nodeIndex]; | |
nodesUsed++; | |
innerNode.minCorner.copy(currentMinCorner); | |
innerNode.maxCorner.copy(currentMaxCorner); | |
innerNode.primitiveCount = 0;//worklist.length; | |
innerNode.leafOrChild_ID = nodesUsed; | |
nodesUsed++; | |
let leftChildIndex = innerNode.leafOrChild_ID; | |
let rightChildIndex = innerNode.leafOrChild_ID + 1; | |
// Begin Spatial Median split plane determination and primitive sorting | |
side0 = currentMaxCorner.x - currentMinCorner.x; // length along X-axis | |
side1 = currentMaxCorner.y - currentMinCorner.y; // length along Y-axis | |
side2 = currentMaxCorner.z - currentMinCorner.z; // length along Z-axis | |
// calculate the middle point of this newly-grown bounding box (aka the 'spatial median') | |
// this simply uses the spatial average of the longest box extent to determine the split plane, | |
// which is very fast and results in a fair quality, fairly balanced binary tree structure | |
spatialAverage.copy(currentMinCorner).add(currentMaxCorner).multiplyScalar(0.5); | |
// initialize variables | |
bestAxis = 0; goodAxis = 1; okayAxis = 2; | |
bestSplit = spatialAverage.x; goodSplit = spatialAverage.y; okaySplit = spatialAverage.z; | |
// determine the longest extent of the box, and start with that as splitting dimension | |
if (side0 >= side1 && side0 >= side2) | |
{ | |
bestAxis = 0; | |
bestSplit = spatialAverage.x; | |
if (side1 >= side2) | |
{ | |
goodAxis = 1; | |
goodSplit = spatialAverage.y; | |
okayAxis = 2; | |
okaySplit = spatialAverage.z; | |
} | |
else | |
{ | |
goodAxis = 2; | |
goodSplit = spatialAverage.z; | |
okayAxis = 1; | |
okaySplit = spatialAverage.y; | |
} | |
} | |
else if (side1 >= side0 && side1 >= side2) | |
{ | |
bestAxis = 1; | |
bestSplit = spatialAverage.y; | |
if (side0 >= side2) | |
{ | |
goodAxis = 0; | |
goodSplit = spatialAverage.x; | |
okayAxis = 2; | |
okaySplit = spatialAverage.z; | |
} | |
else | |
{ | |
goodAxis = 2; | |
goodSplit = spatialAverage.z; | |
okayAxis = 0; | |
okaySplit = spatialAverage.x; | |
} | |
} | |
else // if (side2 >= side0 && side2 >= side1) | |
{ | |
bestAxis = 2; | |
bestSplit = spatialAverage.z; | |
if (side0 >= side1) | |
{ | |
goodAxis = 0; | |
goodSplit = spatialAverage.x; | |
okayAxis = 1; | |
okaySplit = spatialAverage.y; | |
} | |
else | |
{ | |
goodAxis = 1; | |
goodSplit = spatialAverage.y; | |
okayAxis = 0; | |
okaySplit = spatialAverage.x; | |
} | |
} | |
// try best axis first, then try the other two if necessary | |
for (let axis = 0; axis < 3; axis++) | |
{ | |
// distribute the triangle AABBs in either the left child or right child | |
leftWorklist = []; | |
rightWorklist = []; | |
// this loop is to count how many elements we will need for the left branch and the right branch | |
for (let i = 0; i < worklist.length; i++) | |
{ | |
k = worklist[i]; | |
testCentroid.set(aabb_array_copy[9 * k + 6], aabb_array_copy[9 * k + 7], aabb_array_copy[9 * k + 8]); | |
// get bbox center | |
if (bestAxis == 0) value = testCentroid.x; // X-axis | |
else if (bestAxis == 1) value = testCentroid.y; // Y-axis | |
else value = testCentroid.z; // Z-axis | |
if (value < bestSplit) | |
leftWorklist.push(k); | |
else | |
rightWorklist.push(k); | |
} | |
if (leftWorklist.length > 0 && rightWorklist.length > 0) | |
{ | |
break; // success, move on to the next part | |
} | |
else// if (leftWorklist.length == 0 || rightWorklist.length == 0) | |
{ | |
// try another axis | |
if (axis == 0) | |
{ | |
bestAxis = goodAxis; | |
bestSplit = goodSplit; | |
} | |
else if (axis == 1) | |
{ | |
bestAxis = okayAxis; | |
bestSplit = okaySplit; | |
} | |
} | |
} // end for (let axis = 0; axis < 3; axis++) | |
// if the below if statement is true, then we have successfully sorted the primitive(triangle) AABBs | |
if (leftWorklist.length > 0 && rightWorklist.length > 0) | |
{ | |
let leftWorklistCopy = new Uint32Array(leftWorklist); | |
let rightWorklistCopy = new Uint32Array(rightWorklist); | |
// recurse | |
BVH_Create_Node(leftWorklistCopy, leftChildIndex); | |
BVH_Create_Node(rightWorklistCopy, rightChildIndex); | |
} | |
else //if (leftWorklist.length == 0 || rightWorklist.length == 0) | |
{ | |
// if we reached this point, the builder failed to find a decent splitting plane axis, so | |
// we try another strategy to populate the current leftWorkLists and rightWorklists. | |
leftWorklist = []; | |
rightWorklist = []; | |
spatialAverage.set(0, 0, 0); | |
// this loop is to count how many elements we will need for the left branch and the right branch | |
for (let i = 0; i < worklist.length; i++) | |
{ | |
k = worklist[i]; | |
testCentroid.set(aabb_array_copy[9 * k + 6], aabb_array_copy[9 * k + 7], aabb_array_copy[9 * k + 8]); | |
spatialAverage.add(testCentroid); | |
} | |
spatialAverage.multiplyScalar(1 / worklist.length); | |
for (let i = 0; i < worklist.length; i++) | |
{ | |
k = worklist[i]; | |
testCentroid.set(aabb_array_copy[9 * k + 6], aabb_array_copy[9 * k + 7], aabb_array_copy[9 * k + 8]); | |
if (testCentroid.x != spatialAverage.x) | |
{ | |
if (testCentroid.x < spatialAverage.x) | |
leftWorklist.push(k); | |
else | |
rightWorklist.push(k); | |
} | |
else if (testCentroid.y != spatialAverage.y) | |
{ | |
if (testCentroid.y < spatialAverage.y) | |
leftWorklist.push(k); | |
else | |
rightWorklist.push(k); | |
} | |
else if (testCentroid.z != spatialAverage.z) | |
{ | |
if (testCentroid.z < spatialAverage.z) | |
leftWorklist.push(k); | |
else | |
rightWorklist.push(k); | |
} | |
} | |
let leftWorklistCopy = new Uint32Array(leftWorklist); | |
let rightWorklistCopy = new Uint32Array(rightWorklist); | |
// recurse | |
BVH_Create_Node(leftWorklistCopy, leftChildIndex); | |
BVH_Create_Node(rightWorklistCopy, rightChildIndex); | |
} // end else //if (leftWorklist.length == 0 || rightWorklist.length == 0) | |
if (leftWorklist.length == 0 || rightWorklist.length == 0) | |
{ | |
//console.log("entered fail case, worklist item count: " + worklist.length); | |
// if we reached this point, the builder failed to find a decent splitting plane axis, so | |
// manually populate the current leftWorkLists and rightWorklists. | |
leftWorklist = []; | |
rightWorklist = []; | |
for (let i = 0; i < worklist.length; i++) | |
{ | |
k = worklist[i]; | |
if (i % 2 != 0) | |
leftWorklist.push(k); | |
else | |
rightWorklist.push(k); | |
} | |
let leftWorklistCopy = new Uint32Array(leftWorklist); | |
let rightWorklistCopy = new Uint32Array(rightWorklist); | |
// recurse | |
BVH_Create_Node(leftWorklistCopy, leftChildIndex); | |
BVH_Create_Node(rightWorklistCopy, rightChildIndex); | |
} // end if (leftWorklist.length == 0 || rightWorklist.length == 0) | |
} // end if (worklist.length > 1) | |
return; // finished | |
} // end function BVH_Create_Node(worklist, nodeIndex) | |
function BVH_QuickBuild(primitiveAABB_IndexList, aabb_array) | |
{ | |
// the user of this builder has to supply the aabb_array. Then we make a copy of the aabb_array, | |
// so that this builder can use it, while referring to it with a generic variable name (like 'aabb_array_copy'). | |
// This allows users to build multiple different BVHs for all scene models, while using the same BVH_QuickBuild() function | |
aabb_array_copy = new Float32Array(aabb_array); | |
// 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), | |
// where each number refers to a unique triangle (or other type of primitive) from the model's unordered 'triangle soup'(or 'primitive soup'). | |
// now build root node (root nodeIndex = 0), and then recursively build the rest of the binary tree | |
BVH_Create_Node(primitiveAABB_IndexList, 0); | |
let nx8 = 0; | |
// Copy the buildnodes array into the aabb_array | |
for (let n = 0; n < buildnodes.length; n++) | |
{ | |
nx8 = 8 * n; | |
// slot 0 | |
aabb_array[nx8 + 0] = buildnodes[n].minCorner.x; // r or x component | |
aabb_array[nx8 + 1] = buildnodes[n].minCorner.y; // g or y component | |
aabb_array[nx8 + 2] = buildnodes[n].minCorner.z; // b or z component | |
aabb_array[nx8 + 3] = buildnodes[n].maxCorner.x; // a or w component | |
// slot 1 | |
aabb_array[nx8 + 4] = buildnodes[n].maxCorner.y; // r or x component | |
aabb_array[nx8 + 5] = buildnodes[n].maxCorner.z; // g or y component | |
aabb_array[nx8 + 6] = buildnodes[n].primitiveCount; // b or z component | |
aabb_array[nx8 + 7] = buildnodes[n].leafOrChild_ID; // a or w component | |
} | |
} // end function BVH_QuickBuild(primitiveAABB_IndexList, aabb_array) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment