UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

280 lines (222 loc) • 8.47 kB
import { assert } from "../../assert.js"; import { clamp } from "../../math/clamp.js"; import { NULL_NODE } from "./BVH.js"; import { ebvh_update_hierarchy_bounds } from "./ebvh_update_hierarchy_bounds.js"; /** * * @see "Thinking Parallel, Part III: Tree Construction on the GPU", 2012 by Tero Karras * * @param {number[]|Uint32Array} sortedMortonCodes * @param {number} first * @param {number} last * @return {number} */ function find_split( sortedMortonCodes, first, last ) { // Identical Morton codes => split the range in the middle. const firstCode = sortedMortonCodes[first]; const lastCode = sortedMortonCodes[last]; if (firstCode === lastCode) { return (first + last) >>> 1; } // Calculate the number of highest bits that are the same // for all objects, using the count-leading-zeros intrinsic. const commonPrefix = Math.clz32(firstCode ^ lastCode); // Use binary search to find where the next bit differs. // Specifically, we are looking for the highest object that // shares more than commonPrefix bits with the first one. let split = first; // initial guess let step = last - first; do { step = (step + 1) >>> 1; // exponential decrease const newSplit = split + step; // proposed new position if (newSplit < last) { const splitCode = sortedMortonCodes[newSplit]; const splitPrefix = Math.clz32(firstCode ^ splitCode); if (splitPrefix > commonPrefix) { split = newSplit; // accept proposal } } } while (step > 1); return split; } /** * * @param {number} indexA * @param {number} indexB * @param {number} elementCount * @param {number[]} mortonCodes * @return {number} * @see https://github.com/turanszkij/WickedEngine/blob/506749de321c2ab66fd33fbe41efb95afbbb7ff8/WickedEngine/shaders/bvh_hierarchyCS.hlsl#L28C1-L48C2 */ function GetLongestCommonPrefix(indexA, indexB, elementCount, mortonCodes) { assert.isNonNegativeInteger(indexA, 'indexA'); assert.isNonNegativeInteger(indexB, 'indexB'); if (indexA >= elementCount || indexB >= elementCount) { return -1; } else { const mortonCodeA = mortonCodes[indexA]; const mortonCodeB = mortonCodes[indexB]; if (mortonCodeA !== mortonCodeB) { return Math.clz32(mortonCodeA ^ mortonCodeB); } else { // TODO: Technically this should be primitive ID return Math.clz32(indexA ^ indexB) + 31; } } } /** * @see https://github.com/turanszkij/WickedEngine/blob/506749de321c2ab66fd33fbe41efb95afbbb7ff8/WickedEngine/shaders/bvh_hierarchyCS.hlsl#L28C1-L48C2 * @param {number[]|Uint32Array} output * @param {number[]} sortedMortonCodes * @param {number} numTriangles * @param {number} idx */ function determineRangeW(output, sortedMortonCodes, numTriangles, idx) { if (idx === 0) { // root output[0] = 0; output[1] = numTriangles - 1; return; } let d = GetLongestCommonPrefix(idx, idx + 1, numTriangles, sortedMortonCodes) - GetLongestCommonPrefix(idx, idx - 1, numTriangles, sortedMortonCodes); d = clamp(d, -1, 1); let minPrefix = GetLongestCommonPrefix(idx, idx - d, numTriangles, sortedMortonCodes); // TODO: Consider starting this at a higher number let maxLength = 2; while (GetLongestCommonPrefix(idx, idx + maxLength * d, numTriangles, sortedMortonCodes) > minPrefix) { maxLength <<= 2; } let length = 0; for (let t = maxLength >>> 1; t > 0; t >>>= 1) { if (GetLongestCommonPrefix(idx, idx + (length + t) * d, numTriangles, sortedMortonCodes) > minPrefix) { length = length + t; } } let j = idx + length * d; output[0] = Math.min(idx, j); output[1] = Math.max(idx, j); } /** * @see https://github.com/mbartling/cuda-bvh/blob/7f2f98d9d29956c3559632e59104ba66f31f80b8/kernels/bvh.cu#L276C1-L352C2 * @param {number[]|Uint32Array} output * @param {number[]} sortedMortonCodes * @param {number} numTriangles * @param {number} idx */ function determineRange(output, sortedMortonCodes, numTriangles, idx) { //determine the range of keys covered by each internal node (as well as its children) //direction is found by looking at the neighboring keys ki-1 , ki , ki+1 //the index is either the beginning of the range or the end of the range let direction = 0; let common_prefix_with_left = 0; let common_prefix_with_right = 0; common_prefix_with_right = Math.clz32(sortedMortonCodes[idx] ^ sortedMortonCodes[idx + 1]); if (idx === 0) { common_prefix_with_left = -1; } else { common_prefix_with_left = Math.clz32(sortedMortonCodes[idx] ^ sortedMortonCodes[idx - 1]); } direction = ((common_prefix_with_right - common_prefix_with_left) > 0) ? 1 : -1; let min_prefix_range = 0; if (idx === 0) { min_prefix_range = -1; } else { min_prefix_range = Math.clz32(sortedMortonCodes[idx] ^ sortedMortonCodes[idx - direction]); } let lmax = 2; let next_key = idx + lmax * direction; while ((next_key >= 0) && (next_key < numTriangles) && (Math.clz32(sortedMortonCodes[idx] ^ sortedMortonCodes[next_key]) > min_prefix_range)) { lmax *= 2; next_key = idx + lmax * direction; } //find the other end using binary search let l = 0; do { lmax = (lmax + 1) >> 1; // exponential decrease const new_val = idx + (l + lmax) * direction; if (new_val >= 0 && new_val < numTriangles) { const Code = sortedMortonCodes[new_val]; const Prefix = Math.clz32(sortedMortonCodes[idx] ^ Code); if (Prefix > min_prefix_range) { l = l + lmax; } } } while (lmax > 1); const j = idx + l * direction; let left = 0; let right = 0; if (idx < j) { left = idx; right = j; } else { left = j; right = idx; } // printf("idx : (%d) returning range (%d, %d) \n" , idx , left, right); output[0] = left output[1] = right; } /** * DO NOT USE, currently broken * @param {BVH} bvh * @param {number[]|Uint32Array} leaf_nodes * @param {number[]} sorted_morton_codes * @param {number} leaf_count * @param {number[]} internal_nodes * @returns {number} new root */ export function ebvh_build_hierarchy_radix( bvh, leaf_nodes, sorted_morton_codes, leaf_count, internal_nodes, ) { console.warn("IMPLEMENTATION IS INCOMPLETE, DO NOT USE") assert.isNonNegativeInteger(leaf_count, 'leaf_count'); // Construct leaf nodes. // Note: This step can be avoided by storing // the tree in a slightly different way. const range = new Uint32Array(2); // Construct internal nodes. const intermediate_node_count = leaf_count - 1; for (let idx = 0; idx < intermediate_node_count; idx++) // in parallel { // Find out which range of objects the node corresponds to. // (This is where the magic happens!) determineRange(range, sorted_morton_codes, leaf_count, idx); const first = range[0]; const last = range[1]; // Determine where to split the range. const split = find_split(sorted_morton_codes, first, last); assert.isNonNegativeInteger(split, 'split'); // Select childA. let childA; if (split === first) { childA = leaf_nodes[split]; } else { childA = internal_nodes[split]; } // Select childB. let childB; if (split + 1 === last) { childB = leaf_nodes[split + 1]; } else { childB = internal_nodes[split + 1]; } // Record parent-child relationships. const parent = internal_nodes[idx]; bvh.node_assign_children_only(parent, childA, childB); } // Node 0 is the root. const root = internal_nodes[0]; bvh.node_set_parent(root, NULL_NODE); // update bounds ebvh_update_hierarchy_bounds(bvh, root); return root; }