diff --git a/src/builder/hybridBuilder.ts b/src/builder/hybridBuilder.ts index 3eb06e0..c4f3588 100644 --- a/src/builder/hybridBuilder.ts +++ b/src/builder/hybridBuilder.ts @@ -1,12 +1,11 @@ import { BVHNode, FloatArray, FloatArrayType } from '../core/BVHNode.js'; import { areaBox, areaFromTwoBoxes, expandBoxByMargin, getLongestAxis, isBoxInsideBox, isExpanded, unionBox, unionBoxChanged } from '../utils/boxUtils.js'; -import { SortedListPriority } from '../utils/sortedListPriority.js'; +import { HeapItem, MinHeap } from '../utils/heap.js'; import { IBVHBuilder, onLeafCreationCallback } from './IBVHBuilder.js'; export class HybridBuilder implements IBVHBuilder { public root: BVHNode = null; public readonly highPrecision: boolean; - protected _sortedList = new SortedListPriority(); protected _typeArray: FloatArrayType; protected count = 0; @@ -253,58 +252,64 @@ export class HybridBuilder implements IBVHBuilder { return { parent, left: sibling, right: leaf, box: new this._typeArray(6) } as BVHNode; } + // TODO precalcola area nodo protected findBestSibling(leafBox: FloatArray): BVHNode { const root = this.root; + if (root.object !== undefined) return root; + let bestNode = root; let bestCost = areaFromTwoBoxes(leafBox, root.box); const leafArea = areaBox(leafBox); + _findBestSibling({ node: root, value: bestCost - areaBox(root.box) }); - if (root.object !== undefined) return root; - - const sortedList = this._sortedList; - sortedList.clear(); - let nodeObj = { node: root, inheritedCost: bestCost - areaBox(root.box) }; + function _findBestSibling(nodeObj: HeapItem): void { + const minHeap = new MinHeap(); - do { - const { node, inheritedCost } = nodeObj; + do { + if (minHeap.isFull()) { + _findBestSibling(nodeObj); + } else { + const { node, value: inheritedCost } = nodeObj; - if (leafArea + inheritedCost >= bestCost) break; + if (leafArea + inheritedCost >= bestCost) break; - const nodeL = node.left; - const nodeR = node.right; + const nodeL = node.left; + const nodeR = node.right; - const directCostL = areaFromTwoBoxes(leafBox, nodeL.box); - const currentCostL = directCostL + inheritedCost; - const inheritedCostL = currentCostL - areaBox(nodeL.box); + const directCostL = areaFromTwoBoxes(leafBox, nodeL.box); + const currentCostL = directCostL + inheritedCost; + const inheritedCostL = currentCostL - areaBox(nodeL.box); - const directCostR = areaFromTwoBoxes(leafBox, nodeR.box); - const currentCostR = directCostR + inheritedCost; - const inheritedCostR = currentCostR - areaBox(nodeR.box); + const directCostR = areaFromTwoBoxes(leafBox, nodeR.box); + const currentCostR = directCostR + inheritedCost; + const inheritedCostR = currentCostR - areaBox(nodeR.box); - if (currentCostL > currentCostR) { - if (bestCost > currentCostR) { - bestNode = nodeR; - bestCost = currentCostR; - } - } else if (bestCost > currentCostL) { - bestNode = nodeL; - bestCost = currentCostL; - } + if (currentCostL > currentCostR) { + if (bestCost > currentCostR) { + bestNode = nodeR as BVHNode; + bestCost = currentCostR; + } + } else if (bestCost > currentCostL) { + bestNode = nodeL as BVHNode; + bestCost = currentCostL; + } - if (inheritedCostR > inheritedCostL) { - if (leafArea + inheritedCostL >= bestCost) continue; - if (nodeL.object === undefined) sortedList.push({ node: nodeL, inheritedCost: inheritedCostL }); + if (inheritedCostR > inheritedCostL) { + if (leafArea + inheritedCostL >= bestCost) continue; + if (nodeL.object === undefined) minHeap.add({ node: nodeL, value: inheritedCostL }); - if (leafArea + inheritedCostR >= bestCost) continue; - if (nodeR.object === undefined) sortedList.push({ node: nodeR, inheritedCost: inheritedCostR }); - } else { - if (leafArea + inheritedCostR >= bestCost) continue; - if (nodeR.object === undefined) sortedList.push({ node: nodeR, inheritedCost: inheritedCostR }); + if (leafArea + inheritedCostR >= bestCost) continue; + if (nodeR.object === undefined) minHeap.add({ node: nodeR, value: inheritedCostR }); + } else { + if (leafArea + inheritedCostR >= bestCost) continue; + if (nodeR.object === undefined) minHeap.add({ node: nodeR, value: inheritedCostR }); - if (leafArea + inheritedCostL >= bestCost) continue; - if (nodeL.object === undefined) sortedList.push({ node: nodeL, inheritedCost: inheritedCostL }); - } - } while ((nodeObj = sortedList.pop())); + if (leafArea + inheritedCostL >= bestCost) continue; + if (nodeL.object === undefined) minHeap.add({ node: nodeL, value: inheritedCostL }); + } + } + } while ((nodeObj = minHeap.poll())); + } return bestNode; } diff --git a/src/index.ts b/src/index.ts index e4fe245..86cb89a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,6 @@ export * from './core/BVHNode.js'; export * from './utils/boxUtils.js'; export * from './utils/conversionUtils.js'; export * from './utils/frustum.js'; +export * from './utils/heap.js'; export * from './utils/inspector.js'; export * from './utils/intersectUtils.js'; -export * from './utils/sortedListDesc.js'; -export * from './utils/sortedListPriority.js'; diff --git a/src/utils/heap.ts b/src/utils/heap.ts new file mode 100644 index 0000000..ac41f63 --- /dev/null +++ b/src/utils/heap.ts @@ -0,0 +1,69 @@ +import { BVHNode } from '../core/BVHNode.js'; + +export type HeapItem = { value: number; node: BVHNode }; + +/** + * @reference https://github.com/zrwusa/data-structure-typed/blob/main/src/data-structures/heap/heap.ts + */ +export class MinHeap { + public maxSize = 16; + protected _elements: HeapItem[] = []; + + public add(element: HeapItem): boolean { + this._elements.push(element); + return this._bubbleUp(this._elements.length - 1); + } + + public poll(): HeapItem | undefined { + const elements = this._elements; + if (elements.length === 0) return; + const value = elements[0]; + const last = elements.pop(); + if (elements.length) { + elements[0] = last; + this._sinkDown(0, elements.length >> 1); + } + return value; + } + + public isFull(): boolean { + return this._elements.length >= this.maxSize; + } + + public clear(): void { + this._elements.length = 0; + } + + protected _bubbleUp(index: number): boolean { + const elements = this._elements; + const element = elements[index]; + while (index > 0) { + const parent = (index - 1) >> 1; + const parentItem = elements[parent]; + if (parentItem.value <= element.value) break; + elements[index] = parentItem; + index = parent; + } + elements[index] = element; + return true; + } + + protected _sinkDown(index: number, halfLength: number): boolean { + const elements = this._elements; + const element = elements[index]; + while (index < halfLength) { + let left = (index << 1) | 1; + const right = left + 1; + let minItem = elements[left]; + if (right < elements.length && minItem.value > elements[right].value) { + left = right; + minItem = elements[right]; + } + if (minItem.value >= element.value) break; + elements[index] = minItem; + index = left; + } + elements[index] = element; + return true; + } +} diff --git a/src/utils/sortedListDesc.ts b/src/utils/sortedListDesc.ts deleted file mode 100644 index 9c23b51..0000000 --- a/src/utils/sortedListDesc.ts +++ /dev/null @@ -1,29 +0,0 @@ -type ItemListType = { node: any; inheritedCost: number }; // fix d.ts - -export class SortedListDesc { - public array: ItemListType[] = []; - - public clear(): void { - this.array.length = 0; - } - - public push(node: ItemListType): void { - const index = this.binarySearch(node.inheritedCost); - this.array.splice(index, 0, node); - } - - public pop(): ItemListType { - return this.array.pop(); - } - - public binarySearch(score: number): number { - const array = this.array; - let low = 0, high = array.length; - while (low < high) { - const mid = (low + high) >>> 1; - if (array[mid].inheritedCost > score) low = mid + 1; - else high = mid; - } - return low; - } -} diff --git a/src/utils/sortedListPriority.ts b/src/utils/sortedListPriority.ts deleted file mode 100644 index 4f3dde3..0000000 --- a/src/utils/sortedListPriority.ts +++ /dev/null @@ -1,26 +0,0 @@ -type ItemListType = { node: any; inheritedCost: number }; // fix d.ts - -export class SortedListPriority { - public array: ItemListType[] = []; - - public clear(): void { - this.array = []; - } - - public push(node: ItemListType): void { - const array = this.array; - const cost = node.inheritedCost; - const end = array.length > 6 ? array.length - 6 : 0; - let i: number; - - for (i = array.length - 1; i >= end; i--) { - if (cost <= array[i].inheritedCost) break; - } - - if (i > array.length - 7) array.splice(i + 1, 0, node); // if in last 6 place, add it do the list - } - - public pop(): ItemListType { - return this.array.pop(); - } -}