@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
99 lines (75 loc) • 3.7 kB
JavaScript
import { assert } from "../../assert.js";
import { NULL_NODE } from "./BVH.js";
import { ebvh_nodes_sort_sah_local4 } from "./ebvh_nodes_sort_sah_local4.js";
/**
* Given a set of leaves, build intermediate node hierarchy on top, all the way up to the root
* Assumes nodes are spatially sorted, if not - no guarantees can be made about quality
* NOTE: {@link unprocessed_nodes} will be modified during execution to save memory
* @param {BVH} bvh
* @param {number[]|Uint32Array} unprocessed_nodes contains nodes that need to be built into a hierarchy, these must not be present in the BVH hierarchy
* @param {number} input_node_count
* @param {number[]|Uint32Array} node_pool Contains node indices that can be used to build ancestry hierarchy, need to be pre-allocated before calling this method
* @param {number} node_pool_offset
* @param {number} [sah_optimization_level]
* @param {number} [sah_optimization_bias] Adds an extra optimization run every X depth levels. Nodes closer to root are generally traversed more, so quality is significantly more important there. Plus, these levels have 1/2 of the nodes progressively, so it's much cheaper to optimize
* @returns {number} new root
*/
export function ebvh_build_hierarchy(
bvh,
unprocessed_nodes,
input_node_count,
node_pool,
node_pool_offset,
sah_optimization_level = 1,
sah_optimization_bias = 0.5
) {
assert.isNonNegativeInteger(input_node_count, 'input_node_count');
assert.isNonNegativeInteger(node_pool_offset, 'node_pool_offset');
let used_index = node_pool_offset;
// Assemble hierarchy
let unprocessed_node_count = input_node_count;
let current_construction_depth = 0;
while (unprocessed_node_count > 1) {
const sah_optimization_cycle_count = Math.floor(sah_optimization_level + sah_optimization_bias * current_construction_depth);
// do long-distance swaps, starting high and coming down
for (let power = 5; power > Math.max(5 - sah_optimization_cycle_count, 1); power--) {
ebvh_nodes_sort_sah_local4(
bvh,
unprocessed_nodes,
0,
1 << power,
unprocessed_node_count
);
}
for (let i = 0; i < sah_optimization_cycle_count; i++) {
// Sort intermediate nodes using small locality and SAH metric
// Sorting progressively larger distance pairs yields best results, based on empirical tests (AG - 2024/12/28)
const swap_distance = (i + 1) * 2;
ebvh_nodes_sort_sah_local4(
bvh,
unprocessed_nodes,
0,
swap_distance,
unprocessed_node_count
);
}
let added_nodes = 0;
let cursor = 0;
while (cursor + 1 < unprocessed_node_count) {
const child_2 = unprocessed_nodes[cursor++];
const child_1 = unprocessed_nodes[cursor++];
const parent = node_pool[used_index++];
bvh.node_assign_children(parent, child_1, child_2);
unprocessed_nodes[added_nodes++] = parent;
}
while (cursor < unprocessed_node_count) {
// dangling nodes, push them onto the next level
unprocessed_nodes[added_nodes++] = unprocessed_nodes[cursor++];
}
unprocessed_node_count = added_nodes;
current_construction_depth++;
}
// set the root to have no parent
bvh.node_set_parent(unprocessed_nodes[0], NULL_NODE);
return unprocessed_nodes[0];
}