UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

115 lines (114 loc) 4.08 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { NUM_BUCKETS } from "./constants.js"; class GSplatBudgetBalancer { constructor() { /** * Buckets storing NodeInfo references. * @type {Array<Array>|null} * @private */ __publicField(this, "_buckets", null); } /** * Initialize bucket infrastructure on first use. * @private */ _initBuckets() { if (!this._buckets) { this._buckets = new Array(NUM_BUCKETS); for (let i = 0; i < NUM_BUCKETS; i++) { this._buckets[i] = []; } } } /** * Balances splat budget across all octree instances by adjusting LOD levels. * Uses sqrt-based bucket distribution to give more precision to nearby geometry. * Makes multiple passes, adjusting by one LOD level per pass, until budget is reached * or all nodes hit their respective limits (per-instance rangeMin or rangeMax). * * @param {Map<GSplatPlacement, GSplatOctreeInstance>} octreeInstances - Map of * GSplatOctreeInstance objects. * @param {number} budget - Target splat budget for octrees. */ balance(octreeInstances, budget) { this._initBuckets(); for (let i = 0; i < NUM_BUCKETS; i++) { this._buckets[i].length = 0; } let totalOptimalSplats = 0; for (const [, inst] of octreeInstances) { const nodes = inst.octree.nodes; const nodeInfos = inst.nodeInfos; for (let nodeIndex = 0, len = nodes.length; nodeIndex < len; nodeIndex++) { const nodeInfo = nodeInfos[nodeIndex]; const optimalLod = nodeInfo.optimalLod; if (optimalLod < 0) continue; const lods = nodes[nodeIndex].lods; nodeInfo.lods = lods; this._buckets[nodeInfo.budgetBucket].push(nodeInfo); totalOptimalSplats += lods[optimalLod].count; } } let currentSplats = totalOptimalSplats; if (currentSplats === budget) { return; } const isOverBudget = currentSplats > budget; let done = false; while (!done && (isOverBudget ? currentSplats > budget : currentSplats < budget)) { let modified = false; if (isOverBudget) { for (let b = NUM_BUCKETS - 1; b >= 0 && !done; b--) { const bucket = this._buckets[b]; for (let i = 0, len = bucket.length; i < len; i++) { const nodeInfo = bucket[i]; if (nodeInfo.optimalLod < nodeInfo.inst.rangeMax) { const lods = nodeInfo.lods; const optimalLod = nodeInfo.optimalLod; currentSplats -= lods[optimalLod].count - lods[optimalLod + 1].count; nodeInfo.optimalLod = optimalLod + 1; modified = true; if (currentSplats <= budget) { done = true; break; } } } } } else { for (let b = 0; b < NUM_BUCKETS && !done; b++) { const bucket = this._buckets[b]; for (let i = 0, len = bucket.length; i < len; i++) { const nodeInfo = bucket[i]; if (nodeInfo.optimalLod > nodeInfo.inst.rangeMin) { const lods = nodeInfo.lods; const optimalLod = nodeInfo.optimalLod; const splatsAdded = lods[optimalLod - 1].count - lods[optimalLod].count; if (currentSplats + splatsAdded <= budget) { nodeInfo.optimalLod = optimalLod - 1; currentSplats += splatsAdded; modified = true; if (currentSplats >= budget) { done = true; break; } } else { done = true; break; } } } } } if (!modified) { break; } } } } export { GSplatBudgetBalancer };