UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

99 lines (75 loc) 3.7 kB
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]; }